Merge branch 'stable-2.9' into stable-2.10

* stable-2.9
  Bump revision number
  Update NEWS file for 2.9.7 release
  Improve RAPI section on security

Conflicts:
  NEWS - leave 2.9.7 info in
  configure.ac - revert version bump

Signed-off-by: Hrvoje Ribicic <riba@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>
diff --git a/.gitignore b/.gitignore
index fdd50d7..7b26697 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,9 +79,10 @@
 /doc/examples/hooks/ipsec
 
 # lib
-/lib/_autoconf.py
+/lib/_constants.py
 /lib/_vcsversion.py
 /lib/_generated_rpc.py
+/lib/opcodes.py
 
 # man
 /man/*.[0-9]
@@ -113,6 +114,7 @@
 /tools/node-cleanup
 /tools/node-daemon-setup
 /tools/prepare-node-join
+/tools/shebang/
 
 # scripts
 /scripts/gnt-backup
@@ -131,13 +133,16 @@
 /src/htools
 /src/hconfd
 /src/hluxid
+/src/hs2py
+/src/hs2py-constants
 /src/ganeti-confd
 /src/ganeti-luxid
 /src/ganeti-mond
 /src/rpc-test
 
 # automatically-built Haskell files
-/src/Ganeti/Constants.hs
+/src/AutoConf.hs
 /src/Ganeti/Curl/Internal.hs
+/src/Ganeti/Hs2Py/ListConstants.hs
 /src/Ganeti/Version.hs
 /test/hs/Test/Ganeti/TestImports.hs
diff --git a/COPYING b/COPYING
index 623b625..82fd7f8 100644
--- a/COPYING
+++ b/COPYING
@@ -1,340 +1,25 @@
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
+Copyright (C) 2006-2014 Google Inc.
+All rights reserved.
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-			    Preamble
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.)  You can apply it to
-your programs, too.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
 
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-		    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-			    NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-		     END OF TERMS AND CONDITIONS
-
-	    How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year  name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  <signature of Ty Coon>, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Library General
-Public License instead of this License.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/INSTALL b/INSTALL
index fa9ca65..fe2d1dc 100644
--- a/INSTALL
+++ b/INSTALL
@@ -43,12 +43,15 @@
   ``ganeti-listrunner``
 - `affinity Python module <http://pypi.python.org/pypi/affinity/0.1.0>`_,
   optional python package for supporting CPU pinning under KVM
+- `fdsend Python module <https://gitorious.org/python-fdsend>`_,
+  optional Python package for supporting NIC hotplugging under KVM
 - `qemu-img <http://qemu.org/>`_, if you want to use ``ovfconverter``
 - `fping <http://fping.sourceforge.net/>`_
 - `Python IP address manipulation library
   <http://code.google.com/p/ipaddr-py/>`_
 - `Bitarray Python library <http://pypi.python.org/pypi/bitarray/>`_
 - `GNU Make <http://www.gnu.org/software/make/>`_
+- `GNU M4 <http://www.gnu.org/software/m4/>`_
 
 These programs are supplied as part of most Linux distributions, so
 usually they can be installed via the standard package manager. Also
@@ -56,7 +59,7 @@
 Debian/Ubuntu, you can use this command line to install all required
 packages, except for RBD, DRBD and Xen::
 
-  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping make \
+  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping make m4 \
                     ndisc6 python python-openssl openssl \
                     python-pyparsing python-simplejson python-bitarray \
                     python-pyinotify python-pycurl python-ipaddr socat fping
@@ -75,14 +78,14 @@
 
 Note that this does not install optional packages::
 
-  $ apt-get install python-paramiko python-affinity qemu-img
+  $ apt-get install python-paramiko python-affinity qemu-utils
 
 If some of the python packages are not available in your system,
 you can try installing them using ``easy_install`` command.
 For example::
 
   $ apt-get install python-setuptools python-dev
-  $ cd / && sudo easy_install \
+  $ cd / && easy_install \
             affinity \
             bitarray \
             ipaddr
@@ -205,6 +208,7 @@
 
   $ apt-get install libghc-crypto-dev libghc-text-dev \
                     libghc-hinotify-dev libghc-regex-pcre-dev \
+                    libpcre3-dev \
                     libghc-attoparsec-dev libghc-vector-dev \
                     libghc-snap-server-dev
 
@@ -219,7 +223,7 @@
 In case you still use ghc-6.12, note that ``cabal`` would automatically try to
 install newer versions of some of the libraries snap-server depends on, that
 cannot be compiled with ghc-6.12, so you have to install snap-server on its
-own, esplicitly forcing the installation of compatible versions::
+own, explicitly forcing the installation of compatible versions::
 
   $ cabal install MonadCatchIO-transformers==0.2.2.0 mtl==2.0.1.0 \
                   hashable==1.1.2.0 case-insensitive==0.3 parsec==3.0.1 \
diff --git a/Makefile.am b/Makefile.am
index 700d708..b1a8509 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,12 +36,48 @@
 CHECK_IMPORTS = $(top_srcdir)/autotools/check-imports
 DOCPP = $(top_srcdir)/autotools/docpp
 REPLACE_VARS_SED = autotools/replace_vars.sed
-CONVERT_CONSTANTS = $(top_srcdir)/autotools/convert-constants
+PRINT_PY_CONSTANTS = $(top_srcdir)/autotools/print-py-constants
 BUILD_RPC = $(top_srcdir)/autotools/build-rpc
 SHELL_ENV_INIT = autotools/shell-env-init
 
+# starting as of Ganeti 2.10, all files are stored in two directories,
+# with only symbolic links added at other places.
+#
+# $(versiondir) contains most of Ganeti and all architecture-dependent files
+# $(versionedsharedir) contains only architecture-independent files; all python
+# executables need to go directly to $(versionedsharedir), as all ganeti python
+# mdules are installed outside the usual python path, i.e., as private modules.
+#
+# $(defaultversiondir) and $(defaultversionedsharedir) are the corresponding
+# directories for "the currently running" version of Ganeti. We never install
+# there, but all symbolic links go there, rather than directory to $(versiondir)
+# or $(versionedsharedir). Note that all links to $(default*dir) need to be stable;
+# so, if some currently architecture-independent executable is replaced by an
+# architecture-dependent one (and hence has to go under $(versiondir)), add a link
+# under $(versionedsharedir) but do not change the external links.
+if USE_VERSION_FULL
+DIRVERSION=$(VERSION_FULL)
+else
+DIRVERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
+endif
+versiondir = $(libdir)/ganeti/$(DIRVERSION)
+defaultversiondir = $(libdir)/ganeti/default
+versionedsharedir = $(prefix)/share/ganeti/$(DIRVERSION)
+defaultversionedsharedir = $(prefix)/share/ganeti/default
+
 # Note: these are automake-specific variables, and must be named after
 # the directory + 'dir' suffix
+pkglibdir = $(versiondir)$(libdir)/ganeti
+myexeclibdir = $(pkglibdir)
+bindir = $(versiondir)/$(BINDIR)
+sbindir = $(versiondir)$(SBINDIR)
+mandir = $(versionedsharedir)/root$(MANDIR)
+pkgpythondir = $(versionedsharedir)/ganeti
+gntpythondir = $(versionedsharedir)
+pkgpython_bindir = $(versionedsharedir)
+gnt_python_sbindir = $(versionedsharedir)
+tools_pythondir = $(versionedsharedir)
+
 clientdir = $(pkgpythondir)/client
 cmdlibdir = $(pkgpythondir)/cmdlib
 hypervisordir = $(pkgpythondir)/hypervisor
@@ -57,8 +93,24 @@
 toolsdir = $(pkglibdir)/tools
 iallocatorsdir = $(pkglibdir)/iallocators
 pytoolsdir = $(pkgpythondir)/tools
-docdir = $(datadir)/doc/$(PACKAGE)
-myexeclibdir = $(pkglibdir)
+docdir = $(versiondir)$(datadir)/doc/$(PACKAGE)
+
+if USE_BACKUP_DIR
+backup_dir = $(BACKUP_DIR)
+else
+backup_dir = $(localstatedir)/lib
+endif
+
+SYMLINK_TARGET_DIRS = \
+	$(sysconfdir)/ganeti \
+	$(libdir)/ganeti/iallocators \
+	$(libdir)/ganeti/tools \
+	$(prefix)/share/ganeti \
+	$(BINDIR) \
+	$(SBINDIR) \
+	$(MANDIR)/man1 \
+	$(MANDIR)/man7 \
+	$(MANDIR)/man8
 
 # Delete output file if an error occurred while building it
 .DELETE_ON_ERROR:
@@ -68,7 +120,9 @@
 	src/Ganeti \
 	src/Ganeti/Confd \
 	src/Ganeti/Curl \
+	src/Ganeti/Cpu \
 	src/Ganeti/DataCollectors \
+	src/Ganeti/Hs2Py \
 	src/Ganeti/HTools \
 	src/Ganeti/HTools/Backend \
 	src/Ganeti/HTools/Program \
@@ -137,6 +191,8 @@
 	test/data/ovfdata \
 	test/data/ovfdata/other \
 	test/py \
+	test/py/cmdlib \
+	test/py/cmdlib/testsupport \
 	tools
 
 ALL_APIDOC_HS_DIRS = \
@@ -163,6 +219,7 @@
 	ganeti-[0-9]*.[0-9]*.[0-9]* \
 	doc/html/_* \
 	doc/man-html/_* \
+	tools/shebang \
 	autom4te.cache
 
 # some helper vars
@@ -188,6 +245,7 @@
 	$(addsuffix /*.hi,$(HS_DIRS)) \
 	$(addsuffix /*.o,$(HS_DIRS)) \
 	$(PYTHON_BOOTSTRAP) \
+	$(gnt_python_sbin_SCRIPTS) \
 	epydoc.conf \
 	$(REPLACE_VARS_SED) \
 	$(SHELL_ENV_INIT) \
@@ -209,21 +267,27 @@
 	tools/net-common \
 	tools/users-setup \
 	tools/vcluster-setup \
+	$(python_scripts_shebang) \
 	stamp-directories \
 	stamp-srclinks \
 	$(nodist_pkgpython_PYTHON) \
+	$(gnt_scripts) \
 	$(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
 	$(HS_BUILT_TEST_HELPERS) \
 	src/ganeti-confd \
 	src/ganeti-luxid \
 	src/ganeti-mond \
-	.hpc/*.mix src/*.tix test/hs/*.tix \
+	.hpc/*.mix src/*.tix test/hs/*.tix *.tix \
 	doc/hs-lint.html
 
 GENERATED_FILES = \
 	$(built_base_sources) \
 	$(BUILT_PYTHON_SOURCES) \
-	$(PYTHON_BOOTSTRAP)
+	$(PYTHON_BOOTSTRAP) \
+	$(gnt_python_sbin_SCRIPTS)
+
+clean-local:
+	rm -rf tools/shebang
 
 HS_GENERATED_FILES =
 if WANT_HTOOLS
@@ -242,8 +306,9 @@
 	stamp-srclinks
 
 built_python_base_sources = \
-	lib/_autoconf.py \
-	lib/_vcsversion.py
+	lib/_constants.py \
+	lib/_vcsversion.py \
+	lib/opcodes.py
 
 BUILT_PYTHON_SOURCES = \
 	$(built_python_base_sources) \
@@ -267,6 +332,12 @@
 nodist_pkgpython_PYTHON = \
 	$(BUILT_PYTHON_SOURCES)
 
+nodist_pkgpython_bin_SCRIPTS = \
+	$(nodist_pkglib_python_scripts)
+
+pkgpython_bin_SCRIPTS = \
+	$(pkglib_python_scripts)
+
 noinst_PYTHON = \
 	lib/build/__init__.py \
 	lib/build/shell_example_lexer.py \
@@ -292,7 +363,7 @@
 	lib/mcpu.py \
 	lib/netutils.py \
 	lib/objects.py \
-	lib/opcodes.py \
+	lib/opcodes_base.py \
 	lib/outils.py \
 	lib/ovf.py \
 	lib/pathutils.py \
@@ -422,14 +493,16 @@
 	lib/utils/retry.py \
 	lib/utils/storage.py \
 	lib/utils/text.py \
+	lib/utils/version.py \
 	lib/utils/wrapper.py \
 	lib/utils/x509.py
 
 docinput = \
+	doc/admin.rst \
+	doc/cluster-keys-replacement.rst \
+	doc/cluster-merge.rst \
 	doc/conf.py \
 	doc/css/style.css \
-	doc/admin.rst \
-	doc/cluster-merge.rst \
 	doc/design-2.0.rst \
 	doc/design-2.1.rst \
 	doc/design-2.2.rst \
@@ -440,39 +513,50 @@
 	doc/design-2.7.rst \
 	doc/design-2.8.rst \
 	doc/design-2.9.rst \
+	doc/design-2.10.rst \
 	doc/design-autorepair.rst \
 	doc/design-bulk-create.rst \
+	doc/design-ceph-ganeti-support.rst \
 	doc/design-chained-jobs.rst \
+	doc/design-cmdlib-unittests.rst \
 	doc/design-cpu-pinning.rst \
+	doc/design-daemons.rst \
 	doc/design-device-uuid-name.rst \
 	doc/design-draft.rst \
+	doc/design-file-based-storage.rst \
 	doc/design-glusterfs-ganeti-support.rst \
-	doc/design-daemons.rst \
+	doc/design-hotplug.rst \
+	doc/design-hroller.rst \
+	doc/design-hsqueeze.rst \
 	doc/design-htools-2.3.rst \
 	doc/design-http-server.rst \
+	doc/design-hugepages-support.rst \
 	doc/design-impexp2.rst \
 	doc/design-internal-shutdown.rst \
-	doc/design-lu-generated-jobs.rst \
 	doc/design-linuxha.rst \
+	doc/design-lu-generated-jobs.rst \
+	doc/design-monitoring-agent.rst \
 	doc/design-multi-reloc.rst \
 	doc/design-network.rst \
 	doc/design-node-add.rst \
 	doc/design-oob.rst \
 	doc/design-openvswitch.rst \
-	doc/design-ovf-support.rst \
 	doc/design-opportunistic-locking.rst \
+	doc/design-optables.rst \
+	doc/design-ovf-support.rst \
 	doc/design-partitioned.rst \
+	doc/design-performance-tests.rst \
 	doc/design-query-splitting.rst \
 	doc/design-query2.rst \
 	doc/design-reason-trail.rst \
 	doc/design-resource-model.rst \
 	doc/design-restricted-commands.rst \
 	doc/design-shared-storage.rst \
-	doc/design-monitoring-agent.rst \
+	doc/design-storagetypes.rst \
+	doc/design-upgrade.rst \
 	doc/design-virtual-clusters.rst \
 	doc/design-x509-ca.rst \
-	doc/design-hroller.rst \
-	doc/design-storagetypes.rst \
+	doc/dev-codestyle.rst \
 	doc/devnotes.rst \
 	doc/glossary.rst \
 	doc/hooks.rst \
@@ -510,6 +594,7 @@
 	src/ganeti-mond \
 	src/hconfd \
 	src/hluxid \
+	src/hs2py \
 	src/rpc-test
 
 # All Haskell non-test programs to be compiled but not automatically installed
@@ -518,21 +603,28 @@
 HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller
 HS_HTOOLS_PROGS = $(HS_BIN_ROLES) hail
 
-HS_ALL_PROGS = \
-	$(HS_PROGS) \
+# Haskell programs that cannot be disabled at configure (e.g., unlike
+# 'mon-collector')
+HS_DEFAULT_PROGS = \
+	$(HS_BIN_PROGS) \
 	test/hs/hpc-htools \
 	test/hs/hpc-mon-collector \
 	test/hs/htest \
 	$(HS_COMPILE_PROGS)
 
-HS_PROG_SRCS = $(patsubst %,%.hs,$(HS_ALL_PROGS))
+HS_ALL_PROGS = $(HS_DEFAULT_PROGS) $(HS_MYEXECLIB_PROGS)
+
+HS_PROG_SRCS = $(patsubst %,%.hs,$(HS_DEFAULT_PROGS)) src/mon-collector.hs
 HS_BUILT_TEST_HELPERS = $(HS_BIN_ROLES:%=test/hs/%) test/hs/hail
 
 HFLAGS = \
-	-O -Wall -Werror -isrc \
+	-O -Wall -isrc \
 	-fwarn-monomorphism-restriction \
 	-fwarn-tabs \
 	$(GHC_BYVERSION_FLAGS)
+if DEVELOPER_MODE
+HFLAGS += -Werror
+endif
 
 # extra flags that can be overriden on the command line (e.g. -Wwarn, etc.)
 HEXTRA =
@@ -564,9 +656,14 @@
 	src/Ganeti/Confd/Utils.hs \
 	src/Ganeti/Config.hs \
 	src/Ganeti/ConfigReader.hs \
+	src/Ganeti/Constants.hs \
+	src/Ganeti/ConstantUtils.hs \
+	src/Ganeti/Cpu/LoadParser.hs \
+	src/Ganeti/Cpu/Types.hs \
 	src/Ganeti/Curl/Multi.hs \
 	src/Ganeti/Daemon.hs \
 	src/Ganeti/DataCollectors/CLI.hs \
+	src/Ganeti/DataCollectors/CPUload.hs \
 	src/Ganeti/DataCollectors/Diskstats.hs \
 	src/Ganeti/DataCollectors/Drbd.hs \
 	src/Ganeti/DataCollectors/InstStatus.hs \
@@ -605,6 +702,9 @@
 	src/Ganeti/Hypervisor/Xen/XmParser.hs \
 	src/Ganeti/Hypervisor/Xen/Types.hs \
 	src/Ganeti/Hash.hs \
+	src/Ganeti/Hs2Py/GenConstants.hs \
+	src/Ganeti/Hs2Py/GenOpCodes.hs \
+	src/Ganeti/Hs2Py/OpDoc.hs \
 	src/Ganeti/JQueue.hs \
 	src/Ganeti/JSON.hs \
 	src/Ganeti/Jobs.hs \
@@ -616,6 +716,8 @@
 	src/Ganeti/OpCodes.hs \
 	src/Ganeti/OpParams.hs \
 	src/Ganeti/Path.hs \
+	src/Ganeti/Parsers.hs \
+	src/Ganeti/PyValueInstances.hs \
 	src/Ganeti/Query/Cluster.hs \
 	src/Ganeti/Query/Common.hs \
 	src/Ganeti/Query/Export.hs \
@@ -643,11 +745,13 @@
 	src/Ganeti/Utils.hs
 
 HS_TEST_SRCS = \
+	test/hs/Test/AutoConf.hs \
 	test/hs/Test/Ganeti/Attoparsec.hs \
 	test/hs/Test/Ganeti/BasicTypes.hs \
 	test/hs/Test/Ganeti/Common.hs \
 	test/hs/Test/Ganeti/Confd/Types.hs \
 	test/hs/Test/Ganeti/Confd/Utils.hs \
+	test/hs/Test/Ganeti/Constants.hs \
 	test/hs/Test/Ganeti/Daemon.hs \
 	test/hs/Test/Ganeti/Errors.hs \
 	test/hs/Test/Ganeti/HTools/Backend/Simu.hs \
@@ -655,6 +759,7 @@
 	test/hs/Test/Ganeti/HTools/CLI.hs \
 	test/hs/Test/Ganeti/HTools/Cluster.hs \
 	test/hs/Test/Ganeti/HTools/Container.hs \
+	test/hs/Test/Ganeti/HTools/ExtLoader.hs \
 	test/hs/Test/Ganeti/HTools/Graph.hs \
 	test/hs/Test/Ganeti/HTools/Instance.hs \
 	test/hs/Test/Ganeti/HTools/Loader.hs \
@@ -691,12 +796,16 @@
 
 HS_BUILT_SRCS = \
 	test/hs/Test/Ganeti/TestImports.hs \
-	src/Ganeti/Constants.hs \
+	src/AutoConf.hs \
+	src/Ganeti/Hs2Py/ListConstants.hs \
 	src/Ganeti/Curl/Internal.hs \
 	src/Ganeti/Version.hs
 HS_BUILT_SRCS_IN = \
 	$(patsubst %,%.in,$(filter-out src/Ganeti/Curl/Internal.hs,$(HS_BUILT_SRCS))) \
-	src/Ganeti/Curl/Internal.hsc
+	src/Ganeti/Curl/Internal.hsc \
+	lib/_constants.py.in \
+	lib/opcodes.py.in_after \
+	lib/opcodes.py.in_before
 
 HS_LIBTESTBUILT_SRCS = $(HS_LIBTEST_SRCS) $(HS_BUILT_SRCS)
 
@@ -707,15 +816,15 @@
 doc/man-html/index.html: doc/manpages-enabled.rst $(mandocrst)
 
 # Note: we use here an order-only prerequisite, as the contents of
-# _autoconf.py are not actually influencing the html build output: it
+# _constants.py are not actually influencing the html build output: it
 # has to exist in order for the sphinx module to be loaded
 # successfully, but we certainly don't want the docs to be rebuilt if
 # it changes
 doc/html/index.html doc/man-html/index.html: $(docinput) doc/conf.py \
 	configure.ac $(RUN_IN_TEMPDIR) lib/build/sphinx_ext.py \
-	lib/build/shell_example_lexer.py lib/opcodes.py lib/ht.py \
+	lib/build/shell_example_lexer.py lib/ht.py \
 	doc/css/style.css lib/rapi/connector.py lib/rapi/rlib2.py \
-	| $(BUILT_PYTHON_SOURCES)
+	autotools/sphinx-wrapper | $(BUILT_PYTHON_SOURCES)
 	@test -n "$(SPHINX)" || \
 	    { echo 'sphinx-build' not found during configure; exit 1; }
 if !MANPAGES_IN_DOC
@@ -824,15 +933,21 @@
 	scripts/gnt-os \
 	scripts/gnt-storage
 
+gnt_scripts_basenames = \
+	$(patsubst scripts/%,%,$(patsubst daemons/%,%,$(gnt_scripts) $(gnt_python_sbin_SCRIPTS)))
+
+gnt_python_sbin_SCRIPTS = \
+	$(PYTHON_BOOTSTRAP_SBIN)
+
+gntpython_SCRIPTS = $(gnt_scripts)
+
 PYTHON_BOOTSTRAP_SBIN = \
 	daemons/ganeti-masterd \
 	daemons/ganeti-noded \
 	daemons/ganeti-rapi \
-	daemons/ganeti-watcher \
-	$(gnt_scripts)
+	daemons/ganeti-watcher
 
 PYTHON_BOOTSTRAP = \
-	$(PYTHON_BOOTSTRAP_SBIN) \
 	tools/burnin \
 	tools/ensure-dirs \
 	tools/node-cleanup \
@@ -851,6 +966,7 @@
 	qa/qa_instance.py \
 	qa/qa_instance_utils.py \
 	qa/qa_job.py \
+	qa/qa_job_utils.py \
 	qa/qa_monitoring.py \
 	qa/qa_logging.py \
 	qa/qa_node.py \
@@ -924,9 +1040,13 @@
 	tools/ganeti-listrunner
 
 nodist_sbin_SCRIPTS = \
-	$(PYTHON_BOOTSTRAP_SBIN) \
 	daemons/ganeti-cleaner
 
+# strip path prefixes off the sbin scripts
+all_sbin_scripts = \
+	$(patsubst tools/%,%,$(patsubst daemons/%,%,$(patsubst scripts/%,%,\
+	$(patsubst src/%,%,$(dist_sbin_SCRIPTS) $(nodist_sbin_SCRIPTS)))))
+
 if ENABLE_CONFD
 src/ganeti-confd: src/hconfd
 	cp -f $< $@
@@ -952,23 +1072,41 @@
 	tools/lvmstrap \
 	tools/move-instance \
 	tools/ovfconverter \
+	tools/post-upgrade \
 	tools/sanitize-config
 
+python_scripts_shebang = \
+	$(patsubst tools/%,tools/shebang/%, $(python_scripts))
+
+tools/shebang/%: tools/%
+	mkdir -p tools/shebang
+	head -1 $< | sed 's|#!/usr/bin/python|#!$(PYTHON)|' > $@
+	echo '# Generated file; do not edit.' >> $@
+	tail -n +2 $< >> $@
+
 dist_tools_SCRIPTS = \
-	$(python_scripts) \
-	tools/burnin \
 	tools/kvm-console-wrapper \
 	tools/master-ip-setup \
 	tools/xen-console-wrapper
 
-nodist_tools_python_scripts = \
-	tools/node-cleanup
+dist_tools_python_SCRIPTS = \
+	tools/burnin
+
+nodist_tools_python_SCRIPTS = \
+	tools/node-cleanup \
+        $(python_scripts_shebang)
+
+tools_python_basenames = \
+	$(patsubst shebang/%,%,\
+	$(patsubst tools/%,%,\
+	$(dist_tools_python_SCRIPTS) $(nodist_tools_python_SCRIPTS)))
 
 nodist_tools_SCRIPTS = \
-	$(nodist_tools_python_scripts) \
 	tools/users-setup \
 	tools/vcluster-setup
 
+tools_basenames = $(patsubst tools/%,%,$(nodist_tools_SCRIPTS) $(dist_tools_SCRIPTS))
+
 pkglib_python_scripts = \
 	daemons/import-export \
 	tools/check-cert-expired
@@ -978,22 +1116,27 @@
 	tools/node-daemon-setup \
 	tools/prepare-node-join
 
+pkglib_python_basenames = \
+	$(patsubst daemons/%,%,$(patsubst tools/%,%,\
+	$(pkglib_python_scripts) $(nodist_pkglib_python_scripts)))
+
 myexeclib_SCRIPTS = \
 	daemons/daemon-util \
 	tools/kvm-ifup \
 	tools/vif-ganeti \
 	tools/net-common \
-	$(pkglib_python_scripts) \
 	$(HS_MYEXECLIB_PROGS)
 
-nodist_myexeclib_SCRIPTS = \
-	$(nodist_pkglib_python_scripts)
+# compute the basenames of the myexeclib_scripts
+myexeclib_scripts_basenames = \
+	$(patsubst tools/%,%,$(patsubst daemons/%,%,$(patsubst src/%,%,$(myexeclib_SCRIPTS))))
 
 EXTRA_DIST = \
 	NEWS \
 	UPGRADE \
 	epydoc.conf.in \
 	pylintrc \
+	pylintrc-test \
 	autotools/build-bash-completion \
 	autotools/build-rpc \
 	autotools/check-header \
@@ -1005,9 +1148,9 @@
 	autotools/check-python-code \
 	autotools/check-tar \
 	autotools/check-version \
-	autotools/convert-constants \
 	autotools/docpp \
 	autotools/gen-py-coverage \
+	autotools/print-py-constants \
 	autotools/sphinx-wrapper \
 	autotools/testrunner \
 	autotools/wrong-hardcoded-paths \
@@ -1015,12 +1158,14 @@
 	daemons/daemon-util.in \
 	daemons/ganeti-cleaner.in \
 	$(pkglib_python_scripts) \
+	devel/build_chroot \
 	devel/upload \
 	devel/webserver \
 	tools/kvm-ifup.in \
 	tools/vif-ganeti.in \
 	tools/net-common.in \
 	tools/vcluster-setup.in \
+        $(python_scripts) \
 	$(docinput) \
 	doc/html \
 	$(BUILT_EXAMPLES:%=%.in) \
@@ -1033,11 +1178,9 @@
 	doc/users/groupmemberships.in \
 	doc/users/groups.in \
 	doc/users/users.in \
-	test/py/lockperf.py \
-	test/py/testutils.py \
-	test/py/mocks.py \
 	$(dist_TESTS) \
 	$(TEST_FILES) \
+	$(python_test_support) \
 	man/footer.rst \
 	$(manrst) \
 	$(maninput) \
@@ -1095,6 +1238,10 @@
 	$(patsubst %.html,%.html.in,$(manhtml)) \
 	$(mangen)
 
+manfullpath = $(patsubst man/%.1,man1/%.1,\
+	$(patsubst man/%.7,man7/%.7,\
+	$(patsubst man/%.8,man8/%.8,$(man_MANS))))
+
 TEST_FILES = \
 	test/autotools/autotools-check-news.test \
 	test/data/htools/clean-nonzero-score.data \
@@ -1110,8 +1257,12 @@
 	test/data/htools/hail-invalid-reloc.json \
 	test/data/htools/hail-node-evac.json \
 	test/data/htools/hail-reloc-drbd.json \
+	test/data/htools/hbal-dyn.data \
+	test/data/htools/hbal-evac.data \
 	test/data/htools/hbal-excl-tags.data \
 	test/data/htools/hbal-split-insts.data \
+	test/data/htools/hspace-groups-one.data \
+	test/data/htools/hspace-groups-two.data \
 	test/data/htools/hspace-tiered-dualspec-exclusive.data \
 	test/data/htools/hspace-tiered-dualspec.data \
 	test/data/htools/hspace-tiered-exclusive.data \
@@ -1139,6 +1290,7 @@
 	test/hs/shelltests/htools-dynutil.test \
 	test/hs/shelltests/htools-excl.test \
 	test/hs/shelltests/htools-hail.test \
+	test/hs/shelltests/htools-hbal-evac.test \
 	test/hs/shelltests/htools-hroller.test \
 	test/hs/shelltests/htools-hspace.test \
 	test/hs/shelltests/htools-invalid.test \
@@ -1187,6 +1339,7 @@
 	test/data/kvm_0.9.1_help_boot_test.txt \
 	test/data/kvm_1.0_help.txt \
 	test/data/kvm_1.1.2_help.txt \
+	test/data/kvm_runtime.json \
 	test/data/lvs_lv.txt \
 	test/data/NEWS_OK.txt \
 	test/data/NEWS_previous_unreleased.txt \
@@ -1247,6 +1400,16 @@
 
 python_tests = \
 	doc/examples/rapi_testutils.py \
+	test/py/cmdlib/backup_unittest.py \
+	test/py/cmdlib/cluster_unittest.py \
+	test/py/cmdlib/cmdlib_unittest.py \
+	test/py/cmdlib/group_unittest.py \
+	test/py/cmdlib/instance_unittest.py \
+	test/py/cmdlib/instance_migration_unittest.py \
+	test/py/cmdlib/instance_query_unittest.py \
+	test/py/cmdlib/instance_storage_unittest.py \
+	test/py/cmdlib/node_unittest.py \
+	test/py/cmdlib/test_unittest.py \
 	test/py/cfgupgrade_unittest.py \
 	test/py/docs_unittest.py \
 	test/py/ganeti.asyncnotifier_unittest.py \
@@ -1257,9 +1420,6 @@
 	test/py/ganeti.client.gnt_cluster_unittest.py \
 	test/py/ganeti.client.gnt_instance_unittest.py \
 	test/py/ganeti.client.gnt_job_unittest.py \
-	test/py/ganeti.cmdlib_unittest.py \
-	test/py/ganeti.cmdlib.cluster_unittest.py \
-	test/py/ganeti.cmdlib.instance_storage_unittest.py \
 	test/py/ganeti.compat_unittest.py \
 	test/py/ganeti.confd.client_unittest.py \
 	test/py/ganeti.config_unittest.py \
@@ -1323,6 +1483,7 @@
 	test/py/ganeti.utils.retry_unittest.py \
 	test/py/ganeti.utils.storage_unittest.py \
 	test/py/ganeti.utils.text_unittest.py \
+	test/py/ganeti.utils.version_unittest.py \
 	test/py/ganeti.utils.wrapper_unittest.py \
 	test/py/ganeti.utils.x509_unittest.py \
 	test/py/ganeti.utils_unittest.py \
@@ -1332,6 +1493,24 @@
 	test/py/qa.qa_config_unittest.py \
 	test/py/tempfile_fork_unittest.py
 
+python_test_support = \
+	test/py/__init__.py \
+	test/py/lockperf.py \
+	test/py/testutils.py \
+	test/py/mocks.py \
+	test/py/cmdlib/__init__.py \
+	test/py/cmdlib/testsupport/__init__.py \
+	test/py/cmdlib/testsupport/cmdlib_testcase.py \
+	test/py/cmdlib/testsupport/config_mock.py \
+	test/py/cmdlib/testsupport/iallocator_mock.py \
+	test/py/cmdlib/testsupport/lock_manager_mock.py \
+	test/py/cmdlib/testsupport/netutils_mock.py \
+	test/py/cmdlib/testsupport/processor_mock.py \
+	test/py/cmdlib/testsupport/rpc_runner_mock.py \
+	test/py/cmdlib/testsupport/ssh_mock.py \
+	test/py/cmdlib/testsupport/utils_mock.py \
+	test/py/cmdlib/testsupport/util.py
+
 haskell_tests = test/hs/htest
 
 dist_TESTS = \
@@ -1362,7 +1541,7 @@
 
 # Environment for all tests
 PLAIN_TESTS_ENVIRONMENT = \
-	PYTHONPATH=. \
+	PYTHONPATH=.:./test/py \
 	TOP_SRCDIR=$(abs_top_srcdir) TOP_BUILDDIR=$(abs_top_builddir) \
 	PYTHON=$(PYTHON) FAKEROOT=$(FAKEROOT_PATH) \
 	$(RUN_IN_TEMPDIR)
@@ -1396,6 +1575,7 @@
 
 if PY_UNIT
 all_python_code += $(python_tests)
+all_python_code += $(python_test_support)
 endif
 
 srclink_files = \
@@ -1429,6 +1609,7 @@
 	$(CHECK_IMPORTS) \
 	$(CHECK_HEADER) \
 	$(DOCPP) \
+	$(gnt_python_sbin_SCRIPTS) \
 	$(PYTHON_BOOTSTRAP)
 
 standalone_python_modules = \
@@ -1445,7 +1626,9 @@
 	$(CHECK_HEADER) \
 	$(DOCPP) \
 	$(PYTHON_BOOTSTRAP) \
-	qa
+	$(gnt_python_sbin_SCRIPTS) \
+	qa \
+	$(python_test_support)
 
 test/py/daemon-util_unittest.bash: daemons/daemon-util
 
@@ -1576,15 +1759,22 @@
 	VCSVER=`cat $(abs_top_srcdir)/vcs-version`; \
 	sed -e "s/%ver%/$$VCSVER/" < $< > $@
 
-src/Ganeti/Constants.hs: src/Ganeti/Constants.hs.in \
-	lib/constants.py lib/_autoconf.py lib/luxi.py lib/errors.py \
-	lib/jstore.py $(RUN_IN_TEMPDIR) \
-	$(CONVERT_CONSTANTS) $(built_base_sources) \
-	| lib/_vcsversion.py
-	set -e; \
-	{ cat $< ; \
-	  PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CONVERT_CONSTANTS); \
-	} > $@
+src/Ganeti/Hs2Py/ListConstants.hs: src/Ganeti/Hs2Py/ListConstants.hs.in \
+				   src/Ganeti/Constants.hs \
+			         | stamp-directories
+	@echo Generating $@
+	@set -e; \
+## Extract constant names from 'Constants.hs' by extracting the left
+## side of all lines containing an equal sign (i.e., '=') and
+## prepending the apostrophe sign (i.e., "'").
+##
+## For example, the constant
+##   adminstDown = ...
+## becomes
+##   'adminstDown
+	NAMES=$$(sed -e "/^--/ d" $(abs_top_srcdir)/src/Ganeti/Constants.hs |\
+		 sed -n -e "/=/ s/\(.*\) =.*/    '\1:/g p"); \
+	m4 -DPY_CONSTANT_NAMES="$$NAMES" $(abs_top_srcdir)/$< > $@
 
 src/Ganeti/Curl/Internal.hs: src/Ganeti/Curl/Internal.hsc | stamp-directories
 	hsc2hs -o $@ $<
@@ -1599,86 +1789,82 @@
 	  done ; \
 	} > $@
 
-lib/_autoconf.py: Makefile | stamp-directories
-	set -e; \
-	{ echo '# This file is automatically generated, do not edit!'; \
-	  echo '#'; \
-	  echo ''; \
-	  echo '"""Build-time configuration for Ganeti.'; \
-	  echo '';\
-	  echo 'This file is autogenerated by the build process.'; \
-	  echo 'For any changes you need to re-run ./configure (and'; \
-	  echo 'not edit by hand).'; \
-	  echo ''; \
-	  echo '"""'; \
-	  echo ''; \
-	  echo '# pylint: disable=C0301,C0324'; \
-	  echo '# because this is autogenerated, we do not want'; \
-	  echo '# style warnings' ; \
-	  echo ''; \
-	  echo "PACKAGE_VERSION = '$(PACKAGE_VERSION)'"; \
-	  echo "VERSION_MAJOR = '$(VERSION_MAJOR)'"; \
-	  echo "VERSION_MINOR = '$(VERSION_MINOR)'"; \
-	  echo "VERSION_REVISION = '$(VERSION_REVISION)'"; \
-	  echo "VERSION_SUFFIX = '$(VERSION_SUFFIX)'"; \
-	  echo "VERSION_FULL = '$(VERSION_FULL)'"; \
-	  echo "LOCALSTATEDIR = '$(localstatedir)'"; \
-	  echo "SYSCONFDIR = '$(sysconfdir)'"; \
-	  echo "SSH_CONFIG_DIR = '$(SSH_CONFIG_DIR)'"; \
-	  echo "SSH_LOGIN_USER = '$(SSH_LOGIN_USER)'"; \
-	  echo "SSH_CONSOLE_USER = '$(SSH_CONSOLE_USER)'"; \
-	  echo "EXPORT_DIR = '$(EXPORT_DIR)'"; \
-	  echo "OS_SEARCH_PATH = [$(OS_SEARCH_PATH)]"; \
-	  echo "ES_SEARCH_PATH = [$(ES_SEARCH_PATH)]"; \
-	  echo "XEN_BOOTLOADER = '$(XEN_BOOTLOADER)'"; \
-	  echo "XEN_CONFIG_DIR = '$(XEN_CONFIG_DIR)'"; \
-	  echo "XEN_KERNEL = '$(XEN_KERNEL)'"; \
-	  echo "XEN_INITRD = '$(XEN_INITRD)'"; \
-	  echo "KVM_KERNEL = '$(KVM_KERNEL)'"; \
-	  echo "IALLOCATOR_SEARCH_PATH = [$(IALLOCATOR_SEARCH_PATH)]"; \
-	  echo "KVM_PATH = '$(KVM_PATH)'"; \
-	  echo "IP_PATH = '$(IP_PATH)'"; \
-	  echo "SOCAT_PATH = '$(SOCAT)'"; \
-	  echo "SOCAT_USE_ESCAPE = $(SOCAT_USE_ESCAPE)"; \
-	  echo "SOCAT_USE_COMPRESS = $(SOCAT_USE_COMPRESS)"; \
-	  echo "LVM_STRIPECOUNT = $(LVM_STRIPECOUNT)"; \
-	  echo "TOOLSDIR = '$(toolsdir)'"; \
-	  echo "GNT_SCRIPTS = [$(foreach i,$(notdir $(gnt_scripts)),'$(i)',)]"; \
-	  echo "HTOOLS_PROGS = [$(foreach i,$(HS_HTOOLS_PROGS),'$(i)',)]"; \
-	  echo "PKGLIBDIR = '$(pkglibdir)'"; \
-	  echo "DRBD_BARRIERS = '$(DRBD_BARRIERS)'"; \
-	  echo "DRBD_NO_META_FLUSH = $(DRBD_NO_META_FLUSH)"; \
-	  echo "SYSLOG_USAGE = '$(SYSLOG_USAGE)'"; \
-	  echo "DAEMONS_GROUP = '$(DAEMONS_GROUP)'"; \
-	  echo "ADMIN_GROUP = '$(ADMIN_GROUP)'"; \
-	  echo "MASTERD_USER = '$(MASTERD_USER)'"; \
-	  echo "MASTERD_GROUP = '$(MASTERD_GROUP)'"; \
-	  echo "RAPI_USER = '$(RAPI_USER)'"; \
-	  echo "RAPI_GROUP = '$(RAPI_GROUP)'"; \
-	  echo "CONFD_USER = '$(CONFD_USER)'"; \
-	  echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \
-	  echo "LUXID_USER = '$(LUXID_USER)'"; \
-	  echo "LUXID_GROUP = '$(LUXID_GROUP)'"; \
-	  echo "NODED_USER = '$(NODED_USER)'"; \
-	  echo "NODED_GROUP = '$(NODED_GROUP)'"; \
-	  echo "MOND_USER = '$(MOND_USER)'"; \
-	  echo "MOND_GROUP = '$(MOND_GROUP)'"; \
-	  echo "DISK_SEPARATOR = '$(DISK_SEPARATOR)'"; \
-	  echo "QEMUIMG_PATH = '$(QEMUIMG_PATH)'"; \
-	  echo "HTOOLS = True"; \
-	  echo "ENABLE_CONFD = $(ENABLE_CONFD)"; \
-	  echo "XEN_CMD = '$(XEN_CMD)'"; \
-	  echo "ENABLE_SPLIT_QUERY = $(ENABLE_SPLIT_QUERY)"; \
-	  echo "ENABLE_RESTRICTED_COMMANDS = $(ENABLE_RESTRICTED_COMMANDS)"; \
-	  echo "ENABLE_MOND = $(ENABLE_MOND)"; \
-## Write dictionary with man page name as the key and the section number as the
-## value
-	  echo "MAN_PAGES = {"; \
-	  for i in $(notdir $(man_MANS)); do \
-	    echo "$$i" | sed -re 's/^(.*)\.([0-9]+)$$/  "\1": \2,/g'; \
-	  done; \
-	  echo "}"; \
-	} > $@
+lib/_constants.py: Makefile src/hs2py lib/_constants.py.in | stamp-directories
+	cat $(abs_top_srcdir)/lib/_constants.py.in > $@
+	src/hs2py --constants >> $@
+
+lib/constants.py: lib/_constants.py
+
+src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
+	       | $(built_base_sources)
+	@echo "m4 ... >" $@
+	@m4 -DPACKAGE_VERSION="$(PACKAGE_VERSION)" \
+	    -DVERSION_MAJOR="$(VERSION_MAJOR)" \
+	    -DVERSION_MINOR="$(VERSION_MINOR)" \
+	    -DVERSION_REVISION="$(VERSION_REVISION)" \
+	    -DVERSION_SUFFIX="$(VERSION_SUFFIX)" \
+	    -DVERSION_FULL="$(VERSION_FULL)" \
+	    -DDIRVERSION="$(DIRVERSION)" \
+	    -DLOCALSTATEDIR="$(localstatedir)" \
+	    -DSYSCONFDIR="$(sysconfdir)" \
+	    -DSSH_CONFIG_DIR="$(SSH_CONFIG_DIR)" \
+	    -DSSH_LOGIN_USER="$(SSH_LOGIN_USER)" \
+	    -DSSH_CONSOLE_USER="$(SSH_CONSOLE_USER)" \
+	    -DEXPORT_DIR="$(EXPORT_DIR)" \
+	    -DBACKUP_DIR="$(backup_dir)" \
+	    -DOS_SEARCH_PATH="\"$(OS_SEARCH_PATH)\"" \
+	    -DES_SEARCH_PATH="\"$(ES_SEARCH_PATH)\"" \
+	    -DXEN_BOOTLOADER="$(XEN_BOOTLOADER)" \
+	    -DXEN_CONFIG_DIR="$(XEN_CONFIG_DIR)" \
+	    -DXEN_KERNEL="$(XEN_KERNEL)" \
+	    -DXEN_INITRD="$(XEN_INITRD)" \
+	    -DKVM_KERNEL="$(KVM_KERNEL)" \
+	    -DSHARED_FILE_STORAGE_DIR="$(SHARED_FILE_STORAGE_DIR)" \
+	    -DIALLOCATOR_SEARCH_PATH="\"$(IALLOCATOR_SEARCH_PATH)\"" \
+	    -DKVM_PATH="$(KVM_PATH)" \
+	    -DIP_PATH="$(IP_PATH)" \
+	    -DSOCAT_PATH="$(SOCAT)" \
+	    -DSOCAT_USE_ESCAPE="$(SOCAT_USE_ESCAPE)" \
+	    -DSOCAT_USE_COMPRESS="$(SOCAT_USE_COMPRESS)" \
+	    -DLVM_STRIPECOUNT="$(LVM_STRIPECOUNT)" \
+	    -DTOOLSDIR="$(libdir)/ganeti/tools" \
+	    -DGNT_SCRIPTS="$(foreach i,$(notdir $(gnt_scripts)),\"$(i)\":)" \
+	    -DHS_HTOOLS_PROGS="$(foreach i,$(HS_HTOOLS_PROGS),\"$(i)\":)" \
+	    -DPKGLIBDIR="$(libdir)/ganeti" \
+	    -DSHAREDIR="$(prefix)/share/ganeti" \
+	    -DVERSIONEDSHAREDIR="$(versionedsharedir)" \
+	    -DDRBD_BARRIERS="$(DRBD_BARRIERS)" \
+	    -DDRBD_NO_META_FLUSH="$(DRBD_NO_META_FLUSH)" \
+	    -DSYSLOG_USAGE="$(SYSLOG_USAGE)" \
+	    -DDAEMONS_GROUP="$(DAEMONS_GROUP)" \
+	    -DADMIN_GROUP="$(ADMIN_GROUP)" \
+	    -DMASTERD_USER="$(MASTERD_USER)" \
+	    -DMASTERD_GROUP="$(MASTERD_GROUP)" \
+	    -DRAPI_USER="$(RAPI_USER)" \
+	    -DRAPI_GROUP="$(RAPI_GROUP)" \
+	    -DCONFD_USER="$(CONFD_USER)" \
+	    -DCONFD_GROUP="$(CONFD_GROUP)" \
+	    -DLUXID_USER="$(LUXID_USER)" \
+	    -DLUXID_GROUP="$(LUXID_GROUP)" \
+	    -DNODED_USER="$(NODED_USER)" \
+	    -DNODED_GROUP="$(NODED_GROUP)" \
+	    -DMOND_USER="$(MOND_USER)" \
+	    -DMOND_GROUP="$(MOND_GROUP)" \
+	    -DDISK_SEPARATOR="$(DISK_SEPARATOR)" \
+	    -DQEMUIMG_PATH="$(QEMUIMG_PATH)" \
+	    -DHTOOLS="True" \
+	    -DENABLE_CONFD="$(ENABLE_CONFD)" \
+	    -DXEN_CMD="$(XEN_CMD)" \
+	    -DENABLE_SPLIT_QUERY="$(ENABLE_SPLIT_QUERY)" \
+	    -DENABLE_RESTRICTED_COMMANDS="$(ENABLE_RESTRICTED_COMMANDS)" \
+	    -DENABLE_MOND="$(ENABLE_MOND)" \
+	    -DHAS_GNU_LN="$(HAS_GNU_LN)" \
+	    -DMAN_PAGES="$$(for i in $(notdir $(man_MANS)); do \
+	                    echo -n "$$i" | sed -re 's/^(.*)\.([0-9]+)$$/("\1",\2):/g'; \
+	                    done)" \
+	    -DAF_INET4="$$(PYTHONPATH=. $(PYTHON) $(PRINT_PY_CONSTANTS) AF_INET4)" \
+	    -DAF_INET6="$$(PYTHONPATH=. $(PYTHON) $(PRINT_PY_CONSTANTS) AF_INET6)" \
+	$(abs_top_srcdir)/src/AutoConf.hs.in > $@
 
 lib/_vcsversion.py: Makefile vcs-version | stamp-directories
 	set -e; \
@@ -1701,6 +1887,12 @@
 	  echo "VCS_VERSION = '$$VCSVER'"; \
 	} > $@
 
+lib/opcodes.py: Makefile src/hs2py lib/opcodes.py.in_before \
+		lib/opcodes.py.in_after | stamp-directories
+	cat $(abs_top_srcdir)/lib/opcodes.py.in_before > $@
+	src/hs2py --opcodes >> $@
+	cat $(abs_top_srcdir)/lib/opcodes.py.in_after >> $@
+
 lib/_generated_rpc.py: lib/rpc_defs.py $(BUILD_RPC)
 	PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(BUILD_RPC) lib/rpc_defs.py > $@
 
@@ -1710,7 +1902,7 @@
 	  echo 'readonly LOCALSTATEDIR=$${LOCALSTATEDIR:-$${GANETI_ROOTDIR:-}$(localstatedir)}'; \
 	  echo 'readonly SYSCONFDIR=$${SYSCONFDIR:-$${GANETI_ROOTDIR:-}$(sysconfdir)}'; \
 	  echo; \
-	  echo 'readonly PKGLIBDIR=$(pkglibdir)'; \
+	  echo 'readonly PKGLIBDIR=$(libdir)/ganeti'; \
 	  echo 'readonly LOG_DIR="$$LOCALSTATEDIR/log/ganeti"'; \
 	  echo 'readonly RUN_DIR="$$LOCALSTATEDIR/run/ganeti"'; \
 	  echo 'readonly DATA_DIR="$$LOCALSTATEDIR/lib/ganeti"'; \
@@ -1725,8 +1917,8 @@
 	{ echo 's#@''PREFIX@#$(prefix)#g'; \
 	  echo 's#@''SYSCONFDIR@#$(sysconfdir)#g'; \
 	  echo 's#@''LOCALSTATEDIR@#$(localstatedir)#g'; \
-	  echo 's#@''BINDIR@#$(bindir)#g'; \
-	  echo 's#@''SBINDIR@#$(sbindir)#g'; \
+	  echo 's#@''BINDIR@#$(BINDIR)#g'; \
+	  echo 's#@''SBINDIR@#$(SBINDIR)#g'; \
 	  echo 's#@''LIBDIR@#$(libdir)#g'; \
 	  echo 's#@''GANETI_VERSION@#$(PACKAGE_VERSION)#g'; \
 	  echo 's#@''CUSTOM_XEN_BOOTLOADER@#$(XEN_BOOTLOADER)#g'; \
@@ -1735,7 +1927,7 @@
 	  echo 's#@''CUSTOM_IALLOCATOR_SEARCH_PATH@#$(IALLOCATOR_SEARCH_PATH)#g'; \
 	  echo 's#@''CUSTOM_EXPORT_DIR@#$(EXPORT_DIR)#g'; \
 	  echo 's#@''RPL_SSH_INITD_SCRIPT@#$(SSH_INITD_SCRIPT)#g'; \
-	  echo 's#@''PKGLIBDIR@#$(pkglibdir)#g'; \
+	  echo 's#@''PKGLIBDIR@#$(libdir)/ganeti#g'; \
 	  echo 's#@''GNTMASTERUSER@#$(MASTERD_USER)#g'; \
 	  echo 's#@''GNTRAPIUSER@#$(RAPI_USER)#g'; \
 	  echo 's#@''GNTCONFDUSER@#$(CONFD_USER)#g'; \
@@ -1772,10 +1964,10 @@
 tools/node-cleanup: MODULE = ganeti.tools.node_cleanup
 $(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst test/hs/%,%,$@)
 
-$(PYTHON_BOOTSTRAP): Makefile | stamp-directories
+$(PYTHON_BOOTSTRAP) $(gnt_scripts) $(gnt_python_sbin_SCRIPTS): Makefile | stamp-directories
 	test -n "$(MODULE)" || { echo Missing module; exit 1; }
 	set -e; \
-	{ echo '#!/usr/bin/python'; \
+	{ echo '#!${PYTHON}'; \
 	  echo '# This file is automatically generated, do not edit!'; \
 	  echo "# Edit $(MODULE) instead."; \
 	  echo; \
@@ -1850,12 +2042,16 @@
 	  test -z "$$error"; \
 	}
 
-.PHONY: check-local
-check-local: check-dirs $(GENERATED_FILES)
-	$(CHECK_PYTHON_CODE) $(check_python_code)
-	PYTHONPATH=. $(CHECK_HEADER) $(check_python_code)
-	$(CHECK_VERSION) $(VERSION) $(top_srcdir)/NEWS
+.PHONY: check-news
+check-news:
 	RELEASE=$(PACKAGE_VERSION) $(CHECK_NEWS) < $(top_srcdir)/NEWS
+
+.PHONY: check-local
+check-local: check-dirs check-news $(GENERATED_FILES)
+	$(CHECK_PYTHON_CODE) $(check_python_code)
+	PYTHONPATH=. $(CHECK_HEADER) \
+	    $(filter-out $(GENERATED_FILES),$(check_python_code))
+	$(CHECK_VERSION) $(VERSION) $(top_srcdir)/NEWS
 	PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CHECK_IMPORTS) . $(standalone_python_modules)
 	error= ; \
 	if [ "x`echo $(VERSION_SUFFIX)|grep 'alpha'`" == "x" ]; then \
@@ -1937,7 +2133,7 @@
 # For excluding pep8 expects filenames only, not whole paths
 PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES))))
 
-LINT_TARGETS = pylint pylint-qa
+LINT_TARGETS = pylint pylint-qa pylint-test
 if HAS_PEP8
 LINT_TARGETS += pep8
 endif
@@ -1959,6 +2155,12 @@
 	cd $(top_srcdir)/qa && \
 	  PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \
 	  --rcfile  ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts))
+# FIXME: lint all test code, not just the newly added test support
+pylint-test: $(GENERATED_FILES)
+	@test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
+	cd $(top_srcdir) && \
+		PYTHONPATH=.:./test/py $(PYLINT) $(LINT_OPTS) \
+		--rcfile=pylintrc-test  $(python_test_support)
 
 .PHONY: pep8
 pep8: $(GENERATED_FILES)
@@ -2047,6 +2249,42 @@
 	@mkdir_p@ "$(DESTDIR)${localstatedir}/lib/ganeti" \
 	  "$(DESTDIR)${localstatedir}/log/ganeti" \
 	  "$(DESTDIR)${localstatedir}/run/ganeti"
+	for dir in $(SYMLINK_TARGET_DIRS); do \
+	  @mkdir_p@  $(DESTDIR)$$dir; \
+	done
+	$(LN_S) -f $(sysconfdir)/ganeti/lib $(DESTDIR)$(defaultversiondir)
+	$(LN_S) -f $(sysconfdir)/ganeti/share $(DESTDIR)$(defaultversionedsharedir)
+	for prog in $(HS_BIN_ROLES); do \
+	  $(LN_S) -f $(defaultversiondir)$(BINDIR)/$$prog $(DESTDIR)$(BINDIR)/$$prog; \
+	done
+	$(LN_S) -f $(defaultversiondir)$(libdir)/ganeti/iallocators/hail $(DESTDIR)$(libdir)/ganeti/iallocators/hail
+	for prog in $(all_sbin_scripts); do \
+	  $(LN_S) -f $(defaultversiondir)$(SBINDIR)/$$prog $(DESTDIR)$(SBINDIR)/$$prog; \
+	done
+	for prog in $(gnt_scripts_basenames); do \
+	  $(LN_S) -f $(defaultversionedsharedir)/$$prog $(DESTDIR)$(SBINDIR)/$$prog; \
+	done
+	for prog in $(pkglib_python_basenames); do \
+	  $(LN_S) -f $(defaultversionedsharedir)/$$prog $(DESTDIR)$(libdir)/ganeti/$$prog; \
+	done
+	for prog in $(tools_python_basenames); do \
+	  $(LN_S) -f $(defaultversionedsharedir)/$$prog $(DESTDIR)$(libdir)/ganeti/tools/$$prog; \
+	done
+	for prog in $(tools_basenames); do \
+	  $(LN_S) -f $(defaultversiondir)/$(libdir)/ganeti/tools/$$prog $(DESTDIR)$(libdir)/ganeti/tools/$$prog; \
+	done
+	if ! test -n '$(ENABLE_MANPAGES)'; then \
+	  for man in $(manfullpath); do \
+	    $(LN_S) -f $(defaultversionedsharedir)/root$(MANDIR)/$$man $(DESTDIR)$(MANDIR)/$$man; \
+	  done; \
+	fi
+	for prog in $(myexeclib_scripts_basenames); do \
+	  $(LN_S) -f $(defaultversiondir)$(libdir)/ganeti/$$prog $(DESTDIR)$(libdir)/ganeti/$$prog; \
+	done
+if INSTALL_SYMLINKS
+	$(LN_S) -f $(versionedsharedir) $(DESTDIR)$(sysconfdir)/ganeti/share
+	$(LN_S) -f $(versiondir) $(DESTDIR)$(sysconfdir)/ganeti/lib
+endif
 
 .PHONY: apidoc
 if WANT_HSAPIDOC
@@ -2142,7 +2380,7 @@
 	hpc sum --union $(HPCEXCL) \
 	  htest.tix hpc-htools.tix hpc-mon-collector.tix > coverage-hs.tix
 	hpc markup --destdir=$(COVERAGE_HS_DIR) coverage-hs.tix
-	hpc report coverage-hs.tix
+	hpc report coverage-hs.tix | tee $(COVERAGE_HS_DIR)/report.txt
 	$(LN_S) -f hpc_index.html $(COVERAGE_HS_DIR)/index.html
 
 # Special "kind-of-QA" target for htools, needs special setup (all
@@ -2194,4 +2432,9 @@
 
 -include ./Makefile.local
 
+# support inspecting the value of a make variable
+
+print-%:
+	@echo $($*)
+
 # vim: set noet :
diff --git a/NEWS b/NEWS
index d9ccae3..ea07c71 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,354 @@
 ====
 
 
+Version 2.10.7
+--------------
+
+*(Released Thu, 7 Aug 2014)*
+
+Important security release. In 2.10.0, the
+'gnt-cluster upgrade' command was introduced. Before
+performing an upgrade, the configuration directory of
+the cluster is backed up. Unfortunately, the archive was
+written with permissions that make it possible for
+non-privileged users to read the archive and thus have
+access to cluster and RAPI keys. After this release,
+the archive will be created with privileged access only.
+
+We strongly advise you to restrict the permissions of
+previously created archives. The archives are found in
+/var/lib/ganeti*.tar (unless otherwise configured with
+--localstatedir or --with-backup-dir).
+
+If you suspect that non-privileged users have accessed
+your archives already, we advise you to renew the
+cluster's crypto keys using 'gnt-cluster renew-crypto'
+and to reset the RAPI credentials by editing
+/var/lib/ganeti/rapi_users (respectively under a
+different path if configured differently with
+--localstatedir).
+
+Other changes included in this release:
+
+- Fix handling of Xen instance states.
+- Fix NIC configuration with absent NIC VLAN
+- Adapt relative path expansion in PATH to new environment
+- Exclude archived jobs from configuration backups
+- Fix RAPI for split query setup
+- Allow disk hot-remove even with chroot or SM
+
+Inherited from the 2.9 branch:
+
+- Make htools tolerate missing 'spfree' on luxi
+
+
+Version 2.10.6
+--------------
+
+*(Released Mon, 30 Jun 2014)*
+
+- Make Ganeti tolerant towards differnt openssl library
+  version on different nodes (issue 853).
+- Allow hspace to make useful predictions in multi-group
+  clusters with one group overfull (isse 861).
+- Various gnt-network related fixes.
+- Fix disk hotplug with userspace access.
+- Various documentation errors fixed.
+
+
+Version 2.10.5
+--------------
+
+*(Released Mon, 2 Jun 2014)*
+
+- Two new options have been added to gnt-group evacuate.
+  The 'sequential' option forces all the evacuation steps to
+  be carried out sequentially, thus avoiding congestion on a
+  slow link between node groups. The 'force-failover' option
+  disallows migrations and forces failovers to be used instead.
+  In this way evacuation to a group with vastly differnet
+  hypervisor is possible.
+- In tiered allocation, when looking for ways on how to shrink
+  an instance, the canoncial path is tried first, i.e., in each
+  step reduce on the resource most placements are blocked on. Only
+  if no smaller fitting instance can be found shrinking a single
+  resource till fit is tried.
+- For finding the placement of an instance, the duplicate computations
+  in the computation of the various cluster scores are computed only
+  once. This significantly improves the performance of hspace for DRBD
+  on large clusters; for other clusters, a slight performance decrease
+  might occur. Moreover, due to the changed order, floating point
+  number inaccuracies accumulate differently, thus resulting in different
+  cluster scores. It has been verified that the effect of these different
+  roundings is less than 1e-12.
+- network queries fixed with respect to instances
+- relax too strict prerequisite in LUClusterSetParams for DRBD helpers
+- VArious improvements to QA and build-time tests
+
+
+Version 2.10.4
+--------------
+
+*(Released Thu, 15 May 2014)*
+
+- Support restricted migration in hbal
+- Fix for the --shared-file-storage-dir of gnt-cluster modify (issue 811)
+- Fail in replace-disks if attaching disks fails (issue 814)
+- Set IFF_ONE_QUEUE on created tap interfaces for KVM
+- Small fixes and enhancements in the build system
+- Various documentation fixes (e.g. issue 810)
+
+
+Version 2.10.3
+--------------
+
+*(Released Wed, 16 Apr 2014)*
+
+- Fix filtering of pending jobs with -o id (issue 778)
+- Make RAPI API calls more symmetric (issue 770)
+- Make parsing of old cluster configuration more robust (issue 783)
+- Fix wrong output of gnt-instance info after migrations
+- Fix reserved PCI slots for KVM hotplugging
+- Use runtime hypervisor parameters to calculate bockdevice options for KVM
+- Fix high node daemon load during disk sync if the sync is paused manually
+  (issue 792)
+- Improve opportunistic locking during instance creation (issue 791)
+
+Inherited from the 2.9 branch:
+
+- Make watcher submit queries low priority (issue 772)
+- Add reason parameter to RAPI client functions (issue 776)
+- Fix failing gnt-node list-drbd command (issue 777)
+- Properly display fake job locks in gnt-debug.
+- small fixes in documentation
+
+
+Version 2.10.2
+--------------
+
+*(Released Mon, 24 Mar 2014)*
+
+- Fix conflict between virtio + spice or soundhw (issue 757)
+- accept relative paths in gnt-cluster copyfile (issue 754)
+- Introduce shutdown timeout for 'xm shutdown' command
+- Improve RAPI detection of the watcher (issue 752)
+
+
+Version 2.10.1
+--------------
+
+*(Released Wed, 5 Mar 2014)*
+
+- Fix incorrect invocation of hooks on offline nodes (issue 742)
+- Fix incorrect exit code of gnt-cluster verify in certain circumstances
+  (issue 744)
+
+Inherited from the 2.9 branch:
+
+- Fix overflow problem in hbal that caused it to break when waiting for
+  jobs for more than 10 minutes (issue 717)
+- Make hbal properly handle non-LVM storage
+- Properly export and import NIC parameters, and do so in a backwards
+  compatible way (issue 716)
+- Fix net-common script in case of routed mode (issue 728)
+- Improve documentation (issues 724, 730)
+
+
+Version 2.10.0
+--------------
+
+*(Released Thu, 20 Feb 2014)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Adding disks with 'gnt-instance modify' now waits for the disks to sync per
+  default. Specify --no-wait-for-sync to override this behavior.
+- The Ganeti python code now adheres to a private-module layout. In particular,
+  the module 'ganeti' is no longer in the python search path.
+- On instance allocation, the iallocator now considers non-LVM storage
+  properly. In particular, actual file storage space information is used
+  when allocating space for a file/sharedfile instance.
+- When disabling disk templates cluster-wide, the cluster now first
+  checks whether there are instances still using those templates.
+- 'gnt-node list-storage' now also reports storage information about
+  file-based storage types.
+- In case of non drbd instances, export \*_SECONDARY environment variables
+  as empty strings (and not "None") during 'instance-migrate' related hooks.
+
+New features
+~~~~~~~~~~~~
+
+- KVM hypervisors can now access RBD storage directly without having to
+  go through a block device.
+- A new command 'gnt-cluster upgrade' was added that automates the upgrade
+  procedure between two Ganeti versions that are both 2.10 or higher.
+- The move-instance command can now change disk templates when moving
+  instances, and does not require any node placement options to be
+  specified if the destination cluster has a default iallocator.
+- Users can now change the soundhw and cpuid settings for XEN hypervisors.
+- Hail and hbal now have the (optional) capability of accessing average CPU
+  load information through the monitoring deamon, and to use it to dynamically
+  adapt the allocation of instances.
+- Hotplug support. Introduce new option '--hotplug' to ``gnt-instance modify``
+  so that disk and NIC modifications take effect without the need of actual
+  reboot. There are a couple of constrains currently for this feature:
+
+   - only KVM hypervisor (versions >= 1.0) supports it,
+   - one can not (yet) hotplug a disk using userspace access mode for RBD
+   - in case of a downgrade instances should suffer a reboot in order to
+     be migratable (due to core change of runtime files)
+   - ``python-fdsend`` is required for NIC hotplugging.
+
+Misc changes
+~~~~~~~~~~~~
+
+- A new test framework for logical units was introduced and the test
+  coverage for logical units was improved significantly.
+- Opcodes are entirely generated from Haskell using the tool 'hs2py' and
+  the module 'src/Ganeti/OpCodes.hs'.
+- Constants are also generated from Haskell using the tool
+  'hs2py-constants' and the module 'src/Ganeti/Constants.hs', with the
+  exception of socket related constants, which require changing the
+  cluster configuration file, and HVS related constants, because they
+  are part of a port of instance queries to Haskell.  As a result, these
+  changes will be part of the next release of Ganeti.
+
+New dependencies
+~~~~~~~~~~~~~~~~
+
+The following new dependencies have been added/updated.
+
+Python
+
+- The version requirements for ``python-mock`` have increased to at least
+  version 1.0.1. It is still used for testing only.
+- ``python-fdsend`` (https://gitorious.org/python-fdsend) is optional
+  but required for KVM NIC hotplugging to work.
+
+Since 2.10.0 rc3
+~~~~~~~~~~~~~~~~
+
+- Fix integer overflow problem in hbal
+
+
+Version 2.10.0 rc3
+------------------
+
+*(Released Wed, 12 Feb 2014)*
+
+This was the third RC release of the 2.10 series. Since 2.10.0 rc2:
+
+- Improved hotplug robustness
+- Start Ganeti daemons after ensure-dirs during upgrade
+- Documentation improvements
+
+Inherited from the 2.9 branch:
+
+- Fix the RAPI instances-multi-alloc call
+- assign unique filenames to file-based disks
+- gracefully handle degraded non-diskless instances with 0 disks (issue 697)
+- noded now runs with its specified group, which is the default group,
+  defaulting to root (issue 707)
+- make using UUIDs to identify nodes in gnt-node consistently possible
+  (issue 703)
+
+
+Version 2.10.0 rc2
+------------------
+
+*(Released Fri, 31 Jan 2014)*
+
+This was the second RC release of the 2.10 series. Since 2.10.0 rc1:
+
+- Documentation improvements
+- Run drbdsetup syncer only on network attach
+- Include target node in hooks nodes for migration
+- Fix configure dirs
+- Support post-upgrade hooks during cluster upgrades
+
+Inherited from the 2.9 branch:
+
+- Ensure that all the hypervisors exist in the config file (Issue 640)
+- Correctly recognise the role as master node (Issue 687)
+- configure: allow detection of Sphinx 1.2+ (Issue 502)
+- gnt-instance now honors the KVM path correctly (Issue 691)
+
+Inherited from the 2.8 branch:
+
+- Change the list separator for the usb_devices parameter from comma to space.
+  Commas could not work because they are already the hypervisor option
+  separator (Issue 649)
+- Add support for blktap2 file-driver (Issue 638)
+- Add network tag definitions to the haskell codebase (Issue 641)
+- Fix RAPI network tag handling
+- Add the network tags to the tags searched by gnt-cluster search-tags
+- Fix caching bug preventing jobs from being cancelled
+- Start-master/stop-master was always failing if ConfD was disabled. (Issue 685)
+
+
+Version 2.10.0 rc1
+------------------
+
+*(Released Tue, 17 Dec 2013)*
+
+This was the first RC release of the 2.10 series. Since 2.10.0 beta1:
+
+- All known issues in 2.10.0 beta1 have been resolved (see changes from
+  the 2.8 branch).
+- Improve handling of KVM runtime files from earlier Ganeti versions
+- Documentation fixes
+
+Inherited from the 2.9 branch:
+
+- use custom KVM path if set for version checking
+- SingleNotifyPipeCondition: don't share pollers
+
+Inherited from the 2.8 branch:
+
+- Fixed Luxi daemon socket permissions after master-failover
+- Improve IP version detection code directly checking for colons rather than
+  passing the family from the cluster object
+- Fix NODE/NODE_RES locking in LUInstanceCreate by not acquiring NODE_RES locks
+  opportunistically anymore (Issue 622)
+- Allow link local IPv6 gateways (Issue 624)
+- Fix error printing (Issue 616)
+- Fix a bug in InstanceSetParams concerning names: in case no name is passed in
+  disk modifications, keep the old one. If name=none then set disk name to
+  None.
+- Update build_chroot script to work with the latest hackage packages
+- Add a packet number limit to "fping" in master-ip-setup (Issue 630)
+- Fix evacuation out of drained node (Issue 615)
+- Add default file_driver if missing (Issue 571)
+- Fix job error message after unclean master shutdown (Issue 618)
+- Lock group(s) when creating instances (Issue 621)
+- SetDiskID() before accepting an instance (Issue 633)
+- Allow the ext template disks to receive arbitrary parameters, both at creation
+  time and while being modified
+- Xen handle domain shutdown (future proofing cherry-pick)
+- Refactor reading live data in htools (future proofing cherry-pick)
+
+
+Version 2.10.0 beta1
+--------------------
+
+*(Released Wed, 27 Nov 2013)*
+
+This was the first beta release of the 2.10 series. All important changes
+are listed in the latest 2.10 entry.
+
+Known issues
+~~~~~~~~~~~~
+
+The following issues are known to be present in the beta and will be fixed
+before rc1.
+
+- Issue 477: Wrong permissions for confd LUXI socket
+- Issue 621: Instance related opcodes do not aquire network/group locks
+- Issue 622: Assertion Error: Node locks differ from node resource locks
+- Issue 623: IPv6 Masterd <-> Luxid communication error
+
+
 Version 2.9.7
 -------------
 
diff --git a/README b/README
index 4266b70..6ef2668 100644
--- a/README
+++ b/README
@@ -1,5 +1,5 @@
-Ganeti 2.9
-==========
+Ganeti 2.10
+===========
 
 For installation instructions, read the INSTALL and the doc/install.rst
 files.
diff --git a/UPGRADE b/UPGRADE
index e193cfb..aef642c 100644
--- a/UPGRADE
+++ b/UPGRADE
@@ -42,6 +42,10 @@
 
     $ tar czf /var/lib/ganeti-$(date +\%FT\%T).tar.gz -C /var/lib ganeti
 
+    (``/var/lib/ganeti`` can also contain exported instances, so make sure to
+    backup only files you are interested in. Use ``--exclude export`` for
+    example)
+
 #. Install new Ganeti version on all nodes
 #. Run cfgupgrade on the master node::
 
diff --git a/autotools/ac_ghc_pkg.m4 b/autotools/ac_ghc_pkg.m4
index 702666e..163ceda 100644
--- a/autotools/ac_ghc_pkg.m4
+++ b/autotools/ac_ghc_pkg.m4
@@ -1,21 +1,30 @@
 #####
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #####
 #
diff --git a/autotools/build-bash-completion b/autotools/build-bash-completion
index 63def12..d8cddd9 100755
--- a/autotools/build-bash-completion
+++ b/autotools/build-bash-completion
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to generate bash_completion script for Ganeti.
@@ -33,6 +42,10 @@
 import optparse
 from cStringIO import StringIO
 
+# _constants shouldn't be imported from anywhere except constants.py, but we're
+# making an exception here because this script is only used at build time.
+from ganeti import _constants
+
 from ganeti import constants
 from ganeti import cli
 from ganeti import utils
@@ -41,24 +54,12 @@
 
 from ganeti.tools import burnin
 
-# _autoconf shouldn't be imported from anywhere except constants.py, but we're
-# making an exception here because this script is only used at build time.
-from ganeti import _autoconf
-
 #: Regular expression describing desired format of option names. Long names can
 #: contain lowercase characters, numbers and dashes only.
 _OPT_NAME_RE = re.compile(r"^-[a-zA-Z0-9]|--[a-z][-a-z0-9]+$")
 
 
-def WritePreamble(sw, support_debug):
-  """Writes the script preamble.
-
-  Helper functions should be written here.
-
-  """
-  sw.Write("# This script is automatically generated at build time.")
-  sw.Write("# Do not modify manually.")
-
+def _WriteGntLog(sw, support_debug):
   if support_debug:
     sw.Write("_gnt_log() {")
     sw.IncIndent()
@@ -82,6 +83,8 @@
       sw.DecIndent()
     sw.Write("}")
 
+
+def _WriteNodes(sw):
   sw.Write("_ganeti_nodes() {")
   sw.IncIndent()
   try:
@@ -91,6 +94,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteInstances(sw):
   sw.Write("_ganeti_instances() {")
   sw.IncIndent()
   try:
@@ -101,6 +106,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteJobs(sw):
   sw.Write("_ganeti_jobs() {")
   sw.IncIndent()
   try:
@@ -113,6 +120,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteOSAndIAllocator(sw):
   for (fnname, paths) in [
     ("os", pathutils.OS_SEARCH_PATH),
     ("iallocator", constants.IALLOCATOR_SEARCH_PATH),
@@ -128,6 +137,8 @@
       sw.DecIndent()
     sw.Write("}")
 
+
+def _WriteNodegroup(sw):
   sw.Write("_ganeti_nodegroup() {")
   sw.IncIndent()
   try:
@@ -137,6 +148,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteNetwork(sw):
   sw.Write("_ganeti_network() {")
   sw.IncIndent()
   try:
@@ -146,6 +159,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteFindFirstArg(sw):
   # Params: <offset> <options with values> <options without values>
   # Result variable: $first_arg_idx
   sw.Write("_ganeti_find_first_arg() {")
@@ -175,6 +190,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteListOptions(sw):
   # Params: <list of options separated by space>
   # Input variable: $first_arg_idx
   # Result variables: $arg_idx, $choices
@@ -205,6 +222,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteGntCheckopt(sw, support_debug):
   # Params: <long options with equal sign> <all options>
   # Result variable: $optcur
   sw.Write("_gnt_checkopt() {")
@@ -234,6 +253,8 @@
     sw.DecIndent()
   sw.Write("}")
 
+
+def _WriteGntCompgen(sw, support_debug):
   # Params: <compgen options>
   # Result variable: $COMPREPLY
   sw.Write("_gnt_compgen() {")
@@ -247,12 +268,34 @@
   sw.Write("}")
 
 
+def WritePreamble(sw, support_debug):
+  """Writes the script preamble.
+
+  Helper functions should be written here.
+
+  """
+  sw.Write("# This script is automatically generated at build time.")
+  sw.Write("# Do not modify manually.")
+
+  _WriteGntLog(sw, support_debug)
+  _WriteNodes(sw)
+  _WriteInstances(sw)
+  _WriteJobs(sw)
+  _WriteOSAndIAllocator(sw)
+  _WriteNodegroup(sw)
+  _WriteNetwork(sw)
+  _WriteFindFirstArg(sw)
+  _WriteListOptions(sw)
+  _WriteGntCheckopt(sw, support_debug)
+  _WriteGntCompgen(sw, support_debug)
+
+
 def WriteCompReply(sw, args, cur="\"$cur\""):
   sw.Write("_gnt_compgen %s -- %s", args, cur)
   sw.Write("return")
 
 
-class CompletionWriter:
+class CompletionWriter(object):
   """Command completion writer class.
 
   """
@@ -832,7 +875,7 @@
   WritePreamble(sw, debug)
 
   # gnt-* scripts
-  for scriptname in _autoconf.GNT_SCRIPTS:
+  for scriptname in _constants.GNT_SCRIPTS:
     filename = "scripts/%s" % scriptname
 
     WriteCompletion(sw, scriptname, GetFunctionName(scriptname), debug,
@@ -849,17 +892,17 @@
                          debug=not options.compact)
 
   # htools, if enabled
-  if _autoconf.HTOOLS:
-    for script in _autoconf.HTOOLS_PROGS:
+  if _constants.HTOOLS:
+    for script in _constants.HTOOLS_PROGS:
       WriteHaskellCompletion(sw, script, htools=True, debug=debug)
 
   # ganeti-confd, if enabled
-  if _autoconf.ENABLE_CONFD:
+  if _constants.ENABLE_CONFD:
     WriteHaskellCompletion(sw, "src/ganeti-confd", htools=False,
                            debug=debug)
 
   # mon-collector, if monitoring is enabled
-  if _autoconf.ENABLE_MOND:
+  if _constants.ENABLE_MOND:
     WriteHaskellCmdCompletion(sw, "src/mon-collector", debug=debug)
 
   # Reset extglob to original value
diff --git a/autotools/build-rpc b/autotools/build-rpc
index 2de71d5..ea0f3b6 100755
--- a/autotools/build-rpc
+++ b/autotools/build-rpc
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to generate RPC code.
diff --git a/autotools/check-header b/autotools/check-header
index 7529fe6..f7be173 100755
--- a/autotools/check-header
+++ b/autotools/check-header
@@ -1,22 +1,31 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2011 Google Inc.
+# Copyright (C) 2011, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to verify file header.
@@ -38,21 +47,31 @@
 #: Assume header is always in the first 8kB of a file
 _READ_SIZE = 8 * 1024
 
-_GPLv2 = [
-  "This program is free software; you can redistribute it and/or modify",
-  "it under the terms of the GNU General Public License as published by",
-  "the Free Software Foundation; either version 2 of the License, or",
-  "(at your option) any later version.",
+_BSD2 = [
+  "All rights reserved.",
   "",
-  "This program is distributed in the hope that it will be useful, but",
-  "WITHOUT ANY WARRANTY; without even the implied warranty of",
-  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU",
-  "General Public License for more details.",
+  "Redistribution and use in source and binary forms, with or without",
+  "modification, are permitted provided that the following conditions are",
+  "met:",
   "",
-  "You should have received a copy of the GNU General Public License",
-  "along with this program; if not, write to the Free Software",
-  "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA",
-  "02110-1301, USA.",
+  "1. Redistributions of source code must retain the above copyright notice,",
+  "this list of conditions and the following disclaimer.",
+  "",
+  "2. Redistributions in binary form must reproduce the above copyright",
+  "notice, this list of conditions and the following disclaimer in the",
+  "documentation and/or other materials provided with the distribution.",
+  "",
+  "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS",
+  "IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED",
+  "TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR",
+  "PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR",
+  "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,",
+  "EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,",
+  "PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR",
+  "PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF",
+  "LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING",
+  "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS",
+  "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
   ]
 
 
@@ -98,11 +117,7 @@
   if not _COPYRIGHT.match(line):
     _Fail(lineno, "Must contain copyright information (%s)" % _COPYRIGHT_DESC)
 
-  (lineno, line) = getline_fn()
-  if line != "#":
-    _Fail(lineno, "Must contain nothing but hash character (#)")
-
-  for licence_line in _GPLv2:
+  for licence_line in _BSD2:
     (lineno, line) = getline_fn()
     if line != ("# %s" % licence_line).rstrip():
       _Fail(lineno, "Does not match expected licence line (%s)" % licence_line)
diff --git a/autotools/check-imports b/autotools/check-imports
index d50cb31..d82bd40 100755
--- a/autotools/check-imports
+++ b/autotools/check-imports
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to check module imports.
diff --git a/autotools/check-man-dashes b/autotools/check-man-dashes
index 9ad8724..ca2df6c 100755
--- a/autotools/check-man-dashes
+++ b/autotools/check-man-dashes
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/autotools/check-man-references b/autotools/check-man-references
index 34b9a32..b0a2c6a 100755
--- a/autotools/check-man-references
+++ b/autotools/check-man-references
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u -o pipefail
 
diff --git a/autotools/check-man-warnings b/autotools/check-man-warnings
index fb001a1..0207395 100755
--- a/autotools/check-man-warnings
+++ b/autotools/check-man-warnings
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/autotools/check-news b/autotools/check-news
index 1405b58..694ca01 100755
--- a/autotools/check-news
+++ b/autotools/check-news
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to check NEWS file.
@@ -42,7 +51,7 @@
 VERSION_RE = re.compile(r"^Version (\d+(\.\d+)+( (alpha|beta|rc)\d+)?)$")
 
 #: How many days release timestamps may be in the future
-TIMESTAMP_FUTURE_DAYS_MAX = 3
+TIMESTAMP_FUTURE_DAYS_MAX = 5
 
 errors = []
 
diff --git a/autotools/check-python-code b/autotools/check-python-code
index 051ef71..c33e57b 100755
--- a/autotools/check-python-code
+++ b/autotools/check-python-code
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/autotools/check-tar b/autotools/check-tar
index d540763..b77d5f7 100755
--- a/autotools/check-tar
+++ b/autotools/check-tar
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to check tarball generated by Automake.
diff --git a/autotools/check-version b/autotools/check-version
index 9b9c31a..d76f70b 100755
--- a/autotools/check-version
+++ b/autotools/check-version
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010,2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/autotools/convert-constants b/autotools/convert-constants
deleted file mode 100755
index 0cb07bb..0000000
--- a/autotools/convert-constants
+++ /dev/null
@@ -1,324 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-"""Script for converting Python constants to Haskell code fragments.
-
-"""
-
-import re
-import types
-
-from ganeti import _autoconf
-from ganeti import compat
-from ganeti import constants
-from ganeti import errors
-from ganeti import luxi
-from ganeti import opcodes
-from ganeti import qlang
-from ganeti import jstore
-
-
-#: Constant name regex
-CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
-
-#: Private name regex
-PRIVATE_RE = re.compile("^__.+__$")
-
-#: The type of regex objects
-RE_TYPE = type(CONSTANT_RE)
-
-#: Keys which do not declare a value (manually maintained). By adding
-# values here, we can make more lists use the actual names; otherwise
-# we'll have (e.g.) both DEFAULT_ENABLED_HYPERVISOR and HT_XEN_PVM
-# declare the same value, and thus the list of valid hypervisors will
-# have strings instead of easily looked-up names.
-IGNORED_DECL_NAMES = ["DEFAULT_ENABLED_HYPERVISOR"]
-
-
-def NameRules(name):
-  """Converts the upper-cased Python name to Haskell camelCase.
-
-  """
-  name = name.replace("-", "_")
-  elems = name.split("_")
-  return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
-
-
-def StringValueRules(value):
-  """Converts a string value from Python to Haskell.
-
-  """
-  value = value.encode("string_escape") # escapes backslashes
-  value = value.replace("\"", "\\\"")
-  return value
-
-
-def DictKeyName(dict_name, key_name):
-  """Converts a dict plus key name to a full name.
-
-  """
-  return"%s_%s" % (dict_name, str(key_name).upper())
-
-
-def HaskellTypeVal(value):
-  """Returns the Haskell type and value for a Python value.
-
-  Note that this only work for 'plain' Python types.
-
-  @returns: (string, string) or None, if we can't determine the type.
-
-  """
-  if isinstance(value, basestring):
-    return ("String", "\"%s\"" % StringValueRules(value))
-  elif isinstance(value, bool):
-    return ("Bool", "%s" % value)
-  elif isinstance(value, int):
-    return ("Int", "%d" % value)
-  elif isinstance(value, long):
-    return ("Integer", "%d" % value)
-  elif isinstance(value, float):
-    return ("Double", "%f" % value)
-  else:
-    return None
-
-
-def IdentifyOrigin(all_items, value):
-  """Tries to identify a constant name from a constant's value.
-
-  This uses a simple algorithm: is there a constant (and only one)
-  with the same value? If so, then it returns that constants' name.
-
-  @note: it is recommended to use this only for tuples/lists/sets, and
-      not for individual (top-level) values
-  @param all_items: a dictionary of name/values for the current module
-  @param value: the value for which we try to find an origin
-
-  """
-  found = [name for (name, v) in all_items.items()
-           if v is value and name not in IGNORED_DECL_NAMES]
-  if len(found) == 1:
-    return found[0]
-  else:
-    return None
-
-
-def FormatListElems(all_items, pfx_name, ovals, tvals):
-  """Formats a list's elements.
-
-  This formats the elements as either values or, if we find all
-  origins, as names.
-
-  @param all_items: a dictionary of name/values for the current module
-  @param pfx_name: the prefix name currently used
-  @param ovals: the list of actual (Python) values
-  @param tvals: the list of values we want to format in the Haskell form
-
-  """
-  origins = [IdentifyOrigin(all_items, v) for v in ovals]
-  if compat.all(x is not None for x in origins):
-    values = [NameRules(pfx_name + origin) for origin in origins]
-  else:
-    values = tvals
-  return ", ".join(values)
-
-
-def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
-  """Converts a dictionary to a Haskell association list ([(k, v)]),
-  if possible.
-
-  @param all_items: a dictionary of name/values for the current module
-  @param pfx_name: the prefix name currently used
-  @param py_name: the Python name
-  @param hs_name: the Haskell name
-  @param mydict: a dictonary, unknown yet if homogenous or not
-
-  """
-  # need this for ordering
-  orig_list = mydict.items()
-  list_form = [(HaskellTypeVal(k), HaskellTypeVal(v)) for k, v in orig_list]
-  if compat.any(v is None or k is None for k, v in list_form):
-    # type not known
-    return []
-  all_keys = [k for k, _ in list_form]
-  all_vals = [v for _, v in list_form]
-  key_types = set(k[0] for k in all_keys)
-  val_types = set(v[0] for v in all_vals)
-  if not(len(key_types) == 1 and len(val_types) == 1):
-    # multiple types
-    return []
-  # record the key and value Haskell types
-  key_type = key_types.pop()
-  val_type = val_types.pop()
-
-  # now try to find names for the keys, instead of raw values
-  key_origins = [IdentifyOrigin(all_items, k) for k, _ in orig_list]
-  if compat.all(x is not None for x in key_origins):
-    key_v = [NameRules(pfx_name + origin) for origin in key_origins]
-  else:
-    key_v = [k[1] for k in all_keys]
-  # ... and for values
-  val_origins = [IdentifyOrigin(all_items, v) for _, v in orig_list]
-  if compat.all(x is not None for x in val_origins):
-    val_v = [NameRules(pfx_name + origin) for origin in val_origins]
-  else:
-    val_v = [v[1] for v in all_vals]
-
-  # finally generate the output
-  kv_pairs = ["(%s, %s)" % (k, v) for k, v in zip(key_v, val_v)]
-  return ["-- | Converted from Python dictionary @%s@" % py_name,
-          "%s :: [(%s, %s)]" % (hs_name, key_type, val_type),
-          "%s = [%s]" % (hs_name, ", ".join(kv_pairs)),
-          ]
-
-
-def ConvertVariable(prefix, name, value, all_items):
-  """Converts a given variable to Haskell code.
-
-  @param prefix: a prefix for the Haskell name (useful for module
-      identification)
-  @param name: the Python name
-  @param value: the value
-  @param all_items: a dictionary of name/value for the module being
-      processed
-  @return: a list of Haskell code lines
-
-  """
-  lines = []
-  if prefix:
-    pfx_name = prefix + "_"
-    fqn = prefix + "." + name
-  else:
-    pfx_name = ""
-    fqn = name
-  hs_name = NameRules(pfx_name + name)
-  hs_typeval = HaskellTypeVal(value)
-  if (isinstance(value, types.ModuleType) or callable(value) or
-      PRIVATE_RE.match(name)):
-    # no sense in marking these, as we don't _want_ to convert them; the
-    # message in the next if block is for datatypes we don't _know_
-    # (yet) how to convert
-    pass
-  elif not CONSTANT_RE.match(name):
-    lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
-  elif hs_typeval is not None:
-    # this is a simple value
-    (hs_type, hs_val) = hs_typeval
-    lines.append("-- | Converted from Python constant @%s@" % fqn)
-    lines.append("%s :: %s" % (hs_name, hs_type))
-    lines.append("%s = %s" % (hs_name, hs_val))
-  elif isinstance(value, dict):
-    if value:
-      lines.append("-- Following lines come from dictionary %s" % fqn)
-      # try to build a real map here, if all keys have same type, and
-      # all values too (i.e. we have a homogeneous dictionary)
-      lines.extend(FormatDict(all_items, pfx_name, fqn, hs_name, value))
-      # and now create individual names
-      for k in sorted(value.keys()):
-        lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
-                                     value[k], all_items))
-  elif isinstance(value, tuple):
-    tvs = [HaskellTypeVal(elem) for elem in value]
-    # Custom rule for special cluster verify error tuples
-    if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
-      cv_ename = hs_name + "Code"
-      lines.append("-- | Special cluster verify code %s" % name)
-      lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
-      lines.append("%s = %s" % (cv_ename, tvs[1][1]))
-      lines.append("")
-    if compat.all(e is not None for e in tvs):
-      ttypes = ", ".join(e[0] for e in tvs)
-      tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
-      lines.append("-- | Converted from Python tuple @%s@" % fqn)
-      lines.append("%s :: (%s)" % (hs_name, ttypes))
-      lines.append("%s = (%s)" % (hs_name, tvals))
-    else:
-      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
-  elif isinstance(value, (list, set, frozenset)):
-    # Lists and frozensets are handled the same in Haskell: as lists,
-    # since lists are immutable and we don't need for constants the
-    # high-speed of an actual Set type. However, we can only convert
-    # them if they have the same type for all elements (which is a
-    # normal expectation for constants, our code should be well
-    # behaved); note that this is different from the tuples case,
-    # where we always (for some values of always) can convert
-    tvs = [HaskellTypeVal(elem) for elem in value]
-    if compat.all(e is not None for e in tvs):
-      ttypes, tvals = zip(*tvs)
-      uniq_types = set(ttypes)
-      if len(uniq_types) == 1:
-        values = FormatListElems(all_items, pfx_name, value, tvals)
-        lines.append("-- | Converted from Python list or set @%s@" % fqn)
-        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
-        lines.append("%s = [%s]" % (hs_name, values))
-      else:
-        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
-    else:
-      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
-  elif isinstance(value, RE_TYPE):
-    tvs = HaskellTypeVal(value.pattern)
-    assert tvs is not None
-    lines.append("-- | Converted from Python RE object @%s@" % fqn)
-    lines.append("%s :: %s" % (hs_name, tvs[0]))
-    lines.append("%s = %s" % (hs_name, tvs[1]))
-  else:
-    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
-  return lines
-
-
-def Convert(module, prefix):
-  """Converts the constants to Haskell.
-
-  """
-  lines = [""]
-
-  all_items = dict((name, getattr(module, name)) for name in dir(module))
-
-  for name in sorted(all_items.keys()):
-    value = all_items[name]
-    new_lines = ConvertVariable(prefix, name, value, all_items)
-    if new_lines:
-      lines.extend(new_lines)
-      lines.append("")
-
-  return "\n".join(lines)
-
-
-def ConvertMisc():
-  """Convert some extra computed-values to Haskell.
-
-  """
-  lines = [""]
-  lines.extend(ConvertVariable("opcodes", "OP_IDS",
-                               opcodes.OP_MAPPING.keys(), {}))
-  return "\n".join(lines)
-
-
-def main():
-  print Convert(constants, "")
-  print Convert(luxi, "luxi")
-  print Convert(qlang, "qlang")
-  print Convert(_autoconf, "autoconf")
-  print Convert(errors, "errors")
-  print Convert(jstore, "jstore")
-  print ConvertMisc()
-
-
-if __name__ == "__main__":
-  main()
diff --git a/autotools/docpp b/autotools/docpp
index 4ad506b..e56ac57 100755
--- a/autotools/docpp
+++ b/autotools/docpp
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script to replace special directives in documentation.
diff --git a/autotools/gen-py-coverage b/autotools/gen-py-coverage
index d16e66b..909a64e 100755
--- a/autotools/gen-py-coverage
+++ b/autotools/gen-py-coverage
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 set -u
diff --git a/autotools/print-py-constants b/autotools/print-py-constants
new file mode 100755
index 0000000..3aa0540
--- /dev/null
+++ b/autotools/print-py-constants
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Script for printing Python constants related to sockets.
+
+These constants are the remnants of the Haskell to Python constant
+generation.  This solution is transitional until Ganeti 2.11 because
+the solution for eliminating completely the Python to Haskell
+conversion requires updating the configuration file.
+
+"""
+
+import socket
+import sys
+
+
+def main():
+  if len(sys.argv) > 1:
+    if sys.argv[1] == "AF_INET4":
+      print "%s" % socket.AF_INET
+    elif sys.argv[1] == "AF_INET6":
+      print "%s" % socket.AF_INET6
+
+
+if __name__ == "__main__":
+  main()
diff --git a/autotools/run-in-tempdir b/autotools/run-in-tempdir
index 3c1a79b..080abdb 100755
--- a/autotools/run-in-tempdir
+++ b/autotools/run-in-tempdir
@@ -28,7 +28,7 @@
 ln -T -s $tmpdir/ganeti $tmpdir/lib
 
 mkdir -p $tmpdir/src $tmpdir/test/hs
-for hfile in htools ganeti-confd mon-collector; do
+for hfile in htools ganeti-confd mon-collector hs2py; do
   if [ -e src/$hfile ]; then
     ln -s $PWD/src/$hfile $tmpdir/src/
   fi
diff --git a/autotools/sphinx-wrapper b/autotools/sphinx-wrapper
index 63b1051..3e4f59e 100755
--- a/autotools/sphinx-wrapper
+++ b/autotools/sphinx-wrapper
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u -o pipefail
 
diff --git a/autotools/testrunner b/autotools/testrunner
index cc963c7..9189a53 100755
--- a/autotools/testrunner
+++ b/autotools/testrunner
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/configure.ac b/configure.ac
index 3fea89b..fda4298 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [9])
+m4_define([gnt_version_minor], [10])
 m4_define([gnt_version_revision], [7])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version_full],
@@ -24,6 +24,58 @@
 AC_SUBST([VERSION_SUFFIX], gnt_version_suffix)
 AC_SUBST([VERSION_FULL], gnt_version_full)
 
+AC_SUBST([BINDIR], $bindir)
+AC_SUBST([SBINDIR], $sbindir)
+AC_SUBST([MANDIR], $mandir)
+
+# --enable-developer-mode
+AC_ARG_ENABLE([developer-mode],
+  [AS_HELP_STRING([--enable-developer-mode],
+                  m4_normalize([do a developper build with additional
+                  checks and fatal warnings]))],
+  [[if test "$enableval" != no; then
+      DEVELOPER_MODE=yes
+    else
+      DEVELOPER_MODE=no
+    fi
+  ]],
+  [DEVELOPER_MODE=no
+  ])
+AC_SUBST(DEVELOPER_MODE, $DEVELOPER_MODE)
+AM_CONDITIONAL([DEVELOPER_MODE], [test "$DEVELOPER_MODE" = yes])
+
+# --enable-versionfull
+AC_ARG_ENABLE([versionfull],
+  [AS_HELP_STRING([--enable-versionfull],
+                  m4_normalize([use the full version string rather
+                  than major.minor for version directories]))],
+  [[if test "$enableval" != no; then
+      USE_VERSION_FULL=yes
+    else
+      USER_VERSION_FULL=no
+    fi
+  ]],
+  [USE_VERSION_FULL=no
+  ])
+AC_SUBST(USE_VERSION_FULL, $USE_VERSION_FULL)
+AM_CONDITIONAL([USE_VERSION_FULL], [test "$USE_VERSION_FULL" = yes])
+
+# --enable-symlinks
+AC_ARG_ENABLE([symlinks],
+  [AS_HELP_STRING([--enable-symlinks],
+                  m4_normalize([also install version-dependent symlinks under
+                  $sysconfdir (default: enabled)]))],
+  [[if test "$enableval" != no; then
+      INSTALL_SYMLINKS=yes
+    else
+      INSTALL_SYMLINKS=no
+    fi
+  ]],
+  [INSTALL_SYMLINKS=yes
+  ])
+AC_SUBST(INSTALL_SYMLINKS, $INSTALL_SYMLINKS)
+AM_CONDITIONAL([INSTALL_SYMLINKS], [test "$INSTALL_SYMLINKS" = yes])
+
 # --with-haskell-flags=
 AC_ARG_WITH([haskell-flags],
   [AS_HELP_STRING([--with-haskell-flags=FLAGS],
@@ -52,6 +104,21 @@
   [export_dir="/srv/ganeti/export"])
 AC_SUBST(EXPORT_DIR, $export_dir)
 
+# --with-backup-dir=...
+AC_ARG_WITH([backup-dir],
+  [AS_HELP_STRING([--with-backup-dir=DIR],
+    [directory to use for configuration backups]
+    [ on Ganeti upgrades (default is $(localstatedir)/lib)]
+  )],
+  [backup_dir="$withval"
+   USE_BACKUP_DIR=yes
+  ],
+  [backup_dir=
+   USE_BACKUP_DIR=no
+  ])
+AC_SUBST(BACKUP_DIR, $backup_dir)
+AM_CONDITIONAL([USE_BACKUP_DIR], [test "$USE_BACKUP_DIR" = yes])
+
 # --with-ssh-config-dir=...
 AC_ARG_WITH([ssh-config-dir],
   [AS_HELP_STRING([--with-ssh-config-dir=DIR],
@@ -72,37 +139,34 @@
 AC_SUBST(XEN_CONFIG_DIR, $xen_config_dir)
 
 # --with-os-search-path=...
-# do a bit of black sed magic to for quoting of the strings in the list
 AC_ARG_WITH([os-search-path],
   [AS_HELP_STRING([--with-os-search-path=LIST],
     [comma separated list of directories to]
     [ search for OS images (default is /srv/ganeti/os)]
   )],
-  [os_search_path=`echo -n "$withval" | sed -e "s/\([[^,]]*\)/'\1'/g"`],
-  [os_search_path="'/srv/ganeti/os'"])
+  [os_search_path="$withval"],
+  [os_search_path="/srv/ganeti/os"])
 AC_SUBST(OS_SEARCH_PATH, $os_search_path)
 
 # --with-extstorage-search-path=...
-# same black sed magic for quoting of the strings in the list
 AC_ARG_WITH([extstorage-search-path],
   [AS_HELP_STRING([--with-extstorage-search-path=LIST],
     [comma separated list of directories to]
     [ search for External Storage Providers]
     [ (default is /srv/ganeti/extstorage)]
   )],
-  [es_search_path=`echo -n "$withval" | sed -e "s/\([[^,]]*\)/'\1'/g"`],
-  [es_search_path="'/srv/ganeti/extstorage'"])
+  [es_search_path="$withval"],
+  [es_search_path="/srv/ganeti/extstorage"])
 AC_SUBST(ES_SEARCH_PATH, $es_search_path)
 
 # --with-iallocator-search-path=...
-# do a bit of black sed magic to for quoting of the strings in the list
 AC_ARG_WITH([iallocator-search-path],
   [AS_HELP_STRING([--with-iallocator-search-path=LIST],
     [comma separated list of directories to]
     [ search for instance allocators (default is $libdir/ganeti/iallocators)]
   )],
-  [iallocator_search_path=`echo -n "$withval" | sed -e "s/\([[^,]]*\)/'\1'/g"`],
-  [iallocator_search_path="'$libdir/$PACKAGE_NAME/iallocators'"])
+  [iallocator_search_path="$withval"],
+  [iallocator_search_path="$libdir/$PACKAGE_NAME/iallocators"])
 AC_SUBST(IALLOCATOR_SEARCH_PATH, $iallocator_search_path)
 
 # --with-xen-bootloader=...
@@ -309,6 +373,7 @@
 fi
 AC_SUBST(SYSLOG_USAGE, $SYSLOG)
 
+# --enable-restricted-commands[=no/yes]
 AC_ARG_ENABLE([restricted-commands],
   [AS_HELP_STRING([--enable-restricted-commands],
                   m4_normalize([enable restricted commands in the node daemon
@@ -336,6 +401,14 @@
 AC_PROG_INSTALL
 AC_PROG_LN_S
 
+# check if ln is the GNU version of ln (and hence supports -T)
+if ln --version 2> /dev/null | head -1 | grep -q GNU
+then
+  AC_SUBST(HAS_GNU_LN, True)
+else
+  AC_SUBST(HAS_GNU_LN, False)
+fi
+
 # Check for the ip command
 AC_ARG_VAR(IP_PATH, [ip path])
 AC_PATH_PROG(IP_PATH, [ip], [])
@@ -369,7 +442,7 @@
   # Note: Character classes ([...]) need to be double quoted due to autoconf
   # using m4
   elif ! echo "$sphinxver" | grep -q -E \
-       '^Sphinx[[[:space:]]]+(\(sphinx-build\)[[[:space:]]]+|v)[[1-9]]\>'; then
+       '^Sphinx([[[:space:]]]+|\(sphinx-build[[1-9]]?\)|v)*[[1-9]]\>'; then
     AC_MSG_ERROR([Sphinx 1.0 or higher is required])
   fi
 fi
diff --git a/daemons/daemon-util.in b/daemons/daemon-util.in
index 653f765..bb8c79c 100644
--- a/daemons/daemon-util.in
+++ b/daemons/daemon-util.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
diff --git a/daemons/ganeti-cleaner.in b/daemons/ganeti-cleaner.in
index 50972c3..f4e9bba 100644
--- a/daemons/ganeti-cleaner.in
+++ b/daemons/ganeti-cleaner.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u
 
diff --git a/daemons/import-export b/daemons/import-export
index 21d1d34..c17de93 100755
--- a/daemons/import-export
+++ b/daemons/import-export
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Import/export daemon.
@@ -99,7 +108,7 @@
   return child_logger
 
 
-class StatusFile:
+class StatusFile(object):
   """Status file manager.
 
   """
diff --git a/devel/build_chroot b/devel/build_chroot
index 59f11b3..f34ef19 100755
--- a/devel/build_chroot
+++ b/devel/build_chroot
@@ -111,127 +111,178 @@
 
 APT_INSTALL="apt-get install -y --no-install-recommends"
 
-echo "deb http://backports.debian.org/debian-backports" \
-     "$DIST_RELEASE-backports main contrib non-free" \
-     > $CHDIR/etc/apt/sources.list.d/backports.list
+if [ DIST_RELEASE = squeeze ]
+then
+  echo "deb http://backports.debian.org/debian-backports" \
+       "$DIST_RELEASE-backports main contrib non-free" \
+       > $CHDIR/etc/apt/sources.list.d/backports.list
+fi
 
 #Install all the packages
 in_chroot -- \
   apt-get update
 
+case $DIST_RELEASE in
 
-# do not install libghc6-network-dev, since it's too old, and just
-# confuses the dependencies
-in_chroot -- \
-  $APT_INSTALL \
-    autoconf automake \
-    ghc cabal-install \
-    libghc6-curl-dev \
-    libghc6-parallel-dev \
-    libghc6-text-dev \
-    libghc6-vector-dev \
-    libpcre3-dev \
-    hlint hscolour pandoc \
-    graphviz socat qemu-utils \
-    python-docutils \
-    python-simplejson \
-    python-pyparsing \
-    python-pyinotify \
-    python-pycurl \
-    python-ipaddr \
-    python-yaml \
-    python-paramiko
+  squeeze)
 
-in_chroot -- \
-  $APT_INSTALL python-setuptools python-dev build-essential
+    # do not install libghc6-network-dev, since it's too old, and just
+    # confuses the dependencies
+    in_chroot -- \
+      $APT_INSTALL \
+        autoconf automake \
+        ghc cabal-install \
+        libghc6-curl-dev \
+        libghc6-parallel-dev \
+        libghc6-text-dev \
+        libghc6-vector-dev \
+        libpcre3-dev \
+        hlint hscolour pandoc \
+        graphviz qemu-utils \
+        python-docutils \
+        python-simplejson \
+        python-pyparsing \
+        python-pyinotify \
+        python-pycurl \
+        python-ipaddr \
+        python-yaml \
+        python-paramiko
 
-in_chroot -- \
-  easy_install \
-    logilab-astng==0.24.1 \
-    logilab-common==0.58.3 \
-    mock==1.0.1 \
-    pylint==0.26.0
+    in_chroot -- \
+      $APT_INSTALL python-setuptools python-dev build-essential
 
-in_chroot -- \
-  easy_install \
-    sphinx==1.1.3 \
-    pep8==1.3.3 \
-    coverage==3.4 \
-    bitarray==0.8.0
+    in_chroot -- \
+      easy_install \
+        logilab-astng==0.24.1 \
+        logilab-common==0.58.3 \
+        mock==1.0.1 \
+        pylint==0.26.0
 
-in_chroot -- \
-  cabal update
+    in_chroot -- \
+      easy_install \
+        sphinx==1.1.3 \
+        pep8==1.3.3 \
+        coverage==3.4 \
+        bitarray==0.8.0
 
-in_chroot -- \
-  cabal install --global \
+    in_chroot -- \
+      cabal update
+
+    in_chroot -- \
+      cabal install --global \
     blaze-builder==0.3.1.1 \
-    network==2.3 \
-    regex-pcre==0.94.2 \
-    hinotify==0.3.2 \
-    hslogger==1.1.4 \
-    quickcheck==2.5.1.1 \
-    attoparsec==0.10.1.1 \
-    crypto==4.2.4 \
-    MonadCatchIO-transformers==0.2.2.0 \
-    mtl==2.0.1.0 \
-    hashable==1.1.2.0 \
-    case-insensitive==0.3 \
-    parsec==3.0.1 \
-    network==2.3 \
-    snap-server==0.8.1 \
-    json==0.4.4
+        network==2.3 \
+        regex-pcre==0.94.4 \
+        hinotify==0.3.2 \
+        hslogger==1.1.4 \
+        quickcheck==2.5.1.1 \
+        attoparsec==0.10.1.1 \
+        crypto==4.2.4 \
+        MonadCatchIO-transformers==0.2.2.0 \
+        mtl==2.0.1.0 \
+        hashable==1.1.2.0 \
+        case-insensitive==0.3 \
+        parsec==3.0.1 \
+        snap-server==0.8.1 \
+        json==0.4.4
 
-in_chroot -- \
-  cabal install --global \
-    hunit==1.2.5.2 \
-    happy==1.18.10 \
-    hlint==1.8.43 \
-    hscolour==1.20.3 \
-    temporary==1.1.2.3 \
-    test-framework==0.6.1 \
-    test-framework-hunit==0.2.7 \
-    test-framework-quickcheck2==0.2.12.3
+    in_chroot -- \
+      cabal install --global \
+        hunit==1.2.5.2 \
+        happy==1.18.10 \
+        hlint==1.8.43 \
+        hscolour==1.20.3 \
+        temporary==1.1.2.3 \
+        test-framework==0.6.1 \
+        test-framework-hunit==0.2.7 \
+        test-framework-quickcheck2==0.2.12.3
 
-in_chroot -- \
-  cabal install --global cabal-file-th
+    in_chroot -- \
+      cabal install --global cabal-file-th
 
-in_chroot -- \
-  cabal install --global shelltestrunner
+    in_chroot -- \
+      cabal install --global shelltestrunner
 
-#Install selected packages from backports
-in_chroot -- \
-  $APT_INSTALL -t squeeze-backports \
-    git \
-    git-email \
-    vim
+    #Install selected packages from backports
+    in_chroot -- \
+      $APT_INSTALL -t squeeze-backports \
+        git \
+        git-email \
+        vim
 
-in_chroot -- \
-  $APT_INSTALL sudo fakeroot rsync locales less
+;;
+
+  wheezy)
+
+    in_chroot -- \
+      $APT_INSTALL \
+      autoconf automake ghc ghc-haddock libghc-network-dev \
+      libghc-test-framework{,-hunit,-quickcheck2}-dev \
+      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
+      libghc-parallel-dev libghc-utf8-string-dev \
+      libghc-hslogger-dev libghc-crypto-dev \
+      libghc-regex-pcre-dev libghc-attoparsec-dev \
+      libghc-vector-dev libghc-temporary-dev \
+      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
+      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
+      python-simplejson python-pycurl python-paramiko \
+      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
+      shelltestrunner python-dev pylint openssh-client vim git git-email
+
+    # We need version 0.9.4 of pyinotify because the packaged version, 0.9.3, is
+    # incompatibile with the packaged version of python-epydoc 3.0.1.
+    # Reason: a logger class in pyinotify calculates its superclasses at
+    # runtime, which clashes with python-epydoc's static analysis phase.
+    #
+    # Problem introduced in:
+    #   https://github.com/seb-m/pyinotify/commit/2c7e8f8959d2f8528e0d90847df360
+    # and "fixed" in:
+    #   https://github.com/seb-m/pyinotify/commit/98c5f41a6e2e90827a63ff1b878596
+
+    in_chroot -- \
+      easy_install pyinotify==0.9.4
+
+;;
+
+  *)
+
+    in_chroot -- \
+      $APT_INSTALL \
+      autoconf automake ghc ghc-haddock libghc-network-dev \
+      libghc-test-framework{,-hunit,-quickcheck2}-dev \
+      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
+      libghc-parallel-dev libghc-utf8-string-dev \
+      libghc-hslogger-dev libghc-crypto-dev \
+      libghc-regex-pcre-dev libghc-attoparsec-dev \
+      libghc-vector-dev libghc-temporary-dev \
+      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
+      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
+      python-simplejson python-pyinotify python-pycurl python-paramiko \
+      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
+      shelltestrunner python-dev pylint openssh-client vim git git-email
+
+;;
+esac
 
 echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
 
 in_chroot -- \
+  $APT_INSTALL sudo fakeroot rsync locales less socat
+
+in_chroot -- \
   locale-gen
 
 in_chroot -- \
   $APT_INSTALL lvm2 ssh bridge-utils iproute iputils-arping \
-               ndisc6 python python-pyopenssl openssl \
-               python-mock \
-               socat fping
-
-in_chroot -- \
-  $APT_INSTALL qemu-utils
+               ndisc6 python-openssl openssl \
+               python-mock fping qemu-utils
 
 in_chroot -- \
   easy_install affinity
 
-#Python development tools
 in_chroot -- \
-  $APT_INSTALL python-epydoc
-
-#Tools for creating debian packages
-in_chroot -- \
-  $APT_INSTALL debhelper quilt
+  $APT_INSTALL \
+  python-epydoc debhelper quilt
 
 # extra debian packages
 
@@ -261,5 +312,4 @@
 echo "Chroot created. In order to run it:"
 echo " * Copy the file $FINAL_CHROOT_CONF to $CONF_DIR/$FINAL_CHROOT_CONF"
 echo " * Copy the file $COMP_FILEPATH to $CHROOT_DIR/$COMP_FILENAME"
-
 echo "Then run \"schroot -c $CHROOTNAME\""
diff --git a/devel/check-split-query b/devel/check-split-query
index 506de86..f1de6db 100755
--- a/devel/check-split-query
+++ b/devel/check-split-query
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # Checks query equivalence between masterd and confd
 #
diff --git a/devel/release b/devel/release
index dee9f6c..875a430 100755
--- a/devel/release
+++ b/devel/release
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is a test script to ease development and testing on test clusters.
 # It should not be used to update production environments.
diff --git a/devel/review b/devel/review
index d3f6594..930bbc1 100755
--- a/devel/review
+++ b/devel/review
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # To set user mappings, use this command:
 #   git config gnt-review.johndoe 'John Doe <johndoe@example.com>'
diff --git a/devel/upload b/devel/upload
index da2e4df..7ddf0cf 100755
--- a/devel/upload
+++ b/devel/upload
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is a test script to ease development and testing on test clusters.
 # It should not be used to update production environments.
diff --git a/devel/webserver b/devel/webserver
index 1b5aca5..9104004 100755
--- a/devel/webserver
+++ b/devel/webserver
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 import sys
 import BaseHTTPServer
diff --git a/doc/admin.rst b/doc/admin.rst
index dd45a04..fbe4db4 100644
--- a/doc/admin.rst
+++ b/doc/admin.rst
@@ -127,7 +127,8 @@
   configure for high performance. Note that for security reasons the
   file storage directory must be listed under
   ``/etc/ganeti/file-storage-paths``, and that file is not copied
-  automatically to all nodes by Ganeti.
+  automatically to all nodes by Ganeti. The format of that file is a
+  newline-separated list of directories.
 
 sharedfile
   The instance will use plain files as backend, but Ganeti assumes that
@@ -815,6 +816,167 @@
 disks), because we needed to get rid of both the original nodes (A and
 B).
 
+Network Management
+------------------
+
+Ganeti used to describe NICs of an Instance with an IP, a MAC, a connectivity
+link and mode. This had three major shortcomings:
+
+  * there was no easy way to assign a unique IP to an instance
+  * network info (subnet, gateway, domain, etc.) was not available on target
+    node (kvm-ifup, hooks, etc)
+  * one should explicitly pass L2 info (mode, and link) to every NIC
+
+Plus there was no easy way to get the current networking overview (which
+instances are on the same L2 or L3 network, which IPs are reserved, etc).
+
+All the above required an external management tool that has an overall view
+and provides the corresponding info to Ganeti.
+
+gnt-network aims to support a big part of this functionality inside Ganeti and
+abstract the network as a separate entity. Currently, a Ganeti network
+provides the following:
+
+  * A single IPv4 pool, subnet and gateway
+  * Connectivity info per nodegroup (mode, link)
+  * MAC prefix for each NIC inside the network
+  * IPv6 prefix/Gateway related to this network
+  * Tags
+
+IP pool management ensures IP uniqueness inside this network. The user can
+pass `ip=pool,network=test` and will:
+
+1. Get the first available IP in the pool
+2. Inherit the connectivity mode and link of the network's netparams
+3. NIC will obtain the MAC prefix of the network
+4. All network related info will be available as environment variables in
+   kvm-ifup scripts and hooks, so that they can dynamically manage all
+   networking-related setup on the host.
+
+Hands on with gnt-network
++++++++++++++++++++++++++
+
+To create a network do::
+
+  # gnt-network add --network=192.0.2.0/24 --gateway=192.0.2.1 test
+
+Please see all other available options (--add-reserved-ips, --mac-prefix,
+--network6, --gateway6, --tags).
+
+Currently, IPv6 info is not used by Ganeti itself. It only gets exported
+to NIC configuration scripts and hooks via environment variables.
+
+To make this network available on a nodegroup you should specify the
+connectivity mode and link during connection::
+
+  # gnt-network connect --nic-parameters mode=bridged,link=br100 test default nodegroup1
+
+To add a NIC inside this network::
+
+  # gnt-instance modify --net -1:add,ip=pool,network=test inst1
+
+This will let a NIC obtain a unique IP inside this network, and inherit the
+nodegroup's netparams (bridged, br100). IP here is optional. If missing the
+NIC will just get the L2 info.
+
+To move an existing NIC from a network to another and remove its IP::
+
+  # gnt-instance modify --net -1:ip=none,network=test1 inst1
+
+This will release the old IP from the old IP pool and the NIC will inherit the
+new nicparams.
+
+On the above actions there is a extra option `--no-conflicts-ckeck`. This
+does not check for conflicting setups. Specifically:
+
+1. When a network is added, IPs of nodes and master are not being checked.
+2. When connecting a network on a nodegroup, IPs of instances inside this
+   nodegroup are not checked whether they reside inside the subnet or not.
+3. When specifying explicitly a IP without passing a network, Ganeti will not
+   check if this IP is included inside any available network on the nodegroup.
+
+External components
++++++++++++++++++++
+
+All the aforementioned steps assure NIC configuration from the Ganeti
+perspective. Of course this has nothing to do, how the instance eventually will
+get the desired connectivity (IPv4, IPv6, default routes, DNS info, etc) and
+where will the IP resolve.  This functionality is managed by the external
+components.
+
+Let's assume that the VM will need to obtain a dynamic IP via DHCP, get a SLAAC
+address, and use DHCPv6 for other configuration information (in case RFC-6106
+is not supported by the client, e.g.  Windows).  This means that the following
+external services are needed:
+
+1. A DHCP server
+2. An IPv6 router sending Router Advertisements
+3. A DHCPv6 server exporting DNS info
+4. A dynamic DNS server
+
+These components must be configured dynamically and on a per NIC basis.
+The way to do this is by using custom kvm-ifup scripts and hooks.
+
+snf-network
+~~~~~~~~~~~
+
+The snf-network package [1,3] includes custom scripts that will provide the
+aforementioned functionality. `kvm-vif-bridge` and `vif-custom` is an
+alternative to `kvm-ifup` and `vif-ganeti` that take into account all network
+info being exported. Their actions depend on network tags. Specifically:
+
+`dns`: will update an external DDNS server (nsupdate on a bind server)
+
+`ip-less-routed`: will setup routes, rules and proxy ARP
+This setup assumes a pre-existing routing table along with some local
+configuration and provides connectivity to instances via an external
+gateway/router without requiring nodes to have an IP inside this network.
+
+`private-filtered`: will setup ebtables rules to ensure L2 isolation on a
+common bridge. Only packets with the same MAC prefix will be forwarded to the
+corresponding virtual interface.
+
+`nfdhcpd`: will update an external DHCP server
+
+nfdhcpd
+~~~~~~~
+
+snf-network works with nfdhcpd [2,3]: a custom user space DHCP
+server based on NFQUEUE. Currently, nfdhcpd replies on BOOTP/DHCP requests
+originating from a tap or a bridge. Additionally in case of a routed setup it
+provides a ra-stateless configuration by responding to router and neighbour
+solicitations along with DHCPv6 requests for DNS options.  Its db is
+dynamically updated using text files inside a local dir with inotify
+(snf-network just adds a per NIC binding file with all relevant info if the
+corresponding network tag is found). Still we need to mangle all these
+packets and send them to the corresponding NFQUEUE.
+
+Known shortcomings
+++++++++++++++++++
+
+Currently the following things are some know weak points of the gnt-network
+design and implementation:
+
+ * Cannot define a network without an IP pool
+ * The pool defines the size of the network
+ * Reserved IPs must be defined explicitly (inconvenient for a big range)
+ * Cannot define an IPv6 only network
+
+Future work
++++++++++++
+
+Any upcoming patches should target:
+
+ * Separate L2, L3, IPv6, IP pool info
+ * Support a set of IP pools per network
+ * Make IP/network in NIC object take a list of entries
+ * Introduce external scripts for node configuration
+   (dynamically create/destroy bridges/routes upon network connect/disconnect)
+
+[1] https://code.grnet.gr/git/snf-network
+[2] https://code.grnet.gr/git/snf-nfdhcpd
+[3] deb http:/apt.dev.grnet.gr/ wheezy/
+
 Node operations
 ---------------
 
@@ -1005,19 +1167,19 @@
 First is listing the backend storage and their space situation::
 
   $ gnt-node list-storage
-  Node  Name        Size Used   Free
-  node1 /dev/sda7 673.8G   0M 673.8G
-  node1 /dev/sdb1 698.6G 1.5G 697.1G
-  node2 /dev/sda7 673.8G   0M 673.8G
-  node2 /dev/sdb1 698.6G 1.0G 697.6G
+  Node  Type   Name  Size Used Free Allocatable
+  node1 lvm-vg xenvg 3.6T   0M 3.6T Y
+  node2 lvm-vg xenvg 3.6T   0M 3.6T Y
+  node3 lvm-vg xenvg 3.6T 2.0G 3.6T Y
 
 The default is to list LVM physical volumes. It's also possible to list
 the LVM volume groups::
 
   $ gnt-node list-storage -t lvm-vg
-  Node  Name  Size
-  node1 xenvg 1.3T
-  node2 xenvg 1.3T
+  Node  Type   Name  Size Used Free Allocatable
+  node1 lvm-vg xenvg 3.6T   0M 3.6T Y
+  node2 lvm-vg xenvg 3.6T   0M 3.6T Y
+  node3 lvm-vg xenvg 3.6T 2.0G 3.6T Y
 
 Next is repairing storage units, which is currently only implemented for
 volume groups and does the equivalent of ``vgreduce --removemissing``::
@@ -1108,6 +1270,7 @@
 For detailed option list see the :manpage:`gnt-cluster(8)` man page.
 
 The cluster version can be obtained via the ``version`` command::
+
   $ gnt-cluster version
   Software version: 2.1.0
   Internode protocol: 20
@@ -1339,6 +1502,13 @@
 Otherwise, if you plan to re-create the cluster, you can just go ahead
 and rerun ``gnt-cluster init``.
 
+Replacing the SSH and SSL keys
+++++++++++++++++++++++++++++++
+
+Ganeti uses both SSL and SSH keys, and actively modifies the SSH keys on
+the nodes.  As result, in order to replace these keys, a few extra steps
+need to be followed: :doc:`cluster-keys-replacement`
+
 Monitoring the cluster
 ----------------------
 
@@ -1363,7 +1533,7 @@
 Note that the set of characters present in a tag and the maximum tag
 length are restricted. Currently the maximum length is 128 characters,
 there can be at most 4096 tags per object, and the set of characters is
-comprised by alphanumeric characters and additionally ``.+*/:@-``.
+comprised by alphanumeric characters and additionally ``.+*/:@-_``.
 
 Operations
 ++++++++++
diff --git a/doc/cluster-keys-replacement.rst b/doc/cluster-keys-replacement.rst
new file mode 100644
index 0000000..5b193e4
--- /dev/null
+++ b/doc/cluster-keys-replacement.rst
@@ -0,0 +1,102 @@
+========================
+Cluster Keys Replacement
+========================
+
+Ganeti uses both SSL and SSH keys, and actively modifies the SSH keys
+on the nodes.  As result, in order to replace these keys, a few extra
+steps need to be followed.
+
+For an example when this could be needed, see the thread at
+`Regenerating SSL and SSH keys after the security bug in Debian's
+OpenSSL
+<http://groups.google.com/group/ganeti/browse_thread/thread/30cc95102dc2123e>`_.
+
+Ganeti uses OpenSSL for encryption on the RPC layer and SSH for
+executing commands. The SSL certificate is automatically generated
+when the cluster is initialized and it's copied to added nodes
+automatically together with the master's SSH host key.
+
+Note that paths below may vary depending on your distribution. In
+general, modifications should be done on the master node and then
+distributed to all nodes of a cluster (possibly using a pendrive - but
+don't forget to use "shred" to remove files securely afterwards).
+
+Replacing SSL keys
+==================
+
+The cluster SSL key is stored in ``/var/lib/ganeti/server.pem``.
+
+Run the following command to generate a new key::
+
+  gnt-cluster renew-crypto --new-cluster-certificate
+
+  # Older version, which don't have this command, can instead use:
+  chmod 0600 /var/lib/ganeti/server.pem &&
+  openssl req -new -newkey rsa:1024 -days 1825 -nodes \
+   -x509 -keyout /var/lib/ganeti/server.pem \
+   -out /var/lib/ganeti/server.pem -batch &&
+  chmod 0400 /var/lib/ganeti/server.pem &&
+  /etc/init.d/ganeti restart
+
+  gnt-cluster copyfile /var/lib/ganeti/server.pem
+
+  gnt-cluster command /etc/init.d/ganeti restart
+
+Replacing SSH keys
+==================
+
+There are two sets of SSH keys in the cluster: the host keys (both DSA
+and RSA, though Ganeti only uses the RSA one) and the root's DSA key
+(Ganeti uses DSA for historically reasons, in the future RSA will be
+used).
+
+host keys
++++++++++
+
+These are the files named ``/etc/ssh/ssh_host_*``. You need to
+manually recreate them; it's possibly that the startup script of
+OpenSSH will generate them if they don't exist, or that the package
+system regenerates them.
+
+Also make sure to copy the master's SSH host keys to all other nodes.
+
+cluster public key file
++++++++++++++++++++++++
+
+The new public rsa host key created in the previous step must be added
+in two places:
+
+#. known hosts file, ``/var/lib/ganeti/known_hosts``
+#. cluster configuration file, ``/var/lib/ganeti/config.data``
+
+Edit these two files and update them with newly generated SSH host key
+(in the previous step, take it from the
+``/etc/ssh/ssh_host_rsa_key.pub``).
+
+For the ``config.data`` file, please look for an entry named
+``rsahostkeypub`` and replace the value for it with the contents of
+the ``.pub`` file. For the ``known_hosts`` file, you need to replace
+the old key with the new one on each line (for each host).
+
+root's key
+++++++++++
+
+These are the files named ``~root/.ssh/id_dsa*``.
+
+Run this command to rebuild them::
+
+  ssh-keygen -t dsa -f ~root/.ssh/id_dsa -q -N ""
+
+root's ``authorized_keys``
+++++++++++++++++++++++++++
+
+This is the file named ``~root/.ssh/authorized_keys``.
+
+Edit file and update it with the newly generated root key, from the
+``id_dsa.pub`` file generated in the previous step.
+
+Finish
+======
+
+In the end, the files mentioned above should be identical for all
+nodes in a cluster. Also do not forget to run ``gnt-cluster verify``.
diff --git a/doc/css/style.css b/doc/css/style.css
index de565ef..62ffc12 100644
--- a/doc/css/style.css
+++ b/doc/css/style.css
@@ -1,11 +1,5 @@
 @import url(default.css);
 
-a.external {
-  /* Based on MediaWiki's monobook skin (licenced as GPL) */
-  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs%2B9AAAAVklEQVR4Xn3PgQkAMQhDUXfqTu7kTtkpd5RA8AInfArtQ2iRXFWT2QedAfttj2FsPIOE1eCOlEuoWWjgzYaB%2FIkeGOrxXhqB%2BuA9Bfcm0lAZuh%2BYIeAD%2BcAqSz4kCMUAAAAASUVORK5CYII%3D") no-repeat scroll right center transparent;
-  padding-right: 13px;
-}
-
 a {
   text-decoration: underline;
 }
diff --git a/doc/design-2.10.rst b/doc/design-2.10.rst
new file mode 100644
index 0000000..11457ad
--- /dev/null
+++ b/doc/design-2.10.rst
@@ -0,0 +1,18 @@
+==================
+Ganeti 2.10 design
+==================
+
+The following design documents have been implemented in Ganeti 2.10.
+
+- :doc:`design-cmdlib-unittests`
+- :doc:`design-hotplug`
+- :doc:`design-openvswitch`
+- :doc:`design-performance-tests`
+- :doc:`design-storagetypes`
+- :doc:`design-upgrade`
+
+The following designs have been partially implemented in Ganeti 2.10.
+
+- :doc:`design-ceph-ganeti-support`
+- :doc:`design-internal-shutdown`
+- :doc:`design-query-splitting`
diff --git a/doc/design-ceph-ganeti-support.rst b/doc/design-ceph-ganeti-support.rst
new file mode 100644
index 0000000..7ec865c
--- /dev/null
+++ b/doc/design-ceph-ganeti-support.rst
@@ -0,0 +1,185 @@
+============================
+RADOS/Ceph support in Ganeti
+============================
+
+.. contents:: :depth: 4
+
+Objective
+=========
+
+The project aims to improve Ceph RBD support in Ganeti. It can be
+primarily divided into following tasks.
+
+- Use Qemu/KVM RBD driver to provide instances with direct RBD
+  support. [implemented as of Ganeti 2.10]
+- Allow Ceph RBDs' configuration through Ganeti. [unimplemented]
+- Write a data collector to monitor Ceph nodes. [unimplemented]
+
+Background
+==========
+
+Ceph RBD
+--------
+
+Ceph is a distributed storage system which provides data access as
+files, objects and blocks. As part of this project, we're interested in
+integrating ceph's block device (RBD) directly with Qemu/KVM.
+
+Primary components/daemons of Ceph.
+- Monitor - Serve as authentication point for clients.
+- Metadata - Store all the filesystem metadata (Not configured here as
+they are not required for RBD)
+- OSD - Object storage devices. One daemon for each drive/location.
+
+RBD support in Ganeti
+---------------------
+
+Currently, Ganeti supports RBD volumes on a pre-configured Ceph cluster.
+This is enabled through RBD disk templates. These templates allow RBD
+volume's access through RBD Linux driver. The volumes are mapped to host
+as local block devices which are then attached to the instances. This
+method incurs an additional overhead. We plan to resolve it by using
+Qemu's RBD driver to enable direct access to RBD volumes for KVM
+instances.
+
+Also, Ganeti currently uses RBD volumes on a pre-configured ceph cluster.
+Allowing configuration of ceph nodes through Ganeti will be a good
+addition to its prime features.
+
+
+Qemu/KVM Direct RBD Integration
+===============================
+
+A new disk param ``access`` is introduced. It's added at
+cluster/node-group level to simplify prototype implementation.
+It will specify the access method either as ``userspace`` or
+``kernelspace``. It's accessible to StartInstance() in hv_kvm.py. The
+device path, ``rbd:<pool>/<vol_name>``, is generated by RADOSBlockDevice
+and is added to the params dictionary as ``kvm_dev_path``.
+
+This approach ensures that no disk template specific changes are
+required in hv_kvm.py allowing easy integration of other distributed
+storage systems (like Gluster).
+
+Note that the RBD volume is mapped as a local block device as before.
+The local mapping won't be used during instance operation in the
+``userspace`` access mode, but can be used by administrators and OS
+scripts.
+
+Updated commands
+----------------
+::
+  $ gnt-instance info
+
+``access:userspace/kernelspace`` will be added to Disks category. This
+output applies to KVM based instances only.
+
+Ceph configuration on Ganeti nodes
+==================================
+
+This document proposes configuration of distributed storage
+pool (Ceph or Gluster) through ganeti. Currently, this design document
+focuses on configuring a Ceph cluster. A prerequisite of this setup
+would be installation of ceph packages on all the concerned nodes.
+
+At Ganeti Cluster init, the user will set distributed-storage specific
+options which will be stored at cluster level. The Storage cluster
+will be initialized using ``gnt-storage``. For the prototype, only a
+single storage pool/node-group is configured.
+
+Following steps take place when a node-group is initialized as a storage
+cluster.
+
+  - Check for an existing ceph cluster through /etc/ceph/ceph.conf file
+    on each node.
+  - Fetch cluster configuration parameters and create a distributed
+    storage object accordingly.
+  - Issue an 'init distributed storage' RPC to group nodes (if any).
+  - On each node, ``ceph`` cli tool will run appropriate services.
+  - Mark nodes as well as the node-group as distributed-storage-enabled.
+
+The storage cluster will operate at a node-group level. The ceph
+cluster will be initiated using gnt-storage. A new sub-command
+``init-distributed-storage`` will be added to it.
+
+The configuration of the nodes will be handled through an init function
+called by the node daemons running on the respective nodes. A new RPC is
+introduced to handle the calls.
+
+A new object will be created to send the storage parameters to the node
+- storage_type, devices, node_role (mon/osd) etc.
+
+A new node can be directly assigned to the storage enabled node-group.
+During the 'gnt-node add' process, required ceph daemons will be started
+and node will be added to the ceph cluster.
+
+Only an offline node can be assigned to storage enabled node-group.
+``gnt-node --readd`` needs to be performed to issue RPCs for spawning
+appropriate services on the newly assigned node.
+
+Updated Commands
+----------------
+
+Following are the affected commands.::
+
+  $ gnt-cluster init -S ceph:disk=/dev/sdb,option=value...
+
+During cluster initialization, ceph specific options are provided which
+apply at cluster-level.::
+
+  $ gnt-cluster modify -S ceph:option=value2...
+
+For now, cluster modification will be allowed when there is no
+initialized storage cluster.::
+
+  $ gnt-storage init-distributed-storage -s{--storage-type} ceph \
+    <node-group>
+
+Ensure that no other node-group is configured as distributed storage
+cluster and configure ceph on the specified node-group. If there is no
+node in the node-group, it'll only be marked as distributed storage
+enabled and no action will be taken.::
+
+  $ gnt-group assign-nodes <group> <node>
+
+It ensures that the node is offline if the node-group specified is
+distributed storage capable. Ceph configuration on the newly assigned
+node is not performed at this step.::
+
+  $ gnt-node --offline
+
+If the node is part of storage node-group, an offline call will stop/remove
+ceph daemons.::
+
+  $ gnt-node add --readd
+
+If the node is now part of the storage node-group, issue init
+distributed storage RPC to the respective node. This step is required
+after assigning a node to the storage enabled node-group::
+
+  $ gnt-node remove
+
+A warning will be issued stating that the node is part of distributed
+storage, mark it offline before removal.
+
+Data collector for Ceph
+-----------------------
+
+TBD
+
+Future Work
+-----------
+
+Due to the loopback bug in ceph, one may run into daemon hang issues
+while performing writes to a RBD volumes through block device mapping.
+This bug is applicable only when the RBD volume is stored on the OSD
+running on the local node. In order to mitigate this issue, we can
+create storage pools on different nodegroups and access RBD
+volumes on different pools.
+http://tracker.ceph.com/issues/3076
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-cmdlib-unittests.rst b/doc/design-cmdlib-unittests.rst
new file mode 100644
index 0000000..29efec8
--- /dev/null
+++ b/doc/design-cmdlib-unittests.rst
@@ -0,0 +1,177 @@
+=====================================
+Unit tests for cmdlib / LogicalUnit's
+=====================================
+
+.. contents:: :depth: 4
+
+This is a design document describing unit tests for the cmdlib module.
+Other modules are deliberately omitted, as LU's contain the most complex
+logic and are only sparingly tested.
+
+Current state and shortcomings
+==============================
+
+The current test coverage of the cmdlib module is at only ~14%. Given
+the complexity of the code this is clearly too little.
+
+The reasons for this low coverage are numerous. There are organisational
+reasons, like no strict requirements for unit tests for each feature.
+But there are also design and technical reasons, which this design
+document wants to address. First, it's not clear which parts of LU's
+should be tested by unit tests, i.e. the test boundaries are not clearly
+defined. And secondly, it's too hard to actually write unit tests for
+LU's. There exists no good framework or set of tools to write easy to
+understand and concise tests.
+
+Proposed changes
+================
+
+This design document consists of two parts. Initially, the test
+boundaries for cmdlib are laid out, and considerations about writing
+unit tests are given. Then the test framework is described, together
+with a rough overview of the individual parts and how they are meant
+to be used.
+
+Test boundaries
+---------------
+
+For the cmdlib module, every LogicalUnit is seen as a unit for testing.
+Unit tests for LU's may only execute the LU but make sure that no side
+effect (like filesystem access, network access or the like) takes
+place. Smaller test units (like individual methods) are sensible and
+will be supported by the test framework. However, they are not the main
+scope of this document.
+
+LU's require the following environment to be provided by the test code
+in order to be executed:
+
+An input opcode
+  LU's get all the user provided input and parameters from the opcode.
+The command processor
+  Used to get the execution context id and to output logging  messages.
+  It also drives the execution of LU's by calling the appropriate
+  methods in the right order.
+The Ganeti context
+  Provides node-management methods and contains
+
+   * The configuration. This gives access to the cluster configuration.
+   * The Ganeti Lock Manager. Manages locks during the execution.
+
+The RPC runner
+  Used to communicate with node daemons on other nodes and to perform
+  operations on them.
+
+The IAllocator runner
+  Calls the IAllocator with a given request.
+
+All of those components have to be replaced/adapted by the test
+framework.
+
+The goal of unit tests at the LU level is to exercise every possible
+code path in the LU at least once. Shared methods which are used by
+multiple LU's should be made testable by themselves and explicit unit
+tests should be written for them.
+
+Ultimately, the code coverage for the cmdlib module should be higher
+than 90%. As Python is a dynamic language, a portion of those tests
+only exists to exercise the code without actually asserting for
+anything in the test. They merely make sure that no type errors exist
+and that potential typos etc. are caught at unit test time.
+
+Test framework
+--------------
+
+The test framework will it make possible to write short and concise
+tests for LU's. In the simplest case, only an opcode has to be provided
+by the test. The framework will then use default values, like an almost
+empty configuration with only the master node and no instances.
+
+All aspects of the test environment will be configurable by individual
+tests.
+
+MCPU mocking
+************
+
+The MCPU drives the execution of LU's. It has to perform its usual
+sequence of actions, but additionally it has to provide easy access to
+the log output of LU's. It will contain utility assertion methods on the
+output.
+
+The mock will be a sub-class of ``mcpu.Processor`` which overrides
+portions of it in order to support the additional functionality. The
+advantage of being a sub-class of the original processor is the
+automatic compatibility with the code running in real clusters.
+
+Configuration mocking
+*********************
+
+Per default, the mocked configuration will contain only the master node,
+no instances and default parameters. However, convenience methods for
+the following use cases will be provided:
+
+ - "Shortcut" methods to add objects to the configuration.
+ - Helper methods to quickly create standard nodes/instances/etc.
+ - Pre-populated default configurations for standard use-cases (i.e.
+   cluster with three nodes, five instances, etc.).
+ - Convenience assertion methods for checking the configuration.
+
+Lock mocking
+************
+
+Initially, the mocked lock manager always grants all locks. It performs
+the following tasks:
+
+ - It keeps track of requested/released locks.
+ - Provides utility assertion methods for checking locks (current and
+   already released ones).
+
+In the future, this component might be extended to prevent locks from
+being granted. This could eventually be used to test optimistic locking.
+
+RPC mocking
+***********
+
+No actual RPC can be made during unit tests. Therefore, those calls have
+to be replaced and their results mocked. As this will entail a large
+portion of work when writing tests, mocking RPC's will be made as easy as
+possible. This entails:
+
+ - Easy construction of RPC results.
+ - Easy mocking of RPC calls (also multiple ones of the same type during
+   one LU execution).
+ - Asserting for RPC calls (including arguments, affected nodes, etc.).
+
+IAllocator mocking
+******************
+
+Calls (also multiple ones during the execution of a LU) to the
+IAllocator interface have to be mocked. The framework will provide,
+similarly to the RPC mocking, provide means to specify the mocked result
+and to assert on the IAllocator requests.
+
+Future work
+===========
+
+With unit tests for cmdlib in place, further unit testing for other
+modules can and should be added. The test boundaries therefore should be
+aligned with the boundaries from cmdlib.
+
+The mocked locking module can be extended to allow testing of optimistic
+locking in LU's. In this case, on all requested locks are actually
+granted to the LU, so it has to adapt for this situation correctly.
+
+A higher test coverage for LU's will increase confidence in our code and
+tests. Refactorings will be easier to make as more problems are caught
+during tests.
+
+After a baseline of unit tests is established for cmdlib, efficient
+testing guidelines could be put in place. For example, new code could be
+required to not lower the test coverage in cmdlib. Additionally, every
+bug fix could be required to include a test which triggered the bug
+before the fix is created.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-daemons.rst b/doc/design-daemons.rst
index 9e87194..2fb266c 100644
--- a/doc/design-daemons.rst
+++ b/doc/design-daemons.rst
@@ -175,7 +175,7 @@
 
 ``Configuration query daemon (RConfD)``
   It is written in Haskell, and it corresponds to the old ConfD. It will run on
-  all the master candidates and it will serve information about the the static
+  all the master candidates and it will serve information about the static
   configuration of the cluster (the one contained in ``config.data``). The
   provided information will be highly available (as in: a response will be
   available as long as a stable-enough connection between the client and at
diff --git a/doc/design-draft.rst b/doc/design-draft.rst
index 361cbbb..d7811b0 100644
--- a/doc/design-draft.rst
+++ b/doc/design-draft.rst
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.9
+.. Last updated for Ganeti 2.10
 
 .. toctree::
    :maxdepth: 2
@@ -14,8 +14,11 @@
    design-storagetypes.rst
    design-internal-shutdown.rst
    design-glusterfs-ganeti-support.rst
-   design-openvswitch.rst
+   design-hugepages-support.rst
+   design-optables.rst
+   design-ceph-ganeti-support.rst
    design-daemons.rst
+   design-hsqueeze.rst
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-file-based-storage.rst b/doc/design-file-based-storage.rst
new file mode 100644
index 0000000..3ce89f5
--- /dev/null
+++ b/doc/design-file-based-storage.rst
@@ -0,0 +1,370 @@
+==================
+File-based Storage
+==================
+
+This page describes the proposed file-based storage for the 2.0 version
+of Ganeti. The project consists in extending Ganeti in order to support
+a filesystem image as Virtual Block Device (VBD) in Dom0 as the primary
+storage for a VM.
+
+Objective
+=========
+
+Goals:
+
+* file-based storage for virtual machines running in a Xen-based
+  Ganeti cluster
+
+* failover of file-based virtual machines between cluster-nodes
+
+* export/import file-based virtual machines
+
+* reuse existing image files
+
+* allow Ganeti to initialize the cluster without checking for a volume
+  group (e.g. xenvg)
+
+Non Goals:
+
+* any kind of data mirroring between clusters for file-based instances
+  (this should be achieved by using shared storage)
+
+* special support for live-migration
+
+* encryption of VBDs
+
+* compression of VBDs
+
+Background
+==========
+
+Ganeti is a virtual server management software tool built on top of Xen
+VM monitor and other Open Source software.
+
+Since Ganeti currently supports only block devices as storage backend
+for virtual machines, the wish came up to provide a file-based backend.
+Using this file-based option provides the possibility to store the VBDs
+on basically every filesystem and therefore allows to deploy external
+data storages (e.g. SAN, NAS, etc.) in clusters.
+
+Overview
+========
+
+Introduction
+++++++++++++
+
+Xen (and other hypervisors) provide(s) the possibility to use a file as
+the primary storage for a VM. One file represents one VBD.
+
+Advantages/Disadvantages
+++++++++++++++++++++++++
+
+Advantages of file-backed VBD:
+
+* support of sparse allocation
+
+* easy from a management/backup point of view (e.g. you can just copy
+  the files around)
+
+* external storage (e.g. SAN, NAS) can be used to store VMs
+
+Disadvantages of file-backed VBD:
+* possible performance loss for I/O-intensive workloads
+
+* using sparse files requires care to ensure the sparseness is
+  preserved when copying, and there is no header in which metadata
+  relating back to the VM can be stored
+
+Xen-related specifications
+++++++++++++++++++++++++++
+
+Driver
+~~~~~~
+
+There are several ways to realize the required functionality with an
+underlying Xen hypervisor.
+
+1) loopback driver
+^^^^^^^^^^^^^^^^^^
+
+Advantages:
+* available in most precompiled kernels
+* stable, since it is in kernel tree for a long time
+* easy to set up
+
+Disadvantages:
+
+* buffer writes very aggressively, which can affect guest filesystem
+  correctness in the event of a host crash
+
+* can even cause out-of-memory kernel crashes in Dom0 under heavy
+  write load
+
+* substantial slowdowns under heavy I/O workloads
+
+* the default number of supported loopdevices is only 8
+
+* doesn't support QCOW files
+
+``blktap`` driver
+^^^^^^^^^^^^^^^^^
+
+Advantages:
+
+* higher performance than loopback driver
+
+* more scalable
+
+* better safety properties for VBD data
+
+* Xen-team strongly encourages use
+
+* already in Xen tree
+
+* supports QCOW files
+
+* asynchronous driver (i.e. high performance)
+
+Disadvantages:
+
+* not enabled in most precompiled kernels
+
+* stable, but not as much tested as loopback driver
+
+3) ubklback driver
+^^^^^^^^^^^^^^^^^^
+
+The Xen Roadmap states "Work is well under way to implement a
+``ublkback`` driver that supports all of the various qemu file format
+plugins".
+
+Furthermore, the Roadmap includes the following:
+
+  "... A special high-performance qcow plugin is also under
+  development, that supports better metadata caching, asynchronous IO,
+  and allows request reordering with appropriate safety barriers to
+  enforce correctness. It remains both forward and backward compatible
+  with existing qcow disk images, but makes adjustments to qemu's
+  default allocation policy when creating new disks such as to
+  optimize performance."
+
+File types
+~~~~~~~~~~
+
+Raw disk image file
+^^^^^^^^^^^^^^^^^^^
+
+Advantages:
+* Resizing supported
+* Sparse file (filesystem dependend)
+* simple and easily exportable
+
+Disadvantages:
+
+* Underlying filesystem needs to support sparse files (most
+  filesystems do, though)
+
+QCOW disk image file
+^^^^^^^^^^^^^^^^^^^^
+
+Advantages:
+
+* Smaller file size, even on filesystems which don't support holes
+  (i.e. sparse files)
+
+* Snapshot support, where the image only represents changes made to an
+  underlying disk image
+
+* Optional zlib based compression
+
+* Optional AES encryption
+
+Disadvantages:
+* Resizing not supported yet (it's on the way)
+
+VMDK disk image file
+^^^^^^^^^^^^^^^^^^^^
+
+This file format is directly based on the qemu vmdk driver, which is
+synchronous and thus slow.
+
+Detailed Design
+===============
+
+Terminology
++++++++++++
+
+* **VBD** (Virtual Block Device): Persistent storage available to a
+  virtual machine, providing the abstraction of an actual block
+  storage device. VBDs may be actual block devices, filesystem images,
+  or remote/network storage.
+
+* **Dom0** (Domain 0): The first domain to be started on a Xen
+  machine.  Domain 0 is responsible for managing the system.
+
+* **VM** (Virtual Machine): The environment in which a hosted
+  operating system runs, providing the abstraction of a dedicated
+  machine. A VM may be identical to the underlying hardware (as in
+  full virtualization, or it may differ, as in paravirtualization). In
+  the case of Xen the domU (unprivileged domain) instance is meant.
+
+* **QCOW**: QEMU (a processor emulator) image format.
+
+
+Implementation
+++++++++++++++
+
+Managing file-based instances
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The option for file-based storage will be added to the 'gnt-instance'
+utility.
+
+Add Instance
+^^^^^^^^^^^^
+
+Example:
+
+  gnt-instance add -t file:[path\ =[,driver=loop[,reuse[,...]]]] \
+  --disk 0:size=5G --disk 1:size=10G -n node -o debian-etch instance2
+
+This will create a file-based instance with e.g. the following files:
+* ``/sda`` -> 5GB
+* ``/sdb`` -> 10GB
+
+The default directory where files will be stored is
+``/srv/ganeti/file-storage/``. This can be changed by setting the
+``<path>`` option. This option denotes the full path to the directory
+where the files are stored. The filetype will be "raw" for the first
+release of Ganeti 2.0. However, the code will be extensible to more
+file types, since Ganeti will store information about the file type of
+each image file. Internally Ganeti will keep track of the used driver,
+the file-type and the full path to the file for every VBD. Example:
+"logical_id" : ``[FD_LOOP, FT_RAW, "/instance1/sda"]`` If the
+``--reuse`` flag is set, Ganeti checks for existing files in the
+corresponding directory (e.g. ``/xen/instance2/``). If one or more
+files in this directory are present and correctly named (the naming
+conventions will be defined in Ganeti version 2.0) Ganeti will set a
+VM up with these. If no file can be found or the names or invalid the
+operation will be aborted.
+
+Remove instance
+^^^^^^^^^^^^^^^
+
+The instance removal will just differ from the actual one by deleting
+the VBD-files instead of the corresponding block device (e.g. a logical
+volume).
+
+Starting/Stopping Instance
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Here nothing has to be changed, as the xen tools don't differentiate
+between file-based or blockdevice-based instances in this case.
+
+Export/Import instance
+^^^^^^^^^^^^^^^^^^^^^^
+
+Provided "dump/restore" is used in the "export" and "import" guest-os
+scripts, there are no modifications needed when file-based instances are
+exported/imported. If any other backup-tool (which requires access to
+the mounted file-system) is used then the image file can be temporarily
+mounted. This can be done in different ways:
+
+Mount a raw image file via loopback driver::
+
+  mount -o loop /srv/ganeti/file-storage/instance1/sda1 /mnt/disk\
+
+Mount a raw image file via blkfront driver (Dom0 kernel needs this
+module to do the following operation)::
+
+  xm block-attach 0 tap:aio:/srv/ganeti/file-storage/instance1/sda1 /dev/xvda1 w 0\
+
+  mount /dev/xvda1 /mnt/disk
+
+Mount a qcow image file via blkfront driver (Dom0 kernel needs this
+module to do the following operation)
+
+  xm block-attach 0 tap:qcow:/srv/ganeti/file-storage/instance1/sda1 /dev/xvda1 w 0
+
+  mount /dev/xvda1 /mnt/disk
+
+High availability features with file-based instances
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Failing over an instance
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Failover is done in the same way as with block device backends. The
+instance gets stopped on the primary node and started on the secondary.
+The roles of primary and secondary get swapped. Note: If a failover is
+done, Ganeti will assume that the corresponding VBD(s) location (i.e.
+directory) is the same on the source and destination node. In case one
+or more corresponding file(s) are not present on the destination node,
+Ganeti will abort the operation.
+
+Replacing an instance disks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Since there is no data mirroring for file-backed VM there is no such
+operation.
+
+Evacuation of a node
+^^^^^^^^^^^^^^^^^^^^
+
+Since there is no data mirroring for file-backed VMs there is no such
+operation.
+
+Live migration
+^^^^^^^^^^^^^^
+
+Live migration is possible using file-backed VBDs. However, the
+administrator has to make sure that the corresponding files are exactly
+the same on the source and destination node.
+
+Xen Setup
++++++++++
+
+File creation
+~~~~~~~~~~~~~
+
+Creation of a raw file is simple. Example of creating a sparse file of 2
+Gigabytes. The option "seek" instructs "dd" to create a sparse file::
+
+  dd if=/dev/zero of=vm1disk bs=1k seek=2048k count=1
+
+Creation of QCOW image files can be done with the "qemu-img" utility (in
+debian it comes with the "qemu" package).
+
+Config file
+~~~~~~~~~~~
+
+The Xen config file will have the following modification if one chooses
+the file-based disk-template.
+
+1) loopback driver and raw file
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+  disk = ['file:</path/to/file>,sda1,w']
+
+2) blktap driver and raw file
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+  disk = ['tap:aio:,sda1,w']
+
+3) blktap driver and qcow file
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+  disk = ['tap:qcow:,sda1,w']
+
+Other hypervisors
++++++++++++++++++
+
+Other hypervisors have mostly differnet ways to make storage available
+to their virtual instances/machines. This is beyond the scope of this
+document.
diff --git a/doc/design-hotplug.rst b/doc/design-hotplug.rst
new file mode 100644
index 0000000..1a52229
--- /dev/null
+++ b/doc/design-hotplug.rst
@@ -0,0 +1,252 @@
+=======
+Hotplug
+=======
+
+.. contents:: :depth: 4
+
+This is a design document detailing the implementation of device
+hotplugging in Ganeti. The logic used is hypervisor agnostic but still
+the initial implementation will target the KVM hypervisor. The
+implementation adds ``python-fdsend`` as a new dependency. In case
+it is not installed hotplug will not be possible and the user will
+be notified with a warning.
+
+
+Current state and shortcomings
+==============================
+
+Currently, Ganeti supports addition/removal/modification of devices
+(NICs, Disks) but the actual modification takes place only after
+rebooting the instance. To this end an instance cannot change network,
+get a new disk etc. without a hard reboot.
+
+Until now, in case of KVM hypervisor, code does not name devices nor
+places them in specific PCI slots. Devices are appended in the KVM
+command and Ganeti lets KVM decide where to place them. This means that
+there is a possibility a device that resides in PCI slot 5, after a
+reboot (due to another device removal) to be moved to another PCI slot
+and probably get renamed too (due to udev rules, etc.).
+
+In order for a migration to succeed, the process on the target node
+should be started with exactly the same machine version, CPU
+architecture and PCI configuration with the running process. During
+instance creation/startup ganeti creates a KVM runtime file with all the
+necessary information to generate the KVM command. This runtime file is
+used during instance migration to start a new identical KVM process. The
+current format includes the fixed part of the final KVM command, a list
+of NICs', and hvparams dict. It does not favor easy manipulations
+concerning disks, because they are encapsulated in the fixed KVM
+command.
+
+
+Proposed changes
+================
+
+For the case of the KVM hypervisor, QEMU exposes 32 PCI slots to the
+instance. Disks and NICs occupy some of these slots. Recent versions of
+QEMU have introduced monitor commands that allow addition/removal of PCI
+devices. Devices are referenced based on their name or position on the
+virtual PCI bus. To be able to use these commands, we need to be able to
+assign each device a unique name.
+
+To keep track where each device is plugged into, we add the
+``pci`` slot to Disk and NIC objects, but we save it only in runtime
+files, since it is hypervisor specific info. This is added for easy
+object manipulation and is ensured not to be written back to the config.
+
+We propose to make use of QEMU 1.0 monitor commands so that
+modifications to devices take effect instantly without the need for hard
+reboot. The only change exposed to the end-user will be the addition of
+a ``--hotplug`` option to the ``gnt-instance modify`` command.
+
+Upon hotplugging the PCI configuration of an instance is changed.
+Runtime files should be updated correspondingly. Currently this is
+impossible in case of disk hotplug because disks are included in command
+line entry of the runtime file, contrary to NICs that are correctly
+treated separately. We change the format of runtime files, we remove
+disks from the fixed KVM command and create new entry containing them
+only. KVM options concerning disk are generated during
+``_ExecuteKVMCommand()``, just like NICs.
+
+Design decisions
+================
+
+Which should be each device ID? Currently KVM does not support arbitrary
+IDs for devices; supported are only names starting with a letter, max 32
+chars length, and only including '.' '_' '-' special chars.
+For debugging purposes and in order to be more informative, device will be
+named after: <device type>-<part of uuid>-pci-<slot>.
+
+Who decides where to hotplug each device? As long as this is a
+hypervisor specific matter, there is no point for the master node to
+decide such a thing. Master node just has to request noded to hotplug a
+device. To this end, hypervisor specific code should parse the current
+PCI configuration (i.e. ``info pci`` QEMU monitor command), find the first
+available slot and hotplug the device. Having noded to decide where to
+hotplug a device we ensure that no error will occur due to duplicate
+slot assignment (if masterd keeps track of PCI reservations and noded
+fails to return the PCI slot that the device was plugged into then next
+hotplug will fail).
+
+Where should we keep track of devices' PCI slots? As already mentioned,
+we must keep track of devices PCI slots to successfully migrate
+instances. First option is to save this info to config data, which would
+allow us to place each device at the same PCI slot after reboot. This
+would require to make the hypervisor return the PCI slot chosen for each
+device, and storing this information to config data. Additionally the
+whole instance configuration should be returned with PCI slots filled
+after instance start and each instance should keep track of current PCI
+reservations. We decide not to go towards this direction in order to
+keep it simple and do not add hypervisor specific info to configuration
+data (``pci_reservations`` at instance level and ``pci`` at device
+level). For the aforementioned reason, we decide to store this info only
+in KVM runtime files.
+
+Where to place the devices upon instance startup? QEMU has by default 4
+pre-occupied PCI slots. So, hypervisor can use the remaining ones for
+disks and NICs. Currently, PCI configuration is not preserved after
+reboot.  Each time an instance starts, KVM assigns PCI slots to devices
+based on their ordering in Ganeti configuration, i.e. the second disk
+will be placed after the first, the third NIC after the second, etc.
+Since we decided that there is no need to keep track of devices PCI
+slots, there is no need to change current functionality.
+
+How to deal with existing instances? Hotplug depends on runtime file
+manipulation. It stores there pci info and every device the kvm process is
+currently using. Existing files have no pci info in devices and have block
+devices encapsulated inside kvm_cmd entry. Thus hotplugging of existing devices
+will not be possible. Still migration and hotplugging of new devices will
+succeed. The workaround will happen upon loading kvm runtime: if we detect old
+style format we will add an empty list for block devices and upon saving kvm
+runtime we will include this empty list as well. Switching entirely to new
+format will happen upon instance reboot.
+
+
+Configuration changes
+---------------------
+
+The ``NIC`` and ``Disk`` objects get one extra slot: ``pci``. It refers to
+PCI slot that the device gets plugged into.
+
+In order to be able to live migrate successfully, runtime files should
+be updated every time a live modification (hotplug) takes place. To this
+end we change the format of runtime files. The KVM options referring to
+instance's disks are no longer recorded as part of the KVM command line.
+Disks are treated separately, just as we treat NICs right now. We insert
+and remove entries to reflect the current PCI configuration.
+
+
+Backend changes
+---------------
+
+Introduce one new RPC call:
+
+- hotplug_device(DEVICE_TYPE, ACTION, device, ...)
+
+where DEVICE_TYPE can be either NIC or Disk, and ACTION either REMOVE or ADD.
+
+Hypervisor changes
+------------------
+
+We implement hotplug on top of the KVM hypervisor. We take advantage of
+QEMU 1.0 monitor commands (``device_add``, ``device_del``,
+``drive_add``, ``drive_del``, ``netdev_add``,`` netdev_del``). QEMU
+refers to devices based on their id. We use ``uuid`` to name them
+properly. If a device is about to be hotplugged we parse the output of
+``info pci`` and find the occupied PCI slots. We choose the first
+available and the whole device object is appended to the corresponding
+entry in the runtime file.
+
+Concerning NIC handling, we build on the top of the existing logic
+(first create a tap with _OpenTap() and then pass its file descriptor to
+the KVM process). To this end we need to pass access rights to the
+corresponding file descriptor over the monitor socket (UNIX domain
+socket). The open file is passed as a socket-level control message
+(SCM), using the ``fdsend`` python library.
+
+
+User interface
+--------------
+
+The new ``--hotplug`` option to gnt-instance modify is introduced, which
+forces live modifications.
+
+
+Enabling hotplug
+++++++++++++++++
+
+Hotplug will be optional during gnt-instance modify.  For existing
+instance, after installing a version that supports hotplugging we
+have the restriction that hotplug will not be supported for existing
+devices. The reason is that old runtime files lack of:
+
+1. Device pci configuration info.
+
+2. Separate block device entry.
+
+Hotplug will be supported only for KVM in the first implementation. For
+all other hypervisors, backend will raise an Exception case hotplug is
+requested.
+
+
+NIC Hotplug
++++++++++++
+
+The user can add/modify/remove NICs either with hotplugging or not. If a
+NIC is to be added a tap is created first and configured properly with
+kvm-vif-bridge script. Then the instance gets a new network interface.
+Since there is no QEMU monitor command to modify a NIC, we modify a NIC
+by temporary removing the existing one and adding a new with the new
+configuration. When removing a NIC the corresponding tap gets removed as
+well.
+
+::
+
+ gnt-instance modify --net add --hotplug test
+ gnt-instance modify --net 1:mac=aa:00:00:55:44:33 --hotplug test
+ gnt-instance modify --net 1:remove --hotplug test
+
+
+Disk Hotplug
+++++++++++++
+
+The user can add and remove disks with hotplugging or not. QEMU monitor
+supports resizing of disks, however the initial implementation will
+support only disk addition/deletion.
+
+::
+
+ gnt-instance modify --disk add:size=1G --hotplug test
+ gnt-instance modify --net 1:remove --hotplug test
+
+
+Dealing with chroot and uid pool
+--------------------------------
+
+The design so far covers all issues that arise without addressing the
+case where the kvm process will not run with root privileges.
+Specifically:
+
+- in case of chroot, the kvm process cannot see the newly created device
+
+- in case of uid pool security model, the kvm process is not allowed
+  to access the device
+
+For NIC hotplug we address this problem by using the ``getfd`` monitor
+command and passing the file descriptor to the kvm process over the
+monitor socket using SCM_RIGHTS. For disk hotplug and in case of uid
+pool we can let the hypervisor code temporarily ``chown()`` the  device
+before the actual hotplug. Still this is insufficient in case of chroot.
+In this case, we need to ``mknod()`` the device inside the chroot. Both
+workarounds can be avoided, if we make use of the ``add-fd`` qemu
+monitor command, that was introduced in version 1.3. This command is the
+equivalent of NICs' `get-fd`` for disks and will allow disk hotplug in
+every case. So, if the qemu monitor does not support the ``add-fd``
+command, we will not allow disk hotplug for chroot and uid security
+model and notify the user with the corresponding warning.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-hsqueeze.rst b/doc/design-hsqueeze.rst
new file mode 100644
index 0000000..d182490
--- /dev/null
+++ b/doc/design-hsqueeze.rst
@@ -0,0 +1,132 @@
+=============
+HSqueeze tool
+=============
+
+.. contents:: :depth: 4
+
+This is a design document detailing the node-freeing scheduler, HSqueeze.
+
+
+Current state and shortcomings
+==============================
+
+Externally-mirrored instances can be moved between nodes at low
+cost. Therefore, it is attractive to free up nodes and power them down
+at times of low usage, even for small periods of time, like nights or
+weekends.
+
+Currently, the best way to find out a suitable set of nodes to shut down
+is to use the property of our balancedness metric to move instances
+away from drained nodes. So, one would manually drain more and more
+nodes and see, if `hbal` could find a solution freeing up all those
+drained nodes.
+
+
+Proposed changes
+================
+
+We propose the addition of a new htool command-line tool, called
+`hsqueeze`, that aims at keeping resource usage at a constant high
+level by evacuating and powering down nodes, or powering up nodes and
+rebalancing, as appropriate. By default, only externally-mirrored
+instances are moved, but options are provided to additionally take
+DRBD instances (which can be moved without downtimes), or even all
+instances into consideration.
+
+Tagging of standy nodes
+-----------------------
+
+Powering down nodes that are technically healthy effectively creates a
+new node state: nodes on standby. To avoid further state
+proliferation, and as this information is only used by `hsqueeze`,
+this information is recorded in node tags. `hsqueeze` will assume
+that offline nodes having a tag with prefix `htools:standby:` can
+easily be powered on at any time.
+
+Minimum available resources
+---------------------------
+
+To keep the squeezed cluster functional, a minimal amount of resources
+will be left available on every node. While the precise amount will
+be specifiable via command-line options, a sensible default is chosen,
+like enough resource to start an additional instance at standard
+allocation on each node. If the available resources fall below this
+limit, `hsqueeze` will, in fact, try to power on more nodes, till
+enough resources are available, or all standy nodes are online.
+
+To avoid flapping behavior, a second, higher, amount of reserve
+resources can be specified, and `hsqueeze` will only power down nodes,
+if after the power down this higher amount of reserve resources is
+still available.
+
+Computation of the set to free up
+---------------------------------
+
+To determine which nodes can be powered down, `hsqueeze` basically
+follows the same algorithm as the manual process. It greedily goes
+through all non-master nodes and tries if the algorithm used by `hbal`
+would find a solution (with the appropriate move restriction) that
+frees up the extended set of nodes to be drained, while keeping enough
+resources free. Being based on the algorithm used by `hbal`, all
+restrictions respected by `hbal`, in particular memory reservation
+for N+1 redundancy, are also respected by `hsqueeze`.
+The order in which the nodes are tried is choosen by a
+suitable heuristics, like trying the nodes in order of increasing
+number of instances; the hope is that this reduces the number of
+instances that actually have to be moved.
+
+If the amount of free resources has fallen below the lower limit,
+`hsqueeze` will determine the set of nodes to power up in a similar
+way; it will hypothetically add more and more of the standby
+nodes (in some suitable order) till the algorithm used by `hbal` will
+finally balance the cluster in a way that enough resources are available,
+or all standy nodes are online.
+
+
+Instance moves and execution
+----------------------------
+
+Once the final set of nodes to power down is determined, the instance
+moves are determined by the algorithm used by `hbal`. If
+requested by the `-X` option, the nodes freed up are drained, and the
+instance moves are executed in the same way as `hbal` does. Finally,
+those of the freed-up nodes that do not already have a
+`htools:standby:` tag are tagged as `htools:standby:auto`, all free-up
+nodes are marked as offline and powered down via the
+:doc:`design-oob`.
+
+Similarly, if it is determined that nodes need to be added, then first
+the nodes are powered up via the :doc:`design-oob`, then they're marked
+as online and finally,
+the cluster is balanced in the same way, as `hbal` would do. For the
+newly powered up nodes, the `htools:standby:auto` tag, if present, is
+removed, but no other tags are removed (including other
+`htools:standby:` tags).
+
+
+Design choices
+==============
+
+The proposed algorithm builds on top of the already present balancing
+algorithm, instead of greedily packing nodes as full as possible. The
+reason is, that in the end, a balanced cluster is needed anyway;
+therefore, basing on the balancing algorithm reduces the number of
+instance moves. Additionally, the final configuration will also
+benefit from all improvements to the balancing algorithm, like taking
+dynamic CPU data into account.
+
+We decided to have a separate program instead of adding an option to
+`hbal` to keep the interfaces, especially that of `hbal`, cleaner. It is
+not unlikely that, over time, additional `hsqueeze`-specific options
+might be added, specifying, e.g., which nodes to prefer for
+shutdown. With the approach of the `htools` of having a single binary
+showing different behaviors, having an additional program also does not
+introduce significant additional cost.
+
+We decided to have a whole prefix instead of a single tag reserved
+for marking standby nodes (we consider all tags starting with
+`htools:standby:` as serving only this purpose). This is not only in
+accordance with the tag
+reservations for other tools, but it also allows for further extension
+(like specifying priorities on which nodes to power up first) without
+changing name spaces.
diff --git a/doc/design-hugepages-support.rst b/doc/design-hugepages-support.rst
new file mode 100644
index 0000000..62c4bce
--- /dev/null
+++ b/doc/design-hugepages-support.rst
@@ -0,0 +1,95 @@
+===============================
+Huge Pages Support for Ganeti
+===============================
+This is a design document about implementing support for huge pages in
+Ganeti. (Please note that Ganeti works with Transparent Huge Pages i.e.
+THP and any reference in this document to Huge Pages refers to explicit
+Huge Pages).
+
+Current State and Shortcomings:
+-------------------------------
+The Linux kernel allows using pages of larger size by setting aside a
+portion of the memory. Using larger page size may enhance the
+performance of applications that require a lot of memory by improving
+page hits. To use huge pages, memory has to be reserved beforehand. This
+portion of memory is subtracted from free memory and is considered as in
+use. Currently Ganeti cannot take proper advantage of huge pages. On a
+node, if huge pages are reserved and are available to fulfill the VM
+request, Ganeti fails to recognize huge pages and considers the memory
+reserved for huge pages as used memory.  This leads to failure of
+launching VMs on a node where memory is available in the form of huge
+pages rather than normal pages.
+
+Proposed Changes:
+-----------------
+The following components will be changed in order for Ganeti to take
+advantage of Huge Pages.
+
+Hypervisor Parameters:
+----------------------
+Currently, It is possible to set or modify huge pages mount point at
+cluster level via the hypervisor parameter ``mem_path`` as::
+
+	$ gnt-cluster init \
+	>--enabled-hypervisors=kvm -nic-parameters link=br100 \
+	> -H kvm:mem_path=/mount/point/for/hugepages
+
+This hypervisor parameter is inherited by all the instances as
+default although it can be overriden at the instance level.
+
+The following changes will be made to the inheritence behaviour.
+
+-  The hypervisor parameter   ``mem_path`` and all other hypervisor
+   parameters will be made available at the node group level (in
+   addition to the cluster level), so that users can set defaults for
+   the node group::
+
+	$ gnt-group add/modify\
+	> -H hv:parameter=value
+
+   This changes the hypervisor inheritence level as::
+
+     cluster -> group -> OS -> instance
+
+-  Furthermore, the hypervisor parameter ``mem_path`` will be changeable
+   only at the cluster or node group level and users must not be able to
+   override this at OS or instance level. The following command must
+   produce an error message that ``mem_path`` may only be set at either
+   the cluster or the node group level::
+
+	$ gnt-instance add -H kvm:mem_path=/mount/point/for/hugepages
+
+Memory Pools:
+-------------
+Memory management of Ganeti will be improved by creating separate pools
+for memory used by the node itself, memory used by the hypervisor and
+the memory reserved for huge pages as:
+- mtotal/xen (Xen memory)
+- mfree/xen (Xen unused memory)
+- mtotal/hp (Memory reserved for Huge Pages)
+- mfree/hp (Memory available from unused huge pages)
+- mpgsize/hp (Size of a huge page)
+
+mfree and mtotal will be changed to mean "the total and free memory for
+the default method in this cluster/nodegroup". Note that the default
+method depends both on the default hypervisor and its parameters.
+
+iAllocator Changes:
+-------------------
+If huge pages are set as default for a cluster of node group, then
+iAllocator must consider the huge pages memory on the nodes, as a
+parameter when trying to find the best node for the VM.
+Note that the iallocator will also be changed to use the correct
+parameter depending on the cluster/group.
+
+hbal Changes:
+-------------
+The cluster balancer (hbal) will be changed to use the default  memory
+pool and  recognize memory reserved for huge pages when trying to
+rebalance the cluster.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-monitoring-agent.rst b/doc/design-monitoring-agent.rst
index 9546abd..9c4871b 100644
--- a/doc/design-monitoring-agent.rst
+++ b/doc/design-monitoring-agent.rst
@@ -49,6 +49,8 @@
 - Node OS CPU load average report
 - Information from a plugin system
 
+.. _monitoring-agent-format-of-the-report:
+
 Format of the report
 --------------------
 
@@ -814,6 +816,13 @@
 value to the corresponding collector, using the collector's name as the
 key of the map. This map will be stored in mond's memory.
 
+The collectors are divided in two categories:
+
+- stateless collectors, collectors who have immediate access to the
+  reported information
+- stateful collectors, collectors whose report is based on data collected
+  in a previous time window
+
 For example: the collection function of the CPU load collector will
 collect a CPU load value and save it in the map mentioned above. The
 collection function will be called by the collector thread every t
diff --git a/doc/design-network.rst b/doc/design-network.rst
index 837924c..8b52f62 100644
--- a/doc/design-network.rst
+++ b/doc/design-network.rst
@@ -88,21 +88,24 @@
 bitfields, the length of the network size each:
 
 ``reservations``
-  This field holds all IP addresses reserved by Ganeti instances, as
-  well as cluster IP addresses (node addresses + cluster master)
+  This field holds all IP addresses reserved by Ganeti instances.
 
 ``external reservations``
   This field holds all IP addresses that are manually reserved by the
-  administrator, because some other equipment is using them outside the
-  scope of Ganeti.
+  administrator (external gateway, IPs of external servers, etc) or
+  automatically by ganeti (the network/broadcast addresses,
+  Cluster IPs (node addresses + cluster master)). These IPs are excluded
+  from the IP pool and cannot be assigned automatically by ganeti to
+  instances (via ip=pool).
 
 The bitfields are implemented using the python-bitarray package for
 space efficiency and their binary value stored base64-encoded for JSON
 compatibility. This approach gives relatively compact representations
 even for large IPv4 networks (e.g. /20).
 
-Ganeti-owned IP addresses (node + master IPs) are reserved automatically
-if the cluster's data network itself is placed under pool management.
+Cluster IP addresses (node + master IPs) are reserved automatically
+as external if the cluster's data network itself is placed under
+pool management.
 
 Helper ConfigWriter methods provide free IP address generation and
 reservation, using a TemporaryReservationManager.
@@ -129,10 +132,14 @@
 
 We also introduce a new ``ip`` address value, ``constants.NIC_IP_POOL``,
 that specifies that a given NIC's IP address should be obtained using
-the IP address pool of the specified network. This value is only valid
+the first available IP address inside the pool of the specified network.
+(reservations OR external_reservations). This value is only valid
 for NICs belonging to a network. A NIC's IP address can also be
 specified manually, as long as it is contained in the network the NIC
-is connected to.
+is connected to. In case this IP is externally reserved, Ganeti will produce
+an error which the user can override if explicitly requested. Of course
+this IP will be reserved and will not be able to be assigned to another
+instance.
 
 
 Hooks
diff --git a/doc/design-openvswitch.rst b/doc/design-openvswitch.rst
index 68a720e..555ad82 100644
--- a/doc/design-openvswitch.rst
+++ b/doc/design-openvswitch.rst
@@ -21,10 +21,10 @@
 
 Proposed changes
 ----------------
-1. Implement functions into gnt-network to manage Open vSwitch through Ganeti gnt-network
-   should be able to create, modify and delete vSwitches. The resulting configuration shall
-   automatically be done on all members of the node group. Connecting Ethernet devices to
-   vSwitches should be managed through this interface as well.
+1. Implement functions into gnt-cluster and gnt-node to manage Open vSwitch through Ganeti.
+   It should be possible to create, modify and delete vSwitches. The resulting configuration
+   shall automatically be done on all members of the node group, if possible. Connecting Ethernet 
+   devices to vSwitches should be managed through this interface as well.
 
 2. Implement VLAN-capabilities: Instances shall have additional information for every NIC: VLAN-ID
    and port type. These are used to determine their type of connection to Open vSwitch. This will
@@ -39,6 +39,29 @@
    bandwidth and maximum burst. This helps to balance the bandwidth needs between the VMs and to
    ensure fair sharing of the bandwidth.
 
+Automatic configuration of OpenvSwitches
+++++++++++++++++++++++++++++++++++++++++
+Ideally, the OpenvSwitch configuration should be done automatically.
+
+This needs to be done on node level, since each node can be individual and a setting on cluster / node group
+level would be too global is thus not wanted. 
+
+The task that each node needs to do is:
+  ``ovs-vsctl addbr <switchname>`` with <switchname> defaulting to constants.DEFAULT_OVS
+  ``ovs-vsctl add-port <switchname> <ethernet device>`` optional: connection to the outside
+
+This will give us 2 parameters, that are needed for the OpenvSwitch Setup:
+  switchname: Which will default to constants.DEFAULT_OVS when not given
+  ethernet device: Which will default to None when not given, might be more than one (NIC bonding)
+
+These parameters should be set at node level for individuality, _but_ can have defined defaults on cluster
+and node group level, which can be inherited and thus allow a cluster or node group wide configuration.
+If a node is setup without parameters, it should use the settings from the parent node group or cluster. If none
+are given there, defaults should be used.
+
+As a first step, this will be implemented for using 1 ethernet device only. Functions for nic bonding will be added
+later on.
+
 Configuration changes for VLANs
 +++++++++++++++++++++++++++++++
 nicparams shall be extended by a value "vlan" that will store the VLAN information for each NIC.
@@ -55,10 +78,12 @@
 Example:
 switch1 will connect the VM to the default VLAN of the switch1.
 switch1.3 means that the VM is connected to an access port of VLAN 3.
-switch1.2:10:20 means that the VM is connected to a trunk port on switch1, carrying VLANs 2, 10 and 20.
+switch1.2:10:20 means that the VM is connected to a hybrid port on switch1, carrying VLANs 2 untagged and 
+VLANs 10 and 20 tagged.
+switch1:44:55 means that the VM is connected to a trunk port on switch1, carrying VLANS 44 and 55
 
-This configuration string is split at the dot and stored in nicparams[constants.NIC_LINK] and
-nicparams[constants.NIC_VLAN] respectively.
+This configuration string is split at the dot or colon respectively and stored in nicparams[constants.NIC_LINK] 
+and nicparams[constants.NIC_VLAN] respectively. Dot or colon are stored as well in nicparams[constants.NIC_VLAN].
 
 For Xen hypervisors, this information can be concatenated again and stored in the vif config as
 the bridge parameter and will be fully compatible with vif-openvswitch as of Xen 4.3.
@@ -66,12 +91,13 @@
 Users of older Xen versions should be able to grab vif-openvswitch from the Xen repo and use it
 (tested in 4.2).
 
-The differentiation between access port and trunk port is given by the number of VLANs that are
-specified.
-
 gnt-instance modify shall be able to add or remove single VLANs from the vlan string without users needing
 to specify the complete new string.
 
+NIC bonding
++++++++++++
+To be done
+
 Configuration changes for QoS
 +++++++++++++++++++++++++++++
 Instances shall be extended with configuration options for
diff --git a/doc/design-optables.rst b/doc/design-optables.rst
new file mode 100644
index 0000000..db24690
--- /dev/null
+++ b/doc/design-optables.rst
@@ -0,0 +1,191 @@
+==========================================
+Filtering of jobs for the Ganeti job queue
+==========================================
+
+.. contents:: :depth: 4
+
+This is a design document detailing the semantics of the fine-grained control
+of jobs in Ganeti. For the implementation there will be a separate
+design document that also describes the vision for the Ganeti daemon
+structure.
+
+
+Current state and shortcomings
+==============================
+
+Control of the Ganeti job queue is quite limited. There is a single
+status bit, the "drained flag". If set, no new jobs are accepted to
+the queue. This is too coarse for some use cases.
+
+- The queue might be required to be drained for several reasons,
+  initiated by different persons or automatic programs. Each one
+  should be able to indicate that his reasons for draining are over
+  without affecting the others.
+
+- There is no support for partial drains. For example, one might want
+  to allow all jobs belonging to a manual (or externally coordinated)
+  maintenance, while disallowing all other jobs.
+
+- There is no support for blocking jobs by their op-codes, e.g.,
+  disallowing all jobs that bring new instances to a cluster. This might
+  be part of a maintenance preparation.
+
+- There is no support for a soft version of draining, where all
+  jobs currently in the queue are finished, while new jobs entering
+  the queue are delayed until the drain is over.
+
+
+Proposed changes
+================
+
+We propose to add filters on the job queue. These will be part of the
+configuration and as such are persisted with it. Conceptionally, the
+filters are always processed when a job enters the queue and while it
+is still in the queue. Of course, in the implementation, reevaluation
+is only carried out, if something could make the result change, e.g.,
+a new job is entered to the queue, or the filter rules are changed.
+There is no distinction between filter processing when a job is about
+to enter the queue and while it is in the queue, as this can be
+expressed by the filter rules themselves (see predicates below).
+
+Format of a Filter rule
+-----------------------
+
+Filter rules are given by the following data.
+
+- A UUID. This ensures that there can be different filter rules
+  that otherwise have all parameters equal. In this way, multiple
+  drains for different reasons are possible. The UUID is used to
+  address the filter rule, in particular for deletion.
+
+  If no UUID is provided at rule addition, Ganeti will create one.
+
+- The watermark. This is the highest job id ever used, as valid in
+  the moment when the filter was added. This data will be added
+  automatically upon addition of the filter.
+
+- A priority. This is a non-negative integer. Filters are processed
+  in order of increasing priority until a rule applies. While there
+  is a well-defined order in which rules of the same priority are
+  evaluated (increasing watermark, then the uuid, are taken as tie
+  breakers), it is not recommended to have rules of the same priority
+  that overlap and have different actions associated.
+
+- A list of predicates. The rule fires, if all of them hold true
+  for the job.
+
+- An action. For the time being, one of the following, but more
+  actions might be added in the future (in particular, future
+  implementations might add an action making filtering continue with
+  a different filter chain).
+
+  - ACCEPT. The job will be accepted; no further filter rules
+    are applied.
+  - PAUSE. The job will be accepted to the queue and remain there;
+    however, it is not executed. If an opcode is currently running,
+    it continues, but the next opcode will not be started. For a paused
+    job all locks it might have acquired will be released as soon as
+    possible, at the latest when the currently running opcode has
+    finished. The job queue will take care of this.
+  - REJECT. The job is rejected. If it is already in the queue,
+    it will be marked as cancelled.
+  - CONTINUE. The filtering continues processing with the next
+    rule. Such a rule will never have any direct or indirect effect,
+    but it can serve as documentation for a "normally present, but
+    currently disabled" rule.
+
+- A reason trail, in the same format as reason trails for opcodes. 
+  This allows to find out, which maintenance (or other reason) caused
+  the addition of this filter rule.
+
+Predicates available for the filter rules
+-----------------------------------------
+
+A predicate is a list, with the first element being the name of the
+predicate and the rest being parameters suitable for that predicate.
+In most cases, the name of the predicate will be a field of a job,
+and there will be a single parameter, which is a boolean expression
+(``filter``) in the sense
+of the Ganeti query language. However, no assumption should be made
+that all predicates are of this shape. More predicates may be added
+in the future.
+
+- ``jobid``. Only parameter is a boolean expression. For this expression,
+  there is only one field available, ``id``, which represents the id the job to be
+  filtered. In all value positions, the string ``watermark`` will be
+  replaced by the value of the watermark.
+
+- ``opcode``. Only parameter is boolean expresion. For this expression, ``OP_ID``
+  and all other fields present in the opcode are available. This predicate
+  will hold true, if the expression is true for at least one opcode in
+  the job.
+
+- ``reason``. Only parameter is a boolean expression. For this expression, the three
+  fields ``source``, ``reason``, ``timestamp`` of reason trail entries
+  are available. This predicate is true, if one of the entries of one
+  of the opcodes in this job satisfies the expression.
+
+
+Examples
+========
+
+Draining the queue.
+::
+
+   {'priority': 0,
+    'predicates': [['jobid', ['>', 'id', 'watermark']]],
+    'action': 'REJECT'}
+
+Soft draining could be achieved by replacing ``REJECT`` by ``PAUSE`` in the
+above example.
+
+Pausing all new jobs not belonging to a specific maintenance.
+::
+
+   {'priority': 1,
+    'predicates': [['jobid', ['>', 'id', 'watermark']],
+                   ['reason', ['!', ['=~', 'reason', 'maintenance pink bunny']]]],
+    'action': 'PAUSE'}
+
+Canceling all queued instance creations and disallowing new such jobs.
+::
+
+  {'priority': 1,
+   'predicates': [['opcode', ['=', 'OP_ID', 'OP_INSTANCE_CREATE']]],
+   'action': 'REJECT'}
+
+
+
+Interface
+=========
+
+Since queue control is intended to be used by external maintenance-handling
+tools as well, the primary interface for manipulating queue filters is the
+:doc:`rapi`. For convenience, a command-line interface will be added as well.
+
+The following resources will be added.
+
+- /2/filters/
+
+  - GET returns the list of all currently set filters
+
+  - POST adds a new filter
+
+- /2/filters/[uuid]
+
+  - GET returns the description of the specified filter
+
+  - DELETE removes the specified filter
+
+  - PUT replaces the specified filter rule, or creates it,
+    if it doesn't exist already.
+
+Security considerations
+=======================
+
+Filtering of jobs is not a security feature. It merely serves the purpose
+of coordinating efforts and avoiding accidental conflicting
+jobs. Everybody with appropriate credentials can modify the filter
+rules, not just the originator of a rule. To avoid accidental
+lock-out, requests modifying the queue are executed directly and not
+going through the queue themselves.
diff --git a/doc/design-performance-tests.rst b/doc/design-performance-tests.rst
new file mode 100644
index 0000000..e3e2bf6
--- /dev/null
+++ b/doc/design-performance-tests.rst
@@ -0,0 +1,118 @@
+========================
+Performance tests for QA
+========================
+
+.. contents:: :depth: 4
+
+This design document describes performance tests to be added to QA in
+order to measure performance changes over time.
+
+Current state and shortcomings
+==============================
+
+Currently, only functional QA tests are performed. Those tests verify
+the correct behaviour of Ganeti in various configurations, but are not
+designed to continuously monitor the performance of Ganeti.
+
+The current QA tests don't execute multiple tasks/jobs in parallel.
+Therefore, the locking part of Ganeti does not really receive any
+testing, neither functional nor performance wise.
+
+On the plus side, Ganeti's QA code does already measure the runtime of
+individual tests, which is leveraged in this design.
+
+Proposed changes
+================
+
+The tests to be added in the context of this design document focus on
+two areas:
+
+  * Job queue performance. How does Ganeti handle a lot of submitted
+    jobs?
+  * Parallel job execution performance. How well does Ganeti
+    parallelize jobs?
+
+Jobs are submitted to the job queue in sequential order, but the
+execution of the jobs runs in parallel. All job submissions must
+complete within a reasonable timeout.
+
+In order to make it easier to recognize performance related tests, all
+tests added in the context of this design get a description with a
+"PERFORMANCE: " prefix.
+
+Job queue performance
+---------------------
+
+Tests targeting the job queue should eliminate external factors (like
+network/disk performance or hypervisor delays) as much as possible, so
+they are designed to run in a vcluster QA environment.
+
+The following tests are added to the QA:
+
+  * Submit the maximum amount of instance create jobs in parallel. As
+    soon as a creation job succeeds, submit a removal job for this
+    instance.
+  * Submit as many instance create jobs as there are nodes in the
+    cluster in parallel (for non-redundant instances). Removal jobs
+    as above.
+  * For the maximum amount of instances in the cluster, submit modify
+    jobs (modify hypervisor and backend parameters) in parallel.
+  * For the maximum amount of instances in the cluster, submit stop,
+    start, reboot and reinstall jobs in parallel.
+  * For the maximum amount of instances in the cluster, submit multiple
+    list and info jobs in parallel.
+  * For the maximum amount of instances in the cluster, submit move
+    jobs in parallel. While the move operations are running, get
+    instance information using info jobs. Those jobs are required to
+    return within a reasonable low timeout.
+  * For the maximum amount of instances in the cluster, submit add-,
+    remove- and list-tags jobs.
+  * Submit 200 `gnt-debug delay` jobs with a delay of 0.1 seconds. To
+    speed up submission, perform multiple job submissions in parallel.
+    Verify that submitting jobs doesn't significantly slow down during
+    the process. Verify that querying cluster information over CLI and
+    RAPI succeeds in a timely fashion with the delay jobs
+    running/queued.
+
+Parallel job execution performance
+----------------------------------
+
+Tests targeting the performance of parallel execution of "real" jobs
+in close-to-production clusters should actually perform all operations,
+such as creating disks and starting instances. This way, real world
+locking or waiting issues can be reproduced. Performing all those
+operations does requires quite some time though, so only a smaller
+number of instances and parallel jobs can be tested realistically.
+
+The following tests are added to the QA:
+
+  * Submitting twice as many instance creation request as there are
+    nodes in the cluster, using DRBD as disk template. As soon as a
+    creation job succeeds, submit a removal job for this instance.
+  * Submitting twice as many instance creation request as there are
+    nodes in the cluster, using Plain as disk template. As soon as a
+    creation job succeeds, submit a removal job for this instance.
+    This test can make better use of parallelism because only one
+    node must be locked for an instance creation.
+  * Create an instance using DRBD. Fail it over, migrate it, change
+    its secondary node, reboot it and reinstall it while creating an
+    additional instance in parallel to each of those operations.
+
+Future work
+===========
+
+Based on test results of the tests listed above, additional tests can
+be added to cover more real-world use-cases. Also, based on user
+requests, specially crafted performance tests modeling those workloads
+can be added too.
+
+Additionally, the correlations between job submission time and job
+queue size could be detected. Therefore, a snapshot of the job queue
+before job submission could be taken to measure job submission time
+based on the jobs in the queue.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-storagetypes.rst b/doc/design-storagetypes.rst
index ed4721f..f1f022d 100644
--- a/doc/design-storagetypes.rst
+++ b/doc/design-storagetypes.rst
@@ -9,7 +9,7 @@
 
 Currently, there is no consistent management of different variants of storage
 in Ganeti. One direct consequence is that storage space reporting is currently
-broken for all storage that is not based on lvm technolgy. This design looks at
+broken for all storage that is not based on lvm technology. This design looks at
 the root causes and proposes a way to fix it.
 
 Proposed changes
@@ -52,8 +52,8 @@
 The ipolicy also contains a list of enabled disk templates. Since the cluster-
 wide enabled disk templates should be a stronger constraint, the list of
 enabled disk templates in the ipolicy should be a subset of those. In case the
-user tries to create an inconsistent situation here, gnt-cluster should emit
-a warning.
+user tries to create an inconsistent situation here, gnt-cluster should
+display this as an error.
 
 We consider the first disk template in the list to be the default template for
 instance creation and storage reporting. This will remove the need to specify
@@ -71,6 +71,9 @@
 using the option ``--no-lvm-storage``. This will be replaced by adding/removing
 ``drbd`` and ``plain`` from the set of enabled disk templates.
 
+The option ``--no-drbd-storage`` is also subsumed by dis/enabling the
+disk template ``drbd`` on the cluster.
+
 Up till now, file storage and shared file storage could be dis/enabled at
 ``./configure`` time. This will also be replaced by adding/removing the
 respective disk templates from the set of enabled disk templates.
@@ -89,7 +92,6 @@
 update logic to be implemented in the online update of the config in
 the ``Cluster`` class in ``objects.py``:
 - If a ``volume_group_name`` is existing, then enable ``drbd`` and ``plain``.
-(TODO: can we narrow that down further?)
 - If ``file`` or ``sharedfile`` was enabled at configure time, add the
 respective disk template to the list of enabled disk templates.
 - For disk templates ``diskless``, ``blockdev``, ``ext``, and ``rbd``, we
@@ -139,18 +141,19 @@
 unit that is of type ``lvm-pv`` directly, therefore it is not included in the
 mapping.
 
-The storage reporting for file storage will report space on the file storage
-dir, which is currently limited to one directory. In the future, if we'll have
-support for more directories, or for per-nodegroup directories this can be
-changed.
+The storage reporting for file and sharedfile storage will report space
+on the file storage dir, which is currently limited to one directory.
+In the future, if we'll have support for more directories, or for per-nodegroup
+directories this can be changed.
 
-For now, we will implement only the storage reporting for non-shared storage,
-that is disk templates ``file``, ``lvm``, and ``drbd``. For disk template
-``diskless``, there is obviously nothing to report about. When implementing
-storage reporting for file, we can also use it for ``sharedfile``, since it
-uses the same file system mechanisms to determine the free space. In the
-future, we can optimize storage reporting for shared storage by not querying
-all nodes that use a common shared file for the same space information.
+For now, we will implement only the storage reporting for lvm-based and
+file-based storage, that is disk templates ``file``, ``sharedfile``, ``lvm``,
+and ``drbd``. For disk template ``diskless``, there is obviously nothing to
+report about. When implementing storage reporting for file, we can also use
+it for ``sharedfile``, since it uses the same file system mechanisms to
+determine the free space. In the future, we can optimize storage reporting
+for shared storage by not querying all nodes that use a common shared file
+for the same space information.
 
 In the future, we extend storage reporting for shared storage types like
 ``rados`` and ``ext``. Note that it will not make sense to query each node for
@@ -221,6 +224,20 @@
 same disk template, therefore we will then start referencing the storage pool
 name instead of the disk template.
 
+Note: As of version 2.10, ``gnt-node list`` only reports storage space
+information for the default disk template, as supporting more options
+turned out to be not feasible without storage pools.
+
+Besides in ``gnt-node list``, storage space information is also
+displayed in ``gnt-node list-storage``. This will also adapt to the
+extended storage reporting capabilities. The user can specify a storage
+type using ``--storage-type``. If he requests storage information about
+a storage type which does not support space reporting, a warning is
+emitted. If no storage type is specified explicitly, ``gnt-node
+list-storage`` will try to report storage on the storage type of the
+default disk template. If the default disk template's storage type does
+not support space reporting, an error message is emitted.
+
 ``gnt-cluster info`` will report which disk templates are enabled, i.e.
 which ones are supported according to the cluster configuration. Example
 output::
@@ -245,6 +262,13 @@
 are different, and we only consider the main volume group. Fixing this is
 outside the scope of this design.
 
+Although the iallocator protocol itself does not need change, the
+invocation of the iallocator needs quite some adaption. So far, it
+always requested LVM storage information no matter if that was the
+disk template to be considered for the allocation. For instance
+allocation, this is the disk template of the instance.
+TODO: consider other allocator requests.
+
 With this design, we ensure forward-compatibility with respect to storage
 pools. For now, we'll report space for all available disk templates that
 are based on non-shared storage types, in the future, for all available
diff --git a/doc/design-upgrade.rst b/doc/design-upgrade.rst
new file mode 100644
index 0000000..1d62a0b
--- /dev/null
+++ b/doc/design-upgrade.rst
@@ -0,0 +1,296 @@
+========================================
+Automatized Upgrade Procedure for Ganeti
+========================================
+
+.. contents:: :depth: 4
+
+This is a design document detailing the proposed changes to the
+upgrade process, in order to allow it to be more automatic.
+
+
+Current state and shortcomings
+==============================
+
+Ganeti requires to run the same version of Ganeti to be run on all
+nodes of a cluster and this requirement is unlikely to go away in the
+foreseeable future. Also, the configuration may change between minor
+versions (and in the past has proven to do so). This requires a quite
+involved manual upgrade process of draining the queue, stopping
+ganeti, changing the binaries, upgrading the configuration, starting
+ganeti, distributing the configuration, and undraining the queue.
+
+
+Proposed changes
+================
+
+While we will not remove the requirement of the same Ganeti
+version running on all nodes, the transition from one version
+to the other will be made more automatic. It will be possible
+to install new binaries ahead of time, and the actual switch
+between versions will be a single command.
+
+While changing the file layout anyway, we install the python
+code, which is architecture independent, under ``${prefix}/share``,
+in a way that properly separates the Ganeti libraries of the
+various versions. 
+
+Path changes to allow multiple versions installed
+-------------------------------------------------
+
+Currently, Ganeti installs to ``${PREFIX}/bin``, ``${PREFIX}/sbin``,
+and so on, as well as to ``${pythondir}/ganeti``.
+
+These paths will be changed in the following way.
+
+- The python package will be installed to
+  ``${PREFIX}/share/ganeti/${VERSION}/ganeti``.
+  Here ${VERSION} is, depending on configure options, either the full qualified
+  version number, consisting of major, minor, revision, and suffix, or it is
+  just a major.minor pair. All python executables will be installed under
+  ``${PREFIX}/share/ganeti/${VERSION}`` so that they see their respective
+  Ganeti library. ``${PREFIX}/share/ganeti/default`` is a symbolic link to
+  ``${sysconfdir}/ganeti/share`` which, in turn, is a symbolic link to
+  ``${PREFIX}/share/ganeti/${VERSION}``. For all python executatables (like
+  ``gnt-cluster``, ``gnt-node``, etc) symbolic links going through
+  ``${PREFIX}/share/ganeti/default`` are added under ``${PREFIX}/sbin``.
+
+- All other files will be installed to the corresponding path under
+  ``${libdir}/ganeti/${VERSION}`` instead of under ``${PREFIX}``
+  directly, where ``${libdir}`` defaults to ``${PREFIX}/lib``.
+  ``${libdir}/ganeti/default`` will be a symlink to ``${sysconfdir}/ganeti/lib``
+  which, in turn, is a symlink to ``${libdir}/ganeti/${VERSION}``.
+  Symbolic links to the files installed under ``${libdir}/ganeti/${VERSION}``
+  will be added under ``${PREFIX}/bin``, ``${PREFIX}/sbin``, and so on. These
+  symbolic links will go through ``${libdir}/ganeti/default`` so that the
+  version can easily be changed by updating the symbolic link in
+  ``${sysconfdir}``.
+
+The set of links for ganeti binaries might change between the versions.
+However, as the file structure under ``${libdir}/ganeti/${VERSION}`` reflects
+that of ``/``, two links of differnt versions will never conflict. Similarly,
+the symbolic links for the python executables will never conflict, as they
+always point to a file with the same basename directly under
+``${PREFIX}/share/ganeti/default``. Therefore, each version will make sure that
+enough symbolic links are present in ``${PREFIX}/bin``, ``${PREFIX}/sbin`` and
+so on, even though some might be dangling, if a differnt version of ganeti is
+currently active.
+
+The extra indirection through ``${sysconfdir}`` allows installations that choose
+to have ``${sysconfdir}`` and ``${localstatedir}`` outside ``${PREFIX}`` to
+mount ``${PREFIX}`` read-only. The latter is important for systems that choose
+``/usr`` as ``${PREFIX}`` and are following the Filesystem Hierarchy Standard.
+For example, choosing ``/usr`` as ``${PREFIX}`` and ``/etc`` as ``${sysconfdir}``,
+the layout for version 2.10 will look as follows.
+::
+
+   /
+   |
+   +-- etc
+   |   |
+   |   +-- ganeti 
+   |         |
+   |         +-- lib -> /usr/lib/ganeti/2.10
+   |         |
+   |         +-- share  -> /usr/share/ganeti/2.10
+   +-- usr
+        |
+        +-- bin
+        |   |
+        |   +-- harep -> /usr/lib/ganeti/default/usr/bin/harep
+        |   |
+        |   ...  
+        |
+        +-- sbin
+        |   |
+        |   +-- gnt-cluster -> /usr/share/ganeti/default/gnt-cluster
+        |   |
+        |   ...  
+        |
+        +-- ...
+        |
+        +-- lib
+        |   |
+        |   +-- ganeti
+        |       |
+        |       +-- default -> /etc/ganeti/lib
+        |       |
+        |       +-- 2.10
+        |           |
+        |           +-- usr
+        |               |
+        |               +-- bin
+        |               |    |
+        |               |    +-- htools
+        |               |    |
+        |               |    +-- harep -> htools
+        |               |    |
+        |               |    ...
+        |               ...
+        |
+        +-- share
+             |
+             +-- ganeti
+                 |
+                 +-- default -> /etc/ganeti/share
+                 |
+                 +-- 2.10
+                     |
+                     + -- gnt-cluster
+                     |
+                     + -- gnt-node
+                     |
+                     + -- ...
+                     |
+                     + -- ganeti
+                          |
+                          +-- backend.py
+                          |
+                          +-- ...
+                          |
+                          +-- cmdlib
+                          |   |
+                          |   ...
+                          ...
+
+
+
+gnt-cluster upgrade
+-------------------
+
+The actual upgrade process will be done by a new command ``upgrade`` to
+``gnt-cluster``. If called with the option ``--to`` which take precisely
+one argument, the version to
+upgrade (or downgrade) to, given as full string with major, minor, revision,
+and suffix. To be compatible with current configuration upgrade and downgrade
+procedures, the new version must be of the same major version and
+either an equal or higher minor version, or precisely the previous
+minor version.
+
+When executed, ``gnt-cluster upgrade --to=<version>`` will perform the
+following actions.
+
+- It verifies that the version to change to is installed on all nodes
+  of the cluster that are not marked as offline. If this is not the
+  case it aborts with an error. This initial testing is an
+  optimization to allow for early feedback.
+
+- An intent-to-upgrade file is created that contains the current
+  version of ganeti, the version to change to, and the process ID of
+  the ``gnt-cluster upgrade`` process. The latter is not used automatically,
+  but allows manual detection if the upgrade process died
+  unintentionally. The intend-to-upgrade file is persisted to disk
+  before continuing.
+
+- The Ganeti job queue is drained, and the executable waits till there
+  are no more jobs in the queue. Once :doc:`design-optables` is
+  implemented, for upgrades, and only for upgrades, all jobs are paused
+  instead (in the sense that the currently running opcode continues,
+  but the next opcode is not started) and it is continued once all
+  jobs are fully paused.
+
+- All ganeti daemons on the master node are stopped.
+
+- It is verified again that all nodes at this moment not marked as
+  offline have the new version installed. If this is not the case,
+  then all changes so far (stopping ganeti daemons and draining the
+  queue) are undone and failure is reported. This second verification
+  is necessary, as the set of online nodes might have changed during
+  the draining period.
+
+- All ganeti daemons on all remaining (non-offline) nodes are stopped.
+
+- A backup of all Ganeti-related status information is created for
+  manual rollbacks. While the normal way of rolling back after an
+  upgrade should be calling ``gnt-clsuter upgrade`` from the newer version
+  with the older version as argument, a full backup provides an
+  additional safety net, especially for jump-upgrades (skipping
+  intermediate minor versions).
+
+- If the action is a downgrade to the previous minor version, the
+  configuration is downgraded now, using ``cfgupgrade --downgrade``.
+
+- The ``${sysconfdir}/ganeti/lib`` and ``${sysconfdir}/ganeti/share``
+  symbolic links are updated.
+
+- If the action is an upgrade to a higher minor version, the configuration
+  is upgraded now, using ``cfgupgrade``.
+
+- ``ensure-dirs --full-run`` is run on all nodes.
+
+- All daemons are started on all nodes.
+
+- ``gnt-cluster redist-conf`` is run on the master node. 
+
+- All daemons are restarted on all nodes.
+
+- The Ganeti job queue is undrained.
+
+- The intent-to-upgrade file is removed.
+
+- ``post-upgrade`` is run with the original version as argument.
+
+- ``gnt-cluster verify`` is run and the result reported.
+
+
+Considerations on unintended reboots of the master node
+=======================================================
+ 
+During the upgrade procedure, the only ganeti process still running is
+the one instance of ``gnt-cluster upgrade``. This process is also responsible
+for eventually removing the queue drain. Therefore, we have to provide
+means to resume this process, if it dies unintentionally. The process
+itself will handle SIGTERM gracefully by either undoing all changes
+done so far, or by ignoring the signal all together and continuing to
+the end; the choice between these behaviors depends on whether change
+of the configuration has already started (in which case it goes
+through to the end), or not (in which case the actions done so far are
+rolled back).
+
+To achieve this, ``gnt-cluster upgrade`` will support a ``--resume``
+option. It is recommended
+to have ``gnt-cluster upgrade --resume`` as an at-reboot task in the crontab.
+The ``gnt-cluster upgrade --resume`` comand first verifies that
+it is running on the master node, using the same requirement as for
+starting the master daemon, i.e., confirmed by a majority of all
+nodes. If it is not the master node, it will remove any possibly
+existing intend-to-upgrade file and exit. If it is running on the
+master node, it will check for the existence of an intend-to-upgrade
+file. If no such file is found, it will simply exit. If found, it will
+resume at the appropriate stage.
+
+- If the configuration file still is at the initial version,
+  ``gnt-cluster upgrade`` is resumed at the step immediately following the
+  writing of the intend-to-upgrade file. It should be noted that
+  all steps before changing the configuration are idempotent, so
+  redoing them does not do any harm.
+
+- If the configuration is already at the new version, all daemons on
+  all nodes are stopped (as they might have been started again due
+  to a reboot) and then it is resumed at the step immediately
+  following the configuration change. All actions following the
+  configuration change can be repeated without bringing the cluster
+  into a worse state.
+
+
+Caveats
+=======
+
+Since ``gnt-cluster upgrade`` drains the queue and undrains it later, so any
+information about a previous drain gets lost. This problem will
+disappear, once :doc:`design-optables` is implemented, as then the
+undrain will then be restricted to filters by gnt-upgrade.
+
+
+Requirement of opcode backwards compatibility
+==============================================
+
+Since for upgrades we only pause jobs and do not fully drain the
+queue, we need to be able to transform the job queue into a queue for
+the new version. The way this is achieved is by keeping the
+serialization format backwards compatible. This is in line with
+current practice that opcodes do not change between versions, and at
+most new fields are added. Whenever we add a new field to an opcode,
+we will make sure that the deserialization function will provide a
+default value if the field is not present.
+
+
diff --git a/doc/dev-codestyle.rst b/doc/dev-codestyle.rst
new file mode 100644
index 0000000..daab9ad
--- /dev/null
+++ b/doc/dev-codestyle.rst
@@ -0,0 +1,622 @@
+Code style guide
+================
+
+Python
+------
+
+.. highlight:: python
+
+These are a few guidelines for Ganeti code and documentation.
+
+In simple terms: try to stay consistent with the existing code. `PEP 8`_ says:
+
+.. _PEP 8: http://www.python.org/dev/peps/pep-0008/
+
+  A style guide is about consistency. Consistency with this style guide is
+  important. Consistency within a project is more important. Consistency
+  within one module or function is most important.
+
+.. note::
+
+  You might also want to take a look at the `Google style guide`_, since we
+  have some things in common with it.
+
+.. _Google style guide: http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
+
+Indentation
+~~~~~~~~~~~
+In general, always indent using two (2) spaces and don't use tabs.
+
+The two spaces should always be relative to the previous level of indentation,
+even if this means that the final number of spaces is not a multiple of 2.
+
+When going on a new line inside an open parenthesis, align with the content of
+the parenthesis on the previous line.
+
+Valid example::
+
+  v = (somevalue,
+       a_function([
+         list_elem, # 7 spaces, but 2 from the previous indentation level
+         another_elem,
+       ]))
+
+Formatting strings
+~~~~~~~~~~~~~~~~~~
+Always use double quotes (``""``), never single quotes (``''``), except for
+existing code. Examples for formatting strings::
+
+  var = "value"
+
+  # Note: The space character is always on the second line
+  var = ("The quick brown fox jumps over the lazy dog. The quick brown fox"
+         " jumps over the lazy dog. The quick brown fox jumps over the lazy"
+         " dog.")
+
+  fn("The quick brown fox jumps over the lazy dog. The quick brown fox jumps"
+     " over the lazy dog.")
+
+  fn(constants.CONFIG_VERSION,
+     ("The quick brown fox jumps over the lazy dog. The quick brown fox"
+      " jumps over the lazy dog. The quick brown fox jumps over the lazy"
+      " dog."))
+
+Don't format strings like this::
+
+  # Don't use single quotes
+  var = 'value'
+
+  # Don't use backslash for line continuation
+  var = "The quick brown fox jumps over the lazy dog. The quick brown fox"\
+        " jumps over the lazy dog."
+
+  # Space character goes to the beginning of a new line
+  var = ("The quick brown fox jumps over the lazy dog. The quick brown fox "
+         "jumps over the lazy dog. The quick brown fox jumps over the lazy "
+         "dog.")
+
+Formatting sequences
+~~~~~~~~~~~~~~~~~~~~
+Built-in sequence types are list (``[]``), tuple (``()``) and dict (``{}``).
+When splitting to multiple lines, each item should be on its own line and a
+comma must be added on the last line. Don't write multiline dictionaries in
+function calls, except when it's the only parameter. Always indent items by
+two spaces.
+
+::
+
+  # Short lists
+  var = ["foo", "bar"]
+  var = ("foo", "bar")
+
+  # Longer sequences and dictionary
+  var = [
+    constants.XYZ_FILENAME_EXTENSION,
+    constants.FOO_BAR_BAZ,
+    ]
+  var = {
+    "key": func(),
+    "otherkey": None,
+    }
+
+  # Multiline tuples as dictionary values
+  var = {
+    "key":
+      ("long value taking the whole line, requiring you to go to a new one",
+       other_value),
+  }
+
+  # Function calls
+  var = frozenset([1, 2, 3])
+  var = F({
+    "xyz": constants.XYZ,
+    "abc": constants.ABC,
+    })
+
+  # Wrong
+  F(123, "Hello World",
+    { "xyz": constants.XYZ })
+
+We consider tuples as data structures, not containers. So in general please
+use lists when dealing with a sequence of homogeneous items, and tuples when
+dealing with heterogeneous items.
+
+Passing arguments
+~~~~~~~~~~~~~~~~~
+Positional arguments must be passed as positional arguments, keyword arguments
+must be passed as keyword arguments. Everything else will be difficult to
+maintain.
+
+::
+
+  # Function signature
+  def F(data, key, salt=None, key_selector=None):
+    pass
+
+  # Yes
+  F("The quick brown fox", "123456")
+  F("The quick brown fox", "123456", salt="abc")
+  F("The quick brown fox", "123456", key_selector="xyz")
+  F("The quick brown fox", "123456", salt="foo", key_selector="xyz")
+
+  # No: Passing keyword arguments as positional argument
+  F("The quick brown fox", "123456", "xyz", "bar")
+
+  # No: Passing positional arguments as keyword argument
+  F(salt="xyz", data="The quick brown fox", key="123456", key_selector="xyz")
+
+Docstrings
+~~~~~~~~~~
+
+.. note::
+
+  `PEP 257`_ is the canonical document, unless epydoc overrules it (e.g. in how
+  to document the type of an argument).
+
+For docstrings, the recommended format is epytext_, to be processed via
+epydoc_. There is an ``apidoc`` target that builds the documentation and puts it
+into the doc/api subdir. Note that we currently use epydoc version 3.0.
+
+.. _PEP 257: http://www.python.org/dev/peps/pep-0257/
+.. _epytext: http://epydoc.sourceforge.net/manual-epytext.html
+.. _epydoc: http://epydoc.sourceforge.net/
+
+Note that one-line docstrings are only accepted in the unittests.
+
+Rules for writing the docstrings (mostly standard Python rules):
+
+* the docstring should start with a sentence, with punctuation at the end,
+  summarizing the the aim of what is being described. This sentence cannot be
+  longer than one line
+* the second line should be blank
+* afterwards the rest of the docstring
+* special epytext tags should come at the end
+* multi-line docstrings must finish with an empty line
+* do not try to make a table using lots of whitespace
+* use ``L{}`` and ``C{}`` where appropriate
+
+Here's an example::
+
+  def fn(foo, bar):
+    """Compute the sum of foo and bar.
+
+    This functions builds the sum of foo and bar. It's a simple function.
+
+    @type foo: int
+    @param foo: First parameter.
+    @type bar: float
+    @param bar: The second parameter. This line is longer
+      to show wrapping.
+    @rtype: float
+    @return: the sum of the two numbers
+
+    """
+    return foo + bar
+
+Some rules of thumb which should be applied with good judgement on a case-to-
+case basis:
+
+* If the meaning of parameters is already obvious given its name and the
+  methods description, don't document it again. Just add a ``@type`` tag.
+* Refer to the base methods documentation when overwriting methods. Only
+  document more if it applies to the current subclass only, or if you want to
+  clarify on the meaning of parameters for the special subclass.
+
+Rules for classes and modules
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+As `PEP 257`_ says, the docstrings of classes should document their attributes
+and the docstrings of modules should shortly document the exported
+functions/variables/etc.
+
+See for example the pydoc output for the ``os`` or ``ConfigParser`` standard
+modules.
+
+Haskell
+-------
+
+.. highlight:: haskell
+
+The most important consideration is, as usual, to stay consistent with the
+existing code.
+
+As there's no "canonical" style guide for Haskell, this code style has been
+inspired from a few online resources, including the style guide for the
+`Snap framework`_, `this style guide`_ and `this other style guide`_.
+
+.. _Snap framework: http://snapframework.com/docs/style-guide
+.. _this style guide: https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md
+.. _this other style guide: http://www.cs.caltech.edu/courses/cs11/material/haskell/misc/haskell_style_guide.html
+
+Files
+~~~~~
+Use ordinary, non-`literate`_ Haskell ``.hs`` files.
+
+.. _literate: http://www.haskell.org/haskellwiki/Literate_programming
+
+Use proper copyright headers, and proper Haddock style documentation headers::
+
+  {-| Short module summary.
+
+  Longer module description.
+
+  -}
+
+  {-
+
+  Copyright (C) ...
+
+  This program is free software ...
+
+  -}
+
+If there are module-level pragmas add them right at the top, before the short
+summary.
+
+Imports
+~~~~~~~
+Imports should be grouped into the following groups and inside each group they
+should be sorted alphabetically:
+
+1. standard library imports
+2. third-party imports
+3. local imports
+
+It is allowed to use qualified imports with short names for:
+
+* standard library (e.g. ``import qualified Data.Map as M``)
+* local imports (e.g. ``import qualified Ganeti.Constants as C``), although
+  this form should be kept to a minimum
+
+Indentation
+~~~~~~~~~~~
+Use only spaces, never tabs. Indentation level is 2 characters. For Emacs,
+this means setting the variable ``haskell-indent-offset`` to 2.
+
+Line length should be at most 78 chars, and 72 chars inside comments.
+
+Use indentation-based structure, and not braces/semicolons.
+
+.. note::
+
+  Special indendation of if/then/else construct
+
+  For the ``do`` notation, the ``if-then-else`` construct has a non-intuitive
+  behaviour. As such, the indentation of ``if-then-else`` (both in ``do``
+  blocks and in normal blocks) should be as follows::
+
+    if condition
+      then expr1
+      else expr2
+
+  i.e. indent the then/else lines with another level. This can be accomplished
+  in Emacs by setting the variable ``haskell-indent-thenelse`` to 2 (from the
+  default of zero).
+
+If you have more than one line of code please newline/indent after the "=". Do
+`not` do::
+
+  f x = let y = x + 1
+        in  y
+
+Instead do::
+
+  f x =
+    let y = x + 1
+    in  y
+
+or if it is just one line::
+
+  f x = x + 1
+
+Multiline strings
+~~~~~~~~~~~~~~~~~
+Multiline strings are created by closing a line with a backslash and starting
+the following line with a backslash, keeping the indentation level constant.
+Whitespaces go on the new line, right after the backslash.
+
+::
+
+  longString :: String
+  longString = "This is a very very very long string that\
+               \ needs to be split in two lines"
+
+Data declarations
+~~~~~~~~~~~~~~~~~
+.. warning::
+  Note that this is different from the Python style!
+
+When declaring either data types, or using list literals, etc., the columns
+should be aligned, and for lists use a comma at the start of the line, not at
+the end. Examples::
+
+  data OpCode = OpStartupInstance ...
+              | OpShutdownInstance ...
+              | ...
+
+  data Node = Node { name :: String
+                   , ip   :: String
+                   , ...
+                   }
+
+  myList = [ value1
+           , value2
+           , value3
+           ]
+
+The choice of whether to wrap the first element or not is up to you; the
+following is also allowed::
+
+  myList =
+    [ value1
+    , value2
+    ]
+
+For records, always add spaces around the braces and the equality sign.
+::
+
+  foo = Foo { fBar = "bar", fBaz = 4711 }
+
+  foo' = Foo { fBar = "bar 2"
+             , fBaz = 4712
+             }
+
+  node' = node { ip = "127.0.0.1" }
+
+
+White space
+~~~~~~~~~~~
+Like in Python, surround binary operators with one space on either side. Do no
+insert a space after a lamda::
+
+  -- bad
+  map (\ n -> ...) lst
+  -- good
+  foldl (\x y -> ...) ...
+
+Use a blank line between top-level definitions, but no blank lines between
+either the comment and the type signature or between the type signature and
+the actual function definition.
+
+.. note::
+  Ideally it would be two blank lines between top-level definitions, but the
+  code only has one now.
+
+As always, no trailing spaces. Ever.
+
+Spaces after comma
+******************
+
+Instead of::
+
+  ("a","b")
+
+write::
+
+  ("a", "b")
+
+Naming
+~~~~~~
+Functions should be named in mixedCase style, and types in CamelCase. Function
+arguments and local variables should be mixedCase.
+
+When using acronyms, ones longer than 2 characters should be typed capitalised,
+not fully upper-cased (e.g. ``Http``, not ``HTTP``).
+
+For variable names, use descriptive names; it is only allowed to use very
+short names (e.g. ``a``, ``b``, ``i``, ``j``, etc.) when:
+
+* the function is trivial, e.g.::
+
+    sum x y = x + y
+
+* we talk about some very specific cases, e.g.
+  iterators or accumulators in folds::
+
+    map (\v -> v + 1) lst
+
+* using ``x:xs`` for list elements and lists, etc.
+
+In general, short/one-letter names are allowed when we deal with polymorphic
+values; for example the standard map definition from Prelude::
+
+  map :: (a -> b) -> [a] -> [b]
+  map _ []     = []
+  map f (x:xs) = f x : map f xs
+
+In this example, neither the ``a`` nor ``b`` types are known to the map
+function, so we cannot give them more explicit names. Since the body of the
+function is trivial, the variables used are longer.
+
+However, if we deal with explicit types or values, their names should be
+descriptive.
+
+.. todo: add a nice example here.
+
+Finally, the naming should look familiar to people who just read the
+Prelude/standard libraries.
+
+Naming for updated values
+*************************
+
+.. highlight:: python
+
+Since one cannot update a value in Haskell, this presents a particular problem
+on the naming of new versions of the same value. For example, the following
+code in Python::
+
+  def failover(pri, sec, inst):
+    pri.removePrimary(inst)
+    pri.addSecondary(inst)
+    sec.removeSecondary(inst)
+    sec.addPrimary(inst)
+
+.. highlight:: haskell
+
+becomes in Haskell something like the following::
+
+  failover pri sec inst =
+    let pri'  = removePrimary pri inst
+        pri'' = addSecondary pri' inst
+        sec'  = removeSecondary sec inst
+        sec'' = addPrimary sec' inst
+    in (pri'', sec'')
+
+When updating values, one should add single quotes to the name for up to three
+new names (e.g. ``inst``, ``inst'``, ``inst''``, ``inst'''``) and otherwise
+use numeric suffixes (``inst1``, ``inst2``, ``inst3``, ..., ``inst8``), but
+that many updates is already bad style and thus should be avoided.
+
+Type signatures
+~~~~~~~~~~~~~~~
+
+Always declare types for functions (and any other top-level bindings).
+
+If in doubt, feel free to declare the type of the variables/bindings in a
+complex expression; this usually means the expression is too complex, however.
+
+Similarly, provide Haddock-style comments for top-level definitions.
+
+Use sum types instead of exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Exceptions make it hard to write functional code, as alternative
+control flows need to be considered and compiler support is limited.
+Therefore, Ganeti functions should never allow exceptions to escape.
+Function that can fail should report failure by returning an appropriate
+sum type (``Either`` or one of its glorified variants like ``Maybe`` or
+``Result``); the preferred sum type for reporting errors is ``Result``.
+
+As other Ganeti functions also follow these guide lines, they can safely
+be composed. However, be careful when using functions from other libraries;
+if they can raise exceptions, catch them, preferably as close to their
+origin as reasonably possible.
+
+Parentheses, point free style
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Prefer the so-called `point-free`_ style style when declaring functions, if
+applicable::
+
+  -- bad
+  let a x = f (g (h x))
+  -- good
+  let a = f . g . h
+
+Also use function composition in a similar manner in expressions to avoid extra
+parentheses::
+
+  -- bad
+  f (g (h x))
+  -- better
+  f $ g $ h x
+  -- best
+  f . g . h $ x
+
+.. _`point-free`: http://www.haskell.org/haskellwiki/Pointfree
+
+Language features
+~~~~~~~~~~~~~~~~~
+
+Extensions
+**********
+
+It is recommended to keep the use of extensions to a minimum, so that the code
+can be understood even if one is familiar with just Haskel98/Haskell2010. That
+said, some extensions are very common and useful, so they are recommended:
+
+* `Bang patterns`_: useful when you want to enforce strict evaluation (and better
+  than repeated use of ``seq``)
+* CPP: a few modules need this in order to account for configure-time options;
+  don't overuse it, since it breaks multi-line strings
+* `Template Haskell`_: we use this for automatically deriving JSON instances and
+  other similar boiler-plate
+
+.. _Bang patterns: http://www.haskell.org/ghc/docs/latest/html/users_guide/bang-patterns.html
+.. _Template Haskell: http://www.haskell.org/ghc/docs/latest/html/users_guide/template-haskell.html
+
+Such extensions should be declared using the ``Language`` pragma::
+
+  {-# Language BangPatterns #-}
+
+  {-| This is a small module... -}
+
+Comments
+********
+
+Always use proper sentences; start with a capital letter and use punctuation
+in top level comments::
+
+  -- | A function that does something.
+  f :: ...
+
+For inline comments, start with a capital letter but no ending punctuation.
+Furthermore, align the comments together with a 2-space width from the end of
+the item being commented::
+
+  data Maybe a = Nothing  -- ^ Represents empty container
+               | Just a   -- ^ Represents a single value
+
+The comments should be clear enough so that one doesn't need to look at the
+code to understand what the item does/is.
+
+Use ``-- |`` to write doc strings rather than bare comment with ``--``.
+
+Tools
+*****
+
+We generate the API documentation via Haddock, and as such the comments should
+be correct (syntax-wise) for it. Use markup, but sparingly.
+
+We use hlint_ as a lint checker; the code is currently lint-clean, so you must
+not add any warnings/errors.
+
+.. _hlint: http://community.haskell.org/~ndm/darcs/hlint/hlint.htm
+
+Use these two commands during development::
+
+  make hs-apidoc
+  make hlint
+
+QuickCheck best practices
+*************************
+
+If you have big type that takes time to generate and several properties to
+test on that, by default 500 of those big instances are generated for each
+property. In many cases, it would be sufficient to only generate those 500
+instances once and test all properties on those. To do this, create a property
+that uses ``conjoin`` to combine several properties into one. Use
+``printTestCase`` to add expressive error messages. For example::
+
+  prop_myMegaProp :: myBigType -> Property
+  prop_myMegaProp b =
+    conjoin
+      [ printTestCase
+          ("Something failed horribly here: " ++ show b) (subProperty1 b)
+      , printTestCase
+          ("Something else failed horribly here: " ++ show b)
+          (subProperty2 b)
+      , -- more properties here ...
+      ]
+
+  subProperty1 :: myBigType -> Bool
+  subProperty1 b = ...
+
+  subProperty2 :: myBigType -> Property
+  subProperty2 b = ...
+
+  ...
+
+Maybe Generation
+''''''''''''''''
+
+Use ``genMaybe genSomething`` to create ``Maybe`` instances of something
+including some ``Nothing`` instances.
+
+Use ``Just <$> genSomething`` to generate only ``Just`` instances of
+something.
+
+String Generation
+'''''''''''''''''
+
+To generate strings, consider using ``genName`` instead of ``arbitrary``.
+``arbitrary`` has the tendency to generate strings that are too long.
diff --git a/doc/devnotes.rst b/doc/devnotes.rst
index 98b8d23..df89328 100644
--- a/doc/devnotes.rst
+++ b/doc/devnotes.rst
@@ -17,6 +17,7 @@
 - `python-sphinx <http://sphinx.pocoo.org/>`_
   (tested with version 1.1.3)
 - `python-mock <http://www.voidspace.org.uk/python/mock/>`_
+  (tested with version 1.0.1)
 - `graphviz <http://www.graphviz.org/>`_
 - the `en_US.UTF-8` locale must be enabled on the system
 - `pylint <http://www.logilab.org/857>`_ and its associated
@@ -47,14 +48,14 @@
 Installation of all dependencies listed here::
 
      $ apt-get install python-setuptools automake git fakeroot
-     $ apt-get install pandoc python-epydoc graphviz
-     $ apt-get install python-yaml python-mock
-     $ cd / && sudo easy_install \
-               sphinx \
+     $ apt-get install pandoc python-epydoc graphviz python-sphinx
+     $ apt-get install python-yaml
+     $ cd / && easy_install \
                logilab-astng==0.24.1 \
                logilab-common==0.58.3 \
                pylint==0.26.0 \
                pep8==1.3.3 \
+               mock==1.0.1 \
                coverage
 
 For Haskell development, again all things from the quick install
@@ -89,7 +90,7 @@
         libghc-test-framework-dev \
         libghc-test-framework-quickcheck2-dev \
         libghc-test-framework-hunit-dev \
-        libghc-temporary-dev \
+        libghc-temporary-dev shelltestrunner \
         hscolour hlint
 
 Or alternatively via ``cabal``::
@@ -115,6 +116,11 @@
 PYTHONPATH is customised correctly). As such, in general it's
 recommended to use a "clean" machine for ganeti development.
 
+Style guide
+-----------
+
+Please adhere to the :doc:`dev-codestyle` while writing code for Ganeti.
+
 Haskell development notes
 -------------------------
 
@@ -212,6 +218,10 @@
 
   $ make hs-shell-{balancing,basic}
 
+Checking for the correct style of the NEWS file is also possible, by running::
+
+  $ make check-news
+
 Packaging notes
 ===============
 
diff --git a/doc/examples/basic-oob b/doc/examples/basic-oob
index 8e456fd..eeced35 100755
--- a/doc/examples/basic-oob
+++ b/doc/examples/basic-oob
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 SSH_USER='root'
 SSH_FLAGS='-q -oStrictHostKeyChecking=no'
diff --git a/doc/examples/ganeti.cron.in b/doc/examples/ganeti.cron.in
index 1112b21..eedb58b 100644
--- a/doc/examples/ganeti.cron.in
+++ b/doc/examples/ganeti.cron.in
@@ -1,5 +1,8 @@
 PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
 
+# On reboot, continue a Ganeti upgrade, if one was in progress
+@reboot root [ -x @SBINDIR@/gnt-cluster ] && @SBINDIR@/gnt-cluster upgrade --resume
+
 # Restart failed instances (every 5 minutes)
 */5 * * * * root [ -x @SBINDIR@/ganeti-watcher ] && @SBINDIR@/ganeti-watcher
 
diff --git a/doc/examples/gnt-config-backup.in b/doc/examples/gnt-config-backup.in
index d620c2e..99c7ece 100644
--- a/doc/examples/gnt-config-backup.in
+++ b/doc/examples/gnt-config-backup.in
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # This is an example ganeti script that should be run from cron on all
diff --git a/doc/examples/hooks/ethers b/doc/examples/hooks/ethers
index 75e7cc1..dbddb89 100755
--- a/doc/examples/hooks/ethers
+++ b/doc/examples/hooks/ethers
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is an example ganeti hook that writes the instance mac addresses in the
 # node's /etc/ether file. It will pic up the first nic connected to the
diff --git a/doc/examples/hooks/ipsec.in b/doc/examples/hooks/ipsec.in
index 5c9db2e..978cedf 100644
--- a/doc/examples/hooks/ipsec.in
+++ b/doc/examples/hooks/ipsec.in
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # This is an example ganeti hook that sets up an IPsec ESP link between all the
diff --git a/doc/examples/rapi_testutils.py b/doc/examples/rapi_testutils.py
index 8a4ceab..691d726 100755
--- a/doc/examples/rapi_testutils.py
+++ b/doc/examples/rapi_testutils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Example for using L{ganeti.rapi.testutils}"""
diff --git a/doc/hooks.rst b/doc/hooks.rst
index a60e4ac..3fe1b3b 100644
--- a/doc/hooks.rst
+++ b/doc/hooks.rst
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.9
+Documents Ganeti version 2.10
 
 .. contents::
 
@@ -245,7 +245,7 @@
 
 :directory: network-connect
 :env. vars: GROUP_NAME, NETWORK_NAME,
-            GROUP_NETWORK_MODE, GROUP_NETWORK_LINK,
+            GROUP_NETWORK_MODE, GROUP_NETWORK_LINK, GROUP_NETWORK_VLAN,
             NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
             NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: nodegroup nodes
@@ -259,7 +259,7 @@
 
 :directory: network-disconnect
 :env. vars: GROUP_NAME, NETWORK_NAME,
-            GROUP_NETWORK_MODE, GROUP_NETWORK_LINK,
+            GROUP_NETWORK_MODE, GROUP_NETWORK_LINK, GROUP_NETWORK_VLAN,
             NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
             NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: nodegroup nodes
@@ -379,8 +379,8 @@
 
 :directory: instance-failover
 :env. vars: IGNORE_CONSISTENCY, SHUTDOWN_TIMEOUT, OLD_PRIMARY, OLD_SECONDARY, NEW_PRIMARY, NEW_SECONDARY
-:pre-execution: master node, secondary node
-:post-execution: master node, primary and secondary nodes
+:pre-execution: master node, secondary (target) node
+:post-execution: master node, primary (source) and secondary (target) nodes
 
 OP_INSTANCE_MIGRATE
 ++++++++++++++++++++
@@ -391,8 +391,8 @@
 
 :directory: instance-migrate
 :env. vars: MIGRATE_LIVE, MIGRATE_CLEANUP, OLD_PRIMARY, OLD_SECONDARY, NEW_PRIMARY, NEW_SECONDARY
-:pre-execution: master node, primary and secondary nodes
-:post-execution: master node, primary and secondary nodes
+:pre-execution: master node, primary (source) and secondary (target) nodes
+:post-execution: master node, primary (source) and secondary (target) nodes
 
 
 OP_INSTANCE_REMOVE
diff --git a/doc/iallocator.rst b/doc/iallocator.rst
index f62f549..456d5ee 100644
--- a/doc/iallocator.rst
+++ b/doc/iallocator.rst
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.9
+Documents Ganeti version 2.10
 
 .. contents::
 
@@ -325,6 +325,16 @@
   instances
     a list of request dicts
 
+MonD data
++++++++++
+
+Additional information is available from mond. Mond's data collectors
+provide information that can help an allocator script make better
+decisions when allocating a new instance. Mond's information may also be
+accessible from a mock file mainly for testing purposes. The file will
+be in JSON format and will present an array of :ref:`report objects
+<monitoring-agent-format-of-the-report>`.
+
 Response message
 ~~~~~~~~~~~~~~~~
 
diff --git a/doc/index.rst b/doc/index.rst
index d3b153a..a8206de 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -75,6 +75,7 @@
    design-2.7.rst
    design-2.8.rst
    design-2.9.rst
+   design-2.10.rst
 
 Draft designs
 -------------
@@ -88,12 +89,16 @@
 
    admin.rst
    cluster-merge.rst
+   cluster-keys-replacement.rst
    design-autorepair.rst
    design-bulk-create.rst
    design-chained-jobs.rst
+   design-cmdlib-unittests.rst
    design-cpu-pinning.rst
    design-device-uuid-name.rst
+   design-file-based-storage.rst
    design-hroller.rst
+   design-hotplug.rst
    design-linuxha.rst
    design-lu-generated-jobs.rst
    design-monitoring-agent.rst
@@ -101,16 +106,21 @@
    design-network.rst
    design-node-add.rst
    design-oob.rst
+   design-openvswitch.rst
    design-opportunistic-locking.rst
    design-ovf-support.rst
    design-partitioned
+   design-performance-tests.rst
    design-query2.rst
    design-query-splitting.rst
    design-reason-trail.rst
    design-restricted-commands.rst
    design-shared-storage.rst
+   design-storagetypes.rst
+   design-upgrade.rst
    design-virtual-clusters.rst
    devnotes.rst
+   dev-codestyle.rst
    glossary.rst
    hooks.rst
    iallocator.rst
diff --git a/doc/install.rst b/doc/install.rst
index 1fab01f..3b47bec 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -367,6 +367,31 @@
 
       $ apt-get install ceph-common
 
+KVM userspace access
+~~~~~~~~~~~~~~~~~~~~
+
+If your cluster uses a sufficiently new version of KVM (you will need at
+least QEMU 0.14 with RBD support compiled in), you can take advantage of
+KVM's native support for ceph in order to have better performance and
+avoid potential deadlocks_ in low memory scenarios.
+
+.. _deadlocks: http://tracker.ceph.com/issues/3076
+
+To initialize a cluster with support for this feature, use a command
+such as::
+
+  $ gnt-cluster init \
+      --enabled-disk-templates rbd \
+      --ipolicy-disk-templates rbd \
+      --enabled-hypervisors=kvm \
+      -D rbd:access=userspace
+
+(You may want to enable more templates than just ``rbd``.)
+
+You can also change this setting on a live cluster by giving the same
+switches to ``gnt-cluster modify``, or change those settings at the node
+group level with ``gnt-group modify``.
+
 Configuration file
 ~~~~~~~~~~~~~~~~~~
 
@@ -562,7 +587,7 @@
 **Mandatory** on all nodes.
 
 It's now time to install the Ganeti software itself.  Download the
-source from the project page at `<http://code.google.com/p/ganeti/>`_,
+source from the project page at `<http://downloads.ganeti.org/releases/>`_,
 and install it (replace 2.6.0 with the latest version)::
 
   $ tar xvzf ganeti-%2.6.0%.tar.gz
@@ -618,13 +643,13 @@
 installation script. An example OS that works under Debian and can
 install Debian and Ubuntu instace OSes is provided on the project web
 site.  Download it from the project page and follow the instructions in
-the ``README`` file.  Here is the installation procedure (replace 0.12
+the ``README`` file.  Here is the installation procedure (replace 0.14
 with the latest version that is compatible with your ganeti version)::
 
   $ cd /usr/local/src/
-  $ wget http://ganeti.googlecode.com/files/ganeti-instance-debootstrap-%0.12%.tar.gz
-  $ tar xzf ganeti-instance-debootstrap-%0.12%.tar.gz
-  $ cd ganeti-instance-debootstrap-%0.12%
+  $ wget http://ganeti.googlecode.com/files/ganeti-instance-debootstrap-%0.14%.tar.gz
+  $ tar xzf ganeti-instance-debootstrap-%0.14%.tar.gz
+  $ cd ganeti-instance-debootstrap-%0.14%
   $ ./configure --with-os-dir=/srv/ganeti/os
   $ make
   $ make install
diff --git a/doc/move-instance.rst b/doc/move-instance.rst
index a7f06b7..a6317d6 100644
--- a/doc/move-instance.rst
+++ b/doc/move-instance.rst
@@ -85,6 +85,8 @@
   When moving a single instance: Primary node on destination cluster.
 ``--dest-secondary-node``
   When moving a single instance: Secondary node on destination cluster.
+``--dest-disk-template``
+  Disk template to use after the move. Can be used to change disk templates.
 ``--iallocator``
   Iallocator for creating instance on destination cluster.
 ``--hypervisor-parameters``/``--backend-parameters``/``--os-parameters``/``--net``
diff --git a/doc/rapi.rst b/doc/rapi.rst
index 3bab0f7..c206a63 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -2109,18 +2109,17 @@
 ``GET``
 ~~~~~~~
 
-FIXME: enable ".. pyassert::" again when all storage types are
-implemented::
+.. pyassert::
 
-   constants.STORAGE_TYPES == set([constants.ST_FILE,
-                                   constants.ST_LVM_PV,
-                                   constants.ST_LVM_VG])
+   constants.STS_REPORT == set([constants.ST_FILE,
+                                constants.ST_LVM_PV,
+                                constants.ST_LVM_VG])
 
 Requests a list of storage units on a node. Requires the parameters
-``storage_type`` (one of :pyeval:`constants.ST_FILE`,
-:pyeval:`constants.ST_LVM_PV` or :pyeval:`constants.ST_LVM_VG`) and
-``output_fields``. The result will be a job id, using which the result
-can be retrieved.
+``storage_type`` for storage types that support space reporting
+(one of :pyeval:`constants.ST_FILE`, :pyeval:`constants.ST_LVM_PV`
+or :pyeval:`constants.ST_LVM_VG`) and ``output_fields``. The result
+will be a job id, using which the result can be retrieved.
 
 
 .. _rapi-res-nodes-node_name-storage-modify:
diff --git a/doc/security.rst b/doc/security.rst
index 5c7e122..58683f5 100644
--- a/doc/security.rst
+++ b/doc/security.rst
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.9
+Documents Ganeti version 2.10
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
diff --git a/doc/virtual-cluster.rst b/doc/virtual-cluster.rst
index 41e4dba..6b11c03 100644
--- a/doc/virtual-cluster.rst
+++ b/doc/virtual-cluster.rst
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.9
+Documents Ganeti version 2.10
 
 .. contents::
 
diff --git a/lib/__init__.py b/lib/__init__.py
index 54a27c4..1bb4f1a 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # empty file for package definition
diff --git a/lib/_constants.py.in b/lib/_constants.py.in
new file mode 100644
index 0000000..49f444c
--- /dev/null
+++ b/lib/_constants.py.in
@@ -0,0 +1,14 @@
+# This file is automatically generated, do not edit!
+#
+
+"""Automatically generated constants for Python
+
+Note that this file is autogenerated with @lib/_constants.py.in@ as a
+header.
+
+"""
+
+# pylint: disable=C0301,C0324
+# because this is autogenerated, we do not want
+# style warnings
+
diff --git a/lib/asyncnotifier.py b/lib/asyncnotifier.py
index f4b0815..6053bef 100644
--- a/lib/asyncnotifier.py
+++ b/lib/asyncnotifier.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Asynchronous pyinotify implementation"""
diff --git a/lib/backend.py b/lib/backend.py
index ef492ba..d4c6b49 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Functions used by the node daemon
@@ -28,12 +37,14 @@
 
 """
 
-# pylint: disable=E1103
+# pylint: disable=E1103,C0302
 
 # E1103: %s %r has no %r member (but some types could not be
 # inferred), because the _TryOSFromDisk returns either (True, os_obj)
 # or (False, "string") which confuses pylint
 
+# C0302: This module has become too big and should be split up
+
 
 import os
 import os.path
@@ -330,7 +341,7 @@
       cfg = _GetConfig()
       hr = HooksRunner()
       hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
-                                   hr.RunLocalHooks, None, env_fn,
+                                   hr.RunLocalHooks, None, env_fn, None,
                                    logging.warning, cfg.GetClusterName(),
                                    cfg.GetMasterNode())
       hm.RunPhase(constants.HOOKS_PHASE_PRE)
@@ -961,7 +972,7 @@
   result = {}
   my_name = netutils.Hostname.GetSysName()
   port = netutils.GetDaemonPort(constants.NODED)
-  vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
+  vm_capable = my_name not in what.get(constants.NV_NONVMNODES, [])
 
   _VerifyHypervisors(what, vm_capable, result, all_hvparams)
   _VerifyHvparams(what, vm_capable, result)
@@ -1051,7 +1062,7 @@
 
   if constants.NV_LVLIST in what and vm_capable:
     try:
-      val = GetVolumeList(utils.ListVolumeGroups().keys())
+      val = GetVolumeList([what[constants.NV_LVLIST]])
     except RPCFail, err:
       val = str(err)
     result[constants.NV_LVLIST] = val
@@ -1607,6 +1618,28 @@
         logging.exception("Can't remove symlink '%s'", link_name)
 
 
+def _CalculateDeviceURI(instance, disk, device):
+  """Get the URI for the device.
+
+  @type instance: L{objects.Instance}
+  @param instance: the instance which disk belongs to
+  @type disk: L{objects.Disk}
+  @param disk: the target disk object
+  @type device: L{bdev.BlockDev}
+  @param device: the corresponding BlockDevice
+  @rtype: string
+  @return: the device uri if any else None
+
+  """
+  access_mode = disk.params.get(constants.LDP_ACCESS,
+                                constants.DISK_KERNELSPACE)
+  if access_mode == constants.DISK_USERSPACE:
+    # This can raise errors.BlockDeviceError
+    return device.GetUserspaceAccessUri(instance.hypervisor)
+  else:
+    return None
+
+
 def _GatherAndLinkBlockDevs(instance):
   """Set up an instance's block device(s).
 
@@ -1614,9 +1647,9 @@
   devices must be already assembled.
 
   @type instance: L{objects.Instance}
-  @param instance: the instance whose disks we shoul assemble
+  @param instance: the instance whose disks we should assemble
   @rtype: list
-  @return: list of (disk_object, device_path)
+  @return: list of (disk_object, link_name, drive_uri)
 
   """
   block_devices = []
@@ -1631,8 +1664,9 @@
     except OSError, e:
       raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
                                     e.strerror)
+    uri = _CalculateDeviceURI(instance, disk, device)
 
-    block_devices.append((disk, link_name))
+    block_devices.append((disk, link_name, uri))
 
   return block_devices
 
@@ -1695,7 +1729,7 @@
     logging.info("Instance %s not running, doing nothing", iname)
     return
 
-  class _TryShutdown:
+  class _TryShutdown(object):
     def __init__(self):
       self.tried_once = False
 
@@ -1939,6 +1973,54 @@
     _Fail("Failed to get migration status: %s", err, exc=True)
 
 
+def HotplugDevice(instance, action, dev_type, device, extra, seq):
+  """Hotplug a device
+
+  Hotplug is currently supported only for KVM Hypervisor.
+  @type instance: L{objects.Instance}
+  @param instance: the instance to which we hotplug a device
+  @type action: string
+  @param action: the hotplug action to perform
+  @type dev_type: string
+  @param dev_type: the device type to hotplug
+  @type device: either L{objects.NIC} or L{objects.Disk}
+  @param device: the device object to hotplug
+  @type extra: tuple
+  @param extra: extra info used for disk hotplug (disk link, drive uri)
+  @type seq: int
+  @param seq: the index of the device from master perspective
+  @raise RPCFail: in case instance does not have KVM hypervisor
+
+  """
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
+  try:
+    hyper.VerifyHotplugSupport(instance, action, dev_type)
+  except errors.HotplugError, err:
+    _Fail("Hotplug is not supported: %s", err)
+
+  if action == constants.HOTPLUG_ACTION_ADD:
+    fn = hyper.HotAddDevice
+  elif action == constants.HOTPLUG_ACTION_REMOVE:
+    fn = hyper.HotDelDevice
+  elif action == constants.HOTPLUG_ACTION_MODIFY:
+    fn = hyper.HotModDevice
+  else:
+    assert action in constants.HOTPLUG_ALL_ACTIONS
+
+  return fn(instance, dev_type, device, extra, seq)
+
+
+def HotplugSupported(instance):
+  """Checks if hotplug is generally supported.
+
+  """
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
+  try:
+    hyper.HotplugSupported(instance)
+  except errors.HotplugError, err:
+    _Fail("Hotplug is not supported: %s", err)
+
+
 def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   """Creates a block device for an instance.
 
@@ -2114,10 +2196,18 @@
     rdev = None
   if rdev is not None:
     r_path = rdev.dev_path
-    try:
-      rdev.Remove()
-    except errors.BlockDeviceError, err:
-      msgs.append(str(err))
+
+    def _TryRemove():
+      try:
+        rdev.Remove()
+        return []
+      except errors.BlockDeviceError, err:
+        return [str(err)]
+
+    msgs.extend(utils.SimpleRetry([], _TryRemove,
+                                  constants.DISK_REMOVE_RETRY_INTERVAL,
+                                  constants.DISK_REMOVE_RETRY_TIMEOUT))
+
     if not msgs:
       DevCacheManager.RemoveCache(r_path)
 
@@ -2185,29 +2275,36 @@
   return result
 
 
-def BlockdevAssemble(disk, owner, as_primary, idx):
+def BlockdevAssemble(disk, instance, as_primary, idx):
   """Activate a block device for an instance.
 
   This is a wrapper over _RecursiveAssembleBD.
 
   @rtype: str or boolean
-  @return: a C{/dev/...} path for primary nodes, and
-      C{True} for secondary nodes
+  @return: a tuple with the C{/dev/...} path and the created symlink
+      for primary nodes, and (C{True}, C{True}) for secondary nodes
 
   """
   try:
-    result = _RecursiveAssembleBD(disk, owner, as_primary)
+    result = _RecursiveAssembleBD(disk, instance.name, as_primary)
     if isinstance(result, BlockDev):
       # pylint: disable=E1103
-      result = result.dev_path
+      dev_path = result.dev_path
+      link_name = None
+      uri = None
       if as_primary:
-        _SymlinkBlockDev(owner, result, idx)
+        link_name = _SymlinkBlockDev(instance.name, dev_path, idx)
+        uri = _CalculateDeviceURI(instance, disk, result)
+    elif result:
+      return result, result
+    else:
+      _Fail("Unexpected result from _RecursiveAssembleBD")
   except errors.BlockDeviceError, err:
     _Fail("Error while assembling disk: %s", err, exc=True)
   except OSError, err:
     _Fail("Error while symlinking disk: %s", err, exc=True)
 
-  return result
+  return dev_path, link_name, uri
 
 
 def BlockdevShutdown(disk):
@@ -2847,7 +2944,7 @@
       result["DISK_%d_BACKEND_TYPE" % idx] = "block"
     elif disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
       result["DISK_%d_BACKEND_TYPE" % idx] = \
-        "file:%s" % disk.physical_id[0]
+        "file:%s" % disk.logical_id[0]
 
   # NICs
   for idx, nic in enumerate(instance.nics):
@@ -2982,7 +3079,7 @@
       _Fail("Cannot find block device %s", disk)
   else:
     _Fail("Cannot snapshot non-lvm block device '%s' of type '%s'",
-          disk.unique_id, disk.dev_type)
+          disk.logical_id, disk.dev_type)
 
 
 def BlockdevSetInfo(disk, info):
@@ -3074,7 +3171,7 @@
       config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
                  ("%s" % disk.iv_name))
       config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
-                 ("%s" % disk.physical_id[1]))
+                 ("%s" % disk.logical_id[1]))
       config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
                  ("%d" % disk.size))
       config.set(constants.INISECT_INS, "disk%d_name" % disk_count,
@@ -3159,11 +3256,9 @@
   """Rename a list of block devices.
 
   @type devlist: list of tuples
-  @param devlist: list of tuples of the form  (disk,
-      new_logical_id, new_physical_id); disk is an
-      L{objects.Disk} object describing the current disk,
-      and new logical_id/physical_id is the name we
-      rename it to
+  @param devlist: list of tuples of the form  (disk, new_unique_id); disk is
+      an L{objects.Disk} object describing the current disk, and new
+      unique_id is the name we rename it to
   @rtype: boolean
   @return: True if all renames succeeded, False otherwise
 
@@ -3634,8 +3729,6 @@
 
     assert isinstance(disk_index, (int, long))
 
-    real_disk = _OpenRealBD(disk)
-
     inst_os = OSFromDisk(instance.os)
     env = OSEnvironment(instance, inst_os)
 
@@ -3645,6 +3738,7 @@
       script = inst_os.import_script
 
     elif mode == constants.IEM_EXPORT:
+      real_disk = _OpenRealBD(disk)
       env["EXPORT_DEVICE"] = real_disk.dev_path
       env["EXPORT_INDEX"] = str(disk_index)
       script = inst_os.export_script
@@ -3872,35 +3966,31 @@
   shutil.rmtree(status_dir, ignore_errors=True)
 
 
-def _SetPhysicalId(target_node_uuid, nodes_ip, disks):
-  """Sets the correct physical ID on all passed disks.
+def _FindDisks(disks):
+  """Finds attached L{BlockDev}s for the given disks.
+
+  @type disks: list of L{objects.Disk}
+  @param disks: the disk objects we need to find
+
+  @return: list of L{BlockDev} objects or C{None} if a given disk
+           was not found or was no attached.
 
   """
-  for cf in disks:
-    cf.SetPhysicalID(target_node_uuid, nodes_ip)
-
-
-def _FindDisks(target_node_uuid, nodes_ip, disks):
-  """Sets the physical ID on disks and returns the block devices.
-
-  """
-  _SetPhysicalId(target_node_uuid, nodes_ip, disks)
-
   bdevs = []
 
-  for cf in disks:
-    rd = _RecursiveFindBD(cf)
+  for disk in disks:
+    rd = _RecursiveFindBD(disk)
     if rd is None:
-      _Fail("Can't find device %s", cf)
+      _Fail("Can't find device %s", disk)
     bdevs.append(rd)
   return bdevs
 
 
-def DrbdDisconnectNet(target_node_uuid, nodes_ip, disks):
+def DrbdDisconnectNet(disks):
   """Disconnects the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   # disconnect disks
   for rd in bdevs:
@@ -3911,12 +4001,11 @@
             err, exc=True)
 
 
-def DrbdAttachNet(target_node_uuid, nodes_ip, disks, instance_name,
-                  multimaster):
+def DrbdAttachNet(disks, instance_name, multimaster):
   """Attaches the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
@@ -3986,7 +4075,7 @@
         _Fail("Can't change to primary mode: %s", err)
 
 
-def DrbdWaitSync(target_node_uuid, nodes_ip, disks):
+def DrbdWaitSync(disks):
   """Wait until DRBDs have synchronized.
 
   """
@@ -3996,7 +4085,7 @@
       raise utils.RetryAgain()
     return stats
 
-  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   min_resync = 100
   alldone = True
@@ -4016,11 +4105,10 @@
   return (alldone, min_resync)
 
 
-def DrbdNeedsActivation(target_node_uuid, nodes_ip, disks):
+def DrbdNeedsActivation(disks):
   """Checks which of the passed disks needs activation and returns their UUIDs.
 
   """
-  _SetPhysicalId(target_node_uuid, nodes_ip, disks)
   faulty_disks = []
 
   for disk in disks:
@@ -4273,6 +4361,33 @@
     utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644)
 
 
+def ConfigureOVS(ovs_name, ovs_link):
+  """Creates a OpenvSwitch on the node.
+
+  This function sets up a OpenvSwitch on the node with given name nad
+  connects it via a given eth device.
+
+  @type ovs_name: string
+  @param ovs_name: Name of the OpenvSwitch to create.
+  @type ovs_link: None or string
+  @param ovs_link: Ethernet device for outside connection (can be missing)
+
+  """
+  # Initialize the OpenvSwitch
+  result = utils.RunCmd(["ovs-vsctl", "add-br", ovs_name])
+  if result.failed:
+    _Fail("Failed to create openvswitch. Script return value: %s, output: '%s'"
+          % (result.exit_code, result.output), log=True)
+
+  # And connect it to a physical interface, if given
+  if ovs_link:
+    result = utils.RunCmd(["ovs-vsctl", "add-port", ovs_name, ovs_link])
+    if result.failed:
+      _Fail("Failed to connect openvswitch to  interface %s. Script return"
+            " value: %s, output: '%s'" % (ovs_link, result.exit_code,
+            result.output), log=True)
+
+
 class HooksRunner(object):
   """Hook runner.
 
diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index 1b862c5..0c101e2 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Functions to bootstrap a new cluster.
@@ -311,11 +320,36 @@
   if verbose:
     cmd.append("--verbose")
 
+  logging.debug("Node setup command: %s", cmd)
+
+  version = constants.DIR_VERSION
+  all_cmds = [["test", "-d", os.path.join(pathutils.PKGLIBDIR, version)]]
+  if constants.HAS_GNU_LN:
+    all_cmds.extend([["ln", "-s", "-f", "-T",
+                      os.path.join(pathutils.PKGLIBDIR, version),
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")],
+                     ["ln", "-s", "-f", "-T",
+                      os.path.join(pathutils.SHAREDIR, version),
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]])
+  else:
+    all_cmds.extend([["rm", "-f",
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")],
+                     ["ln", "-s", "-f",
+                      os.path.join(pathutils.PKGLIBDIR, version),
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")],
+                     ["rm", "-f",
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/share")],
+                     ["ln", "-s", "-f",
+                      os.path.join(pathutils.SHAREDIR, version),
+                      os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]])
+  all_cmds.append(cmd)
+
   family = ssconf.SimpleStore().GetPrimaryIPFamily()
   srun = ssh.SshRunner(cluster_name,
                        ipv6=(family == netutils.IP6Address.family))
   scmd = srun.BuildCmd(node, constants.SSH_LOGIN_USER,
-                       utils.ShellQuoteArgs(cmd),
+                       utils.ShellQuoteArgs(
+                           utils.ShellCombineCommands(all_cmds)),
                        batch=False, ask_key=ask_key, quiet=False,
                        strict_host_check=strict_host_check,
                        use_cluster_key=use_cluster_key)
@@ -472,6 +506,32 @@
   ipolicy[constants.IPOLICY_DTS] = restricted_disk_templates
 
 
+def _InitCheckDrbdHelper(drbd_helper, drbd_enabled):
+  """Checks the DRBD usermode helper.
+
+  @type drbd_helper: string
+  @param drbd_helper: name of the DRBD usermode helper that the system should
+    use
+
+  """
+  if not drbd_enabled:
+    return
+
+  if drbd_helper is not None:
+    try:
+      curr_helper = drbd.DRBD8.GetUsermodeHelper()
+    except errors.BlockDeviceError, err:
+      raise errors.OpPrereqError("Error while checking drbd helper"
+                                 " (disable drbd with --enabled-disk-templates"
+                                 " if you are not using drbd): %s" % str(err),
+                                 errors.ECODE_ENVIRON)
+    if drbd_helper != curr_helper:
+      raise errors.OpPrereqError("Error: requiring %s as drbd helper but %s"
+                                 " is the current helper" % (drbd_helper,
+                                                             curr_helper),
+                                 errors.ECODE_INVAL)
+
+
 def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                 master_netmask, master_netdev, file_storage_dir,
                 shared_file_storage_dir, candidate_pool_size, secondary_ip=None,
@@ -569,19 +629,8 @@
     if vgstatus:
       raise errors.OpPrereqError("Error: %s" % vgstatus, errors.ECODE_INVAL)
 
-  if drbd_helper is not None:
-    try:
-      curr_helper = drbd.DRBD8.GetUsermodeHelper()
-    except errors.BlockDeviceError, err:
-      raise errors.OpPrereqError("Error while checking drbd helper"
-                                 " (specify --no-drbd-storage if you are not"
-                                 " using drbd): %s" % str(err),
-                                 errors.ECODE_ENVIRON)
-    if drbd_helper != curr_helper:
-      raise errors.OpPrereqError("Error: requiring %s as drbd helper but %s"
-                                 " is the current helper" % (drbd_helper,
-                                                             curr_helper),
-                                 errors.ECODE_INVAL)
+  drbd_enabled = constants.DT_DRBD8 in enabled_disk_templates
+  _InitCheckDrbdHelper(drbd_helper, drbd_enabled)
 
   logging.debug("Stopping daemons (if any are running)")
   result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop-all"])
@@ -599,11 +648,14 @@
     raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix,
                                errors.ECODE_INVAL)
 
-  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
-  if result.failed:
-    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
-                               (master_netdev,
-                                result.output.strip()), errors.ECODE_INVAL)
+  if not nicparams.get('mode', None) == constants.NIC_MODE_OVS:
+    # Do not do this check if mode=openvswitch, since the openvswitch is not
+    # created yet
+    result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
+    if result.failed:
+      raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
+                                 (master_netdev,
+                                  result.output.strip()), errors.ECODE_INVAL)
 
   dirs = [(pathutils.RUN_DIR, constants.RUN_DIRS_MODE)]
   utils.EnsureDirs(dirs)
@@ -893,6 +945,7 @@
   @param no_voting: force the operation without remote nodes agreement
                       (dangerous)
 
+  @returns: the pair of an exit code and warnings to display
   """
   sstore = ssconf.SimpleStore()
 
@@ -933,6 +986,7 @@
   # end checks
 
   rcode = 0
+  warnings = []
 
   logging.info("Setting master to %s, old master: %s", new_master, old_master)
 
@@ -979,13 +1033,17 @@
 
   msg = result.fail_msg
   if msg:
-    logging.warning("Could not disable the master IP: %s", msg)
+    warning = "Could not disable the master IP: %s" % (msg,)
+    logging.warning("%s", warning)
+    warnings.append(warning)
 
   result = runner.call_node_stop_master(old_master)
   msg = result.fail_msg
   if msg:
-    logging.error("Could not disable the master role on the old master"
-                  " %s, please disable manually: %s", old_master, msg)
+    warning = ("Could not disable the master role on the old master"
+               " %s, please disable manually: %s" % (old_master, msg))
+    logging.error("%s", warning)
+    warnings.append(warning)
 
   logging.info("Checking master IP non-reachability...")
 
@@ -993,16 +1051,19 @@
   total_timeout = 30
 
   # Here we have a phase where no master should be running
-  def _check_ip():
-    if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
+  def _check_ip(expected):
+    if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT) != expected:
       raise utils.RetryAgain()
 
   try:
-    utils.Retry(_check_ip, (1, 1.5, 5), total_timeout)
+    utils.Retry(_check_ip, (1, 1.5, 5), total_timeout, args=[False])
   except utils.RetryTimeout:
-    logging.warning("The master IP is still reachable after %s seconds,"
-                    " continuing but activating the master on the current"
-                    " node will probably fail", total_timeout)
+    warning = ("The master IP is still reachable after %s seconds,"
+               " continuing but activating the master IP on the current"
+               " node will probably fail" % total_timeout)
+    logging.warning("%s", warning)
+    warnings.append(warning)
+    rcode = 1
 
   if jstore.CheckDrainFlag():
     logging.info("Undraining job queue")
@@ -1018,8 +1079,21 @@
                   " %s, please check: %s", new_master, msg)
     rcode = 1
 
+  # Finally verify that the new master managed to set up the master IP
+  # and warn if it didn't.
+  try:
+    utils.Retry(_check_ip, (1, 1.5, 5), total_timeout, args=[True])
+  except utils.RetryTimeout:
+    warning = ("The master IP did not come up within %s seconds; the"
+               " cluster should still be working and reachable via %s,"
+               " but not via the master IP address"
+               % (total_timeout, new_master))
+    logging.warning("%s", warning)
+    warnings.append(warning)
+    rcode = 1
+
   logging.info("Master failed over from %s to %s", old_master, new_master)
-  return rcode
+  return rcode, warnings
 
 
 def GetMaster():
diff --git a/lib/build/__init__.py b/lib/build/__init__.py
index be7f32e..83ce852 100644
--- a/lib/build/__init__.py
+++ b/lib/build/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Module used during the Ganeti build process"""
 
diff --git a/lib/build/shell_example_lexer.py b/lib/build/shell_example_lexer.py
index 401ad65..1bb7ddf 100644
--- a/lib/build/shell_example_lexer.py
+++ b/lib/build/shell_example_lexer.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Pygments lexer for our custom shell example sessions.
diff --git a/lib/build/sphinx_ext.py b/lib/build/sphinx_ext.py
index 55e67cb..8731ad3 100644
--- a/lib/build/sphinx_ext.py
+++ b/lib/build/sphinx_ext.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Sphinx extension for building opcode documentation.
@@ -46,17 +55,18 @@
   # Normally the "manpage" role is registered by sphinx/roles.py
   raise Exception("Can't find reST role named 'manpage': %s" % err)
 
+from ganeti import _constants
 from ganeti import constants
 from ganeti import compat
 from ganeti import errors
 from ganeti import utils
 from ganeti import opcodes
+from ganeti import opcodes_base
 from ganeti import ht
 from ganeti import rapi
 from ganeti import luxi
 from ganeti import objects
 from ganeti import http
-from ganeti import _autoconf
 
 import ganeti.rapi.rlib2 # pylint: disable=W0611
 import ganeti.rapi.connector # pylint: disable=W0611
@@ -83,7 +93,7 @@
   names = set(map(compat.fst, opcodes.OpCode.OP_PARAMS))
 
   # The "depends" attribute should be listed
-  names.remove(opcodes.DEPEND_ATTR)
+  names.remove(opcodes_base.DEPEND_ATTR)
 
   return names
 
@@ -164,15 +174,18 @@
     if include is not None and name not in include:
       continue
 
-    has_default = default is not ht.NoDefault
-    has_test = not (test is None or test is ht.NoType)
+    has_default = default is not None or default is not ht.NoDefault
+    has_test = test is not None
 
     buf = StringIO()
     buf.write("``%s``" % (rapi_name,))
     if has_default or has_test:
       buf.write(" (")
       if has_default:
-        buf.write("defaults to ``%s``" % (default,))
+        if default == "":
+          buf.write("defaults to the empty string")
+        else:
+          buf.write("defaults to ``%s``" % (default,))
         if has_test:
           buf.write(", ")
       if has_test:
@@ -222,7 +235,10 @@
     alias = self.options.get("alias", {})
 
     path = op_id
-    include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
+    include_text = "\n\n".join(_BuildOpcodeParams(op_id,
+                                                  include,
+                                                  exclude,
+                                                  alias))
 
     # Inject into state machine
     include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
@@ -381,7 +397,7 @@
     name = m.group("name")
     section = int(m.group("section"))
 
-    wanted_section = _autoconf.MAN_PAGES.get(name, None)
+    wanted_section = _constants.MAN_PAGES.get(name, None)
 
     if not (wanted_section is None or wanted_section == section):
       raise ReSTError("Referenced man page '%s' has section number %s, but the"
@@ -469,7 +485,7 @@
 
   """
   return sorted(method
-                for (method, op_attr, _, _) in rapi.baserlib.OPCODE_ATTRS
+                for (method, op_attr, _, _, _) in rapi.baserlib.OPCODE_ATTRS
                 # Only if handler supports method
                 if hasattr(handler, method) or hasattr(handler, op_attr))
 
diff --git a/lib/cli.py b/lib/cli.py
index bc6723e..02ff3fd 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module dealing with command line parsing"""
@@ -87,6 +96,7 @@
   "FIELDS_OPT",
   "FILESTORE_DIR_OPT",
   "FILESTORE_DRIVER_OPT",
+  "FORCE_FAILOVER_OPT",
   "FORCE_FILTER_OPT",
   "FORCE_OPT",
   "FORCE_VARIANT_OPT",
@@ -95,6 +105,8 @@
   "GLOBAL_FILEDIR_OPT",
   "HID_OS_OPT",
   "GLOBAL_SHARED_FILEDIR_OPT",
+  "HOTPLUG_OPT",
+  "HOTPLUG_IF_POSSIBLE_OPT",
   "HVLIST_OPT",
   "HVOPTS_OPT",
   "HYPERVISOR_OPT",
@@ -135,7 +147,6 @@
   "NODEGROUP_OPT",
   "NODE_PARAMS_OPT",
   "NODE_POWERED_OPT",
-  "NODRBD_STORAGE_OPT",
   "NOHDR_OPT",
   "NOIPCHECK_OPT",
   "NO_INSTALL_OPT",
@@ -195,6 +206,7 @@
   "IPOLICY_STD_SPECS_OPT",
   "IPOLICY_DISK_TEMPLATES",
   "IPOLICY_VCPU_RATIO",
+  "SEQUENTIAL_OPT",
   "SPICE_CACERT_OPT",
   "SPICE_CERT_OPT",
   "SRC_DIR_OPT",
@@ -234,6 +246,7 @@
   "ParseTimespec",
   "RunWhileClusterStopped",
   "SubmitOpCode",
+  "SubmitOpCodeToDrainedQueue",
   "SubmitOrSend",
   "UsesRPC",
   # Formatting functions
@@ -341,7 +354,7 @@
   }
 
 
-class _Argument:
+class _Argument(object):
   def __init__(self, min=0, max=None): # pylint: disable=W0622
     self.min = min
     self.max = max
@@ -465,7 +478,7 @@
     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
   kind = opts.tag_type
   if kind == constants.TAG_CLUSTER:
-    retval = kind, None
+    retval = kind, ""
   elif kind in (constants.TAG_NODEGROUP,
                 constants.TAG_NODE,
                 constants.TAG_NETWORK,
@@ -841,6 +854,10 @@
                              help=("Additionally print the job as first line"
                                    " on stdout (for scripting)."))
 
+SEQUENTIAL_OPT = cli_option("--sequential", dest="sequential",
+                            default=False, action="store_true",
+                            help=("Execute all resulting jobs sequentially"))
+
 SYNC_OPT = cli_option("--sync", dest="do_locking",
                       default=False, action="store_true",
                       help=("Grab locks while doing the queries"
@@ -983,7 +1000,7 @@
 IPOLICY_STD_SPECS_OPT = cli_option(IPOLICY_STD_SPECS_STR,
                                    dest="ipolicy_std_specs",
                                    type="keyval", default=None,
-                                   help="Complte standard instance specs")
+                                   help="Complete standard instance specs")
 
 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
                                     dest="ipolicy_disk_templates",
@@ -1051,6 +1068,12 @@
                                 help="If migration is not possible fallback to"
                                      " failover")
 
+FORCE_FAILOVER_OPT = cli_option("--force-failover",
+                                dest="force_failover",
+                                action="store_true", default=False,
+                                help="Do not use migration, always use"
+                                     " failover")
+
 NONLIVE_OPT = cli_option("--non-live", dest="live",
                          default=True, action="store_false",
                          help="Do a non-live migration (this usually means"
@@ -1468,10 +1491,6 @@
                              action="store", default=None,
                              help="Specifies usermode helper for DRBD")
 
-NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
-                                action="store_false", default=True,
-                                help="Disable support for DRBD")
-
 PRIMARY_IP_VERSION_OPT = \
     cli_option("--primary-ip-version", default=constants.IP4_VERSION,
                action="store", dest="primary_ip_version",
@@ -1646,6 +1665,16 @@
                                  default=False, action="store_true",
                                  help="Include default values")
 
+HOTPLUG_OPT = cli_option("--hotplug", dest="hotplug",
+                         action="store_true", default=False,
+                         help="Hotplug supported devices (NICs and Disks)")
+
+HOTPLUG_IF_POSSIBLE_OPT = cli_option("--hotplug-if-possible",
+                                     dest="hotplug_if_possible",
+                                     action="store_true", default=False,
+                                     help="Hotplug devices in case"
+                                          " hotplug is supported")
+
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
@@ -2088,7 +2117,7 @@
   raise errors.OpExecError(result)
 
 
-class JobPollCbBase:
+class JobPollCbBase(object):
   """Base class for L{GenericPollJob} callbacks.
 
   """
@@ -2116,7 +2145,7 @@
     raise NotImplementedError()
 
 
-class JobPollReportCbBase:
+class JobPollReportCbBase(object):
   """Base class for L{GenericPollJob} reporting callbacks.
 
   """
@@ -2280,6 +2309,16 @@
   return op_results[0]
 
 
+def SubmitOpCodeToDrainedQueue(op):
+  """Forcefully insert a job in the queue, even if it is drained.
+
+  """
+  cl = GetClient()
+  job_id = cl.SubmitJobToDrainedQueue([op])
+  op_results = PollJob(job_id, cl=cl)
+  return op_results[0]
+
+
 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
   """Wrapper around SubmitOpCode or SendJob.
 
@@ -2760,7 +2799,7 @@
   return 0
 
 
-class _RunWhileClusterStoppedHelper:
+class _RunWhileClusterStoppedHelper(object):
   """Helper class for L{RunWhileClusterStopped} to simplify state management
 
   """
@@ -3043,7 +3082,7 @@
   raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
 
 
-class _QueryColumnFormatter:
+class _QueryColumnFormatter(object):
   """Callable class for formatting fields of a query.
 
   """
@@ -3317,7 +3356,7 @@
   return constants.EXIT_SUCCESS
 
 
-class TableColumn:
+class TableColumn(object):
   """Describes a column for L{FormatTable}.
 
   """
diff --git a/lib/client/__init__.py b/lib/client/__init__.py
index 308b5fe..f26e362 100644
--- a/lib/client/__init__.py
+++ b/lib/client/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Common command line client code.
 
diff --git a/lib/client/gnt_backup.py b/lib/client/gnt_backup.py
index e652937..81f7c1b 100644
--- a/lib/client/gnt_backup.py
+++ b/lib/client/gnt_backup.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Backup related commands"""
 
diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py
index 408fde7..84cd0f8 100644
--- a/lib/client/gnt_cluster.py
+++ b/lib/client/gnt_cluster.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Cluster related commands"""
 
@@ -27,9 +36,10 @@
 # C0103: Invalid name gnt-cluster
 
 from cStringIO import StringIO
-import os.path
+import os
 import time
 import OpenSSL
+import tempfile
 import itertools
 
 from ganeti.cli import *
@@ -43,7 +53,10 @@
 from ganeti import uidpool
 from ganeti import compat
 from ganeti import netutils
+from ganeti import ssconf
 from ganeti import pathutils
+from ganeti import serializer
+from ganeti import qlang
 
 
 ON_OPT = cli_option("--on", default=False,
@@ -58,6 +71,18 @@
                             help="Override interactive check for --no-voting",
                             default=False, action="store_true")
 
+FORCE_DISTRIBUTION = cli_option("--yes-do-it", dest="yes_do_it",
+                                help="Unconditionally distribute the"
+                                " configuration, even if the queue"
+                                " is drained",
+                                default=False, action="store_true")
+
+TO_OPT = cli_option("--to", default=None, type="string",
+                    help="The Ganeti version to upgrade to")
+
+RESUME_OPT = cli_option("--resume", default=False, action="store_true",
+                        help="Resume any pending Ganeti upgrades")
+
 _EPO_PING_INTERVAL = 30 # 30 seconds between pings
 _EPO_PING_TIMEOUT = 1 # 1 second
 _EPO_REACHABLE_TIMEOUT = 15 * 60 # 15 minutes
@@ -72,10 +97,63 @@
              " to disable lvm-based storage cluster-wide, use the option"
              " --enabled-disk-templates to disable all of these lvm-base disk "
              "  templates: %s" %
-             utils.CommaJoin(utils.GetLvmDiskTemplates()))
+             utils.CommaJoin(constants.DTS_LVM))
     return 1
 
 
+def _InitEnabledDiskTemplates(opts):
+  """Initialize the list of enabled disk templates.
+
+  """
+  if opts.enabled_disk_templates:
+    return opts.enabled_disk_templates.split(",")
+  else:
+    return constants.DEFAULT_ENABLED_DISK_TEMPLATES
+
+
+def _InitVgName(opts, enabled_disk_templates):
+  """Initialize the volume group name.
+
+  @type enabled_disk_templates: list of strings
+  @param enabled_disk_templates: cluster-wide enabled disk templates
+
+  """
+  vg_name = None
+  if opts.vg_name is not None:
+    vg_name = opts.vg_name
+    if vg_name:
+      if not utils.IsLvmEnabled(enabled_disk_templates):
+        ToStdout("You specified a volume group with --vg-name, but you did not"
+                 " enable any disk template that uses lvm.")
+    elif utils.IsLvmEnabled(enabled_disk_templates):
+      raise errors.OpPrereqError(
+          "LVM disk templates are enabled, but vg name not set.")
+  elif utils.IsLvmEnabled(enabled_disk_templates):
+    vg_name = constants.DEFAULT_VG
+  return vg_name
+
+
+def _InitDrbdHelper(opts, enabled_disk_templates):
+  """Initialize the DRBD usermode helper.
+
+  """
+  drbd_enabled = constants.DT_DRBD8 in enabled_disk_templates
+
+  if not drbd_enabled and opts.drbd_helper is not None:
+    ToStdout("Note: You specified a DRBD usermode helper, while DRBD storage"
+             " is not enabled.")
+
+  if drbd_enabled:
+    if opts.drbd_helper is None:
+      return constants.DEFAULT_DRBD_HELPER
+    if opts.drbd_helper == '':
+      raise errors.OpPrereqError(
+          "Unsetting the drbd usermode helper while enabling DRBD is not"
+          " allowed.")
+
+  return opts.drbd_helper
+
+
 @UsesRPC
 def InitCluster(opts, args):
   """Initialize the cluster.
@@ -90,38 +168,26 @@
   """
   if _CheckNoLvmStorageOptDeprecated(opts):
     return 1
-  enabled_disk_templates = opts.enabled_disk_templates
-  if enabled_disk_templates:
-    enabled_disk_templates = enabled_disk_templates.split(",")
-  else:
-    enabled_disk_templates = constants.DEFAULT_ENABLED_DISK_TEMPLATES
 
-  vg_name = None
-  if opts.vg_name is not None:
-    vg_name = opts.vg_name
-    if vg_name:
-      if not utils.IsLvmEnabled(enabled_disk_templates):
-        ToStdout("You specified a volume group with --vg-name, but you did not"
-                 " enable any disk template that uses lvm.")
-    else:
-      if utils.IsLvmEnabled(enabled_disk_templates):
-        ToStderr("LVM disk templates are enabled, but vg name not set.")
-        return 1
-  else:
-    if utils.IsLvmEnabled(enabled_disk_templates):
-      vg_name = constants.DEFAULT_VG
+  enabled_disk_templates = _InitEnabledDiskTemplates(opts)
 
-  if not opts.drbd_storage and opts.drbd_helper:
-    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
+  try:
+    vg_name = _InitVgName(opts, enabled_disk_templates)
+    drbd_helper = _InitDrbdHelper(opts, enabled_disk_templates)
+  except errors.OpPrereqError, e:
+    ToStderr(str(e))
     return 1
 
-  drbd_helper = opts.drbd_helper
-  if opts.drbd_storage and not opts.drbd_helper:
-    drbd_helper = constants.DEFAULT_DRBD_HELPER
-
   master_netdev = opts.master_netdev
   if master_netdev is None:
-    master_netdev = constants.DEFAULT_BRIDGE
+    nic_mode = opts.nicparams.get(constants.NIC_MODE, None)
+    if not nic_mode:
+      # default case, use bridging
+      master_netdev = constants.DEFAULT_BRIDGE
+    elif nic_mode == constants.NIC_MODE_OVS:
+      # default ovs is different from default bridge
+      master_netdev = constants.DEFAULT_OVS
+      opts.nicparams[constants.NIC_LINK] = constants.DEFAULT_OVS
 
   hvlist = opts.enabled_hypervisors
   if hvlist is None:
@@ -352,7 +418,10 @@
 
   """
   op = opcodes.OpClusterRedistConf()
-  SubmitOrSend(op, opts)
+  if opts.yes_do_it:
+    SubmitOpCodeToDrainedQueue(op)
+  else:
+    SubmitOrSend(op, opts)
   return 0
 
 
@@ -752,7 +821,10 @@
     if not AskUser(usertext):
       return 1
 
-  return bootstrap.MasterFailover(no_voting=opts.no_voting)
+  rvlaue, msgs = bootstrap.MasterFailover(no_voting=opts.no_voting)
+  for msg in msgs:
+    ToStderr(msg)
+  return rvlaue
 
 
 def MasterPing(opts, args):
@@ -966,6 +1038,48 @@
                       opts.force)
 
 
+def _GetEnabledDiskTemplates(opts):
+  """Determine the list of enabled disk templates.
+
+  """
+  if opts.enabled_disk_templates:
+    return opts.enabled_disk_templates.split(",")
+  else:
+    return None
+
+
+def _GetVgName(opts, enabled_disk_templates):
+  """Determine the volume group name.
+
+  @type enabled_disk_templates: list of strings
+  @param enabled_disk_templates: cluster-wide enabled disk-templates
+
+  """
+  # consistency between vg name and enabled disk templates
+  vg_name = None
+  if opts.vg_name is not None:
+    vg_name = opts.vg_name
+  if enabled_disk_templates:
+    if vg_name and not utils.IsLvmEnabled(enabled_disk_templates):
+      ToStdout("You specified a volume group with --vg-name, but you did not"
+               " enable any of the following lvm-based disk templates: %s" %
+               utils.CommaJoin(constants.DTS_LVM))
+  return vg_name
+
+
+def _GetDrbdHelper(opts, enabled_disk_templates):
+  """Determine the DRBD usermode helper.
+
+  """
+  drbd_helper = opts.drbd_helper
+  if enabled_disk_templates:
+    drbd_enabled = constants.DT_DRBD8 in enabled_disk_templates
+    if not drbd_enabled and opts.drbd_helper:
+      ToStdout("You specified a DRBD usermode helper with "
+               " --drbd-usermode-helper while DRBD is not enabled.")
+  return drbd_helper
+
+
 def SetClusterParams(opts, args):
   """Modify the cluster.
 
@@ -976,7 +1090,8 @@
   @return: the desired exit code
 
   """
-  if not (opts.vg_name is not None or opts.drbd_helper or
+  if not (opts.vg_name is not None or
+          opts.drbd_helper is not None or
           opts.enabled_hypervisors or opts.hvparams or
           opts.beparams or opts.nicparams or
           opts.ndparams or opts.diskparams or
@@ -1000,35 +1115,23 @@
           opts.ipolicy_vcpu_ratio is not None or
           opts.ipolicy_spindle_ratio is not None or
           opts.modify_etc_hosts is not None or
-          opts.file_storage_dir is not None):
+          opts.file_storage_dir is not None or
+          opts.shared_file_storage_dir is not None):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
   if _CheckNoLvmStorageOptDeprecated(opts):
     return 1
 
-  enabled_disk_templates = None
-  if opts.enabled_disk_templates:
-    enabled_disk_templates = opts.enabled_disk_templates.split(",")
+  enabled_disk_templates = _GetEnabledDiskTemplates(opts)
+  vg_name = _GetVgName(opts, enabled_disk_templates)
 
-  # consistency between vg name and enabled disk templates
-  vg_name = None
-  if opts.vg_name is not None:
-    vg_name = opts.vg_name
-  if enabled_disk_templates:
-    if vg_name and not utils.IsLvmEnabled(enabled_disk_templates):
-      ToStdout("You specified a volume group with --vg-name, but you did not"
-               " enable any of the following lvm-based disk templates: %s" %
-               utils.CommaJoin(utils.GetLvmDiskTemplates()))
-
-  drbd_helper = opts.drbd_helper
-  if not opts.drbd_storage and opts.drbd_helper:
-    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
+  try:
+    drbd_helper = _GetDrbdHelper(opts, enabled_disk_templates)
+  except errors.OpPrereqError, e:
+    ToStderr(str(e))
     return 1
 
-  if not opts.drbd_storage:
-    drbd_helper = ""
-
   hvlist = opts.enabled_hypervisors
   if hvlist is not None:
     hvlist = hvlist.split(",")
@@ -1125,6 +1228,7 @@
     enabled_disk_templates=enabled_disk_templates,
     force=opts.force,
     file_storage_dir=opts.file_storage_dir,
+    shared_file_storage_dir=opts.shared_file_storage_dir,
     )
   SubmitOrSend(op, opts)
   return 0
@@ -1278,7 +1382,7 @@
   return True
 
 
-class _RunWhenNodesReachableHelper:
+class _RunWhenNodesReachableHelper(object):
   """Helper class to make shared internal state sharing easier.
 
   @ivar success: Indicates if all action_cb calls were successful
@@ -1539,6 +1643,455 @@
   ToStdout(_GetCreateCommand(result))
 
 
+def _RunCommandAndReport(cmd):
+  """Run a command and report its output, iff it failed.
+
+  @param cmd: the command to execute
+  @type cmd: list
+  @rtype: bool
+  @return: False, if the execution failed.
+
+  """
+  result = utils.RunCmd(cmd)
+  if result.failed:
+    ToStderr("Command %s failed: %s; Output %s" %
+             (cmd, result.fail_reason, result.output))
+    return False
+  return True
+
+
+def _VerifyCommand(cmd):
+  """Verify that a given command succeeds on all online nodes.
+
+  As this function is intended to run during upgrades, it
+  is implemented in such a way that it still works, if all Ganeti
+  daemons are down.
+
+  @param cmd: the command to execute
+  @type cmd: list
+  @rtype: list
+  @return: the list of node names that are online where
+      the command failed.
+
+  """
+  command = utils.text.ShellQuoteArgs([str(val) for val in cmd])
+
+  nodes = ssconf.SimpleStore().GetOnlineNodeList()
+  master_node = ssconf.SimpleStore().GetMasterNode()
+  cluster_name = ssconf.SimpleStore().GetClusterName()
+
+  # If master node is in 'nodes', make sure master node is at list end
+  if master_node in nodes:
+    nodes.remove(master_node)
+    nodes.append(master_node)
+
+  failed = []
+
+  srun = ssh.SshRunner(cluster_name=cluster_name)
+  for name in nodes:
+    result = srun.Run(name, constants.SSH_LOGIN_USER, command)
+    if result.exit_code != 0:
+      failed.append(name)
+
+  return failed
+
+
+def _VerifyVersionInstalled(versionstring):
+  """Verify that the given version of ganeti is installed on all online nodes.
+
+  Do nothing, if this is the case, otherwise print an appropriate
+  message to stderr.
+
+  @param versionstring: the version to check for
+  @type versionstring: string
+  @rtype: bool
+  @return: True, if the version is installed on all online nodes
+
+  """
+  badnodes = _VerifyCommand(["test", "-d",
+                             os.path.join(pathutils.PKGLIBDIR, versionstring)])
+  if badnodes:
+    ToStderr("Ganeti version %s not installed on nodes %s"
+             % (versionstring, ", ".join(badnodes)))
+    return False
+
+  return True
+
+
+def _GetRunning():
+  """Determine the list of running jobs.
+
+  @rtype: list
+  @return: the number of jobs still running
+
+  """
+  cl = GetClient()
+  qfilter = qlang.MakeSimpleFilter("status",
+                                   frozenset([constants.JOB_STATUS_RUNNING]))
+  return len(cl.Query(constants.QR_JOB, [], qfilter).data)
+
+
+def _SetGanetiVersion(versionstring):
+  """Set the active version of ganeti to the given versionstring
+
+  @type versionstring: string
+  @rtype: list
+  @return: the list of nodes where the version change failed
+
+  """
+  failed = []
+  if constants.HAS_GNU_LN:
+    failed.extend(_VerifyCommand(
+        ["ln", "-s", "-f", "-T",
+         os.path.join(pathutils.PKGLIBDIR, versionstring),
+         os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")]))
+    failed.extend(_VerifyCommand(
+        ["ln", "-s", "-f", "-T",
+         os.path.join(pathutils.SHAREDIR, versionstring),
+         os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]))
+  else:
+    failed.extend(_VerifyCommand(
+        ["rm", "-f", os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")]))
+    failed.extend(_VerifyCommand(
+        ["ln", "-s", "-f", os.path.join(pathutils.PKGLIBDIR, versionstring),
+         os.path.join(pathutils.SYSCONFDIR, "ganeti/lib")]))
+    failed.extend(_VerifyCommand(
+        ["rm", "-f", os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]))
+    failed.extend(_VerifyCommand(
+        ["ln", "-s", "-f", os.path.join(pathutils.SHAREDIR, versionstring),
+         os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]))
+  return list(set(failed))
+
+
+def _ExecuteCommands(fns):
+  """Execute a list of functions, in reverse order.
+
+  @type fns: list of functions.
+  @param fns: the functions to be executed.
+
+  """
+  for fn in reversed(fns):
+    fn()
+
+
+def _GetConfigVersion():
+  """Determine the version the configuration file currently has.
+
+  @rtype: tuple or None
+  @return: (major, minor, revision) if the version can be determined,
+      None otherwise
+
+  """
+  config_data = serializer.LoadJson(utils.ReadFile(pathutils.CLUSTER_CONF_FILE))
+  try:
+    config_version = config_data["version"]
+  except KeyError:
+    return None
+  return utils.SplitVersion(config_version)
+
+
+def _ReadIntentToUpgrade():
+  """Read the file documenting the intent to upgrade the cluster.
+
+  @rtype: (string, string) or (None, None)
+  @return: (old version, version to upgrade to), if the file exists,
+      and (None, None) otherwise.
+
+  """
+  if not os.path.isfile(pathutils.INTENT_TO_UPGRADE):
+    return (None, None)
+
+  contentstring = utils.ReadFile(pathutils.INTENT_TO_UPGRADE)
+  contents = utils.UnescapeAndSplit(contentstring)
+  if len(contents) != 3:
+    # file syntactically mal-formed
+    return (None, None)
+  return (contents[0], contents[1])
+
+
+def _WriteIntentToUpgrade(version):
+  """Write file documenting the intent to upgrade the cluster.
+
+  @type version: string
+  @param version: the version we intent to upgrade to
+
+  """
+  utils.WriteFile(pathutils.INTENT_TO_UPGRADE,
+                  data=utils.EscapeAndJoin([constants.RELEASE_VERSION, version,
+                                            "%d" % os.getpid()]))
+
+
+def _UpgradeBeforeConfigurationChange(versionstring):
+  """
+  Carry out all the tasks necessary for an upgrade that happen before
+  the configuration file, or Ganeti version, changes.
+
+  @type versionstring: string
+  @param versionstring: the version to upgrade to
+  @rtype: (bool, list)
+  @return: tuple of a bool indicating success and a list of rollback tasks
+
+  """
+  rollback = []
+
+  if not _VerifyVersionInstalled(versionstring):
+    return (False, rollback)
+
+  _WriteIntentToUpgrade(versionstring)
+  rollback.append(
+    lambda: utils.RunCmd(["rm", "-f", pathutils.INTENT_TO_UPGRADE]))
+
+  ToStdout("Draining queue")
+  client = GetClient()
+  client.SetQueueDrainFlag(True)
+
+  rollback.append(lambda: GetClient().SetQueueDrainFlag(False))
+
+  if utils.SimpleRetry(0, _GetRunning,
+                       constants.UPGRADE_QUEUE_POLL_INTERVAL,
+                       constants.UPGRADE_QUEUE_DRAIN_TIMEOUT):
+    ToStderr("Failed to completely empty the queue.")
+    return (False, rollback)
+
+  ToStdout("Pausing the watcher for one hour.")
+  rollback.append(lambda: GetClient().SetWatcherPause(None))
+  GetClient().SetWatcherPause(time.time() + 60 * 60)
+
+  ToStdout("Stopping daemons on master node.")
+  if not _RunCommandAndReport([pathutils.DAEMON_UTIL, "stop-all"]):
+    return (False, rollback)
+
+  if not _VerifyVersionInstalled(versionstring):
+    utils.RunCmd([pathutils.DAEMON_UTIL, "start-all"])
+    return (False, rollback)
+
+  ToStdout("Stopping daemons everywhere.")
+  rollback.append(lambda: _VerifyCommand([pathutils.DAEMON_UTIL, "start-all"]))
+  badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "stop-all"])
+  if badnodes:
+    ToStderr("Failed to stop daemons on %s." % (", ".join(badnodes),))
+    return (False, rollback)
+
+  backuptar = os.path.join(pathutils.BACKUP_DIR, "ganeti%d.tar" % time.time())
+  ToStdout("Backing up configuration as %s" % backuptar)
+  if not _RunCommandAndReport(["mkdir", "-p", pathutils.BACKUP_DIR]):
+    return (False, rollback)
+
+  # Create the archive in a safe manner, as it contains sensitive
+  # information.
+  (_, tmp_name) = tempfile.mkstemp(prefix=backuptar, dir=pathutils.BACKUP_DIR)
+  if not _RunCommandAndReport(["tar", "-cf", tmp_name,
+                               "--exclude=queue/archive",
+                               pathutils.DATA_DIR]):
+    return (False, rollback)
+
+  os.rename(tmp_name, backuptar)
+  return (True, rollback)
+
+
+def _SwitchVersionAndConfig(versionstring, downgrade):
+  """
+  Switch to the new Ganeti version and change the configuration,
+  in correct order.
+
+  @type versionstring: string
+  @param versionstring: the version to change to
+  @type downgrade: bool
+  @param downgrade: True, if the configuration should be downgraded
+  @rtype: (bool, list)
+  @return: tupe of a bool indicating success, and a list of
+      additional rollback tasks
+
+  """
+  rollback = []
+  if downgrade:
+    ToStdout("Downgrading configuration")
+    if not _RunCommandAndReport([pathutils.CFGUPGRADE, "--downgrade", "-f"]):
+      return (False, rollback)
+
+  # Configuration change is the point of no return. From then onwards, it is
+  # safer to push through the up/dowgrade than to try to roll it back.
+
+  ToStdout("Switching to version %s on all nodes" % versionstring)
+  rollback.append(lambda: _SetGanetiVersion(constants.DIR_VERSION))
+  badnodes = _SetGanetiVersion(versionstring)
+  if badnodes:
+    ToStderr("Failed to switch to Ganeti version %s on nodes %s"
+             % (versionstring, ", ".join(badnodes)))
+    if not downgrade:
+      return (False, rollback)
+
+  # Now that we have changed to the new version of Ganeti we should
+  # not communicate over luxi any more, as luxi might have changed in
+  # incompatible ways. Therefore, manually call the corresponding ganeti
+  # commands using their canonical (version independent) path.
+
+  if not downgrade:
+    ToStdout("Upgrading configuration")
+    if not _RunCommandAndReport([pathutils.CFGUPGRADE, "-f"]):
+      return (False, rollback)
+
+  return (True, rollback)
+
+
+def _UpgradeAfterConfigurationChange(oldversion):
+  """
+  Carry out the upgrade actions necessary after switching to the new
+  Ganeti version and updating the configuration.
+
+  As this part is run at a time where the new version of Ganeti is already
+  running, no communication should happen via luxi, as this is not a stable
+  interface. Also, as the configuration change is the point of no return,
+  all actions are pushed trough, even if some of them fail.
+
+  @param oldversion: the version the upgrade started from
+  @type oldversion: string
+  @rtype: int
+  @return: the intended return value
+
+  """
+  returnvalue = 0
+
+  ToStdout("Ensuring directories everywhere.")
+  badnodes = _VerifyCommand([pathutils.ENSURE_DIRS])
+  if badnodes:
+    ToStderr("Warning: failed to ensure directories on %s." %
+             (", ".join(badnodes)))
+    returnvalue = 1
+
+  ToStdout("Starting daemons everywhere.")
+  badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "start-all"])
+  if badnodes:
+    ToStderr("Warning: failed to start daemons on %s." % (", ".join(badnodes),))
+    returnvalue = 1
+
+  ToStdout("Redistributing the configuration.")
+  if not _RunCommandAndReport(["gnt-cluster", "redist-conf", "--yes-do-it"]):
+    returnvalue = 1
+
+  ToStdout("Restarting daemons everywhere.")
+  badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "stop-all"])
+  badnodes.extend(_VerifyCommand([pathutils.DAEMON_UTIL, "start-all"]))
+  if badnodes:
+    ToStderr("Warning: failed to start daemons on %s." %
+             (", ".join(list(set(badnodes))),))
+    returnvalue = 1
+
+  ToStdout("Undraining the queue.")
+  if not _RunCommandAndReport(["gnt-cluster", "queue", "undrain"]):
+    returnvalue = 1
+
+  _RunCommandAndReport(["rm", "-f", pathutils.INTENT_TO_UPGRADE])
+
+  ToStdout("Running post-upgrade hooks")
+  if not _RunCommandAndReport([pathutils.POST_UPGRADE, oldversion]):
+    returnvalue = 1
+
+  ToStdout("Unpausing the watcher.")
+  if not _RunCommandAndReport(["gnt-cluster", "watcher", "continue"]):
+    returnvalue = 1
+
+  ToStdout("Verifying cluster.")
+  if not _RunCommandAndReport(["gnt-cluster", "verify"]):
+    returnvalue = 1
+
+  return returnvalue
+
+
+def UpgradeGanetiCommand(opts, args):
+  """Upgrade a cluster to a new ganeti version.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should be an empty list
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  if ((not opts.resume and opts.to is None)
+      or (opts.resume and opts.to is not None)):
+    ToStderr("Precisely one of the options --to and --resume"
+             " has to be given")
+    return 1
+
+  # If we're not told to resume, verify there is no upgrade
+  # in progress.
+  if not opts.resume:
+    oldversion, versionstring = _ReadIntentToUpgrade()
+    if versionstring is not None:
+      # An upgrade is going on; verify whether the target matches
+      if versionstring == opts.to:
+        ToStderr("An upgrade is already in progress. Target version matches,"
+                 " resuming.")
+        opts.resume = True
+        opts.to = None
+      else:
+        ToStderr("An upgrade from %s to %s is in progress; use --resume to"
+                 " finish it first" % (oldversion, versionstring))
+        return 1
+
+  oldversion = constants.RELEASE_VERSION
+
+  if opts.resume:
+    ssconf.CheckMaster(False)
+    oldversion, versionstring = _ReadIntentToUpgrade()
+    if versionstring is None:
+      return 0
+    version = utils.version.ParseVersion(versionstring)
+    if version is None:
+      return 1
+    configversion = _GetConfigVersion()
+    if configversion is None:
+      return 1
+    # If the upgrade we resume was an upgrade between compatible
+    # versions (like 2.10.0 to 2.10.1), the correct configversion
+    # does not guarantee that the config has been updated.
+    # However, in the case of a compatible update with the configuration
+    # not touched, we are running a different dirversion with the same
+    # config version.
+    config_already_modified = \
+      (utils.IsCorrectConfigVersion(version, configversion) and
+       not (versionstring != constants.DIR_VERSION and
+            configversion == (constants.CONFIG_MAJOR, constants.CONFIG_MINOR,
+                              constants.CONFIG_REVISION)))
+    if not config_already_modified:
+      # We have to start from the beginning; however, some daemons might have
+      # already been stopped, so the only way to get into a well-defined state
+      # is by starting all daemons again.
+      _VerifyCommand([pathutils.DAEMON_UTIL, "start-all"])
+  else:
+    versionstring = opts.to
+    config_already_modified = False
+    version = utils.version.ParseVersion(versionstring)
+    if version is None:
+      ToStderr("Could not parse version string %s" % versionstring)
+      return 1
+
+  msg = utils.version.UpgradeRange(version)
+  if msg is not None:
+    ToStderr("Cannot upgrade to %s: %s" % (versionstring, msg))
+    return 1
+
+  if not config_already_modified:
+    success, rollback = _UpgradeBeforeConfigurationChange(versionstring)
+    if not success:
+      _ExecuteCommands(rollback)
+      return 1
+  else:
+    rollback = []
+
+  downgrade = utils.version.ShouldCfgdowngrade(version)
+
+  success, additionalrollback =  \
+      _SwitchVersionAndConfig(versionstring, downgrade)
+  if not success:
+    rollback.extend(additionalrollback)
+    _ExecuteCommands(rollback)
+    return 1
+
+  return _UpgradeAfterConfigurationChange(oldversion)
+
+
 commands = {
   "init": (
     InitCluster, [ArgHost(min=1, max=1)],
@@ -1546,7 +2099,7 @@
      HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, MASTER_NETMASK_OPT,
      NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT,
      NOMODIFY_SSH_SETUP_OPT, SECONDARY_IP_OPT, VG_NAME_OPT,
-     MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
+     MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT,
      DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
      NODE_PARAMS_OPT, GLOBAL_SHARED_FILEDIR_OPT, USE_EXTERNAL_MIP_SCRIPT,
      DISK_PARAMS_OPT, HV_STATE_OPT, DISK_STATE_OPT, ENABLED_DISK_TEMPLATES_OPT,
@@ -1561,7 +2114,8 @@
     "<new_name>",
     "Renames the cluster"),
   "redist-conf": (
-    RedistributeConfig, ARGS_NONE, SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT],
+    RedistributeConfig, ARGS_NONE, SUBMIT_OPTS +
+    [DRY_RUN_OPT, PRIORITY_OPT, FORCE_DISTRIBUTION],
     "", "Forces a push of the configuration file and ssconf files"
     " to the nodes in the cluster"),
   "verify": (
@@ -1626,12 +2180,12 @@
      BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
      MASTER_NETMASK_OPT, NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT,
      MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT,
-     DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT,
+     DRBD_HELPER_OPT, DEFAULT_IALLOCATOR_OPT,
      RESERVED_LVS_OPT, DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT,
      NODE_PARAMS_OPT, USE_EXTERNAL_MIP_SCRIPT, DISK_PARAMS_OPT, HV_STATE_OPT,
      DISK_STATE_OPT] + SUBMIT_OPTS +
      [ENABLED_DISK_TEMPLATES_OPT, IPOLICY_STD_SPECS_OPT, MODIFY_ETCHOSTS_OPT] +
-     INSTANCE_POLICY_OPTS + [GLOBAL_FILEDIR_OPT],
+     INSTANCE_POLICY_OPTS + [GLOBAL_FILEDIR_OPT, GLOBAL_SHARED_FILEDIR_OPT],
     "[opts...]",
     "Alters the parameters of the cluster"),
   "renew-crypto": (
@@ -1656,6 +2210,9 @@
   "show-ispecs-cmd": (
     ShowCreateCommand, ARGS_NONE, [], "",
     "Show the command line to re-create the cluster"),
+  "upgrade": (
+    UpgradeGanetiCommand, ARGS_NONE, [TO_OPT, RESUME_OPT], "",
+    "Upgrade (or downgrade) to a new Ganeti version"),
   }
 
 
diff --git a/lib/client/gnt_debug.py b/lib/client/gnt_debug.py
index 1a22ea1..558d928 100644
--- a/lib/client/gnt_debug.py
+++ b/lib/client/gnt_debug.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Debugging commands"""
 
@@ -64,7 +73,8 @@
   op = opcodes.OpTestDelay(duration=delay,
                            on_master=opts.on_master,
                            on_nodes=opts.on_nodes,
-                           repeat=opts.repeat)
+                           repeat=opts.repeat,
+                           no_locks=opts.no_locks)
   SubmitOrSend(op, opts)
 
   return 0
@@ -631,6 +641,9 @@
                 action="append", help="Select nodes to sleep on"),
      cli_option("-r", "--repeat", type="int", default="0", dest="repeat",
                 help="Number of times to repeat the sleep"),
+     cli_option("-l", "--no-locks", default=False, dest="no_locks",
+                action="store_true",
+                help="Don't take locks while performing the delay"),
      DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "[opts...] <duration>", "Executes a TestDelay OpCode"),
   "submit-job": (
@@ -673,10 +686,10 @@
                 help="Select number of VCPUs for the instance"),
      cli_option("--tags", default=None,
                 help="Comma separated list of tags"),
-     cli_option("--evac-mode", default=constants.IALLOCATOR_NEVAC_ALL,
-                choices=list(constants.IALLOCATOR_NEVAC_MODES),
+     cli_option("--evac-mode", default=constants.NODE_EVAC_ALL,
+                choices=list(constants.NODE_EVAC_MODES),
                 help=("Node evacuation mode (one of %s)" %
-                      utils.CommaJoin(constants.IALLOCATOR_NEVAC_MODES))),
+                      utils.CommaJoin(constants.NODE_EVAC_MODES))),
      cli_option("--target-groups", help="Target groups for relocation",
                 default=[], action="append"),
      cli_option("--spindle-use", help="How many spindles to use",
diff --git a/lib/client/gnt_group.py b/lib/client/gnt_group.py
index d66ad91..3011a91 100644
--- a/lib/client/gnt_group.py
+++ b/lib/client/gnt_group.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Node group related commands"""
 
@@ -242,7 +251,9 @@
   op = opcodes.OpGroupEvacuate(group_name=group_name,
                                iallocator=opts.iallocator,
                                target_groups=opts.to,
-                               early_release=opts.early_release)
+                               early_release=opts.early_release,
+                               sequential=opts.sequential,
+                               force_failover=opts.force_failover)
   result = SubmitOrSend(op, opts, cl=cl)
 
   # Keep track of submitted jobs
@@ -358,7 +369,9 @@
     "[--dry-run] <group-name> <new-name>", "Rename a node group"),
   "evacuate": (
     EvacuateGroup, [ArgGroup(min=1, max=1)],
-    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT] + SUBMIT_OPTS,
+    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, SEQUENTIAL_OPT,
+     FORCE_FAILOVER_OPT]
+    + SUBMIT_OPTS,
     "[-I <iallocator>] [--to <group>]",
     "Evacuate all instances within a group"),
   "list-tags": (
diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py
index 50d0655..cafc0c0 100644
--- a/lib/client/gnt_instance.py
+++ b/lib/client/gnt_instance.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Instance related commands"""
 
@@ -842,14 +851,17 @@
   cl = GetClient()
   try:
     cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
-    ((console_data, oper_state), ) = \
-      cl.QueryInstances([instance_name], ["console", "oper_state"], False)
+    idata = cl.QueryInstances([instance_name], ["console", "oper_state"], False)
+    if not idata:
+      raise errors.OpPrereqError("Instance '%s' does not exist" % instance_name,
+                                 errors.ECODE_NOENT)
   finally:
     # Ensure client connection is closed while external commands are run
     cl.Close()
 
   del cl
 
+  ((console_data, oper_state), ) = idata
   if not console_data:
     if oper_state:
       # Instance is running
@@ -941,10 +953,6 @@
   return data
 
 
-def _FormatListInfo(data):
-  return list(str(i) for i in data)
-
-
 def _FormatBlockDevInfo(idx, top_level, dev, roman):
   """Show block device information.
 
@@ -1042,8 +1050,6 @@
       data.append(("logical_id", l_id[0]))
     else:
       data.extend(l_id)
-  elif dev["physical_id"] is not None:
-    data.append(("physical_id:", _FormatListInfo(dev["physical_id"])))
 
   if dev["pstatus"]:
     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
@@ -1064,7 +1070,7 @@
 
 def _FormatInstanceNicInfo(idx, nic):
   """Helper function for L{_FormatInstanceInfo()}"""
-  (name, uuid, ip, mac, mode, link, _, netinfo) = nic
+  (name, uuid, ip, mac, mode, link, vlan, _, netinfo) = nic
   network_name = None
   if netinfo:
     network_name = netinfo["name"]
@@ -1074,6 +1080,7 @@
     ("IP", str(ip)),
     ("mode", str(mode)),
     ("link", str(link)),
+    ("vlan", str(vlan)),
     ("network", str(network_name)),
     ("UUID", str(uuid)),
     ("name", str(name)),
@@ -1319,6 +1326,14 @@
   FixHvParams(opts.hvparams)
 
   nics = _ConvertNicDiskModifications(opts.nics)
+  for action, _, __ in nics:
+    if action == constants.DDM_MODIFY and opts.hotplug and not opts.force:
+      usertext = ("You are about to hot-modify a NIC. This will be done"
+                  " by removing the existing NIC and then adding a new one."
+                  " Network connection might be lost. Continue?")
+      if not AskUser(usertext):
+        return 1
+
   disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
 
   if (opts.disk_template and
@@ -1338,6 +1353,8 @@
   op = opcodes.OpInstanceSetParams(instance_name=args[0],
                                    nics=nics,
                                    disks=disks,
+                                   hotplug=opts.hotplug,
+                                   hotplug_if_possible=opts.hotplug_if_possible,
                                    disk_template=opts.disk_template,
                                    remote_node=opts.node,
                                    pnode=opts.new_primary_node,
@@ -1364,6 +1381,10 @@
              " only at the next (re)start of the instance initiated by"
              " ganeti; restarting from within the instance will"
              " not be enough.")
+    if opts.hvparams:
+      ToStdout("Note that changing hypervisor parameters without performing a"
+               " restart might lead to a crash while performing a live"
+               " migration. This will be addressed in future Ganeti versions.")
   return 0
 
 
@@ -1545,7 +1566,8 @@
     [DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
      ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
-     NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT],
+     NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT,
+     HOTPLUG_IF_POSSIBLE_OPT],
     "<instance>", "Alters the parameters of an instance"),
   "shutdown": (
     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
diff --git a/lib/client/gnt_job.py b/lib/client/gnt_job.py
index ef26c0b..52df879 100644
--- a/lib/client/gnt_job.py
+++ b/lib/client/gnt_job.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Job related commands"""
 
diff --git a/lib/client/gnt_network.py b/lib/client/gnt_network.py
index 40f9c5b..3603042 100644
--- a/lib/client/gnt_network.py
+++ b/lib/client/gnt_network.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """IP pool related commands"""
 
@@ -32,6 +41,7 @@
 from ganeti import opcodes
 from ganeti import utils
 from ganeti import errors
+from ganeti import objects
 
 
 #: default list of fields for L{ListNetworks}
@@ -112,15 +122,18 @@
   """
   cl = GetClient()
 
-  (network, mode, link) = args[:3]
-  groups = _GetDefaultGroups(cl, args[3:])
+  network = args[0]
+  nicparams = objects.FillDict(constants.NICC_DEFAULTS, opts.nicparams)
+
+  groups = _GetDefaultGroups(cl, args[1:])
 
   # TODO: Change logic to support "--submit"
   for group in groups:
     op = opcodes.OpNetworkConnect(group_name=group,
                                   network_name=network,
-                                  network_mode=mode,
-                                  network_link=link,
+                                  network_mode=nicparams[constants.NIC_MODE],
+                                  network_link=nicparams[constants.NIC_LINK],
+                                  network_vlan=nicparams[constants.NIC_VLAN],
                                   conflicts_check=opts.conflicts_check)
     SubmitOpCode(op, opts=opts, cl=cl)
 
@@ -160,8 +173,9 @@
   desired_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
   fmtoverride = {
     "group_list":
-      (lambda data: utils.CommaJoin("%s (%s, %s)" % (name, mode, link)
-                                    for (name, mode, link) in data),
+      (lambda data:
+        utils.CommaJoin("%s (%s, %s, %s)" % (name, mode, link, vlan)
+                        for (name, mode, link, vlan) in data),
        False),
     "inst_list": (",".join, False),
     "tags": (",".join, False),
@@ -240,18 +254,15 @@
 
     if group_list:
       ToStdout("  connected to node groups:")
-      for group, nic_mode, nic_link in group_list:
-        ToStdout("    %s (%s on %s)", group, nic_mode, nic_link)
+      for group, nic_mode, nic_link, nic_vlan in group_list:
+        ToStdout("    %s (mode:%s link:%s vlan:%s)",
+                 group, nic_mode, nic_link, nic_vlan)
     else:
       ToStdout("  not connected to any node group")
 
     if instances:
-      idata = cl.QueryInstances([], ["uuid", "name"], False)
-      uuid2name = dict(idata)
-
       ToStdout("  used by %d instances:", len(instances))
-      for inst in instances:
-        name = uuid2name[inst]
+      for name in instances:
         ((ips, networks), ) = cl.QueryInstances([name],
                                                 ["nic.ips", "nic.networks"],
                                                 use_locking=False)
@@ -342,11 +353,9 @@
   "connect": (
     ConnectNetwork,
     [ArgNetwork(min=1, max=1),
-     ArgChoice(min=1, max=1, choices=constants.NIC_VALID_MODES),
-     ArgUnknown(min=1, max=1),
      ArgGroup()],
-    [NOCONFLICTSCHECK_OPT, PRIORITY_OPT],
-    "<network_name> <mode> <link> [<node_group>...]",
+    [NOCONFLICTSCHECK_OPT, PRIORITY_OPT, NIC_PARAMS_OPT],
+    "<network_name> [<node_group>...]",
     "Map a given network to the specified node group"
     " with given mode and link (netparams)"),
   "disconnect": (
diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py
index 48ed7dd..ed96e5b 100644
--- a/lib/client/gnt_node.py
+++ b/lib/client/gnt_node.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Node related commands"""
 
@@ -795,16 +804,10 @@
   @return: the desired exit code
 
   """
-  # TODO: Default to ST_FILE if LVM is disabled on the cluster
-  if opts.user_storage_type is None:
-    opts.user_storage_type = constants.ST_LVM_PV
-
-  storage_type = ConvertStorageType(opts.user_storage_type)
-
   selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
 
   op = opcodes.OpNodeQueryStorage(nodes=args,
-                                  storage_type=storage_type,
+                                  storage_type=opts.user_storage_type,
                                   output_fields=selected_fields)
   output = SubmitOpCode(op, opts=opts)
 
@@ -1084,8 +1087,7 @@
      CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
      DISK_STATE_OPT],
     "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
-    " [--no-node-setup] [--verbose]"
-    " <node_name>",
+    " [--no-node-setup] [--verbose] [--network] <node_name>",
     "Add a node to the cluster"),
   "evacuate": (
     EvacuateNode, ARGS_ONE_NODE,
diff --git a/lib/client/gnt_os.py b/lib/client/gnt_os.py
index f522633..b116af7 100644
--- a/lib/client/gnt_os.py
+++ b/lib/client/gnt_os.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """OS scripts related commands"""
 
@@ -79,7 +88,7 @@
                             names=[])
   result = SubmitOpCode(op, opts=opts)
 
-  if not result:
+  if result is None:
     ToStderr("Can't get the OS list")
     return 1
 
@@ -147,7 +156,7 @@
                                            "blacklisted"], names=[])
   result = SubmitOpCode(op, opts=opts)
 
-  if not result:
+  if result is None:
     ToStderr("Can't get the OS list")
     return 1
 
diff --git a/lib/client/gnt_storage.py b/lib/client/gnt_storage.py
index 09ac14e..f5d51ad 100644
--- a/lib/client/gnt_storage.py
+++ b/lib/client/gnt_storage.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """External Storage related commands"""
 
diff --git a/lib/cmdlib/__init__.py b/lib/cmdlib/__init__.py
index 3f67039..d17850f 100644
--- a/lib/cmdlib/__init__.py
+++ b/lib/cmdlib/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the master-side code.
diff --git a/lib/cmdlib/backup.py b/lib/cmdlib/backup.py
index 3014a21..0ca1ad6 100644
--- a/lib/cmdlib/backup.py
+++ b/lib/cmdlib/backup.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with backup operations."""
@@ -85,12 +94,12 @@
     node_uuids = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
 
     result = []
-
     for (node_uuid, nres) in lu.rpc.call_export_list(node_uuids).items():
+      node = lu.cfg.GetNodeInfo(node_uuid)
       if nres.fail_msg:
-        result.append((node_uuid, None))
+        result.append((node.name, None))
       else:
-        result.extend((node_uuid, expname) for expname in nres.payload)
+        result.extend((node.name, expname) for expname in nres.payload)
 
     return result
 
@@ -393,15 +402,10 @@
                    " node %s" % (self.instance.name,
                                  self.cfg.GetNodeName(src_node_uuid)))
 
-    # set the disks ID correctly since call_instance_start needs the
-    # correct drbd minor to create the symlinks
-    for disk in self.instance.disks:
-      self.cfg.SetDiskID(disk, src_node_uuid)
-
     activate_disks = not self.instance.disks_active
 
     if activate_disks:
-      # Activate the instance disks if we'exporting a stopped instance
+      # Activate the instance disks if we're exporting a stopped instance
       feedback_fn("Activating disks for %s" % self.instance.name)
       StartInstanceDisks(self, self.instance, None)
 
diff --git a/lib/cmdlib/base.py b/lib/cmdlib/base.py
index 46f5a37..feb0de2 100644
--- a/lib/cmdlib/base.py
+++ b/lib/cmdlib/base.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Base classes and functions for cmdlib."""
@@ -31,7 +40,7 @@
 from ganeti.cmdlib.common import ExpandInstanceUuidAndName
 
 
-class ResultWithJobs:
+class ResultWithJobs(object):
   """Data container for LU results with jobs.
 
   Instances of this class returned from L{LogicalUnit.Exec} will be recognized
@@ -269,13 +278,10 @@
   def BuildHooksNodes(self):
     """Build list of nodes to run LU's hooks.
 
-    @rtype: tuple; (list, list) or (list, list, list)
+    @rtype: tuple; (list, list)
     @return: Tuple containing a list of node UUIDs on which the hook
       should run before the execution and a list of node UUIDs on which the
-      hook should run after the execution. As it might be possible that the
-      node UUID is not known at the time this method is invoked, an optional
-      third list can be added which contains node names on which the hook
-      should run after the execution (in case of node add, for instance).
+      hook should run after the execution.
       No nodes should be returned as an empty list (and not None).
     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
       will not be called.
@@ -283,6 +289,27 @@
     """
     raise NotImplementedError
 
+  def PreparePostHookNodes(self, post_hook_node_uuids):
+    """Extend list of nodes to run the post LU hook.
+
+    This method allows LUs to change the list of node UUIDs on which the
+    post hook should run after the LU has been executed but before the post
+    hook is run.
+
+    @type post_hook_node_uuids: list
+    @param post_hook_node_uuids: The initial list of node UUIDs to run the
+      post hook on, as returned by L{BuildHooksNodes}.
+    @rtype: list
+    @return: list of node UUIDs on which the post hook should run. The default
+      implementation returns the passed in C{post_hook_node_uuids}, but
+      custom implementations can choose to alter the list.
+
+    """
+    # For consistency with HooksCallBack we ignore the "could be a function"
+    # warning
+    # pylint: disable=R0201
+    return post_hook_node_uuids
+
   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
     """Notify the LU about the results of its hooks.
 
@@ -401,8 +428,14 @@
     """
     raise AssertionError("BuildHooksNodes called for NoHooksLU")
 
+  def PreparePostHookNodes(self, post_hook_node_uuids):
+    """Empty PreparePostHookNodes for NoHooksLU.
 
-class Tasklet:
+    """
+    raise AssertionError("PreparePostHookNodes called for NoHooksLU")
+
+
+class Tasklet(object):
   """Tasklet base class.
 
   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
@@ -448,7 +481,7 @@
     raise NotImplementedError
 
 
-class QueryBase:
+class QueryBase(object):
   """Base for query utility classes.
 
   """
diff --git a/lib/cmdlib/cluster.py b/lib/cmdlib/cluster.py
index cf020c2..d1d38c7 100644
--- a/lib/cmdlib/cluster.py
+++ b/lib/cmdlib/cluster.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with the cluster."""
@@ -57,7 +66,8 @@
   GetUpdatedIPolicy, ComputeNewInstanceViolations, GetUpdatedParams, \
   CheckOSParams, CheckHVParams, AdjustCandidatePool, CheckNodePVs, \
   ComputeIPolicyInstanceViolation, AnnotateDiskParams, SupportsOob, \
-  CheckIpolicyVsDiskTemplates
+  CheckIpolicyVsDiskTemplates, CheckDiskAccessModeValidity, \
+  CheckDiskAccessModeConsistency
 
 import ganeti.masterd.instance
 
@@ -180,6 +190,21 @@
   HPATH = "cluster-init"
   HTYPE = constants.HTYPE_CLUSTER
 
+  def CheckArguments(self):
+    self.master_uuid = self.cfg.GetMasterNode()
+    self.master_ndparams = self.cfg.GetNdParams(self.cfg.GetMasterNodeInfo())
+
+    # TODO: When Issue 584 is solved, and None is properly parsed when used
+    # as a default value, ndparams.get(.., None) can be changed to
+    # ndparams[..] to access the values directly
+
+    # OpenvSwitch: Warn user if link is missing
+    if (self.master_ndparams[constants.ND_OVS] and not
+        self.master_ndparams.get(constants.ND_OVS_LINK, None)):
+      self.LogInfo("No physical interface for OpenvSwitch was given."
+                   " OpenvSwitch will not have an outside connection. This"
+                   " might not be what you want.")
+
   def BuildHooksEnv(self):
     """Build hooks env.
 
@@ -195,9 +220,15 @@
     return ([], [self.cfg.GetMasterNode()])
 
   def Exec(self, feedback_fn):
-    """Nothing to do.
+    """Create and configure Open vSwitch
 
     """
+    if self.master_ndparams[constants.ND_OVS]:
+      result = self.rpc.call_node_configure_ovs(
+                 self.master_uuid,
+                 self.master_ndparams[constants.ND_OVS_NAME],
+                 self.master_ndparams.get(constants.ND_OVS_LINK, None))
+      result.Raise("Could not successully configure Open vSwitch")
     return True
 
 
@@ -533,9 +564,11 @@
 
     changed = []
     for node_uuid, dskl in per_node_disks.items():
-      newl = [v[2].Copy() for v in dskl]
-      for dsk in newl:
-        self.cfg.SetDiskID(dsk, node_uuid)
+      if not dskl:
+        # no disks on the node
+        continue
+
+      newl = [([v[2].Copy()], v[0]) for v in dskl]
       node_name = self.cfg.GetNodeName(node_uuid)
       result = self.rpc.call_blockdev_getdimensions(node_uuid, newl)
       if result.fail_msg:
@@ -702,6 +735,7 @@
         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
       try:
         utils.VerifyDictOptions(self.op.diskparams, constants.DISK_DT_DEFAULTS)
+        CheckDiskAccessModeValidity(self.op.diskparams)
       except errors.OpPrereqError, err:
         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
                                    errors.ECODE_INVAL)
@@ -787,30 +821,42 @@
                                    errors.ECODE_ENVIRON)
 
   @staticmethod
-  def _GetEnabledDiskTemplatesInner(op_enabled_disk_templates,
-                                    old_enabled_disk_templates):
-    """Determines the enabled disk templates and the subset of disk templates
-       that are newly enabled by this operation.
+  def _GetDiskTemplateSetsInner(op_enabled_disk_templates,
+                                old_enabled_disk_templates):
+    """Computes three sets of disk templates.
+
+    @see: C{_GetDiskTemplateSets} for more details.
 
     """
     enabled_disk_templates = None
     new_enabled_disk_templates = []
+    disabled_disk_templates = []
     if op_enabled_disk_templates:
       enabled_disk_templates = op_enabled_disk_templates
       new_enabled_disk_templates = \
         list(set(enabled_disk_templates)
              - set(old_enabled_disk_templates))
+      disabled_disk_templates = \
+        list(set(old_enabled_disk_templates)
+             - set(enabled_disk_templates))
     else:
       enabled_disk_templates = old_enabled_disk_templates
-    return (enabled_disk_templates, new_enabled_disk_templates)
+    return (enabled_disk_templates, new_enabled_disk_templates,
+            disabled_disk_templates)
 
-  def _GetEnabledDiskTemplates(self, cluster):
-    """Determines the enabled disk templates and the subset of disk templates
-       that are newly enabled by this operation.
+  def _GetDiskTemplateSets(self, cluster):
+    """Computes three sets of disk templates.
+
+    The three sets are:
+      - disk templates that will be enabled after this operation (no matter if
+        they were enabled before or not)
+      - disk templates that get enabled by this operation (thus haven't been
+        enabled before.)
+      - disk templates that get disabled by this operation
 
     """
-    return self._GetEnabledDiskTemplatesInner(self.op.enabled_disk_templates,
-                                              cluster.enabled_disk_templates)
+    return self._GetDiskTemplateSetsInner(self.op.enabled_disk_templates,
+                                          cluster.enabled_disk_templates)
 
   def _CheckIpolicy(self, cluster, enabled_disk_templates):
     """Checks the ipolicy.
@@ -851,6 +897,82 @@
       CheckIpolicyVsDiskTemplates(cluster.ipolicy,
                                   enabled_disk_templates)
 
+  def _CheckDrbdHelperOnNodes(self, drbd_helper, node_uuids):
+    """Checks whether the set DRBD helper actually exists on the nodes.
+
+    @type drbd_helper: string
+    @param drbd_helper: path of the drbd usermode helper binary
+    @type node_uuids: list of strings
+    @param node_uuids: list of node UUIDs to check for the helper
+
+    """
+    # checks given drbd helper on all nodes
+    helpers = self.rpc.call_drbd_helper(node_uuids)
+    for (_, ninfo) in self.cfg.GetMultiNodeInfo(node_uuids):
+      if ninfo.offline:
+        self.LogInfo("Not checking drbd helper on offline node %s",
+                     ninfo.name)
+        continue
+      msg = helpers[ninfo.uuid].fail_msg
+      if msg:
+        raise errors.OpPrereqError("Error checking drbd helper on node"
+                                   " '%s': %s" % (ninfo.name, msg),
+                                   errors.ECODE_ENVIRON)
+      node_helper = helpers[ninfo.uuid].payload
+      if node_helper != drbd_helper:
+        raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
+                                   (ninfo.name, node_helper),
+                                   errors.ECODE_ENVIRON)
+
+  def _CheckDrbdHelper(self, node_uuids, drbd_enabled, drbd_gets_enabled):
+    """Check the DRBD usermode helper.
+
+    @type node_uuids: list of strings
+    @param node_uuids: a list of nodes' UUIDs
+    @type drbd_enabled: boolean
+    @param drbd_enabled: whether DRBD will be enabled after this operation
+      (no matter if it was disabled before or not)
+    @type drbd_gets_enabled: boolen
+    @param drbd_gets_enabled: true if DRBD was disabled before this
+      operation, but will be enabled afterwards
+
+    """
+    if self.op.drbd_helper == '':
+      if drbd_enabled:
+        raise errors.OpPrereqError("Cannot disable drbd helper while"
+                                   " DRBD is enabled.")
+      if self.cfg.HasAnyDiskOfType(constants.DT_DRBD8):
+        raise errors.OpPrereqError("Cannot disable drbd helper while"
+                                   " drbd-based instances exist",
+                                   errors.ECODE_INVAL)
+
+    else:
+      if self.op.drbd_helper is not None and drbd_enabled:
+        self._CheckDrbdHelperOnNodes(self.op.drbd_helper, node_uuids)
+      else:
+        if drbd_gets_enabled:
+          current_drbd_helper = self.cfg.GetClusterInfo().drbd_usermode_helper
+          if current_drbd_helper is not None:
+            self._CheckDrbdHelperOnNodes(current_drbd_helper, node_uuids)
+          else:
+            raise errors.OpPrereqError("Cannot enable DRBD without a"
+                                       " DRBD usermode helper set.")
+
+  def _CheckInstancesOfDisabledDiskTemplates(
+      self, disabled_disk_templates):
+    """Check whether we try to disable a disk template that is in use.
+
+    @type disabled_disk_templates: list of string
+    @param disabled_disk_templates: list of disk templates that are going to
+      be disabled by this operation
+
+    """
+    for disk_template in disabled_disk_templates:
+      if self.cfg.HasAnyDiskOfType(disk_template):
+        raise errors.OpPrereqError(
+            "Cannot disable disk template '%s', because there is at least one"
+            " instance using it." % disk_template)
+
   def CheckPrereq(self):
     """Check prerequisites.
 
@@ -858,12 +980,6 @@
     if the given volume group is valid.
 
     """
-    if self.op.drbd_helper is not None and not self.op.drbd_helper:
-      if self.cfg.HasAnyDiskOfType(constants.DT_DRBD8):
-        raise errors.OpPrereqError("Cannot disable drbd helper while"
-                                   " drbd-based instances exist",
-                                   errors.ECODE_INVAL)
-
     node_uuids = self.owned_locks(locking.LEVEL_NODE)
     self.cluster = cluster = self.cfg.GetClusterInfo()
 
@@ -871,8 +987,9 @@
                              for node in self.cfg.GetAllNodesInfo().values()
                              if node.uuid in node_uuids and node.vm_capable]
 
-    (enabled_disk_templates, new_enabled_disk_templates) = \
-      self._GetEnabledDiskTemplates(cluster)
+    (enabled_disk_templates, new_enabled_disk_templates,
+      disabled_disk_templates) = self._GetDiskTemplateSets(cluster)
+    self._CheckInstancesOfDisabledDiskTemplates(disabled_disk_templates)
 
     self._CheckVgName(vm_capable_node_uuids, enabled_disk_templates,
                       new_enabled_disk_templates)
@@ -886,24 +1003,10 @@
           self.LogWarning, self.op.shared_file_storage_dir,
           enabled_disk_templates)
 
-    if self.op.drbd_helper:
-      # checks given drbd helper on all nodes
-      helpers = self.rpc.call_drbd_helper(node_uuids)
-      for (_, ninfo) in self.cfg.GetMultiNodeInfo(node_uuids):
-        if ninfo.offline:
-          self.LogInfo("Not checking drbd helper on offline node %s",
-                       ninfo.name)
-          continue
-        msg = helpers[ninfo.uuid].fail_msg
-        if msg:
-          raise errors.OpPrereqError("Error checking drbd helper on node"
-                                     " '%s': %s" % (ninfo.name, msg),
-                                     errors.ECODE_ENVIRON)
-        node_helper = helpers[ninfo.uuid].payload
-        if node_helper != self.op.drbd_helper:
-          raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
-                                     (ninfo.name, node_helper),
-                                     errors.ECODE_ENVIRON)
+    drbd_enabled = constants.DT_DRBD8 in enabled_disk_templates
+    drbd_gets_enabled = constants.DT_DRBD8 in new_enabled_disk_templates
+    self._CheckDrbdHelper(vm_capable_node_uuids,
+                          drbd_enabled, drbd_gets_enabled)
 
     # validate params changes
     if self.op.beparams:
@@ -982,6 +1085,7 @@
           self.new_diskparams[dt_name] = dt_params
         else:
           self.new_diskparams[dt_name].update(dt_params)
+      CheckDiskAccessModeConsistency(self.op.diskparams, self.cfg)
 
     # os hypervisor parameters
     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
@@ -1111,21 +1215,26 @@
       else:
         self.cluster.file_storage_dir = self.op.file_storage_dir
 
-  def Exec(self, feedback_fn):
-    """Change the parameters of the cluster.
+  def _SetSharedFileStorageDir(self, feedback_fn):
+    """Set the shared file storage directory.
 
     """
-    if self.op.enabled_disk_templates:
-      self.cluster.enabled_disk_templates = \
-        list(set(self.op.enabled_disk_templates))
+    if self.op.shared_file_storage_dir is not None:
+      if self.cluster.shared_file_storage_dir == \
+          self.op.shared_file_storage_dir:
+        feedback_fn("Global shared file storage dir already set to value '%s'"
+                    % self.cluster.shared_file_storage_dir)
+      else:
+        self.cluster.shared_file_storage_dir = self.op.shared_file_storage_dir
 
-    self._SetVgName(feedback_fn)
-    self._SetFileStorageDir(feedback_fn)
+  def _SetDrbdHelper(self, feedback_fn):
+    """Set the DRBD usermode helper.
 
+    """
     if self.op.drbd_helper is not None:
       if not constants.DT_DRBD8 in self.cluster.enabled_disk_templates:
-        feedback_fn("Note that you specified a drbd user helper, but did"
-                    " enabled the drbd disk template.")
+        feedback_fn("Note that you specified a drbd user helper, but did not"
+                    " enable the drbd disk template.")
       new_helper = self.op.drbd_helper
       if not new_helper:
         new_helper = None
@@ -1134,6 +1243,20 @@
       else:
         feedback_fn("Cluster DRBD helper already in desired state,"
                     " not changing")
+
+  def Exec(self, feedback_fn):
+    """Change the parameters of the cluster.
+
+    """
+    if self.op.enabled_disk_templates:
+      self.cluster.enabled_disk_templates = \
+        list(self.op.enabled_disk_templates)
+
+    self._SetVgName(feedback_fn)
+    self._SetFileStorageDir(feedback_fn)
+    self._SetSharedFileStorageDir(feedback_fn)
+    self._SetDrbdHelper(feedback_fn)
+
     if self.op.hvparams:
       self.cluster.hvparams = self.new_hvparams
     if self.op.os_hvp:
@@ -1829,8 +1952,9 @@
     for node_uuid, ndata in node_verify_infos.items():
       nresult = ndata.payload
       if nresult:
-        version = nresult.get(constants.NV_DRBDVERSION, "Missing DRBD version")
-        node_versions[node_uuid] = version
+        version = nresult.get(constants.NV_DRBDVERSION, None)
+        if version:
+          node_versions[node_uuid] = version
 
     if len(set(node_versions.values())) > 1:
       for node_uuid, version in sorted(node_versions.items()):
@@ -2137,7 +2261,7 @@
          self.all_node_info[node_uuid].group != self.group_uuid:
         # we're skipping nodes marked offline and nodes in other groups from
         # the N+1 warning, since most likely we don't have good memory
-        # infromation from them; we already list instances living on such
+        # information from them; we already list instances living on such
         # nodes, and that's enough warning
         continue
       #TODO(dynmem): also consider ballooning out other instances
@@ -2268,17 +2392,8 @@
                     "File %s found with %s different checksums (%s)",
                     filename, len(checksums), "; ".join(variants))
 
-  def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
-                      drbd_map):
-    """Verifies and the node DRBD status.
-
-    @type ninfo: L{objects.Node}
-    @param ninfo: the node to check
-    @param nresult: the remote results for the node
-    @param instanceinfo: the dict of instances
-    @param drbd_helper: the configured DRBD usermode helper
-    @param drbd_map: the DRBD map as returned by
-        L{ganeti.config.ConfigWriter.ComputeDRBDMap}
+  def _VerifyNodeDrbdHelper(self, ninfo, nresult, drbd_helper):
+    """Verify the drbd helper.
 
     """
     if drbd_helper:
@@ -2295,6 +2410,21 @@
         self._ErrorIf(test, constants.CV_ENODEDRBDHELPER, ninfo.name,
                       "wrong drbd usermode helper: %s", payload)
 
+  def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
+                      drbd_map):
+    """Verifies and the node DRBD status.
+
+    @type ninfo: L{objects.Node}
+    @param ninfo: the node to check
+    @param nresult: the remote results for the node
+    @param instanceinfo: the dict of instances
+    @param drbd_helper: the configured DRBD usermode helper
+    @param drbd_map: the DRBD map as returned by
+        L{ganeti.config.ConfigWriter.ComputeDRBDMap}
+
+    """
+    self._VerifyNodeDrbdHelper(ninfo, nresult, drbd_helper)
+
     # compute the DRBD minors
     node_drbd = {}
     for minor, inst_uuid in drbd_map[ninfo.uuid].items():
@@ -2616,7 +2746,7 @@
 
     """
     node_disks = {}
-    node_disks_devonly = {}
+    node_disks_dev_inst_only = {}
     diskless_instances = set()
     nodisk_instances = set()
     diskless = constants.DT_DISKLESS
@@ -2639,20 +2769,19 @@
       node_disks[nuuid] = disks
 
       # _AnnotateDiskParams makes already copies of the disks
-      devonly = []
+      dev_inst_only = []
       for (inst_uuid, dev) in disks:
         (anno_disk,) = AnnotateDiskParams(instanceinfo[inst_uuid], [dev],
                                           self.cfg)
-        self.cfg.SetDiskID(anno_disk, nuuid)
-        devonly.append(anno_disk)
+        dev_inst_only.append((anno_disk, instanceinfo[inst_uuid]))
 
-      node_disks_devonly[nuuid] = devonly
+      node_disks_dev_inst_only[nuuid] = dev_inst_only
 
-    assert len(node_disks) == len(node_disks_devonly)
+    assert len(node_disks) == len(node_disks_dev_inst_only)
 
     # Collect data from all nodes with disks
-    result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
-                                                          node_disks_devonly)
+    result = self.rpc.call_blockdev_getmirrorstatus_multi(
+               node_disks.keys(), node_disks_dev_inst_only)
 
     assert len(result) == len(node_disks)
 
@@ -2832,7 +2961,7 @@
       constants.NV_TIME: None,
       constants.NV_MASTERIP: (self.cfg.GetMasterNodeName(), master_ip),
       constants.NV_OSLIST: None,
-      constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
+      constants.NV_NONVMNODES: self.cfg.GetNonVmCapableNodeNameList(),
       constants.NV_USERSCRIPTS: user_scripts,
       }
 
@@ -2841,10 +2970,11 @@
       node_verify_param[constants.NV_LVLIST] = vg_name
       node_verify_param[constants.NV_PVLIST] = [vg_name]
 
-    if drbd_helper:
-      node_verify_param[constants.NV_DRBDVERSION] = None
-      node_verify_param[constants.NV_DRBDLIST] = None
-      node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
+    if cluster.IsDiskTemplateEnabled(constants.DT_DRBD8):
+      if drbd_helper:
+        node_verify_param[constants.NV_DRBDVERSION] = None
+        node_verify_param[constants.NV_DRBDLIST] = None
+        node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
 
     if cluster.IsFileStorageEnabled() or \
         cluster.IsSharedFileStorageEnabled():
@@ -2854,6 +2984,9 @@
       if cluster.IsFileStorageEnabled():
         node_verify_param[constants.NV_FILE_STORAGE_PATH] = \
           cluster.file_storage_dir
+      if cluster.IsSharedFileStorageEnabled():
+        node_verify_param[constants.NV_SHARED_FILE_STORAGE_PATH] = \
+          cluster.shared_file_storage_dir
 
     # bridge checks
     # FIXME: this needs to be changed per node-group, not cluster-wide
@@ -3024,10 +3157,13 @@
 
       if nimg.vm_capable:
         self._UpdateVerifyNodeLVM(node_i, nresult, vg_name, nimg)
-        self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
-                             all_drbd_map)
+        if constants.DT_DRBD8 in cluster.enabled_disk_templates:
+          self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
+                               all_drbd_map)
 
-        self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
+        if (constants.DT_PLAIN in cluster.enabled_disk_templates) or \
+            (constants.DT_DRBD8 in cluster.enabled_disk_templates):
+          self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
         self._UpdateNodeInstances(node_i, nresult, nimg)
         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
         self._UpdateNodeOS(node_i, nresult, nimg)
@@ -3143,9 +3279,11 @@
         test = msg and not res.offline
         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
                       "Communication failure in hooks execution: %s", msg)
-        if res.offline or msg:
-          # No need to investigate payload if node is offline or gave
-          # an error.
+        if test:
+          lu_result = False
+          continue
+        if res.offline:
+          # No need to investigate payload if node is offline
           continue
         for script, hkr, output in res.payload:
           test = hkr == constants.HKR_FAIL
diff --git a/lib/cmdlib/common.py b/lib/cmdlib/common.py
index 73132c2..15cbb2d 100644
--- a/lib/cmdlib/common.py
+++ b/lib/cmdlib/common.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Common functions used by multiple logical units."""
@@ -194,7 +203,7 @@
   """
   # Gather target nodes
   cluster = lu.cfg.GetClusterInfo()
-  master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
+  master_info = lu.cfg.GetMasterNodeInfo()
 
   online_node_uuids = lu.cfg.GetOnlineNodeList()
   online_node_uuid_set = frozenset(online_node_uuids)
@@ -720,8 +729,7 @@
   @see L{rpc.AnnotateDiskParams}
 
   """
-  return rpc.AnnotateDiskParams(instance.disk_template, devs,
-                                cfg.GetInstanceDiskParams(instance))
+  return rpc.AnnotateDiskParams(devs, cfg.GetInstanceDiskParams(instance))
 
 
 def SupportsOob(cfg, node):
@@ -1037,9 +1045,6 @@
 def FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_uuid, prereq):
   faulty = []
 
-  for dev in instance.disks:
-    cfg.SetDiskID(dev, node_uuid)
-
   result = rpc_runner.call_blockdev_getmirrorstatus(
              node_uuid, (instance.disks, instance))
   result.Raise("Failed to get disk status from node %s" %
@@ -1137,3 +1142,88 @@
     raise errors.OpPrereqError("The following disk template are allowed"
                                " by the ipolicy, but not enabled on the"
                                " cluster: %s" % utils.CommaJoin(not_enabled))
+
+
+def CheckDiskAccessModeValidity(parameters):
+  """Checks if the access parameter is legal.
+
+  @see: L{CheckDiskAccessModeConsistency} for cluster consistency checks.
+  @raise errors.OpPrereqError: if the check fails.
+
+  """
+  if constants.DT_RBD in parameters:
+    access = parameters[constants.DT_RBD].get(constants.RBD_ACCESS,
+                                              constants.DISK_KERNELSPACE)
+    if access not in constants.DISK_VALID_ACCESS_MODES:
+      valid_vals_str = utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
+      raise errors.OpPrereqError("Invalid value of '{d}:{a}': '{v}' (expected"
+                                 " one of {o})".format(d=constants.DT_RBD,
+                                                       a=constants.RBD_ACCESS,
+                                                       v=access,
+                                                       o=valid_vals_str))
+
+
+def CheckDiskAccessModeConsistency(parameters, cfg, group=None):
+  """Checks if the access param is consistent with the cluster configuration.
+
+  @note: requires a configuration lock to run.
+  @param parameters: the parameters to validate
+  @param cfg: the cfg object of the cluster
+  @param group: if set, only check for consistency within this group.
+  @raise errors.OpPrereqError: if the LU attempts to change the access parameter
+                               to an invalid value, such as "pink bunny".
+  @raise errors.OpPrereqError: if the LU attempts to change the access parameter
+                               to an inconsistent value, such as asking for RBD
+                               userspace access to the chroot hypervisor.
+
+  """
+  CheckDiskAccessModeValidity(parameters)
+
+  if constants.DT_RBD in parameters:
+    access = parameters[constants.DT_RBD].get(constants.RBD_ACCESS,
+                                              constants.DISK_KERNELSPACE)
+
+    #Check the combination of instance hypervisor, disk template and access
+    #protocol is sane.
+    inst_uuids = cfg.GetNodeGroupInstances(group) if group else \
+                 cfg.GetInstanceList()
+
+    for entry in inst_uuids:
+      #hyp, disk, access
+      inst = cfg.GetInstanceInfo(entry)
+      hv = inst.hypervisor
+      dt = inst.disk_template
+
+      #do not check for disk types that don't have this setting.
+      if dt != constants.DT_RBD:
+        continue
+
+      if not IsValidDiskAccessModeCombination(hv, dt, access):
+        raise errors.OpPrereqError("Instance {i}: cannot use '{a}' access"
+                                   " setting with {h} hypervisor and {d} disk"
+                                   " type.".format(i=inst.name,
+                                                   a=access,
+                                                   h=hv,
+                                                   d=dt))
+
+
+def IsValidDiskAccessModeCombination(hv, disk_template, mode):
+  """Checks if an hypervisor can read a disk template with given mode.
+
+  @param hv: the hypervisor that will access the data
+  @param disk_template: the disk template the data is stored as
+  @param mode: how the hypervisor should access the data
+  @return: True if the hypervisor can read a given read disk_template
+           in the specified mode.
+
+  """
+  if mode == constants.DISK_KERNELSPACE:
+    return True
+
+  if (hv == constants.HT_KVM and
+      disk_template == constants.DT_RBD and
+      mode == constants.DISK_USERSPACE):
+    return True
+
+  # Everything else:
+  return False
diff --git a/lib/cmdlib/group.py b/lib/cmdlib/group.py
index 98a3891..28fdc7e 100644
--- a/lib/cmdlib/group.py
+++ b/lib/cmdlib/group.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with node groups."""
@@ -28,6 +37,7 @@
 from ganeti import errors
 from ganeti import locking
 from ganeti import objects
+from ganeti import opcodes
 from ganeti import qlang
 from ganeti import query
 from ganeti import utils
@@ -39,7 +49,8 @@
   CheckNodeGroupInstances, GetUpdatedIPolicy, \
   ComputeNewInstanceViolations, GetDefaultIAllocator, ShareAll, \
   CheckInstancesNodeGroups, LoadNodeEvacResult, MapInstanceLvsToNodes, \
-  CheckIpolicyVsDiskTemplates
+  CheckIpolicyVsDiskTemplates, CheckDiskAccessModeValidity, \
+  CheckDiskAccessModeConsistency
 
 import ganeti.masterd.instance
 
@@ -406,6 +417,9 @@
       raise errors.OpPrereqError("Please pass at least one modification",
                                  errors.ECODE_INVAL)
 
+    if self.op.diskparams:
+      CheckDiskAccessModeValidity(self.op.diskparams)
+
   def ExpandNames(self):
     # This raises errors.OpPrereqError on its own:
     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
@@ -500,8 +514,11 @@
       # As we've all subdicts of diskparams ready, lets merge the actual
       # dict with all updated subdicts
       self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
+
       try:
         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
+        CheckDiskAccessModeConsistency(self.new_diskparams, self.cfg,
+                                       group=self.group)
       except errors.OpPrereqError, err:
         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
                                    errors.ECODE_INVAL)
@@ -827,6 +844,24 @@
 
     return (run_nodes, run_nodes)
 
+  @staticmethod
+  def _MigrateToFailover(op):
+    """Return an equivalent failover opcode for a migrate one.
+
+    If the argument is not a failover opcode, return it unchanged.
+
+    """
+    if not isinstance(op, opcodes.OpInstanceMigrate):
+      return op
+    else:
+      return opcodes.OpInstanceFailover(
+        instance_name=op.instance_name,
+        instance_uuid=getattr(op, "instance_uuid", None),
+        target_node=getattr(op, "target_node", None),
+        target_node_uuid=getattr(op, "target_node_uuid", None),
+        ignore_ipolicy=op.ignore_ipolicy,
+        cleanup=op.cleanup)
+
   def Exec(self, feedback_fn):
     inst_names = list(self.owned_locks(locking.LEVEL_INSTANCE))
 
@@ -849,6 +884,16 @@
     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
                  len(jobs), self.op.group_name)
 
+    if self.op.force_failover:
+      self.LogInfo("Will insist on failovers")
+      jobs = [[self._MigrateToFailover(op) for op in job] for job in jobs]
+
+    if self.op.sequential:
+      self.LogInfo("Jobs will be submitted to run sequentially")
+      for job in jobs[1:]:
+        for op in job:
+          op.depends = [(-1, ["error", "success"])]
+
     return ResultWithJobs(jobs)
 
 
@@ -968,12 +1013,9 @@
                                        inst.secondary_nodes):
         node_to_inst.setdefault(node_uuid, []).append(inst)
 
-    nodes_ip = dict((uuid, node.secondary_ip) for (uuid, node)
-                    in self.cfg.GetMultiNodeInfo(node_to_inst.keys()))
     for (node_uuid, insts) in node_to_inst.items():
       node_disks = [(inst.disks, inst) for inst in insts]
-      node_res = self.rpc.call_drbd_needs_activation(node_uuid, nodes_ip,
-                                                     node_disks)
+      node_res = self.rpc.call_drbd_needs_activation(node_uuid, node_disks)
       msg = node_res.fail_msg
       if msg:
         logging.warning("Error getting DRBD status on node %s: %s",
diff --git a/lib/cmdlib/instance.py b/lib/cmdlib/instance.py
index 4fe1aff..967db39 100644
--- a/lib/cmdlib/instance.py
+++ b/lib/cmdlib/instance.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with instances."""
@@ -36,7 +45,6 @@
 from ganeti import masterd
 from ganeti import netutils
 from ganeti import objects
-from ganeti import opcodes
 from ganeti import pathutils
 from ganeti import rpc
 from ganeti import utils
@@ -50,7 +58,7 @@
   IsExclusiveStorageEnabledNode, CheckHVParams, CheckOSParams, \
   AnnotateDiskParams, GetUpdatedParams, ExpandInstanceUuidAndName, \
   ComputeIPolicySpecViolation, CheckInstanceState, ExpandNodeUuidAndName, \
-  CheckDiskTemplateEnabled
+  CheckDiskTemplateEnabled, IsValidDiskAccessModeCombination
 from ganeti.cmdlib.instance_storage import CreateDisks, \
   CheckNodesFreeDiskPerVG, WipeDisks, WipeOrCleanupDisks, WaitForSync, \
   IsExclusiveStorageEnabledNodeUuid, CreateSingleBlockDev, ComputeDisks, \
@@ -174,6 +182,7 @@
     net = nic.get(constants.INIC_NETWORK, None)
     link = nic.get(constants.NIC_LINK, None)
     ip = nic.get(constants.INIC_IP, None)
+    vlan = nic.get(constants.INIC_VLAN, None)
 
     if net is None or net.lower() == constants.VALUE_NONE:
       net = None
@@ -231,6 +240,8 @@
       nicparams[constants.NIC_MODE] = nic_mode
     if link:
       nicparams[constants.NIC_LINK] = link
+    if vlan:
+      nicparams[constants.NIC_VLAN] = vlan
 
     check_params = cluster.SimpleFillNIC(nicparams)
     objects.NIC.CheckParameterSyntax(check_params)
@@ -379,6 +390,38 @@
 
     self.adopt_disks = has_adopt
 
+  def _CheckVLANArguments(self):
+    """ Check validity of VLANs if given
+
+    """
+    for nic in self.op.nics:
+      vlan = nic.get(constants.INIC_VLAN, None)
+      if vlan:
+        if vlan[0] == ".":
+          # vlan starting with dot means single untagged vlan,
+          # might be followed by trunk (:)
+          if not vlan[1:].isdigit():
+            vlanlist = vlan[1:].split(':')
+            for vl in vlanlist:
+              if not vl.isdigit():
+                raise errors.OpPrereqError("Specified VLAN parameter is "
+                                           "invalid : %s" % vlan,
+                                             errors.ECODE_INVAL)
+        elif vlan[0] == ":":
+          # Trunk - tagged only
+          vlanlist = vlan[1:].split(':')
+          for vl in vlanlist:
+            if not vl.isdigit():
+              raise errors.OpPrereqError("Specified VLAN parameter is invalid"
+                                           " : %s" % vlan, errors.ECODE_INVAL)
+        elif vlan.isdigit():
+          # This is the simplest case. No dots, only single digit
+          # -> Create untagged access port, dot needs to be added
+          nic[constants.INIC_VLAN] = "." + vlan
+        else:
+          raise errors.OpPrereqError("Specified VLAN parameter is invalid"
+                                       " : %s" % vlan, errors.ECODE_INVAL)
+
   def CheckArguments(self):
     """Check arguments.
 
@@ -403,7 +446,10 @@
     # check that NIC's parameters names are unique and valid
     utils.ValidateDeviceNames("NIC", self.op.nics)
 
+    self._CheckVLANArguments()
+
     self._CheckDiskArguments()
+    assert self.op.disk_template is not None
 
     # instance name verification
     if self.op.name_check:
@@ -441,8 +487,6 @@
 
     _CheckOpportunisticLocking(self.op)
 
-    self._cds = GetClusterDomainSecret()
-
     if self.op.mode == constants.INSTANCE_IMPORT:
       # On import force_variant must be True, because if we forced it at
       # initial install, our only chance when importing it back is that it
@@ -460,11 +504,9 @@
         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
                                    " installation" % self.op.os_type,
                                    errors.ECODE_STATE)
-      if self.op.disk_template is None:
-        raise errors.OpPrereqError("No disk template specified",
-                                   errors.ECODE_INVAL)
-
     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
+      self._cds = GetClusterDomainSecret()
+
       # Check handshake to ensure both clusters have the same domain secret
       src_handshake = self.op.source_handshake
       if not src_handshake:
@@ -535,6 +577,7 @@
 
       if self.op.opportunistic_locking:
         self.opportunistic_locks[locking.LEVEL_NODE] = True
+        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
     else:
       (self.op.pnode_uuid, self.op.pnode) = \
         ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
@@ -589,14 +632,6 @@
           self.needed_locks[locking.LEVEL_NODE]))
     self.share_locks[locking.LEVEL_NODEGROUP] = 1
 
-  def DeclareLocks(self, level):
-    if level == locking.LEVEL_NODE_RES and \
-      self.opportunistic_locks[locking.LEVEL_NODE]:
-      # Even when using opportunistic locking, we require the same set of
-      # NODE_RES locks as we got NODE locks
-      self.needed_locks[locking.LEVEL_NODE_RES] = \
-        self.owned_locks(locking.LEVEL_NODE)
-
   def _RunAllocator(self):
     """Run the allocator based on input opcode.
 
@@ -604,12 +639,11 @@
     if self.op.opportunistic_locking:
       # Only consider nodes for which a lock is held
       node_name_whitelist = self.cfg.GetNodeNames(
-        self.owned_locks(locking.LEVEL_NODE))
+        set(self.owned_locks(locking.LEVEL_NODE)) &
+        set(self.owned_locks(locking.LEVEL_NODE_RES)))
     else:
       node_name_whitelist = None
 
-    #TODO Export network to iallocator so that it chooses a pnode
-    #     in a nodegroup that has the desired network connected to
     req = _CreateInstanceAllocRequest(self.op, self.disks,
                                       self.nics, self.be_full,
                                       node_name_whitelist)
@@ -1043,7 +1077,7 @@
         netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.uuid)
         if netparams is None:
           raise errors.OpPrereqError("No netparams found for network"
-                                     " %s. Propably not connected to"
+                                     " %s. Probably not connected to"
                                      " node's %s nodegroup" %
                                      (nobj.name, self.pnode.name),
                                      errors.ECODE_INVAL)
@@ -1061,7 +1095,8 @@
             self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
           else:
             try:
-              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId())
+              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId(),
+                                 check=self.op.conflicts_check)
             except errors.ReservationError:
               raise errors.OpPrereqError("IP address %s already in use"
                                          " or does not belong to network %s" %
@@ -1112,7 +1147,7 @@
       elif self.op.disk_template == constants.DT_EXT:
         # FIXME: Function that checks prereqs if needed
         pass
-      elif self.op.disk_template in utils.GetLvmDiskTemplates():
+      elif self.op.disk_template in constants.DTS_LVM:
         # Check lv size requirements, if not adopting
         req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
         CheckNodesFreeDiskPerVG(self, node_uuids, req_sizes)
@@ -1137,11 +1172,13 @@
                                      lv_name, errors.ECODE_NOTUNIQUE)
 
       vg_names = self.rpc.call_vg_list([pnode.uuid])[pnode.uuid]
-      vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
+      vg_names.Raise("Cannot get VG information from node %s" % pnode.name,
+                     prereq=True)
 
       node_lvs = self.rpc.call_lv_list([pnode.uuid],
                                        vg_names.payload.keys())[pnode.uuid]
-      node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
+      node_lvs.Raise("Cannot get LV information from node %s" % pnode.name,
+                     prereq=True)
       node_lvs = node_lvs.payload
 
       delta = all_lvs.difference(node_lvs.keys())
@@ -1179,7 +1216,7 @@
       node_disks = self.rpc.call_bdev_sizes([pnode.uuid],
                                             list(all_disks))[pnode.uuid]
       node_disks.Raise("Cannot get block device information from node %s" %
-                       pnode.name)
+                       pnode.name, prereq=True)
       node_disks = node_disks.payload
       delta = all_disks.difference(node_disks.keys())
       if delta:
@@ -1190,6 +1227,22 @@
         dsk[constants.IDISK_SIZE] = \
           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
 
+    # Check disk access param to be compatible with specified hypervisor
+    node_info = self.cfg.GetNodeInfo(self.op.pnode_uuid)
+    node_group = self.cfg.GetNodeGroup(node_info.group)
+    disk_params = self.cfg.GetGroupDiskParams(node_group)
+    access_type = disk_params[self.op.disk_template].get(
+      constants.RBD_ACCESS, constants.DISK_KERNELSPACE
+    )
+
+    if not IsValidDiskAccessModeCombination(self.op.hypervisor,
+                                            self.op.disk_template,
+                                            access_type):
+      raise errors.OpPrereqError("Selected hypervisor (%s) cannot be"
+                                 " used with %s disk access param" %
+                                 (self.op.hypervisor, access_type),
+                                  errors.ECODE_STATE)
+
     # Verify instance specs
     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
     ispec = {
@@ -1294,7 +1347,6 @@
         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
           rename_to.append(t_dsk.logical_id)
           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
-          self.cfg.SetDiskID(t_dsk, self.pnode.uuid)
         result = self.rpc.call_blockdev_rename(self.pnode.uuid,
                                                zip(tmp_disks, rename_to))
         result.Raise("Failed to rename adoped LVs")
@@ -1304,7 +1356,7 @@
         CreateDisks(self, iobj)
       except errors.OpExecError:
         self.LogWarning("Device creation failed")
-        self.cfg.ReleaseDRBDMinors(self.op.instance_name)
+        self.cfg.ReleaseDRBDMinors(instance_uuid)
         raise
 
     feedback_fn("adding instance %s to cluster config" % self.op.instance_name)
@@ -1359,11 +1411,6 @@
     ReleaseLocks(self, locking.LEVEL_NODE_RES)
 
     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
-      # we need to set the disks ID to the primary node, since the
-      # preceding code might or might have not done it, depending on
-      # disk template and other options
-      for disk in iobj.disks:
-        self.cfg.SetDiskID(disk, self.pnode.uuid)
       if self.op.mode == constants.INSTANCE_CREATE:
         if not self.op.no_install:
           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
@@ -1411,7 +1458,7 @@
             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
                                                constants.IEIO_FILE, (image, ),
                                                constants.IEIO_SCRIPT,
-                                               (iobj.disks[idx], idx),
+                                               ((iobj.disks[idx], iobj), idx),
                                                None)
             transfers.append(dt)
 
@@ -1595,8 +1642,8 @@
     info = GetInstanceInfoText(renamed_inst)
     for (idx, disk) in enumerate(renamed_inst.disks):
       for node_uuid in renamed_inst.all_nodes:
-        self.cfg.SetDiskID(disk, node_uuid)
-        result = self.rpc.call_blockdev_setinfo(node_uuid, disk, info)
+        result = self.rpc.call_blockdev_setinfo(node_uuid,
+                                                (disk, renamed_inst), info)
         result.Warn("Error setting info on node %s for disk %s" %
                     (self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
     try:
@@ -1846,7 +1893,7 @@
                         idx, result.fail_msg)
         errs.append(result.fail_msg)
         break
-      dev_path = result.payload
+      dev_path, _, __ = result.payload
       result = self.rpc.call_blockdev_export(source_node.uuid, (disk,
                                                                 self.instance),
                                              target_node.secondary_ip,
@@ -1954,6 +2001,7 @@
 
       if self.op.opportunistic_locking:
         self.opportunistic_locks[locking.LEVEL_NODE] = True
+        self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
     else:
       nodeslist = []
       for inst in self.op.instances:
@@ -1970,14 +2018,6 @@
       # prevent accidential modification)
       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
 
-  def DeclareLocks(self, level):
-    if level == locking.LEVEL_NODE_RES and \
-      self.opportunistic_locks[locking.LEVEL_NODE]:
-      # Even when using opportunistic locking, we require the same set of
-      # NODE_RES locks as we got NODE locks
-      self.needed_locks[locking.LEVEL_NODE_RES] = \
-        self.owned_locks(locking.LEVEL_NODE)
-
   def CheckPrereq(self):
     """Check prerequisite.
 
@@ -1990,7 +2030,8 @@
       if self.op.opportunistic_locking:
         # Only consider nodes for which a lock is held
         node_whitelist = self.cfg.GetNodeNames(
-                           list(self.owned_locks(locking.LEVEL_NODE)))
+          set(self.owned_locks(locking.LEVEL_NODE)) &
+          set(self.owned_locks(locking.LEVEL_NODE_RES)))
       else:
         node_whitelist = None
 
@@ -2031,8 +2072,8 @@
       failed_insts = []
 
     return {
-      opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: allocatable_insts,
-      opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed_insts,
+      constants.ALLOCATABLE_KEY: allocatable_insts,
+      constants.FAILED_KEY: failed_insts,
       }
 
   def Exec(self, feedback_fn):
@@ -2053,19 +2094,19 @@
           (op.snode_uuid, op.snode) = \
             ExpandNodeUuidAndName(self.cfg, None, node_names[1])
 
-          jobs.append([op])
+        jobs.append([op])
 
-        missing = set(op2inst.keys()) - set(failed)
-        assert not missing, \
-          "Iallocator did return incomplete result: %s" % \
-          utils.CommaJoin(missing)
+      missing = set(op2inst.keys()) - set(failed)
+      assert not missing, \
+        "Iallocator did return incomplete result: %s" % \
+        utils.CommaJoin(missing)
     else:
       jobs.extend([op] for op in self.op.instances)
 
     return ResultWithJobs(jobs, **self._ConstructPartialResult())
 
 
-class _InstNicModPrivate:
+class _InstNicModPrivate(object):
   """Data structure for network interface modifications.
 
   Used by L{LUInstanceSetParams}.
@@ -2171,7 +2212,8 @@
 
 
 def _ApplyContainerMods(kind, container, chgdesc, mods,
-                        create_fn, modify_fn, remove_fn):
+                        create_fn, modify_fn, remove_fn,
+                        post_add_fn=None):
   """Applies descriptions in C{mods} to C{container}.
 
   @type kind: string
@@ -2195,6 +2237,10 @@
   @type remove_fn: callable
   @param remove_fn: Callback on removing item; receives absolute item index,
     item and private data object as added by L{_PrepareContainerMods}
+  @type post_add_fn: callable
+  @param post_add_fn: Callable for post-processing a newly created item after
+    it has been put into the container. It receives the index of the new item
+    and the new item as parameters.
 
   """
   for (op, identifier, params, private) in mods:
@@ -2231,6 +2277,10 @@
         assert idx <= len(container)
         # list.insert does so before the specified index
         container.insert(idx, item)
+
+      if post_add_fn is not None:
+        post_add_fn(addidx, item)
+
     else:
       # Retrieve existing item
       (absidx, item) = GetItemFromContainer(identifier, kind, container)
@@ -2238,11 +2288,13 @@
       if op == constants.DDM_REMOVE:
         assert not params
 
-        if remove_fn is not None:
-          remove_fn(absidx, item, private)
-
         changes = [("%s/%s" % (kind, absidx), "remove")]
 
+        if remove_fn is not None:
+          msg = remove_fn(absidx, item, private)
+          if msg:
+            changes.append(("%s/%s" % (kind, absidx), msg))
+
         assert container[absidx] == item
         del container[absidx]
       elif op == constants.DDM_MODIFY:
@@ -2339,12 +2391,7 @@
       if size is None:
         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
-
-      try:
-        size = int(size)
-      except (TypeError, ValueError), err:
-        raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
-                                   errors.ECODE_INVAL)
+      size = int(size)
 
       params[constants.IDISK_SIZE] = size
       name = params.get(constants.IDISK_NAME, None)
@@ -2430,9 +2477,9 @@
                            "hypervisor", "instance", "cluster")
 
     self.op.disks = self._UpgradeDiskNicMods(
-      "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
+      "disk", self.op.disks, ht.TSetParamsMods(ht.TIDiskParams))
     self.op.nics = self._UpgradeDiskNicMods(
-      "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
+      "NIC", self.op.nics, ht.TSetParamsMods(ht.TINicParams))
 
     if self.op.disks and self.op.disk_template is not None:
       raise errors.OpPrereqError("Disk template conversion and other disk"
@@ -2638,7 +2685,8 @@
         # Reserve new IP if in the new network if any
         elif new_net_uuid:
           try:
-            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
+            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId(),
+                               check=self.op.conflicts_check)
             self.LogInfo("Reserving IP %s in network %s",
                          new_ip, new_net_obj.name)
           except errors.ReservationError:
@@ -2780,6 +2828,14 @@
                                       constants.DT_EXT),
                                      errors.ECODE_INVAL)
 
+    if not self.op.wait_for_sync and not self.instance.disks_active:
+      for mod in self.diskmod:
+        if mod[0] == constants.DDM_ADD:
+          raise errors.OpPrereqError("Can't add a disk to an instance with"
+                                     " deactivated disks and"
+                                     " --no-wait-for-sync given.",
+                                     errors.ECODE_INVAL)
+
     if self.op.disks and self.instance.disk_template == constants.DT_DISKLESS:
       raise errors.OpPrereqError("Disk operations not supported for"
                                  " diskless instances", errors.ECODE_INVAL)
@@ -2802,9 +2858,14 @@
     ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
     ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
 
-    if self.op.offline is not None and self.op.offline:
+    # either --online or --offline was passed
+    if self.op.offline is not None:
+      if self.op.offline:
+        msg = "can't change to offline without being down first"
+      else:
+        msg = "can't change to online (down) without being offline first"
       CheckInstanceState(self, self.instance, CAN_CHANGE_INSTANCE_OFFLINE,
-                         msg="can't change to offline")
+                         msg=msg)
 
   def CheckPrereq(self):
     """Check prerequisites.
@@ -2815,6 +2876,7 @@
     assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
     self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     self.cluster = self.cfg.GetClusterInfo()
+    cluster_hvparams = self.cluster.hvparams[self.instance.hypervisor]
 
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
@@ -2828,7 +2890,7 @@
       # verify that the instance is not up
       instance_info = self.rpc.call_instance_info(
           pnode_uuid, self.instance.name, self.instance.hypervisor,
-          self.instance.hvparams)
+          cluster_hvparams)
       if instance_info.fail_msg:
         self.warn.append("Can't get instance runtime information: %s" %
                          instance_info.fail_msg)
@@ -2848,6 +2910,20 @@
     # dictionary with instance information after the modification
     ispec = {}
 
+    if self.op.hotplug or self.op.hotplug_if_possible:
+      result = self.rpc.call_hotplug_supported(self.instance.primary_node,
+                                               self.instance)
+      if result.fail_msg:
+        if self.op.hotplug:
+          result.Raise("Hotplug is not possible: %s" % result.fail_msg,
+                       prereq=True)
+        else:
+          self.LogWarning(result.fail_msg)
+          self.op.hotplug = False
+          self.LogInfo("Modification will take place without hotplugging.")
+      else:
+        self.op.hotplug = True
+
     # Prepare NIC modifications
     self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
 
@@ -2947,9 +3023,9 @@
         mem_check_list.extend(self.instance.secondary_nodes)
       instance_info = self.rpc.call_instance_info(
           pnode_uuid, self.instance.name, self.instance.hypervisor,
-          self.instance.hvparams)
+          cluster_hvparams)
       hvspecs = [(self.instance.hypervisor,
-                  self.cluster.hvparams[self.instance.hypervisor])]
+                  cluster_hvparams)]
       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
                                          hvspecs)
       pninfo = nodeinfo[pnode_uuid]
@@ -3010,9 +3086,10 @@
       remote_info = self.rpc.call_instance_info(
          self.instance.primary_node, self.instance.name,
          self.instance.hypervisor,
-         self.cluster.hvparams[self.instance.hypervisor])
+         cluster_hvparams)
       remote_info.Raise("Error checking node %s" %
-                        self.cfg.GetNodeName(self.instance.primary_node))
+                        self.cfg.GetNodeName(self.instance.primary_node),
+                        prereq=True)
       if not remote_info.payload: # not running already
         raise errors.OpPrereqError("Instance %s is not running" %
                                    self.instance.name, errors.ECODE_STATE)
@@ -3071,7 +3148,8 @@
       # Operate on copies as this is still in prereq
       nics = [nic.Copy() for nic in self.instance.nics]
       _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
-                          self._CreateNewNic, self._ApplyNicMods, None)
+                          self._CreateNewNic, self._ApplyNicMods,
+                          self._RemoveNic)
       # Verify that NIC names are unique and valid
       utils.ValidateDeviceNames("NIC", nics)
       self._new_nics = nics
@@ -3133,8 +3211,7 @@
                                      self.instance.uuid, pnode_uuid,
                                      [snode_uuid], disk_info, None, None, 0,
                                      feedback_fn, self.diskparams)
-    anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
-                                        self.diskparams)
+    anno_disks = rpc.AnnotateDiskParams(new_disks, self.diskparams)
     p_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, pnode_uuid)
     s_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, snode_uuid)
     info = GetInstanceInfoText(self.instance)
@@ -3167,8 +3244,6 @@
     except errors.GenericError, e:
       feedback_fn("Initializing of DRBD devices failed;"
                   " renaming back original volumes...")
-      for disk in new_disks:
-        self.cfg.SetDiskID(disk, pnode_uuid)
       rename_back_list = [(n.children[0], o.logical_id)
                           for (n, o) in zip(new_disks, self.instance.disks)]
       result = self.rpc.call_blockdev_rename(pnode_uuid, rename_back_list)
@@ -3196,11 +3271,16 @@
     """Converts an instance from drbd to plain.
 
     """
-    assert len(self.instance.secondary_nodes) == 1
     assert self.instance.disk_template == constants.DT_DRBD8
+    assert len(self.instance.secondary_nodes) == 1 or not self.instance.disks
 
     pnode_uuid = self.instance.primary_node
-    snode_uuid = self.instance.secondary_nodes[0]
+
+    # it will not be possible to calculate the snode_uuid later
+    snode_uuid = None
+    if self.instance.secondary_nodes:
+      snode_uuid = self.instance.secondary_nodes[0]
+
     feedback_fn("Converting template to plain")
 
     old_disks = AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
@@ -3229,22 +3309,36 @@
 
     feedback_fn("Removing volumes on the secondary node...")
     for disk in old_disks:
-      self.cfg.SetDiskID(disk, snode_uuid)
-      msg = self.rpc.call_blockdev_remove(snode_uuid, disk).fail_msg
-      if msg:
-        self.LogWarning("Could not remove block device %s on node %s,"
-                        " continuing anyway: %s", disk.iv_name,
-                        self.cfg.GetNodeName(snode_uuid), msg)
+      result = self.rpc.call_blockdev_remove(snode_uuid, (disk, self.instance))
+      result.Warn("Could not remove block device %s on node %s,"
+                  " continuing anyway" %
+                  (disk.iv_name, self.cfg.GetNodeName(snode_uuid)),
+                  self.LogWarning)
 
     feedback_fn("Removing unneeded volumes on the primary node...")
     for idx, disk in enumerate(old_disks):
       meta = disk.children[1]
-      self.cfg.SetDiskID(meta, pnode_uuid)
-      msg = self.rpc.call_blockdev_remove(pnode_uuid, meta).fail_msg
-      if msg:
-        self.LogWarning("Could not remove metadata for disk %d on node %s,"
-                        " continuing anyway: %s", idx,
-                        self.cfg.GetNodeName(pnode_uuid), msg)
+      result = self.rpc.call_blockdev_remove(pnode_uuid, (meta, self.instance))
+      result.Warn("Could not remove metadata for disk %d on node %s,"
+                  " continuing anyway" %
+                  (idx, self.cfg.GetNodeName(pnode_uuid)),
+                  self.LogWarning)
+
+  def _HotplugDevice(self, action, dev_type, device, extra, seq):
+    self.LogInfo("Trying to hotplug device...")
+    msg = "hotplug:"
+    result = self.rpc.call_hotplug_device(self.instance.primary_node,
+                                          self.instance, action, dev_type,
+                                          (device, self.instance),
+                                          extra, seq)
+    if result.fail_msg:
+      self.LogWarning("Could not hotplug device: %s" % result.fail_msg)
+      self.LogInfo("Continuing execution..")
+      msg += "failed"
+    else:
+      self.LogInfo("Hotplug done.")
+      msg += "done"
+    return msg
 
   def _CreateNewDisk(self, idx, params, _):
     """Creates a new disk.
@@ -3271,9 +3365,37 @@
                          disks=[(idx, disk, 0)],
                          cleanup=new_disks)
 
-    return (disk, [
-      ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
-      ])
+    changes = [
+      ("disk/%d" % idx,
+       "add:size=%s,mode=%s" % (disk.size, disk.mode)),
+      ]
+    if self.op.hotplug:
+      result = self.rpc.call_blockdev_assemble(self.instance.primary_node,
+                                               (disk, self.instance),
+                                               self.instance, True, idx)
+      if result.fail_msg:
+        changes.append(("disk/%d" % idx, "assemble:failed"))
+        self.LogWarning("Can't assemble newly created disk %d: %s",
+                        idx, result.fail_msg)
+      else:
+        _, link_name, uri = result.payload
+        msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
+                                  constants.HOTPLUG_TARGET_DISK,
+                                  disk, (link_name, uri), idx)
+        changes.append(("disk/%d" % idx, msg))
+
+    return (disk, changes)
+
+  def _PostAddDisk(self, _, disk):
+    if not WaitForSync(self, self.instance, disks=[disk],
+                       oneshot=not self.op.wait_for_sync):
+      raise errors.OpExecError("Failed to sync disks of %s" %
+                               self.instance.name)
+
+    # the disk is active at this point, so deactivate it if the instance disks
+    # are supposed to be inactive
+    if not self.instance.disks_active:
+      ShutdownInstanceDisks(self, self.instance, disks=[disk])
 
   def _ModifyDisk(self, idx, disk, params, _):
     """Modifies a disk.
@@ -3308,11 +3430,18 @@
     """Removes a disk.
 
     """
+    hotmsg = ""
+    if self.op.hotplug:
+      hotmsg = self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
+                                   constants.HOTPLUG_TARGET_DISK,
+                                   root, None, idx)
+      ShutdownInstanceDisks(self, self.instance, [root])
+
     (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
     for node_uuid, disk in anno_disk.ComputeNodeTree(
                              self.instance.primary_node):
-      self.cfg.SetDiskID(disk, node_uuid)
-      msg = self.rpc.call_blockdev_remove(node_uuid, disk).fail_msg
+      msg = self.rpc.call_blockdev_remove(node_uuid, (disk, self.instance)) \
+              .fail_msg
       if msg:
         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
                         " continuing anyway", idx,
@@ -3322,6 +3451,8 @@
     if root.dev_type in constants.DTS_DRBD:
       self.cfg.AddTcpUdpPort(root.logical_id[2])
 
+    return hotmsg
+
   def _CreateNewNic(self, idx, params, private):
     """Creates data structure for a new network interface.
 
@@ -3337,13 +3468,20 @@
                        nicparams=nicparams)
     nobj.uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
 
-    return (nobj, [
+    changes = [
       ("nic.%d" % idx,
        "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
        (mac, ip, private.filled[constants.NIC_MODE],
-       private.filled[constants.NIC_LINK],
-       net)),
-      ])
+       private.filled[constants.NIC_LINK], net)),
+      ]
+
+    if self.op.hotplug:
+      msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
+                                constants.HOTPLUG_TARGET_NIC,
+                                nobj, None, idx)
+      changes.append(("nic.%d" % idx, msg))
+
+    return (nobj, changes)
 
   def _ApplyNicMods(self, idx, nic, params, private):
     """Modifies a network interface.
@@ -3368,8 +3506,20 @@
       for (key, val) in nic.nicparams.items():
         changes.append(("nic.%s/%d" % (key, idx), val))
 
+    if self.op.hotplug:
+      msg = self._HotplugDevice(constants.HOTPLUG_ACTION_MODIFY,
+                                constants.HOTPLUG_TARGET_NIC,
+                                nic, None, idx)
+      changes.append(("nic/%d" % idx, msg))
+
     return changes
 
+  def _RemoveNic(self, idx, nic, _):
+    if self.op.hotplug:
+      return self._HotplugDevice(constants.HOTPLUG_ACTION_REMOVE,
+                                 constants.HOTPLUG_TARGET_NIC,
+                                 nic, None, idx)
+
   def Exec(self, feedback_fn):
     """Modifies an instance.
 
@@ -3403,7 +3553,7 @@
     # Apply disk changes
     _ApplyContainerMods("disk", self.instance.disks, result, self.diskmod,
                         self._CreateNewDisk, self._ModifyDisk,
-                        self._RemoveDisk)
+                        self._RemoveDisk, post_add_fn=self._PostAddDisk)
     _UpdateIvNames(0, self.instance.disks)
 
     if self.op.disk_template:
diff --git a/lib/cmdlib/instance_migration.py b/lib/cmdlib/instance_migration.py
index 7d45b4f..5eb7a36 100644
--- a/lib/cmdlib/instance_migration.py
+++ b/lib/cmdlib/instance_migration.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with instance migration an failover."""
@@ -136,11 +145,12 @@
     """
     instance = self._migrater.instance
     source_node_uuid = instance.primary_node
+    target_node_uuid = self._migrater.target_node_uuid
     env = {
       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
       "OLD_PRIMARY": self.cfg.GetNodeName(source_node_uuid),
-      "NEW_PRIMARY": self.op.target_node,
+      "NEW_PRIMARY": self.cfg.GetNodeName(target_node_uuid),
       "FAILOVER_CLEANUP": self.op.cleanup,
       }
 
@@ -160,6 +170,7 @@
     """
     instance = self._migrater.instance
     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
+    nl.append(self._migrater.target_node_uuid)
     return (nl, nl + [instance.primary_node])
 
 
@@ -198,12 +209,13 @@
     """
     instance = self._migrater.instance
     source_node_uuid = instance.primary_node
+    target_node_uuid = self._migrater.target_node_uuid
     env = BuildInstanceHookEnvByObject(self, instance)
     env.update({
       "MIGRATE_LIVE": self._migrater.live,
       "MIGRATE_CLEANUP": self.op.cleanup,
       "OLD_PRIMARY": self.cfg.GetNodeName(source_node_uuid),
-      "NEW_PRIMARY": self.op.target_node,
+      "NEW_PRIMARY": self.cfg.GetNodeName(target_node_uuid),
       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
       })
 
@@ -211,7 +223,7 @@
       env["OLD_SECONDARY"] = self.cfg.GetNodeName(instance.secondary_nodes[0])
       env["NEW_SECONDARY"] = self.cfg.GetNodeName(source_node_uuid)
     else:
-      env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
+      env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
 
     return env
 
@@ -222,6 +234,7 @@
     instance = self._migrater.instance
     snode_uuids = list(instance.secondary_nodes)
     nl = [self.cfg.GetMasterNode(), instance.primary_node] + snode_uuids
+    nl.append(self._migrater.target_node_uuid)
     return (nl, nl)
 
 
@@ -350,7 +363,7 @@
         raise errors.ConfigurationError("No secondary node but using"
                                         " %s disk template" %
                                         self.instance.disk_template)
-      target_node_uuid = secondary_node_uuids[0]
+      self.target_node_uuid = target_node_uuid = secondary_node_uuids[0]
       if self.lu.op.iallocator or \
         (self.lu.op.target_node_uuid and
          self.lu.op.target_node_uuid != target_node_uuid):
@@ -440,7 +453,8 @@
           self.instance.primary_node, self.instance.name,
           self.instance.hypervisor, cluster.hvparams[self.instance.hypervisor])
       remote_info.Raise("Error checking instance on node %s" %
-                        self.cfg.GetNodeName(self.instance.primary_node))
+                        self.cfg.GetNodeName(self.instance.primary_node),
+                        prereq=True)
       instance_running = bool(remote_info.payload)
       if instance_running:
         self.current_mem = int(remote_info.payload["memory"])
@@ -480,7 +494,6 @@
     while not all_done:
       all_done = True
       result = self.rpc.call_drbd_wait_sync(self.all_node_uuids,
-                                            self.nodes_ip,
                                             (self.instance.disks,
                                              self.instance))
       min_percent = 100
@@ -503,11 +516,8 @@
     self.feedback_fn("* switching node %s to secondary mode" %
                      self.cfg.GetNodeName(node_uuid))
 
-    for dev in self.instance.disks:
-      self.cfg.SetDiskID(dev, node_uuid)
-
     result = self.rpc.call_blockdev_close(node_uuid, self.instance.name,
-                                          self.instance.disks)
+                                          (self.instance.disks, self.instance))
     result.Raise("Cannot change disk to secondary on node %s" %
                  self.cfg.GetNodeName(node_uuid))
 
@@ -516,9 +526,8 @@
 
     """
     self.feedback_fn("* changing into standalone mode")
-    result = self.rpc.call_drbd_disconnect_net(self.all_node_uuids,
-                                               self.nodes_ip,
-                                               self.instance.disks)
+    result = self.rpc.call_drbd_disconnect_net(
+               self.all_node_uuids, (self.instance.disks, self.instance))
     for node_uuid, nres in result.items():
       nres.Raise("Cannot disconnect disks node %s" %
                  self.cfg.GetNodeName(node_uuid))
@@ -532,7 +541,7 @@
     else:
       msg = "single-master"
     self.feedback_fn("* changing disks into %s mode" % msg)
-    result = self.rpc.call_drbd_attach_net(self.all_node_uuids, self.nodes_ip,
+    result = self.rpc.call_drbd_attach_net(self.all_node_uuids,
                                            (self.instance.disks, self.instance),
                                            self.instance.name, multimaster)
     for node_uuid, nres in result.items():
@@ -721,9 +730,6 @@
 
     self.feedback_fn("* preparing %s to accept the instance" %
                      self.cfg.GetNodeName(self.target_node_uuid))
-    # This fills physical_id slot that may be missing on newly created disks
-    for disk in self.instance.disks:
-      self.cfg.SetDiskID(disk, self.target_node_uuid)
     result = self.rpc.call_accept_instance(self.target_node_uuid,
                                            self.instance,
                                            migration_info,
diff --git a/lib/cmdlib/instance_operation.py b/lib/cmdlib/instance_operation.py
index 41ccc38..13b5c69 100644
--- a/lib/cmdlib/instance_operation.py
+++ b/lib/cmdlib/instance_operation.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with instance operations (start/stop/...).
@@ -230,9 +239,10 @@
       assert self.op.ignore_offline_nodes
       self.LogInfo("Primary node offline, marked instance as stopped")
     else:
-      result = self.rpc.call_instance_shutdown(self.instance.primary_node,
-                                               self.instance,
-                                               self.op.timeout, self.op.reason)
+      result = self.rpc.call_instance_shutdown(
+        self.instance.primary_node,
+        self.instance,
+        self.op.timeout, self.op.reason)
       msg = result.fail_msg
       if msg:
         self.LogWarning("Could not shutdown instance: %s", msg)
@@ -393,8 +403,6 @@
     if instance_running and \
         self.op.reboot_type in [constants.INSTANCE_REBOOT_SOFT,
                                 constants.INSTANCE_REBOOT_HARD]:
-      for disk in self.instance.disks:
-        self.cfg.SetDiskID(disk, current_node_uuid)
       result = self.rpc.call_instance_reboot(current_node_uuid, self.instance,
                                              self.op.reboot_type,
                                              self.op.shutdown_timeout,
diff --git a/lib/cmdlib/instance_query.py b/lib/cmdlib/instance_query.py
index ee2dcad..9547a1c 100644
--- a/lib/cmdlib/instance_query.py
+++ b/lib/cmdlib/instance_query.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units for querying instances."""
@@ -137,8 +146,8 @@
             if inst_name in insts_by_name:
               instance = insts_by_name[inst_name]
               if instance.primary_node == node_uuid:
-                for iname in result.payload:
-                  live_data[insts_by_name[iname].uuid] = result.payload[iname]
+                live_data[insts_by_name[inst_name].uuid] = \
+                  result.payload[inst_name]
               else:
                 wrongnode_inst_uuids.add(instance.uuid)
             else:
@@ -308,9 +317,7 @@
     if self.op.static or not node_uuid:
       return None
 
-    self.cfg.SetDiskID(dev, node_uuid)
-
-    result = self.rpc.call_blockdev_find(node_uuid, dev)
+    result = self.rpc.call_blockdev_find(node_uuid, (dev, instance))
     if result.offline:
       return None
 
@@ -342,23 +349,25 @@
     """
     drbd_info = None
     output_logical_id = dev.logical_id
-    output_physical_id = dev.physical_id
     if dev.dev_type in constants.DTS_DRBD:
       # we change the snode then (otherwise we use the one passed in)
       if dev.logical_id[0] == instance.primary_node:
         snode_uuid = dev.logical_id[1]
+        snode_minor = dev.logical_id[4]
+        pnode_minor = dev.logical_id[3]
       else:
         snode_uuid = dev.logical_id[0]
+        snode_minor = dev.logical_id[3]
+        pnode_minor = dev.logical_id[4]
       drbd_info = {
         "primary_node": node_uuid2name_fn(instance.primary_node),
-        "primary_minor": dev.logical_id[3],
+        "primary_minor": pnode_minor,
         "secondary_node": node_uuid2name_fn(snode_uuid),
-        "secondary_minor": dev.logical_id[4],
+        "secondary_minor": snode_minor,
         "port": dev.logical_id[2],
       }
       # replace the secret present at the end of the ids with None
       output_logical_id = dev.logical_id[:-1] + (None,)
-      output_physical_id = dev.physical_id[:-1] + (None,)
 
     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
                                               instance, dev)
@@ -377,7 +386,6 @@
       "dev_type": dev.dev_type,
       "logical_id": output_logical_id,
       "drbd_info": drbd_info,
-      "physical_id": output_physical_id,
       "pstatus": dev_pstatus,
       "sstatus": dev_sstatus,
       "children": dev_children,
diff --git a/lib/cmdlib/instance_storage.py b/lib/cmdlib/instance_storage.py
index 9ce9ba8..fe3f23a 100644
--- a/lib/cmdlib/instance_storage.py
+++ b/lib/cmdlib/instance_storage.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with storage of instances."""
@@ -81,16 +90,13 @@
   @param excl_stor: Whether exclusive_storage is active for the node
 
   """
-  lu.cfg.SetDiskID(device, node_uuid)
-  result = lu.rpc.call_blockdev_create(node_uuid, device, device.size,
-                                       instance.name, force_open, info,
-                                       excl_stor)
+  result = lu.rpc.call_blockdev_create(node_uuid, (device, instance),
+                                       device.size, instance.name, force_open,
+                                       info, excl_stor)
   result.Raise("Can't create block device %s on"
                " node %s for instance %s" % (device,
                                              lu.cfg.GetNodeName(node_uuid),
                                              instance.name))
-  if device.physical_id is None:
-    device.physical_id = result.payload
 
 
 def _CreateBlockDevInner(lu, node_uuid, instance, device, force_create,
@@ -186,7 +192,7 @@
                               force_open, excl_stor)
 
 
-def _UndoCreateDisks(lu, disks_created):
+def _UndoCreateDisks(lu, disks_created, instance):
   """Undo the work performed by L{CreateDisks}.
 
   This function is called in case of an error to undo the work of
@@ -195,11 +201,12 @@
   @type lu: L{LogicalUnit}
   @param lu: the logical unit on whose behalf we execute
   @param disks_created: the result returned by L{CreateDisks}
+  @type instance: L{objects.Instance}
+  @param instance: the instance for which disks were created
 
   """
   for (node_uuid, disk) in disks_created:
-    lu.cfg.SetDiskID(disk, node_uuid)
-    result = lu.rpc.call_blockdev_remove(node_uuid, disk)
+    result = lu.rpc.call_blockdev_remove(node_uuid, (disk, instance))
     result.Warn("Failed to remove newly-created disk %s on node %s" %
                 (disk, lu.cfg.GetNodeName(node_uuid)), logging.warning)
 
@@ -261,7 +268,7 @@
         logging.warning("Creating disk %s for instance '%s' failed",
                         idx, instance.name)
         disks_created.extend(e.created_devices)
-        _UndoCreateDisks(lu, disks_created)
+        _UndoCreateDisks(lu, disks_created, instance)
         raise errors.OpExecError(e.message)
   return disks_created
 
@@ -936,9 +943,9 @@
 
   """
   nodeinfo = _PerformNodeInfoCall(lu, node_uuids, vg)
-  for node in node_uuids:
-    node_name = lu.cfg.GetNodeName(node)
-    info = nodeinfo[node]
+  for node_uuid in node_uuids:
+    node_name = lu.cfg.GetNodeName(node_uuid)
+    info = nodeinfo[node_uuid]
     info.Raise("Cannot get current information from node %s" % node_name,
                prereq=True, ecode=errors.ECODE_ENVIRON)
     _CheckVgCapacityForNode(node_name, info.payload, vg, requested)
@@ -1016,9 +1023,6 @@
     disks = [(idx, disk, 0)
              for (idx, disk) in enumerate(instance.disks)]
 
-  for (_, device, _) in disks:
-    lu.cfg.SetDiskID(device, node_uuid)
-
   logging.info("Pausing synchronization of disks of instance '%s'",
                instance.name)
   result = lu.rpc.call_blockdev_pause_resume_sync(node_uuid,
@@ -1112,7 +1116,7 @@
   except errors.OpExecError:
     logging.warning("Wiping disks for instance '%s' failed",
                     instance.name)
-    _UndoCreateDisks(lu, cleanup)
+    _UndoCreateDisks(lu, cleanup, instance)
     raise
 
 
@@ -1150,9 +1154,6 @@
   node_uuid = instance.primary_node
   node_name = lu.cfg.GetNodeName(node_uuid)
 
-  for dev in disks:
-    lu.cfg.SetDiskID(dev, node_uuid)
-
   # TODO: Convert to utils.Retry
 
   retries = 0
@@ -1189,6 +1190,7 @@
           max_time = mstat.estimated_time
         else:
           rem_time = "no time estimate"
+          max_time = 5 # sleep at least a bit between retries
         lu.LogInfo("- device %s: %5.2f%% done, %s",
                    disks[i].iv_name, mstat.sync_percent, rem_time)
 
@@ -1221,13 +1223,15 @@
   ignored.
 
   """
-  lu.cfg.MarkInstanceDisksInactive(instance.uuid)
   all_result = True
+
+  if disks is None:
+    # only mark instance disks as inactive if all disks are affected
+    lu.cfg.MarkInstanceDisksInactive(instance.uuid)
   disks = ExpandCheckDisks(instance, disks)
 
   for disk in disks:
     for node_uuid, top_disk in disk.ComputeNodeTree(instance.primary_node):
-      lu.cfg.SetDiskID(top_disk, node_uuid)
       result = lu.rpc.call_blockdev_shutdown(node_uuid, (top_disk, instance))
       msg = result.fail_msg
       if msg:
@@ -1251,7 +1255,7 @@
 
 
 def AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
-                           ignore_size=False):
+                          ignore_size=False):
   """Prepare the block devices for an instance.
 
   This sets up the block devices on all nodes.
@@ -1276,6 +1280,11 @@
   """
   device_info = []
   disks_ok = True
+
+  if disks is None:
+    # only mark instance disks as active if all disks are affected
+    lu.cfg.MarkInstanceDisksActive(instance.uuid)
+
   disks = ExpandCheckDisks(instance, disks)
 
   # With the two passes mechanism we try to reduce the window of
@@ -1287,10 +1296,6 @@
   # into any other network-connected state (Connected, SyncTarget,
   # SyncSource, etc.)
 
-  # mark instance disks as active before doing actual work, so watcher does
-  # not try to shut them down erroneously
-  lu.cfg.MarkInstanceDisksActive(instance.uuid)
-
   # 1st pass, assemble on all nodes in secondary mode
   for idx, inst_disk in enumerate(disks):
     for node_uuid, node_disk in inst_disk.ComputeNodeTree(
@@ -1298,9 +1303,8 @@
       if ignore_size:
         node_disk = node_disk.Copy()
         node_disk.UnsetSize()
-      lu.cfg.SetDiskID(node_disk, node_uuid)
       result = lu.rpc.call_blockdev_assemble(node_uuid, (node_disk, instance),
-                                             instance.name, False, idx)
+                                             instance, False, idx)
       msg = result.fail_msg
       if msg:
         is_offline_secondary = (node_uuid in instance.secondary_nodes and
@@ -1324,9 +1328,8 @@
       if ignore_size:
         node_disk = node_disk.Copy()
         node_disk.UnsetSize()
-      lu.cfg.SetDiskID(node_disk, node_uuid)
       result = lu.rpc.call_blockdev_assemble(node_uuid, (node_disk, instance),
-                                             instance.name, True, idx)
+                                             instance, True, idx)
       msg = result.fail_msg
       if msg:
         lu.LogWarning("Could not prepare block device %s on node %s"
@@ -1334,17 +1337,11 @@
                       inst_disk.iv_name, lu.cfg.GetNodeName(node_uuid), msg)
         disks_ok = False
       else:
-        dev_path = result.payload
+        dev_path, _, __ = result.payload
 
     device_info.append((lu.cfg.GetNodeName(instance.primary_node),
                         inst_disk.iv_name, dev_path))
 
-  # leave the disks configured for the primary node
-  # this is a workaround that would be fixed better by
-  # improving the logical/physical id handling
-  for disk in disks:
-    lu.cfg.SetDiskID(disk, instance.primary_node)
-
   if not disks_ok:
     lu.cfg.MarkInstanceDisksInactive(instance.uuid)
 
@@ -1481,7 +1478,6 @@
 
     # First run all grow ops in dry-run mode
     for node_uuid in self.instance.all_nodes:
-      self.cfg.SetDiskID(self.disk, node_uuid)
       result = self.rpc.call_blockdev_grow(node_uuid,
                                            (self.disk, self.instance),
                                            self.delta, True, True,
@@ -1491,9 +1487,8 @@
 
     if wipe_disks:
       # Get disk size from primary node for wiping
-      self.cfg.SetDiskID(self.disk, self.instance.primary_node)
-      result = self.rpc.call_blockdev_getdimensions(self.instance.primary_node,
-                                                    [self.disk])
+      result = self.rpc.call_blockdev_getdimensions(
+                 self.instance.primary_node, [([self.disk], self.instance)])
       result.Raise("Failed to retrieve disk size from node '%s'" %
                    self.instance.primary_node)
 
@@ -1515,7 +1510,6 @@
     # We know that (as far as we can test) operations across different
     # nodes will succeed, time to run it for real on the backing storage
     for node_uuid in self.instance.all_nodes:
-      self.cfg.SetDiskID(self.disk, node_uuid)
       result = self.rpc.call_blockdev_grow(node_uuid,
                                            (self.disk, self.instance),
                                            self.delta, False, True,
@@ -1525,7 +1519,6 @@
 
     # And now execute it for logical storage, on the primary node
     node_uuid = self.instance.primary_node
-    self.cfg.SetDiskID(self.disk, node_uuid)
     result = self.rpc.call_blockdev_grow(node_uuid, (self.disk, self.instance),
                                          self.delta, False, False,
                                          self.node_es_flags[node_uuid])
@@ -1798,12 +1791,10 @@
   the device(s)) to the ldisk (representing the local storage status).
 
   """
-  lu.cfg.SetDiskID(dev, node_uuid)
-
   result = True
 
   if on_primary or dev.AssembleOnSecondary():
-    rstats = lu.rpc.call_blockdev_find(node_uuid, dev)
+    rstats = lu.rpc.call_blockdev_find(node_uuid, (dev, instance))
     msg = rstats.fail_msg
     if msg:
       lu.LogWarning("Can't find disk on node %s: %s",
@@ -1846,7 +1837,7 @@
 
   """
   (disk,) = AnnotateDiskParams(instance, [dev], lu.cfg)
-  return lu.rpc.call_blockdev_find(node_uuid, disk)
+  return lu.rpc.call_blockdev_find(node_uuid, (disk, instance))
 
 
 def _GenerateUniqueNames(lu, exts):
@@ -1943,7 +1934,6 @@
       for node_uuid in node_uuids:
         self.lu.LogInfo("Checking disk/%d on %s", idx,
                         self.cfg.GetNodeName(node_uuid))
-        self.cfg.SetDiskID(dev, node_uuid)
 
         result = _BlockdevFind(self, node_uuid, dev, instance)
 
@@ -2140,7 +2130,7 @@
                 (utils.CommaJoin(self.disks), self.instance.name))
     feedback_fn("Current primary node: %s" %
                 self.cfg.GetNodeName(self.instance.primary_node))
-    feedback_fn("Current seconary node: %s" %
+    feedback_fn("Current secondary node: %s" %
                 utils.CommaJoin(self.cfg.GetNodeNames(
                                   self.instance.secondary_nodes)))
 
@@ -2203,7 +2193,6 @@
       for node_uuid in node_uuids:
         self.lu.LogInfo("Checking disk/%d on %s", idx,
                         self.cfg.GetNodeName(node_uuid))
-        self.cfg.SetDiskID(dev, node_uuid)
 
         result = _BlockdevFind(self, node_uuid, dev, self.instance)
 
@@ -2253,8 +2242,6 @@
       self.lu.LogInfo("Adding storage on %s for disk/%d",
                       self.cfg.GetNodeName(node_uuid), idx)
 
-      self.cfg.SetDiskID(dev, node_uuid)
-
       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
       names = _GenerateUniqueNames(self.lu, lv_names)
 
@@ -2287,8 +2274,6 @@
 
   def _CheckDevices(self, node_uuid, iv_names):
     for name, (dev, _, _) in iv_names.iteritems():
-      self.cfg.SetDiskID(dev, node_uuid)
-
       result = _BlockdevFind(self, node_uuid, dev, self.instance)
 
       msg = result.fail_msg
@@ -2306,9 +2291,8 @@
       self.lu.LogInfo("Remove logical volumes for %s", name)
 
       for lv in old_lvs:
-        self.cfg.SetDiskID(lv, node_uuid)
-
-        msg = self.rpc.call_blockdev_remove(node_uuid, lv).fail_msg
+        msg = self.rpc.call_blockdev_remove(node_uuid, (lv, self.instance)) \
+                .fail_msg
         if msg:
           self.lu.LogWarning("Can't remove old LV: %s", msg,
                              hint="remove unused LVs manually")
@@ -2357,8 +2341,9 @@
     for dev, old_lvs, new_lvs in iv_names.itervalues():
       self.lu.LogInfo("Detaching %s drbd from local storage", dev.iv_name)
 
-      result = self.rpc.call_blockdev_removechildren(self.target_node_uuid, dev,
-                                                     old_lvs)
+      result = self.rpc.call_blockdev_removechildren(self.target_node_uuid,
+                                                     (dev, self.instance),
+                                                     (old_lvs, self.instance))
       result.Raise("Can't detach drbd from local storage on node"
                    " %s for device %s" %
                    (self.cfg.GetNodeName(self.target_node_uuid), dev.iv_name))
@@ -2368,18 +2353,18 @@
       # ok, we created the new LVs, so now we know we have the needed
       # storage; as such, we proceed on the target node to rename
       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
-      # using the assumption that logical_id == physical_id (which in
-      # turn is the unique_id on that node)
+      # using the assumption that logical_id == unique_id on that node
 
       # FIXME(iustin): use a better name for the replaced LVs
       temp_suffix = int(time.time())
-      ren_fn = lambda d, suff: (d.physical_id[0],
-                                d.physical_id[1] + "_replaced-%s" % suff)
+      ren_fn = lambda d, suff: (d.logical_id[0],
+                                d.logical_id[1] + "_replaced-%s" % suff)
 
       # Build the rename list based on what LVs exist on the node
       rename_old_to_new = []
       for to_ren in old_lvs:
-        result = self.rpc.call_blockdev_find(self.target_node_uuid, to_ren)
+        result = self.rpc.call_blockdev_find(self.target_node_uuid,
+                                             (to_ren, self.instance))
         if not result.fail_msg and result.payload:
           # device exists
           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
@@ -2392,7 +2377,7 @@
 
       # Now we rename the new LVs to the old LVs
       self.lu.LogInfo("Renaming the new LVs on the target node")
-      rename_new_to_old = [(new, old.physical_id)
+      rename_new_to_old = [(new, old.logical_id)
                            for old, new in zip(old_lvs, new_lvs)]
       result = self.rpc.call_blockdev_rename(self.target_node_uuid,
                                              rename_new_to_old)
@@ -2402,25 +2387,24 @@
       # Intermediate steps of in memory modifications
       for old, new in zip(old_lvs, new_lvs):
         new.logical_id = old.logical_id
-        self.cfg.SetDiskID(new, self.target_node_uuid)
 
       # We need to modify old_lvs so that removal later removes the
       # right LVs, not the newly added ones; note that old_lvs is a
       # copy here
       for disk in old_lvs:
         disk.logical_id = ren_fn(disk, temp_suffix)
-        self.cfg.SetDiskID(disk, self.target_node_uuid)
 
       # Now that the new lvs have the old name, we can add them to the device
       self.lu.LogInfo("Adding new mirror component on %s",
                       self.cfg.GetNodeName(self.target_node_uuid))
       result = self.rpc.call_blockdev_addchildren(self.target_node_uuid,
-                                                  (dev, self.instance), new_lvs)
+                                                  (dev, self.instance),
+                                                  (new_lvs, self.instance))
       msg = result.fail_msg
       if msg:
         for new_lv in new_lvs:
           msg2 = self.rpc.call_blockdev_remove(self.target_node_uuid,
-                                               new_lv).fail_msg
+                                               (new_lv, self.instance)).fail_msg
           if msg2:
             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
                                hint=("cleanup manually the unused logical"
@@ -2559,7 +2543,6 @@
     # We have new devices, shutdown the drbd on the old secondary
     for idx, dev in enumerate(self.instance.disks):
       self.lu.LogInfo("Shutting down drbd for disk/%d on old node", idx)
-      self.cfg.SetDiskID(dev, self.target_node_uuid)
       msg = self.rpc.call_blockdev_shutdown(self.target_node_uuid,
                                             (dev, self.instance)).fail_msg
       if msg:
@@ -2569,8 +2552,8 @@
                                  " soon as possible"))
 
     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
-    result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
-                                               self.instance.disks)[pnode]
+    result = self.rpc.call_drbd_disconnect_net(
+               [pnode], (self.instance.disks, self.instance))[pnode]
 
     msg = result.fail_msg
     if msg:
@@ -2584,7 +2567,6 @@
     self.lu.LogInfo("Updating instance configuration")
     for dev, _, new_logical_id in iv_names.itervalues():
       dev.logical_id = new_logical_id
-      self.cfg.SetDiskID(dev, self.instance.primary_node)
 
     self.cfg.Update(self.instance, feedback_fn)
 
@@ -2596,17 +2578,16 @@
                     " (standalone => connected)")
     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
                                             self.new_node_uuid],
-                                           self.node_secondary_ip,
                                            (self.instance.disks, self.instance),
                                            self.instance.name,
                                            False)
     for to_node, to_result in result.items():
       msg = to_result.fail_msg
       if msg:
-        self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
-                           self.cfg.GetNodeName(to_node), msg,
-                           hint=("please do a gnt-instance info to see the"
-                                 " status of disks"))
+        raise errors.OpExecError(
+          "Can't attach drbd disks on node %s: %s (please do a gnt-instance "
+          "info %s to see the status of disks)" %
+          (self.cfg.GetNodeName(to_node), msg, self.instance.name))
 
     cstep = itertools.count(5)
 
diff --git a/lib/cmdlib/instance_utils.py b/lib/cmdlib/instance_utils.py
index 4c62f70..bbb9453 100644
--- a/lib/cmdlib/instance_utils.py
+++ b/lib/cmdlib/instance_utils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Utility function mainly, but not only used by instance LU's."""
@@ -59,8 +68,8 @@
   @type vcpus: string
   @param vcpus: the count of VCPUs the instance has
   @type nics: list
-  @param nics: list of tuples (name, uuid, ip, mac, mode, link, net, netinfo)
-      representing the NICs the instance has
+  @param nics: list of tuples (name, uuid, ip, mac, mode, link, vlan, net,
+      netinfo) representing the NICs the instance has
   @type disk_template: string
   @param disk_template: the disk template of the instance
   @type disks: list
@@ -94,7 +103,8 @@
     }
   if nics:
     nic_count = len(nics)
-    for idx, (name, uuid, ip, mac, mode, link, net, netinfo) in enumerate(nics):
+    for idx, (name, uuid, ip, mac, mode, link, vlan, net, netinfo) \
+        in enumerate(nics):
       if ip is None:
         ip = ""
       if name:
@@ -104,6 +114,7 @@
       env["INSTANCE_NIC%d_MAC" % idx] = mac
       env["INSTANCE_NIC%d_MODE" % idx] = mode
       env["INSTANCE_NIC%d_LINK" % idx] = link
+      env["INSTANCE_NIC%d_VLAN" % idx] = vlan
       if netinfo:
         nobj = objects.Network.FromDict(netinfo)
         env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx))
@@ -112,7 +123,8 @@
         # 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:
+      if mode == constants.NIC_MODE_BRIDGED or \
+         mode == constants.NIC_MODE_OVS:
         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
   else:
     nic_count = 0
@@ -271,8 +283,7 @@
     else:
       edata = device.ComputeNodeTree(instance.primary_node)
     for node_uuid, disk in edata:
-      lu.cfg.SetDiskID(disk, node_uuid)
-      result = lu.rpc.call_blockdev_remove(node_uuid, disk)
+      result = lu.rpc.call_blockdev_remove(node_uuid, (disk, instance))
       if result.fail_msg:
         lu.LogWarning("Could not remove disk %s on node %s,"
                       " continuing anyway: %s", idx,
@@ -326,11 +337,13 @@
   filled_params = cluster.SimpleFillNIC(nic.nicparams)
   mode = filled_params[constants.NIC_MODE]
   link = filled_params[constants.NIC_LINK]
+  vlan = filled_params[constants.NIC_VLAN]
   netinfo = None
   if nic.network:
     nobj = lu.cfg.GetNetwork(nic.network)
     netinfo = objects.Network.ToDict(nobj)
-  return (nic.name, nic.uuid, nic.ip, nic.mac, mode, link, nic.network, netinfo)
+  return (nic.name, nic.uuid, nic.ip, nic.mac, mode, link, vlan,
+          nic.network, netinfo)
 
 
 def NICListToTuple(lu, nics):
diff --git a/lib/cmdlib/misc.py b/lib/cmdlib/misc.py
index 9770f1f..2809c09 100644
--- a/lib/cmdlib/misc.py
+++ b/lib/cmdlib/misc.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Miscellaneous logical units that don't fit into any category."""
diff --git a/lib/cmdlib/network.py b/lib/cmdlib/network.py
index 4f172a4..f38dab2 100644
--- a/lib/cmdlib/network.py
+++ b/lib/cmdlib/network.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with networks."""
@@ -169,7 +178,7 @@
         for ip in [node.primary_ip, node.secondary_ip]:
           try:
             if pool.Contains(ip):
-              pool.Reserve(ip)
+              pool.Reserve(ip, external=True)
               self.LogInfo("Reserved IP address of node '%s' (%s)",
                            node.name, ip)
           except errors.AddressPoolError, err:
@@ -179,7 +188,7 @@
       master_ip = self.cfg.GetClusterInfo().master_ip
       try:
         if pool.Contains(master_ip):
-          pool.Reserve(master_ip)
+          pool.Reserve(master_ip, external=True)
           self.LogInfo("Reserved cluster master IP address (%s)", master_ip)
       except errors.AddressPoolError, err:
         self.LogWarning("Cannot reserve cluster master IP address (%s): %s",
@@ -365,10 +374,7 @@
     if self.op.add_reserved_ips:
       for ip in self.op.add_reserved_ips:
         try:
-          if self.pool.IsReserved(ip):
-            self.LogWarning("IP address %s is already reserved", ip)
-          else:
-            self.pool.Reserve(ip, external=True)
+          self.pool.Reserve(ip, external=True)
         except errors.AddressPoolError, err:
           self.LogWarning("Cannot reserve IP address %s: %s", ip, err)
 
@@ -378,10 +384,7 @@
           self.LogWarning("Cannot unreserve Gateway's IP")
           continue
         try:
-          if not self.pool.IsReserved(ip):
-            self.LogWarning("IP address %s is already unreserved", ip)
-          else:
-            self.pool.Release(ip, external=True)
+          self.pool.Release(ip, external=True)
         except errors.AddressPoolError, err:
           self.LogWarning("Cannot release IP address %s: %s", ip, err)
 
@@ -461,7 +464,8 @@
           netparams = group.networks.get(net_uuid, None)
           if netparams:
             info = (group.name, netparams[constants.NIC_MODE],
-                    netparams[constants.NIC_LINK])
+                    netparams[constants.NIC_LINK],
+                    netparams[constants.NIC_VLAN])
 
             network_to_groups[net_uuid].append(info)
 
@@ -471,8 +475,8 @@
       for instance in all_instances.values():
         for nic in instance.nics:
           if nic.network in network_uuids:
-            network_to_instances[nic.network].append(instance.uuid)
-            break
+            if instance.name not in network_to_instances[nic.network]:
+              network_to_instances[nic.network].append(instance.name)
 
     if query.NETQ_STATS in self.requested_data:
       stats = \
@@ -577,6 +581,7 @@
     self.group_name = self.op.group_name
     self.network_mode = self.op.network_mode
     self.network_link = self.op.network_link
+    self.network_vlan = self.op.network_vlan
 
     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
@@ -607,6 +612,7 @@
       "GROUP_NAME": self.group_name,
       "GROUP_NETWORK_MODE": self.network_mode,
       "GROUP_NETWORK_LINK": self.network_link,
+      "GROUP_NETWORK_VLAN": self.network_vlan,
       }
     return ret
 
@@ -627,7 +633,9 @@
     self.netparams = {
       constants.NIC_MODE: self.network_mode,
       constants.NIC_LINK: self.network_link,
+      constants.NIC_VLAN: self.network_vlan,
       }
+
     objects.NIC.CheckParameterSyntax(self.netparams)
 
     self.group = self.cfg.GetNodeGroup(self.group_uuid)
@@ -690,6 +698,13 @@
     ret = {
       "GROUP_NAME": self.group_name,
       }
+
+    if self.connected:
+      ret.update({
+        "GROUP_NETWORK_MODE": self.netparams[constants.NIC_MODE],
+        "GROUP_NETWORK_LINK": self.netparams[constants.NIC_LINK],
+        "GROUP_NETWORK_VLAN": self.netparams[constants.NIC_VLAN],
+        })
     return ret
 
   def BuildHooksNodes(self):
@@ -718,6 +733,7 @@
         self, lambda nic: nic.network == self.network_uuid, "disconnect from",
         [instance_info for (_, instance_info) in
          self.cfg.GetMultiInstanceInfoByName(owned_instances)])
+      self.netparams = self.group.networks.get(self.network_uuid)
 
   def Exec(self, feedback_fn):
     # Disconnect the network and update the group only if network is connected
diff --git a/lib/cmdlib/node.py b/lib/cmdlib/node.py
index 1d98f29..c1de9db 100644
--- a/lib/cmdlib/node.py
+++ b/lib/cmdlib/node.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with nodes."""
@@ -138,7 +147,10 @@
       hook_nodes = list(set(hook_nodes) - set([new_node_info.uuid]))
 
     # add the new node as post hook node by name; it does not have an UUID yet
-    return (hook_nodes, hook_nodes, [self.op.node_name, ])
+    return (hook_nodes, hook_nodes)
+
+  def PreparePostHookNodes(self, post_hook_node_uuids):
+    return post_hook_node_uuids + [self.new_node.uuid]
 
   def CheckPrereq(self):
     """Check prerequisites.
@@ -217,7 +229,7 @@
 
     # check that the type of the node (single versus dual homed) is the
     # same as for the master
-    myself = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
+    myself = self.cfg.GetMasterNodeInfo()
     master_singlehomed = myself.secondary_ip == myself.primary_ip
     newbie_singlehomed = secondary_ip == self.op.primary_ip
     if master_singlehomed != newbie_singlehomed:
@@ -279,7 +291,8 @@
     #       it a property on the base class.
     rpcrunner = rpc.DnsOnlyRunner()
     result = rpcrunner.call_version([node_name])[node_name]
-    result.Raise("Can't get version information from node %s" % node_name)
+    result.Raise("Can't get version information from node %s" % node_name,
+                 prereq=True)
     if constants.PROTOCOL_VERSION == result.payload:
       logging.info("Communication to node %s fine, sw version %s match",
                    node_name, result.payload)
@@ -302,6 +315,24 @@
         raise errors.OpPrereqError("Checks on node PVs failed: %s" %
                                    "; ".join(errmsgs), errors.ECODE_ENVIRON)
 
+  def _InitOpenVSwitch(self):
+    filled_ndparams = self.cfg.GetClusterInfo().FillND(
+      self.new_node, self.cfg.GetNodeGroup(self.new_node.group))
+
+    ovs = filled_ndparams.get(constants.ND_OVS, None)
+    ovs_name = filled_ndparams.get(constants.ND_OVS_NAME, None)
+    ovs_link = filled_ndparams.get(constants.ND_OVS_LINK, None)
+
+    if ovs:
+      if not ovs_link:
+        self.LogInfo("No physical interface for OpenvSwitch was given."
+                     " OpenvSwitch will not have an outside connection. This"
+                     " might not be what you want.")
+
+      result = self.rpc.call_node_configure_ovs(
+                 self.new_node.name, ovs_name, ovs_link)
+      result.Raise("Failed to initialize OpenVSwitch on new node")
+
   def Exec(self, feedback_fn):
     """Adds the new node to the cluster.
 
@@ -376,6 +407,8 @@
                       (verifier, nl_payload[failed]))
         raise errors.OpExecError("ssh/hostname verification failed")
 
+    self._InitOpenVSwitch()
+
     if self.op.readd:
       self.context.ReaddNode(self.new_node)
       RedistributeAncillaryFiles(self)
@@ -636,7 +669,7 @@
     # restrictions.
     if self.op.secondary_ip:
       # Ok even without locking, because this can't be changed by any LU
-      master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
+      master = self.cfg.GetMasterNodeInfo()
       master_singlehomed = master.secondary_ip == master.primary_ip
       if master_singlehomed and self.op.secondary_ip != node.primary_ip:
         if self.op.force and node.uuid == master.uuid:
@@ -832,15 +865,6 @@
   """
   REQ_BGL = False
 
-  _MODE2IALLOCATOR = {
-    constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
-    constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
-    constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
-    }
-  assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
-  assert (frozenset(_MODE2IALLOCATOR.values()) ==
-          constants.IALLOCATOR_NEVAC_MODES)
-
   def CheckArguments(self):
     CheckIAllocatorOrNode(self, "iallocator", "remote_node")
 
@@ -997,8 +1021,7 @@
 
     elif self.op.iallocator is not None:
       # TODO: Implement relocation to other group
-      evac_mode = self._MODE2IALLOCATOR[self.op.mode]
-      req = iallocator.IAReqNodeEvac(evac_mode=evac_mode,
+      req = iallocator.IAReqNodeEvac(evac_mode=self.op.mode,
                                      instances=list(self.instance_names))
       ial = iallocator.IAllocator(self.cfg, self.rpc, req)
 
@@ -1190,13 +1213,9 @@
       # filter out non-vm_capable nodes
       toquery_node_uuids = [node.uuid for node in all_info.values()
                             if node.vm_capable and node.uuid in node_uuids]
-      lvm_enabled = utils.storage.IsLvmEnabled(
-          lu.cfg.GetClusterInfo().enabled_disk_templates)
-      # FIXME: this per default asks for storage space information for all
-      # enabled disk templates. Fix this by making it possible to specify
-      # space report fields for specific disk templates.
-      raw_storage_units = utils.storage.GetStorageUnitsOfCluster(
-          lu.cfg, include_spindles=lvm_enabled)
+      default_template = lu.cfg.GetClusterInfo().enabled_disk_templates[0]
+      raw_storage_units = utils.storage.GetStorageUnits(
+          lu.cfg, [default_template])
       storage_units = rpc.PrepareStorageUnitsForNodes(
           lu.cfg, raw_storage_units, toquery_node_uuids)
       default_hypervisor = lu.cfg.GetHypervisorType()
@@ -1205,8 +1224,7 @@
       node_data = lu.rpc.call_node_info(toquery_node_uuids, storage_units,
                                         hvspecs)
       live_data = dict(
-          (uuid, rpc.MakeLegacyNodeInfo(nresult.payload,
-                                        require_spindles=lvm_enabled))
+          (uuid, rpc.MakeLegacyNodeInfo(nresult.payload, default_template))
           for (uuid, nresult) in node_data.items()
           if not nresult.fail_msg and nresult.payload)
     else:
@@ -1270,20 +1288,16 @@
     return self.nq.OldStyleQuery(self)
 
 
-def _CheckOutputFields(static, dynamic, selected):
-  """Checks whether all selected fields are valid.
+def _CheckOutputFields(fields, selected):
+  """Checks whether all selected fields are valid according to fields.
 
-  @type static: L{utils.FieldSet}
-  @param static: static fields set
-  @type dynamic: L{utils.FieldSet}
-  @param dynamic: dynamic fields set
+  @type fields: L{utils.FieldSet}
+  @param fields: fields set
+  @type selected: L{utils.FieldSet}
+  @param selected: fields set
 
   """
-  f = utils.FieldSet()
-  f.Extend(static)
-  f.Extend(dynamic)
-
-  delta = f.NonMatching(selected)
+  delta = fields.NonMatching(selected)
   if delta:
     raise errors.OpPrereqError("Unknown output fields selected: %s"
                                % ",".join(delta), errors.ECODE_INVAL)
@@ -1294,13 +1308,12 @@
 
   """
   REQ_BGL = False
-  _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
-  _FIELDS_STATIC = utils.FieldSet("node")
 
   def CheckArguments(self):
-    _CheckOutputFields(static=self._FIELDS_STATIC,
-                       dynamic=self._FIELDS_DYNAMIC,
-                       selected=self.op.output_fields)
+    _CheckOutputFields(utils.FieldSet(constants.VF_NODE, constants.VF_PHYS,
+                                      constants.VF_VG, constants.VF_NAME,
+                                      constants.VF_SIZE, constants.VF_INSTANCE),
+                       self.op.output_fields)
 
   def ExpandNames(self):
     self.share_locks = ShareAll()
@@ -1337,24 +1350,24 @@
         continue
 
       node_vols = sorted(nresult.payload,
-                         key=operator.itemgetter("dev"))
+                         key=operator.itemgetter(constants.VF_DEV))
 
       for vol in node_vols:
         node_output = []
         for field in self.op.output_fields:
-          if field == "node":
+          if field == constants.VF_NODE:
             val = self.cfg.GetNodeName(node_uuid)
-          elif field == "phys":
-            val = vol["dev"]
-          elif field == "vg":
-            val = vol["vg"]
-          elif field == "name":
-            val = vol["name"]
-          elif field == "size":
-            val = int(float(vol["size"]))
-          elif field == "instance":
-            inst = vol2inst.get((node_uuid, vol["vg"] + "/" + vol["name"]),
-                                None)
+          elif field == constants.VF_PHYS:
+            val = vol[constants.VF_DEV]
+          elif field == constants.VF_VG:
+            val = vol[constants.VF_VG]
+          elif field == constants.VF_NAME:
+            val = vol[constants.VF_NAME]
+          elif field == constants.VF_SIZE:
+            val = int(float(vol[constants.VF_SIZE]))
+          elif field == constants.VF_INSTANCE:
+            inst = vol2inst.get((node_uuid, vol[constants.VF_VG] + "/" +
+                                 vol[constants.VF_NAME]), None)
             if inst is not None:
               val = inst.name
             else:
@@ -1372,13 +1385,11 @@
   """Logical unit for getting information on storage units on node(s).
 
   """
-  _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
   REQ_BGL = False
 
   def CheckArguments(self):
-    _CheckOutputFields(static=self._FIELDS_STATIC,
-                       dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
-                       selected=self.op.output_fields)
+    _CheckOutputFields(utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
+                       self.op.output_fields)
 
   def ExpandNames(self):
     self.share_locks = ShareAll()
@@ -1393,16 +1404,41 @@
         locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
         }
 
+  def _DetermineStorageType(self):
+    """Determines the default storage type of the cluster.
+
+    """
+    enabled_disk_templates = self.cfg.GetClusterInfo().enabled_disk_templates
+    default_storage_type = \
+        constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[enabled_disk_templates[0]]
+    return default_storage_type
+
   def CheckPrereq(self):
     """Check prerequisites.
 
     """
-    CheckStorageTypeEnabled(self.cfg.GetClusterInfo(), self.op.storage_type)
+    if self.op.storage_type:
+      CheckStorageTypeEnabled(self.cfg.GetClusterInfo(), self.op.storage_type)
+      self.storage_type = self.op.storage_type
+    else:
+      self.storage_type = self._DetermineStorageType()
+      if self.storage_type not in constants.STS_REPORT:
+        raise errors.OpPrereqError(
+            "Storage reporting for storage type '%s' is not supported. Please"
+            " use the --storage-type option to specify one of the supported"
+            " storage types (%s) or set the default disk template to one that"
+            " supports storage reporting." %
+            (self.storage_type, utils.CommaJoin(constants.STS_REPORT)))
 
   def Exec(self, feedback_fn):
     """Computes the list of nodes and their attributes.
 
     """
+    if self.op.storage_type:
+      self.storage_type = self.op.storage_type
+    else:
+      self.storage_type = self._DetermineStorageType()
+
     self.node_uuids = self.owned_locks(locking.LEVEL_NODE)
 
     # Always get name to sort by
@@ -1419,9 +1455,9 @@
     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
     name_idx = field_idx[constants.SF_NAME]
 
-    st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
+    st_args = _GetStorageTypeArgs(self.cfg, self.storage_type)
     data = self.rpc.call_storage_list(self.node_uuids,
-                                      self.op.storage_type, st_args,
+                                      self.storage_type, st_args,
                                       self.op.name, fields)
 
     result = []
@@ -1449,7 +1485,7 @@
           if field == constants.SF_NODE:
             val = node_name
           elif field == constants.SF_TYPE:
-            val = self.op.storage_type
+            val = self.storage_type
           elif field in field_idx:
             val = row[field_idx[field]]
           else:
diff --git a/lib/cmdlib/operating_system.py b/lib/cmdlib/operating_system.py
index 31501c6..0e0228e 100644
--- a/lib/cmdlib/operating_system.py
+++ b/lib/cmdlib/operating_system.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with OS."""
diff --git a/lib/cmdlib/query.py b/lib/cmdlib/query.py
index 6cfd207..68b6d9e 100644
--- a/lib/cmdlib/query.py
+++ b/lib/cmdlib/query.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units for queries."""
diff --git a/lib/cmdlib/tags.py b/lib/cmdlib/tags.py
index b480729..2405f8f 100644
--- a/lib/cmdlib/tags.py
+++ b/lib/cmdlib/tags.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Logical units dealing with tags."""
diff --git a/lib/cmdlib/test.py b/lib/cmdlib/test.py
index 2647fb7..2af23f2 100644
--- a/lib/cmdlib/test.py
+++ b/lib/cmdlib/test.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Test logical units."""
@@ -54,7 +63,7 @@
     """
     self.needed_locks = {}
 
-    if self.op.on_nodes or self.op.on_master:
+    if not self.op.no_locks and (self.op.on_nodes or self.op.on_master):
       self.needed_locks[locking.LEVEL_NODE] = []
 
     if self.op.on_nodes:
@@ -63,9 +72,10 @@
       # more information.
       (self.op.on_node_uuids, self.op.on_nodes) = \
         GetWantedNodes(self, self.op.on_nodes)
-      self.needed_locks[locking.LEVEL_NODE].extend(self.op.on_node_uuids)
+      if not self.op.no_locks:
+        self.needed_locks[locking.LEVEL_NODE].extend(self.op.on_node_uuids)
 
-    if self.op.on_master:
+    if not self.op.no_locks and self.op.on_master:
       # The node lock should be acquired for the master as well.
       self.needed_locks[locking.LEVEL_NODE].append(self.cfg.GetMasterNode())
 
@@ -74,7 +84,7 @@
 
     """
     if self.op.on_master:
-      if not utils.TestDelay(self.op.duration):
+      if not utils.TestDelay(self.op.duration)[0]:
         raise errors.OpExecError("Error during master delay test")
     if self.op.on_node_uuids:
       result = self.rpc.call_test_delay(self.op.on_node_uuids, self.op.duration)
@@ -245,21 +255,10 @@
     """
     if self.op.mode in (constants.IALLOCATOR_MODE_ALLOC,
                         constants.IALLOCATOR_MODE_MULTI_ALLOC):
-      for attr in ["memory", "disks", "disk_template",
-                   "os", "tags", "nics", "vcpus"]:
-        if not hasattr(self.op, attr):
-          raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
-                                     attr, errors.ECODE_INVAL)
       (self.inst_uuid, iname) = self.cfg.ExpandInstanceName(self.op.name)
       if iname is not None:
         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
                                    iname, errors.ECODE_EXISTS)
-      if not isinstance(self.op.nics, list):
-        raise errors.OpPrereqError("Invalid parameter 'nics'",
-                                   errors.ECODE_INVAL)
-      if not isinstance(self.op.disks, list):
-        raise errors.OpPrereqError("Invalid parameter 'disks'",
-                                   errors.ECODE_INVAL)
       for row in self.op.disks:
         if (not isinstance(row, dict) or
             constants.IDISK_SIZE not in row or
@@ -271,10 +270,10 @@
       if self.op.hypervisor is None:
         self.op.hypervisor = self.cfg.GetHypervisorType()
     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
-      (fuuid, fname) = ExpandInstanceUuidAndName(self.cfg, None, self.op.name)
-      self.op.name = fname
+      (self.inst_uuid, self.op.name) = ExpandInstanceUuidAndName(self.cfg, None,
+                                                                 self.op.name)
       self.relocate_from_node_uuids = \
-          list(self.cfg.GetInstanceInfo(fuuid).secondary_nodes)
+          list(self.cfg.GetInstanceInfo(self.inst_uuid).secondary_nodes)
     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
                           constants.IALLOCATOR_MODE_NODE_EVAC):
       if not self.op.instances:
@@ -288,9 +287,6 @@
       if self.op.iallocator is None:
         raise errors.OpPrereqError("Missing allocator name",
                                    errors.ECODE_INVAL)
-    elif self.op.direction != constants.IALLOCATOR_DIR_IN:
-      raise errors.OpPrereqError("Wrong allocator test '%s'" %
-                                 self.op.direction, errors.ECODE_INVAL)
 
   def Exec(self, feedback_fn):
     """Run the allocator test.
@@ -329,7 +325,8 @@
                                              nics=self.op.nics,
                                              vcpus=self.op.vcpus,
                                              spindle_use=self.op.spindle_use,
-                                             hypervisor=self.op.hypervisor)
+                                             hypervisor=self.op.hypervisor,
+                                             node_whitelist=None)
                for idx in range(self.op.count)]
       req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
     else:
diff --git a/lib/compat.py b/lib/compat.py
index 5f1409e..ebe0ba0 100644
--- a/lib/compat.py
+++ b/lib/compat.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module containing backported language/library functionality.
diff --git a/lib/confd/__init__.py b/lib/confd/__init__.py
index c9cfc07..3236cff 100644
--- a/lib/confd/__init__.py
+++ b/lib/confd/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti confd client/server library
diff --git a/lib/confd/client.py b/lib/confd/client.py
index e37a3cd..e94f131 100644
--- a/lib/confd/client.py
+++ b/lib/confd/client.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti confd client
@@ -106,7 +115,7 @@
     self.rcvd = set()
 
 
-class ConfdClient:
+class ConfdClient(object):
   """Send queries to confd, and get back answers.
 
   Since the confd model works by querying multiple master candidates, and
@@ -462,7 +471,7 @@
       raise errors.ConfdClientError("Invalid request type")
 
 
-class ConfdFilterCallback:
+class ConfdFilterCallback(object):
   """Callback that calls another callback, but filters duplicate results.
 
   @ivar consistent: a dictionary indexed by salt; for each salt, if
@@ -567,7 +576,7 @@
       self._callback(up)
 
 
-class ConfdCountingCallback:
+class ConfdCountingCallback(object):
   """Callback that calls another callback, and counts the answers
 
   """
@@ -629,7 +638,7 @@
     self._callback(up)
 
 
-class StoreResultCallback:
+class StoreResultCallback(object):
   """Callback that simply stores the most recent answer.
 
   @ivar _answers: dict of salt to (have_answer, reply)
diff --git a/lib/config.py b/lib/config.py
index 2ecd511..f246543 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Configuration management for Ganeti
@@ -62,7 +71,7 @@
 
 
 def _ValidateConfig(data):
-  """Verifies that a configuration objects looks valid.
+  """Verifies that a configuration dict looks valid.
 
   This only verifies the version of the configuration.
 
@@ -70,11 +79,12 @@
       we expect
 
   """
-  if data.version != constants.CONFIG_VERSION:
-    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
+  if data['version'] != constants.CONFIG_VERSION:
+    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION,
+                                       data['version'])
 
 
-class TemporaryReservationManager:
+class TemporaryReservationManager(object):
   """A temporary resource reservation manager.
 
   This is used to reserve resources in a job, before using them, making sure
@@ -167,7 +177,7 @@
   return result
 
 
-class ConfigWriter:
+class ConfigWriter(object):
   """The interface to the cluster configuration.
 
   @ivar _temporary_lvs: reservation manager for temporary LVs
@@ -384,7 +394,7 @@
     _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
     return address
 
-  def _UnlockedReserveIp(self, net_uuid, address, ec_id):
+  def _UnlockedReserveIp(self, net_uuid, address, ec_id, check=True):
     """Reserve a given IPv4 address for use by an instance.
 
     """
@@ -392,22 +402,25 @@
     pool = network.AddressPool(nobj)
     try:
       isreserved = pool.IsReserved(address)
+      isextreserved = pool.IsReserved(address, external=True)
     except errors.AddressPoolError:
       raise errors.ReservationError("IP address not in network")
     if isreserved:
       raise errors.ReservationError("IP address already in use")
+    if check and isextreserved:
+      raise errors.ReservationError("IP is externally reserved")
 
     return self._temporary_ips.Reserve(ec_id,
                                        (constants.RESERVE_ACTION,
                                         address, net_uuid))
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def ReserveIp(self, net_uuid, address, ec_id):
+  def ReserveIp(self, net_uuid, address, ec_id, check=True):
     """Reserve a given IPv4 address for use by an instance.
 
     """
     if net_uuid:
-      return self._UnlockedReserveIp(net_uuid, address, ec_id)
+      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
 
   @locking.ssynchronized(_config_lock, shared=1)
   def ReserveLV(self, lv_name, ec_id):
@@ -553,15 +566,13 @@
 
     return result
 
-  def _CheckDiskIDs(self, disk, l_ids, p_ids):
+  def _CheckDiskIDs(self, disk, l_ids):
     """Compute duplicate disk IDs
 
     @type disk: L{objects.Disk}
     @param disk: the disk at which to start searching
     @type l_ids: list
     @param l_ids: list of current logical ids
-    @type p_ids: list
-    @param p_ids: list of current physical ids
     @rtype: list
     @return: a list of error messages
 
@@ -572,15 +583,10 @@
         result.append("duplicate logical id %s" % str(disk.logical_id))
       else:
         l_ids.append(disk.logical_id)
-    if disk.physical_id is not None:
-      if disk.physical_id in p_ids:
-        result.append("duplicate physical id %s" % str(disk.physical_id))
-      else:
-        p_ids.append(disk.physical_id)
 
     if disk.children:
       for child in disk.children:
-        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
+        result.extend(self._CheckDiskIDs(child, l_ids))
     return result
 
   def _UnlockedVerifyConfig(self):
@@ -598,7 +604,6 @@
     data = self._config_data
     cluster = data.cluster
     seen_lids = []
-    seen_pids = []
 
     # global cluster checks
     if not cluster.enabled_hypervisors:
@@ -675,6 +680,16 @@
             constants.NDS_PARAMETER_TYPES)
     _helper_ipolicy("cluster", cluster.ipolicy, True)
 
+    if constants.DT_RBD in cluster.diskparams:
+      access = cluster.diskparams[constants.DT_RBD][constants.RBD_ACCESS]
+      if access not in constants.DISK_VALID_ACCESS_MODES:
+        result.append(
+          "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
+            constants.DT_RBD, constants.RBD_ACCESS, access,
+            utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
+          )
+        )
+
     # per-instance checks
     for instance_uuid in data.instances:
       instance = data.instances[instance_uuid]
@@ -729,7 +744,7 @@
       for idx, disk in enumerate(instance.disks):
         result.extend(["instance '%s' disk %d error: %s" %
                        (instance.name, idx, msg) for msg in disk.Verify()])
-        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
+        result.extend(self._CheckDiskIDs(disk, seen_lids))
 
       wrong_names = _CheckInstanceDiskIvNames(instance.disks)
       if wrong_names:
@@ -847,6 +862,8 @@
           link = "bridge:%s" % nic_link
         elif nic_mode == constants.NIC_MODE_ROUTED:
           link = "route:%s" % nic_link
+        elif nic_mode == constants.NIC_MODE_OVS:
+          link = "ovs:%s" % nic_link
         else:
           raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
 
@@ -873,57 +890,6 @@
     """
     return self._UnlockedVerifyConfig()
 
-  def _UnlockedSetDiskID(self, disk, node_uuid):
-    """Convert the unique ID to the ID needed on the target nodes.
-
-    This is used only for drbd, which needs ip/port configuration.
-
-    The routine descends down and updates its children also, because
-    this helps when the only the top device is passed to the remote
-    node.
-
-    This function is for internal use, when the config lock is already held.
-
-    """
-    if disk.children:
-      for child in disk.children:
-        self._UnlockedSetDiskID(child, node_uuid)
-
-    if disk.logical_id is None and disk.physical_id is not None:
-      return
-    if disk.dev_type == constants.DT_DRBD8:
-      pnode, snode, port, pminor, sminor, secret = disk.logical_id
-      if node_uuid not in (pnode, snode):
-        raise errors.ConfigurationError("DRBD device not knowing node %s" %
-                                        node_uuid)
-      pnode_info = self._UnlockedGetNodeInfo(pnode)
-      snode_info = self._UnlockedGetNodeInfo(snode)
-      if pnode_info is None or snode_info is None:
-        raise errors.ConfigurationError("Can't find primary or secondary node"
-                                        " for %s" % str(disk))
-      p_data = (pnode_info.secondary_ip, port)
-      s_data = (snode_info.secondary_ip, port)
-      if pnode == node_uuid:
-        disk.physical_id = p_data + s_data + (pminor, secret)
-      else: # it must be secondary, we tested above
-        disk.physical_id = s_data + p_data + (sminor, secret)
-    else:
-      disk.physical_id = disk.logical_id
-    return
-
-  @locking.ssynchronized(_config_lock)
-  def SetDiskID(self, disk, node_uuid):
-    """Convert the unique ID to the ID needed on the target nodes.
-
-    This is used only for drbd, which needs ip/port configuration.
-
-    The routine descends down and updates its children also, because
-    this helps when the only the top device is passed to the remote
-    node.
-
-    """
-    return self._UnlockedSetDiskID(disk, node_uuid)
-
   @locking.ssynchronized(_config_lock)
   def AddTcpUdpPort(self, port):
     """Adds a new port to the available port pool.
@@ -1153,6 +1119,16 @@
     return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
 
   @locking.ssynchronized(_config_lock, shared=1)
+  def GetMasterNodeInfo(self):
+    """Get the master node information for this cluster.
+
+    @rtype: objects.Node
+    @return: Master node L{objects.Node} object
+
+    """
+    return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetMasterIP(self):
     """Get the IP of the master node for this cluster.
 
@@ -1576,7 +1552,6 @@
         disk.logical_id = (disk.logical_id[0],
                            utils.PathJoin(file_storage_dir, inst.name,
                                           os.path.basename(disk.logical_id[1])))
-        disk.physical_id = disk.logical_id
 
     # Force update of ssconf files
     self._config_data.cluster.serial_no += 1
@@ -2006,7 +1981,7 @@
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetNonVmCapableNodeList(self):
-    """Return the list of nodes which are not vm capable.
+    """Return the list of nodes' uuids which are not vm capable.
 
     """
     all_nodes = [self._UnlockedGetNodeInfo(node)
@@ -2014,6 +1989,15 @@
     return [node.uuid for node in all_nodes if not node.vm_capable]
 
   @locking.ssynchronized(_config_lock, shared=1)
+  def GetNonVmCapableNodeNameList(self):
+    """Return the list of nodes' names which are not vm capable.
+
+    """
+    all_nodes = [self._UnlockedGetNodeInfo(node)
+                 for node in self._UnlockedGetNodeList()]
+    return [node.name for node in all_nodes if not node.vm_capable]
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetMultiNodeInfo(self, node_uuids):
     """Get the configuration of multiple nodes.
 
@@ -2312,13 +2296,15 @@
     raw_data = utils.ReadFile(self._cfg_file)
 
     try:
-      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
+      data_dict = serializer.Load(raw_data)
+      # Make sure the configuration has the right version
+      _ValidateConfig(data_dict)
+      data = objects.ConfigData.FromDict(data_dict)
+    except errors.ConfigVersionMismatch:
+      raise
     except Exception, err:
       raise errors.ConfigurationError(err)
 
-    # Make sure the configuration has the right version
-    _ValidateConfig(data)
-
     if (not hasattr(data, "cluster") or
         not hasattr(data.cluster, "rsahostkeypub")):
       raise errors.ConfigurationError("Incomplete configuration"
diff --git a/lib/constants.py b/lib/constants.py
index 3c8e072..fefe18d 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -2,264 +2,61 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module holding different constants."""
 
+# pylint: disable=W0401,W0614
+#
+# The modules 'ganeti._constants' and 'ganeti._vcsversion' are meant
+# to be re-exported but pylint complains because the imported names
+# are not actually used in this module.
+
 import re
 import socket
 
-from ganeti import _autoconf
-from ganeti import _vcsversion
+from ganeti._constants import *
+from ganeti._vcsversion import *
 from ganeti import compat
 from ganeti import pathutils
 
-
-# various versions
-RELEASE_VERSION = _autoconf.PACKAGE_VERSION
-OS_API_V10 = 10
-OS_API_V15 = 15
-OS_API_V20 = 20
-OS_API_VERSIONS = compat.UniqueFrozenset([
-  OS_API_V10,
-  OS_API_V15,
-  OS_API_V20,
-  ])
-VCS_VERSION = _vcsversion.VCS_VERSION
-EXPORT_VERSION = 0
-RAPI_VERSION = 2
-
-
-# Format for CONFIG_VERSION:
-#   01 03 0123 = 01030123
-#   ^^ ^^ ^^^^
-#   |  |  + Configuration version/revision
-#   |  + Minor version
-#   + Major version
-#
-# It is stored as an integer. Make sure not to write an octal number.
-
-# BuildVersion and SplitVersion must be in here because we can't import other
-# modules. The cfgupgrade tool must be able to read and write version numbers
-# and thus requires these functions. To avoid code duplication, they're kept in
-# here.
-
-def BuildVersion(major, minor, revision):
-  """Calculates int version number from major, minor and revision numbers.
-
-  Returns: int representing version number
-
-  """
-  assert isinstance(major, int)
-  assert isinstance(minor, int)
-  assert isinstance(revision, int)
-  return (1000000 * major +
-            10000 * minor +
-                1 * revision)
-
-
-def SplitVersion(version):
-  """Splits version number stored in an int.
-
-  Returns: tuple; (major, minor, revision)
-
-  """
-  assert isinstance(version, int)
-
-  (major, remainder) = divmod(version, 1000000)
-  (minor, revision) = divmod(remainder, 10000)
-
-  return (major, minor, revision)
-
-
-CONFIG_MAJOR = int(_autoconf.VERSION_MAJOR)
-CONFIG_MINOR = int(_autoconf.VERSION_MINOR)
-CONFIG_REVISION = 0
-CONFIG_VERSION = BuildVersion(CONFIG_MAJOR, CONFIG_MINOR, CONFIG_REVISION)
-
-#: RPC protocol version
-PROTOCOL_VERSION = BuildVersion(CONFIG_MAJOR, CONFIG_MINOR, 0)
-
-# user separation
-DAEMONS_GROUP = _autoconf.DAEMONS_GROUP
-ADMIN_GROUP = _autoconf.ADMIN_GROUP
-MASTERD_USER = _autoconf.MASTERD_USER
-MASTERD_GROUP = _autoconf.MASTERD_GROUP
-RAPI_USER = _autoconf.RAPI_USER
-RAPI_GROUP = _autoconf.RAPI_GROUP
-CONFD_USER = _autoconf.CONFD_USER
-CONFD_GROUP = _autoconf.CONFD_GROUP
-LUXID_USER = _autoconf.LUXID_USER
-LUXID_GROUP = _autoconf.LUXID_GROUP
-NODED_USER = _autoconf.NODED_USER
-NODED_GROUP = _autoconf.NODED_GROUP
-MOND_USER = _autoconf.MOND_USER
-MOND_GROUP = _autoconf.MOND_GROUP
-SSH_LOGIN_USER = _autoconf.SSH_LOGIN_USER
-SSH_CONSOLE_USER = _autoconf.SSH_CONSOLE_USER
-
-# cpu pinning separators and constants
-CPU_PINNING_SEP = ":"
-CPU_PINNING_ALL = "all"
-# internal representation of "all"
-CPU_PINNING_ALL_VAL = -1
-# one "all" entry in a CPU list means CPU pinning is off
-CPU_PINNING_OFF = [CPU_PINNING_ALL_VAL]
-
-# A Xen-specific implementation detail - there is no way to actually say
-# "use any cpu for pinning" in a Xen configuration file, as opposed to the
-# command line, where you can say "xm vcpu-pin <domain> <vcpu> all".
-# The workaround used in Xen is "0-63" (see source code function
-# xm_vcpu_pin in <xen-source>/tools/python/xen/xm/main.py).
-# To support future changes, the following constant is treated as a
-# blackbox string that simply means use-any-cpu-for-pinning-under-xen.
-CPU_PINNING_ALL_XEN = "0-63"
-
-# A KVM-specific implementation detail - the following value is used
-# to set CPU affinity to all processors (#0 through #31), per taskset
-# man page.
-# FIXME: This only works for machines with up to 32 CPU cores
-CPU_PINNING_ALL_KVM = 0xFFFFFFFF
-
-# Wipe
-DD_CMD = "dd"
-MAX_WIPE_CHUNK = 1024 # 1GB
-MIN_WIPE_CHUNK_PERCENT = 10
-
-RUN_DIRS_MODE = 0775
-SECURE_DIR_MODE = 0700
-SECURE_FILE_MODE = 0600
-ADOPTABLE_BLOCKDEV_ROOT = "/dev/disk/"
-ENABLE_CONFD = _autoconf.ENABLE_CONFD
-ENABLE_MOND = _autoconf.ENABLE_MOND
-ENABLE_SPLIT_QUERY = _autoconf.ENABLE_SPLIT_QUERY
-ENABLE_RESTRICTED_COMMANDS = _autoconf.ENABLE_RESTRICTED_COMMANDS
-
-# SSH constants
-SSH = "ssh"
-SCP = "scp"
-
-NODED = "ganeti-noded"
-CONFD = "ganeti-confd"
-LUXID = "ganeti-luxid"
-RAPI = "ganeti-rapi"
-MASTERD = "ganeti-masterd"
-MOND = "ganeti-mond"
-
-DAEMONS = compat.UniqueFrozenset([
-  NODED,
-  CONFD,
-  LUXID,
-  RAPI,
-  MASTERD,
-  MOND,
-  ])
-
-DAEMONS_PORTS = {
-  # daemon-name: ("proto", "default-port")
-  NODED: ("tcp", 1811),
-  CONFD: ("udp", 1814),
-  MOND: ("tcp", 1815),
-  RAPI: ("tcp", 5080),
-  SSH: ("tcp", 22),
-}
-
-DEFAULT_NODED_PORT = DAEMONS_PORTS[NODED][1]
-DEFAULT_CONFD_PORT = DAEMONS_PORTS[CONFD][1]
-DEFAULT_MOND_PORT = DAEMONS_PORTS[MOND][1]
-DEFAULT_RAPI_PORT = DAEMONS_PORTS[RAPI][1]
-
-FIRST_DRBD_PORT = 11000
-LAST_DRBD_PORT = 14999
-
-DAEMONS_LOGBASE = {
-  NODED: "node-daemon",
-  CONFD: "conf-daemon",
-  LUXID: "luxi-daemon",
-  RAPI: "rapi-daemon",
-  MASTERD: "master-daemon",
-  MOND: "monitoring-daemon",
-  }
+ALLOCATABLE_KEY = "allocatable"
+FAILED_KEY = "failed"
 
 DAEMONS_LOGFILES = \
     dict((daemon, pathutils.GetLogFilename(DAEMONS_LOGBASE[daemon]))
          for daemon in DAEMONS_LOGBASE)
 
-# Some daemons might require more than one logfile.
-# Specifically, right now only the Haskell http library "snap", used by the
-# monitoring daemon, requires multiple log files.
-
-# These are the only valid reasons for having an extra logfile
-EXTRA_LOGREASON_ACCESS = "access"
-EXTRA_LOGREASON_ERROR = "error"
-
-VALID_EXTRA_LOGREASONS = compat.UniqueFrozenset([
-  EXTRA_LOGREASON_ACCESS,
-  EXTRA_LOGREASON_ERROR,
-  ])
-
-# These are the extra logfiles, grouped by daemon
-DAEMONS_EXTRA_LOGBASE = {
-  MOND: {
-    EXTRA_LOGREASON_ACCESS: "monitoring-daemon-access",
-    EXTRA_LOGREASON_ERROR: "monitoring-daemon-error",
-    }
-  }
-
 DAEMONS_EXTRA_LOGFILES = \
   dict((daemon, dict((extra,
        pathutils.GetLogFilename(DAEMONS_EXTRA_LOGBASE[daemon][extra]))
        for extra in DAEMONS_EXTRA_LOGBASE[daemon]))
          for daemon in DAEMONS_EXTRA_LOGBASE)
 
-DEV_CONSOLE = "/dev/console"
-
-PROC_MOUNTS = "/proc/mounts"
-
-# Local UniX Interface related constants
-LUXI_EOM = chr(3)
-LUXI_VERSION = CONFIG_VERSION
-#: Environment variable for the luxi override socket
-LUXI_OVERRIDE = "FORCE_LUXI_SOCKET"
-LUXI_OVERRIDE_MASTER = "master"
-LUXI_OVERRIDE_QUERY = "query"
-LUXI_SOCKET_PERMS = 0660
-
-# one of "no", "yes", "only"
-SYSLOG_USAGE = _autoconf.SYSLOG_USAGE
-SYSLOG_NO = "no"
-SYSLOG_YES = "yes"
-SYSLOG_ONLY = "only"
-SYSLOG_SOCKET = "/dev/log"
-
-EXPORT_CONF_FILE = "config.ini"
-
-XEN_BOOTLOADER = _autoconf.XEN_BOOTLOADER
-XEN_KERNEL = _autoconf.XEN_KERNEL
-XEN_INITRD = _autoconf.XEN_INITRD
-XEN_CMD_XM = "xm"
-XEN_CMD_XL = "xl"
-
-KNOWN_XEN_COMMANDS = compat.UniqueFrozenset([
-  XEN_CMD_XM,
-  XEN_CMD_XL,
-  ])
-
 # When the Xen toolstack used is "xl", live migration requires the source host
 # to connect to the target host via ssh (xl runs this command). We need to pass
 # the command xl runs some extra info so that it can use Ganeti's key
@@ -271,2268 +68,20 @@
               " -oHostKeyAlias=%%s") % (SSH_LOGIN_USER,
                                         pathutils.SSH_KNOWN_HOSTS_FILE)
 
-KVM_PATH = _autoconf.KVM_PATH
-KVM_KERNEL = _autoconf.KVM_KERNEL
-SOCAT_PATH = _autoconf.SOCAT_PATH
-SOCAT_USE_ESCAPE = _autoconf.SOCAT_USE_ESCAPE
-SOCAT_USE_COMPRESS = _autoconf.SOCAT_USE_COMPRESS
-SOCAT_ESCAPE_CODE = "0x1d"
-
-#: Console as SSH command
-CONS_SSH = "ssh"
-
-#: Console as VNC server
-CONS_VNC = "vnc"
-
-#: Console as SPICE server
-CONS_SPICE = "spice"
-
-#: Display a message for console access
-CONS_MESSAGE = "msg"
-
-#: All console types
-CONS_ALL = compat.UniqueFrozenset([
-  CONS_SSH,
-  CONS_VNC,
-  CONS_SPICE,
-  CONS_MESSAGE,
-  ])
-
-# For RSA keys more bits are better, but they also make operations more
-# expensive. NIST SP 800-131 recommends a minimum of 2048 bits from the year
-# 2010 on.
-RSA_KEY_BITS = 2048
-
-# Ciphers allowed for SSL connections. For the format, see ciphers(1). A better
-# way to disable ciphers would be to use the exclamation mark (!), but socat
-# versions below 1.5 can't parse exclamation marks in options properly. When
-# modifying the ciphers, ensure not to accidentially add something after it's
-# been removed. Use the "openssl" utility to check the allowed ciphers, e.g.
-# "openssl ciphers -v HIGH:-DES".
-OPENSSL_CIPHERS = "HIGH:-DES:-3DES:-EXPORT:-ADH"
-
-# Digest used to sign certificates ("openssl x509" uses SHA1 by default)
-X509_CERT_SIGN_DIGEST = "SHA1"
-
-# Default validity of certificates in days
-X509_CERT_DEFAULT_VALIDITY = 365 * 5
-
-# commonName (CN) used in certificates
-X509_CERT_CN = "ganeti.example.com"
-
-X509_CERT_SIGNATURE_HEADER = "X-Ganeti-Signature"
-
-# Import/export daemon mode
-IEM_IMPORT = "import"
-IEM_EXPORT = "export"
-
-# Import/export transport compression
-IEC_NONE = "none"
-IEC_GZIP = "gzip"
-IEC_ALL = compat.UniqueFrozenset([
-  IEC_NONE,
-  IEC_GZIP,
-  ])
-
-IE_CUSTOM_SIZE = "fd"
-
 IE_MAGIC_RE = re.compile(r"^[-_.a-zA-Z0-9]{5,100}$")
 
-# Import/export I/O
-# Direct file I/O, equivalent to a shell's I/O redirection using '<' or '>'
-IEIO_FILE = "file"
-# Raw block device I/O using "dd"
-IEIO_RAW_DISK = "raw"
-# OS definition import/export script
-IEIO_SCRIPT = "script"
-
-VALUE_DEFAULT = "default"
-VALUE_AUTO = "auto"
-VALUE_GENERATE = "generate"
-VALUE_NONE = "none"
-VALUE_TRUE = "true"
-VALUE_FALSE = "false"
-
 # External script validation mask
 EXT_PLUGIN_MASK = re.compile("^[a-zA-Z0-9_-]+$")
 
-# hooks-related constants
-HOOKS_PHASE_PRE = "pre"
-HOOKS_PHASE_POST = "post"
-HOOKS_NAME_CFGUPDATE = "config-update"
-HOOKS_NAME_WATCHER = "watcher"
-HOOKS_VERSION = 2
-HOOKS_PATH = "/sbin:/bin:/usr/sbin:/usr/bin"
-
-# hooks subject type (what object type does the LU deal with)
-HTYPE_CLUSTER = "CLUSTER"
-HTYPE_NODE = "NODE"
-HTYPE_GROUP = "GROUP"
-HTYPE_INSTANCE = "INSTANCE"
-HTYPE_NETWORK = "NETWORK"
-
-HKR_SKIP = 0
-HKR_FAIL = 1
-HKR_SUCCESS = 2
-
-# Storage types
-ST_BLOCK = "blockdev"
-ST_DISKLESS = "diskless"
-ST_EXT = "ext"
-ST_FILE = "file"
-ST_LVM_PV = "lvm-pv"
-ST_LVM_VG = "lvm-vg"
-ST_RADOS = "rados"
-
-STORAGE_TYPES = compat.UniqueFrozenset([
-  ST_BLOCK,
-  ST_DISKLESS,
-  ST_EXT,
-  ST_FILE,
-  ST_LVM_PV,
-  ST_LVM_VG,
-  ST_RADOS,
-  ])
-
-# the set of storage types for which storage reporting is available
-# FIXME: Remove this, once storage reporting is available for all types.
-STS_REPORT = compat.UniqueFrozenset([ST_FILE, ST_LVM_PV, ST_LVM_VG])
-
-# Storage fields
-# first two are valid in LU context only, not passed to backend
-SF_NODE = "node"
-SF_TYPE = "type"
-# and the rest are valid in backend
-SF_NAME = "name"
-SF_SIZE = "size"
-SF_FREE = "free"
-SF_USED = "used"
-SF_ALLOCATABLE = "allocatable"
-
-# Storage operations
-SO_FIX_CONSISTENCY = "fix-consistency"
-
-# Available fields per storage type
-VALID_STORAGE_FIELDS = compat.UniqueFrozenset([
-  SF_NAME,
-  SF_TYPE,
-  SF_SIZE,
-  SF_USED,
-  SF_FREE,
-  SF_ALLOCATABLE,
-  ])
-
-MODIFIABLE_STORAGE_FIELDS = {
-  ST_LVM_PV: frozenset([SF_ALLOCATABLE]),
-  }
-
-VALID_STORAGE_OPERATIONS = {
-  ST_LVM_VG: frozenset([SO_FIX_CONSISTENCY]),
-  }
-
-# Local disk status
-# Note: Code depends on LDS_OKAY < LDS_UNKNOWN < LDS_FAULTY
-(LDS_OKAY,
- LDS_UNKNOWN,
- LDS_FAULTY) = range(1, 4)
-
-LDS_NAMES = {
-  LDS_OKAY: "ok",
-  LDS_UNKNOWN: "unknown",
-  LDS_FAULTY: "faulty",
-}
-
-# disk template types
-DT_BLOCK = "blockdev"
-DT_DISKLESS = "diskless"
-DT_DRBD8 = "drbd"
-DT_EXT = "ext"
-DT_FILE = "file"
-DT_PLAIN = "plain"
-DT_RBD = "rbd"
-DT_SHARED_FILE = "sharedfile"
-
-# This is used to order determine the default disk template when the list
-# of enabled disk templates is inferred from the current state of the cluster.
-# This only happens on an upgrade from a version of Ganeti that did not
-# support the 'enabled_disk_templates' so far.
-DISK_TEMPLATE_PREFERENCE = [
-  DT_BLOCK,
-  DT_DISKLESS,
-  DT_DRBD8,
-  DT_EXT,
-  DT_FILE,
-  DT_PLAIN,
-  DT_RBD,
-  DT_SHARED_FILE,
-  ]
-
-DISK_TEMPLATES = compat.UniqueFrozenset([
-  DT_DISKLESS,
-  DT_PLAIN,
-  DT_DRBD8,
-  DT_FILE,
-  DT_SHARED_FILE,
-  DT_BLOCK,
-  DT_RBD,
-  DT_EXT
-  ])
-
-# disk templates that are enabled by default
-DEFAULT_ENABLED_DISK_TEMPLATES = [
-  DT_DRBD8,
-  DT_PLAIN,
-  ]
-
-# mapping of disk templates to storage types
-MAP_DISK_TEMPLATE_STORAGE_TYPE = {
-  DT_BLOCK: ST_BLOCK,
-  DT_DISKLESS: ST_DISKLESS,
-  DT_DRBD8: ST_LVM_VG,
-  DT_EXT: ST_EXT,
-  DT_FILE: ST_FILE,
-  DT_PLAIN: ST_LVM_VG,
-  DT_RBD: ST_RADOS,
-  DT_SHARED_FILE: ST_FILE,
-  }
-
-# the set of network-mirrored disk templates
-DTS_INT_MIRROR = compat.UniqueFrozenset([DT_DRBD8])
-
-# the set of externally-mirrored disk templates (e.g. SAN, NAS)
-DTS_EXT_MIRROR = compat.UniqueFrozenset([
-  DT_DISKLESS, # 'trivially' externally mirrored
-  DT_SHARED_FILE,
-  DT_BLOCK,
-  DT_RBD,
-  DT_EXT,
-  ])
-
-# the set of non-lvm-based disk templates
-DTS_NOT_LVM = compat.UniqueFrozenset([
-  DT_DISKLESS,
-  DT_FILE,
-  DT_SHARED_FILE,
-  DT_BLOCK,
-  DT_RBD,
-  DT_EXT,
-  ])
-
-# the set of disk templates which can be grown
-DTS_GROWABLE = compat.UniqueFrozenset([
-  DT_PLAIN,
-  DT_DRBD8,
-  DT_FILE,
-  DT_SHARED_FILE,
-  DT_RBD,
-  DT_EXT,
-  ])
-
-# the set of disk templates that allow adoption
-DTS_MAY_ADOPT = compat.UniqueFrozenset([
-  DT_PLAIN,
-  DT_BLOCK,
-  ])
-
-# the set of disk templates that *must* use adoption
-DTS_MUST_ADOPT = compat.UniqueFrozenset([DT_BLOCK])
-
-# the set of disk templates that allow migrations
-DTS_MIRRORED = frozenset.union(DTS_INT_MIRROR, DTS_EXT_MIRROR)
-
-# the set of file based disk templates
-DTS_FILEBASED = compat.UniqueFrozenset([
-  DT_FILE,
-  DT_SHARED_FILE,
-  ])
-
-# the set of disk templates that can be moved by copying
-# Note: a requirement is that they're not accessed externally or shared between
-# nodes; in particular, sharedfile is not suitable.
-DTS_COPYABLE = compat.UniqueFrozenset([
-  DT_FILE,
-  DT_PLAIN,
-  ])
-
-# the set of disk templates that are supported by exclusive_storage
-DTS_EXCL_STORAGE = compat.UniqueFrozenset([DT_PLAIN])
-
-# templates for which we don't perform checks on free space
-DTS_NO_FREE_SPACE_CHECK = compat.UniqueFrozenset([
-  DT_FILE,
-  DT_SHARED_FILE,
-  DT_RBD,
-  DT_EXT,
-  ])
-
-DTS_BLOCK = compat.UniqueFrozenset([
-  DT_PLAIN,
-  DT_DRBD8,
-  DT_BLOCK,
-  DT_RBD,
-  DT_EXT,
-  ])
-
-# the set of drbd-like disk types
-DTS_DRBD = compat.UniqueFrozenset([DT_DRBD8])
-
-# drbd constants
-DRBD_HMAC_ALG = "md5"
-DRBD_DEFAULT_NET_PROTOCOL = "C"
-DRBD_MIGRATION_NET_PROTOCOL = "C"
-DRBD_STATUS_FILE = "/proc/drbd"
-
-#: Size of DRBD meta block device
-DRBD_META_SIZE = 128
-
-# drbd barrier types
-DRBD_B_NONE = "n"
-DRBD_B_DISK_BARRIERS = "b"
-DRBD_B_DISK_DRAIN = "d"
-DRBD_B_DISK_FLUSH = "f"
-
-# Valid barrier combinations: "n" or any non-null subset of "bfd"
-DRBD_VALID_BARRIER_OPT = compat.UniqueFrozenset([
-  frozenset([DRBD_B_NONE]),
-  frozenset([DRBD_B_DISK_BARRIERS]),
-  frozenset([DRBD_B_DISK_DRAIN]),
-  frozenset([DRBD_B_DISK_FLUSH]),
-  frozenset([DRBD_B_DISK_DRAIN, DRBD_B_DISK_FLUSH]),
-  frozenset([DRBD_B_DISK_BARRIERS, DRBD_B_DISK_DRAIN]),
-  frozenset([DRBD_B_DISK_BARRIERS, DRBD_B_DISK_FLUSH]),
-  frozenset([DRBD_B_DISK_BARRIERS, DRBD_B_DISK_FLUSH, DRBD_B_DISK_DRAIN]),
-  ])
-
-# rbd tool command
-RBD_CMD = "rbd"
-
-# file backend driver
-FD_LOOP = "loop"
-FD_BLKTAP = "blktap"
-FD_BLKTAP2 = "blktap2"
-FD_DEFAULT = FD_LOOP
-
-# disk access mode
-DISK_RDONLY = "ro"
-DISK_RDWR = "rw"
-DISK_ACCESS_SET = compat.UniqueFrozenset([DISK_RDONLY, DISK_RDWR])
-
-# disk replacement mode
-REPLACE_DISK_PRI = "replace_on_primary"    # replace disks on primary
-REPLACE_DISK_SEC = "replace_on_secondary"  # replace disks on secondary
-REPLACE_DISK_CHG = "replace_new_secondary" # change secondary node
-REPLACE_DISK_AUTO = "replace_auto"
-REPLACE_MODES = compat.UniqueFrozenset([
-  REPLACE_DISK_PRI,
-  REPLACE_DISK_SEC,
-  REPLACE_DISK_CHG,
-  REPLACE_DISK_AUTO,
-  ])
-
-# Instance export mode
-EXPORT_MODE_LOCAL = "local"
-EXPORT_MODE_REMOTE = "remote"
-EXPORT_MODES = compat.UniqueFrozenset([
-  EXPORT_MODE_LOCAL,
-  EXPORT_MODE_REMOTE,
-  ])
-
-# instance creation modes
-INSTANCE_CREATE = "create"
-INSTANCE_IMPORT = "import"
-INSTANCE_REMOTE_IMPORT = "remote-import"
-INSTANCE_CREATE_MODES = compat.UniqueFrozenset([
-  INSTANCE_CREATE,
-  INSTANCE_IMPORT,
-  INSTANCE_REMOTE_IMPORT,
-  ])
-
-# Remote import/export handshake message and version
-RIE_VERSION = 0
-RIE_HANDSHAKE = "Hi, I'm Ganeti"
-
-# Remote import/export certificate validity in seconds
-RIE_CERT_VALIDITY = 24 * 60 * 60
-
-# Overall timeout for establishing connection
-RIE_CONNECT_TIMEOUT = 180
-
-# Export only: how long to wait per connection attempt (seconds)
-RIE_CONNECT_ATTEMPT_TIMEOUT = 20
-
-# Export only: number of attempts to connect
-RIE_CONNECT_RETRIES = 10
-
-#: Give child process up to 5 seconds to exit after sending a signal
-CHILD_LINGER_TIMEOUT = 5.0
-
-FILE_DRIVER = compat.UniqueFrozenset([FD_LOOP, FD_BLKTAP, FD_BLKTAP2])
-
-# import/export config options
-INISECT_EXP = "export"
-INISECT_INS = "instance"
-INISECT_HYP = "hypervisor"
-INISECT_BEP = "backend"
-INISECT_OSP = "os"
-
-# dynamic device modification
-DDM_ADD = "add"
-DDM_MODIFY = "modify"
-DDM_REMOVE = "remove"
-DDMS_VALUES = compat.UniqueFrozenset([DDM_ADD, DDM_REMOVE])
-DDMS_VALUES_WITH_MODIFY = (DDMS_VALUES | frozenset([
-  DDM_MODIFY,
-  ]))
-# TODO: DDM_SWAP, DDM_MOVE?
-
-# common exit codes
-EXIT_SUCCESS = 0
-EXIT_FAILURE = 1
-EXIT_NOTCLUSTER = 5
-EXIT_NOTMASTER = 11
-EXIT_NODESETUP_ERROR = 12
-EXIT_CONFIRMATION = 13 # need user confirmation
-
-#: Exit code for query operations with unknown fields
-EXIT_UNKNOWN_FIELD = 14
-
-# tags
-TAG_CLUSTER = "cluster"
-TAG_NODEGROUP = "nodegroup"
-TAG_NODE = "node"
-TAG_INSTANCE = "instance"
-TAG_NETWORK = "network"
-VALID_TAG_TYPES = compat.UniqueFrozenset([
-  TAG_CLUSTER,
-  TAG_NODEGROUP,
-  TAG_NODE,
-  TAG_INSTANCE,
-  TAG_NETWORK,
-  ])
-MAX_TAG_LEN = 128
-MAX_TAGS_PER_OBJ = 4096
-
-# others
-DEFAULT_BRIDGE = "xen-br0"
-CLASSIC_DRBD_SYNC_SPEED = 60 * 1024  # 60 MiB, expressed in KiB
-IP4_ADDRESS_LOCALHOST = "127.0.0.1"
-IP4_ADDRESS_ANY = "0.0.0.0"
-IP6_ADDRESS_LOCALHOST = "::1"
-IP6_ADDRESS_ANY = "::"
-IP4_VERSION = 4
-IP6_VERSION = 6
-VALID_IP_VERSIONS = compat.UniqueFrozenset([IP4_VERSION, IP6_VERSION])
-# for export to htools
-IP4_FAMILY = socket.AF_INET
-IP6_FAMILY = socket.AF_INET6
-
-TCP_PING_TIMEOUT = 10
-DEFAULT_VG = "xenvg"
-DEFAULT_DRBD_HELPER = "/bin/true"
-MIN_VG_SIZE = 20480
-DEFAULT_MAC_PREFIX = "aa:00:00"
-# default maximum instance wait time, in seconds.
-DEFAULT_SHUTDOWN_TIMEOUT = 120
-NODE_MAX_CLOCK_SKEW = 150
-# Time for an intra-cluster disk transfer to wait for a connection
-DISK_TRANSFER_CONNECT_TIMEOUT = 60
-# Disk index separator
-DISK_SEPARATOR = _autoconf.DISK_SEPARATOR
-IP_COMMAND_PATH = _autoconf.IP_PATH
-
-#: Key for job IDs in opcode result
-JOB_IDS_KEY = "jobs"
-
-# runparts results
-(RUNPARTS_SKIP,
- RUNPARTS_RUN,
- RUNPARTS_ERR) = range(3)
-
-RUNPARTS_STATUS = compat.UniqueFrozenset([
-  RUNPARTS_SKIP,
-  RUNPARTS_RUN,
-  RUNPARTS_ERR,
-  ])
-
-# RPC constants
-(RPC_ENCODING_NONE,
- RPC_ENCODING_ZLIB_BASE64) = range(2)
-
-# Various time constants for the timeout table
-RPC_TMO_URGENT = 60 # one minute
-RPC_TMO_FAST = 5 * 60 # five minutes
-RPC_TMO_NORMAL = 15 * 60 # 15 minutes
-RPC_TMO_SLOW = 3600 # one hour
-RPC_TMO_4HRS = 4 * 3600
-RPC_TMO_1DAY = 86400
-
-# Timeout for connecting to nodes (seconds)
-RPC_CONNECT_TIMEOUT = 5
-
-# os related constants
-OS_SCRIPT_CREATE = "create"
-OS_SCRIPT_IMPORT = "import"
-OS_SCRIPT_EXPORT = "export"
-OS_SCRIPT_RENAME = "rename"
-OS_SCRIPT_VERIFY = "verify"
-OS_SCRIPTS = compat.UniqueFrozenset([
-  OS_SCRIPT_CREATE,
-  OS_SCRIPT_IMPORT,
-  OS_SCRIPT_EXPORT,
-  OS_SCRIPT_RENAME,
-  OS_SCRIPT_VERIFY,
-  ])
-
-OS_API_FILE = "ganeti_api_version"
-OS_VARIANTS_FILE = "variants.list"
-OS_PARAMETERS_FILE = "parameters.list"
-
-OS_VALIDATE_PARAMETERS = "parameters"
-OS_VALIDATE_CALLS = compat.UniqueFrozenset([OS_VALIDATE_PARAMETERS])
-
-# External Storage (ES) related constants
-ES_ACTION_CREATE = "create"
-ES_ACTION_REMOVE = "remove"
-ES_ACTION_GROW = "grow"
-ES_ACTION_ATTACH = "attach"
-ES_ACTION_DETACH = "detach"
-ES_ACTION_SETINFO = "setinfo"
-ES_ACTION_VERIFY = "verify"
-
-ES_SCRIPT_CREATE = ES_ACTION_CREATE
-ES_SCRIPT_REMOVE = ES_ACTION_REMOVE
-ES_SCRIPT_GROW = ES_ACTION_GROW
-ES_SCRIPT_ATTACH = ES_ACTION_ATTACH
-ES_SCRIPT_DETACH = ES_ACTION_DETACH
-ES_SCRIPT_SETINFO = ES_ACTION_SETINFO
-ES_SCRIPT_VERIFY = ES_ACTION_VERIFY
-ES_SCRIPTS = frozenset([
-  ES_SCRIPT_CREATE,
-  ES_SCRIPT_REMOVE,
-  ES_SCRIPT_GROW,
-  ES_SCRIPT_ATTACH,
-  ES_SCRIPT_DETACH,
-  ES_SCRIPT_SETINFO,
-  ES_SCRIPT_VERIFY
-  ])
-
-ES_PARAMETERS_FILE = "parameters.list"
-
-# reboot types
-INSTANCE_REBOOT_SOFT = "soft"
-INSTANCE_REBOOT_HARD = "hard"
-INSTANCE_REBOOT_FULL = "full"
-
-REBOOT_TYPES = compat.UniqueFrozenset([
-  INSTANCE_REBOOT_SOFT,
-  INSTANCE_REBOOT_HARD,
-  INSTANCE_REBOOT_FULL,
-  ])
-
-# instance reboot behaviors
-INSTANCE_REBOOT_ALLOWED = "reboot"
-INSTANCE_REBOOT_EXIT = "exit"
-
-REBOOT_BEHAVIORS = compat.UniqueFrozenset([
-  INSTANCE_REBOOT_ALLOWED,
-  INSTANCE_REBOOT_EXIT,
-  ])
-
-VTYPE_STRING = "string"
-VTYPE_MAYBE_STRING = "maybe-string"
-VTYPE_BOOL = "bool"
-VTYPE_SIZE = "size" # size, in MiBs
-VTYPE_INT = "int"
-ENFORCEABLE_TYPES = compat.UniqueFrozenset([
-  VTYPE_STRING,
-  VTYPE_MAYBE_STRING,
-  VTYPE_BOOL,
-  VTYPE_SIZE,
-  VTYPE_INT,
-  ])
-
-# Constant representing that the user does not specify any IP version
-IFACE_NO_IP_VERSION_SPECIFIED = 0
-
-VALID_SERIAL_SPEEDS = compat.UniqueFrozenset([
-  75,
-  110,
-  300,
-  600,
-  1200,
-  1800,
-  2400,
-  4800,
-  9600,
-  14400,
-  19200,
-  28800,
-  38400,
-  57600,
-  115200,
-  230400,
-  345600,
-  460800,
-  ])
-
-# HV parameter names (global namespace)
-HV_BOOT_ORDER = "boot_order"
-HV_CDROM_IMAGE_PATH = "cdrom_image_path"
-HV_KVM_CDROM2_IMAGE_PATH = "cdrom2_image_path"
-HV_KVM_FLOPPY_IMAGE_PATH = "floppy_image_path"
-HV_NIC_TYPE = "nic_type"
-HV_DISK_TYPE = "disk_type"
-HV_KVM_CDROM_DISK_TYPE = "cdrom_disk_type"
-HV_VNC_BIND_ADDRESS = "vnc_bind_address"
-HV_VNC_PASSWORD_FILE = "vnc_password_file"
-HV_VNC_TLS = "vnc_tls"
-HV_VNC_X509 = "vnc_x509_path"
-HV_VNC_X509_VERIFY = "vnc_x509_verify"
-HV_KVM_SPICE_BIND = "spice_bind"
-HV_KVM_SPICE_IP_VERSION = "spice_ip_version"
-HV_KVM_SPICE_PASSWORD_FILE = "spice_password_file"
-HV_KVM_SPICE_LOSSLESS_IMG_COMPR = "spice_image_compression"
-HV_KVM_SPICE_JPEG_IMG_COMPR = "spice_jpeg_wan_compression"
-HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR = "spice_zlib_glz_wan_compression"
-HV_KVM_SPICE_STREAMING_VIDEO_DETECTION = "spice_streaming_video"
-HV_KVM_SPICE_AUDIO_COMPR = "spice_playback_compression"
-HV_KVM_SPICE_USE_TLS = "spice_use_tls"
-HV_KVM_SPICE_TLS_CIPHERS = "spice_tls_ciphers"
-HV_KVM_SPICE_USE_VDAGENT = "spice_use_vdagent"
-HV_ACPI = "acpi"
-HV_PAE = "pae"
-HV_USE_BOOTLOADER = "use_bootloader"
-HV_BOOTLOADER_ARGS = "bootloader_args"
-HV_BOOTLOADER_PATH = "bootloader_path"
-HV_KERNEL_ARGS = "kernel_args"
-HV_KERNEL_PATH = "kernel_path"
-HV_INITRD_PATH = "initrd_path"
-HV_ROOT_PATH = "root_path"
-HV_SERIAL_CONSOLE = "serial_console"
-HV_SERIAL_SPEED = "serial_speed"
-HV_USB_MOUSE = "usb_mouse"
-HV_KEYMAP = "keymap"
-HV_DEVICE_MODEL = "device_model"
-HV_INIT_SCRIPT = "init_script"
-HV_MIGRATION_PORT = "migration_port"
-HV_MIGRATION_BANDWIDTH = "migration_bandwidth"
-HV_MIGRATION_DOWNTIME = "migration_downtime"
-HV_MIGRATION_MODE = "migration_mode"
-HV_USE_LOCALTIME = "use_localtime"
-HV_DISK_CACHE = "disk_cache"
-HV_SECURITY_MODEL = "security_model"
-HV_SECURITY_DOMAIN = "security_domain"
-HV_KVM_FLAG = "kvm_flag"
-HV_VHOST_NET = "vhost_net"
-HV_KVM_USE_CHROOT = "use_chroot"
-HV_CPU_MASK = "cpu_mask"
-HV_MEM_PATH = "mem_path"
-HV_PASSTHROUGH = "pci_pass"
-HV_BLOCKDEV_PREFIX = "blockdev_prefix"
-HV_REBOOT_BEHAVIOR = "reboot_behavior"
-HV_CPU_TYPE = "cpu_type"
-HV_CPU_CAP = "cpu_cap"
-HV_CPU_WEIGHT = "cpu_weight"
-HV_CPU_CORES = "cpu_cores"
-HV_CPU_THREADS = "cpu_threads"
-HV_CPU_SOCKETS = "cpu_sockets"
-HV_SOUNDHW = "soundhw"
-HV_USB_DEVICES = "usb_devices"
-HV_VGA = "vga"
-HV_KVM_EXTRA = "kvm_extra"
-HV_KVM_MACHINE_VERSION = "machine_version"
-HV_KVM_PATH = "kvm_path"
-HV_VIF_TYPE = "vif_type"
-HV_VIF_SCRIPT = "vif_script"
-HV_XEN_CMD = "xen_cmd"
-HV_VNET_HDR = "vnet_hdr"
-HV_VIRIDIAN = "viridian"
-
-
-HVS_PARAMETER_TYPES = {
-  HV_KVM_PATH: VTYPE_STRING,
-  HV_BOOT_ORDER: VTYPE_STRING,
-  HV_KVM_FLOPPY_IMAGE_PATH: VTYPE_STRING,
-  HV_CDROM_IMAGE_PATH: VTYPE_STRING,
-  HV_KVM_CDROM2_IMAGE_PATH: VTYPE_STRING,
-  HV_NIC_TYPE: VTYPE_STRING,
-  HV_DISK_TYPE: VTYPE_STRING,
-  HV_KVM_CDROM_DISK_TYPE: VTYPE_STRING,
-  HV_VNC_PASSWORD_FILE: VTYPE_STRING,
-  HV_VNC_BIND_ADDRESS: VTYPE_STRING,
-  HV_VNC_TLS: VTYPE_BOOL,
-  HV_VNC_X509: VTYPE_STRING,
-  HV_VNC_X509_VERIFY: VTYPE_BOOL,
-  HV_KVM_SPICE_BIND: VTYPE_STRING,
-  HV_KVM_SPICE_IP_VERSION: VTYPE_INT,
-  HV_KVM_SPICE_PASSWORD_FILE: VTYPE_STRING,
-  HV_KVM_SPICE_LOSSLESS_IMG_COMPR: VTYPE_STRING,
-  HV_KVM_SPICE_JPEG_IMG_COMPR: VTYPE_STRING,
-  HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: VTYPE_STRING,
-  HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: VTYPE_STRING,
-  HV_KVM_SPICE_AUDIO_COMPR: VTYPE_BOOL,
-  HV_KVM_SPICE_USE_TLS: VTYPE_BOOL,
-  HV_KVM_SPICE_TLS_CIPHERS: VTYPE_STRING,
-  HV_KVM_SPICE_USE_VDAGENT: VTYPE_BOOL,
-  HV_ACPI: VTYPE_BOOL,
-  HV_PAE: VTYPE_BOOL,
-  HV_USE_BOOTLOADER: VTYPE_BOOL,
-  HV_BOOTLOADER_PATH: VTYPE_STRING,
-  HV_BOOTLOADER_ARGS: VTYPE_STRING,
-  HV_KERNEL_PATH: VTYPE_STRING,
-  HV_KERNEL_ARGS: VTYPE_STRING,
-  HV_INITRD_PATH: VTYPE_STRING,
-  HV_ROOT_PATH: VTYPE_MAYBE_STRING,
-  HV_SERIAL_CONSOLE: VTYPE_BOOL,
-  HV_SERIAL_SPEED: VTYPE_INT,
-  HV_USB_MOUSE: VTYPE_STRING,
-  HV_KEYMAP: VTYPE_STRING,
-  HV_DEVICE_MODEL: VTYPE_STRING,
-  HV_INIT_SCRIPT: VTYPE_STRING,
-  HV_MIGRATION_PORT: VTYPE_INT,
-  HV_MIGRATION_BANDWIDTH: VTYPE_INT,
-  HV_MIGRATION_DOWNTIME: VTYPE_INT,
-  HV_MIGRATION_MODE: VTYPE_STRING,
-  HV_USE_LOCALTIME: VTYPE_BOOL,
-  HV_DISK_CACHE: VTYPE_STRING,
-  HV_SECURITY_MODEL: VTYPE_STRING,
-  HV_SECURITY_DOMAIN: VTYPE_STRING,
-  HV_KVM_FLAG: VTYPE_STRING,
-  HV_VHOST_NET: VTYPE_BOOL,
-  HV_KVM_USE_CHROOT: VTYPE_BOOL,
-  HV_CPU_MASK: VTYPE_STRING,
-  HV_MEM_PATH: VTYPE_STRING,
-  HV_PASSTHROUGH: VTYPE_STRING,
-  HV_BLOCKDEV_PREFIX: VTYPE_STRING,
-  HV_REBOOT_BEHAVIOR: VTYPE_STRING,
-  HV_CPU_TYPE: VTYPE_STRING,
-  HV_CPU_CAP: VTYPE_INT,
-  HV_CPU_WEIGHT: VTYPE_INT,
-  HV_CPU_CORES: VTYPE_INT,
-  HV_CPU_THREADS: VTYPE_INT,
-  HV_CPU_SOCKETS: VTYPE_INT,
-  HV_SOUNDHW: VTYPE_STRING,
-  HV_USB_DEVICES: VTYPE_STRING,
-  HV_VGA: VTYPE_STRING,
-  HV_KVM_EXTRA: VTYPE_STRING,
-  HV_KVM_MACHINE_VERSION: VTYPE_STRING,
-  HV_VIF_TYPE: VTYPE_STRING,
-  HV_VIF_SCRIPT: VTYPE_STRING,
-  HV_XEN_CMD: VTYPE_STRING,
-  HV_VNET_HDR: VTYPE_BOOL,
-  HV_VIRIDIAN: VTYPE_BOOL,
-  }
-
-HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys())
-
-HVS_PARAMETER_TITLES = {
-  HV_ACPI: "ACPI",
-  HV_BOOT_ORDER: "Boot_order",
-  HV_CDROM_IMAGE_PATH: "CDROM_image_path",
-  HV_DISK_TYPE: "Disk_type",
-  HV_INITRD_PATH: "Initrd_path",
-  HV_KERNEL_PATH: "Kernel_path",
-  HV_NIC_TYPE: "NIC_type",
-  HV_PAE: "PAE",
-  HV_VNC_BIND_ADDRESS: "VNC_bind_address",
-  HV_PASSTHROUGH: "pci_pass",
-  HV_CPU_TYPE: "cpu_type",
-  }
-
-# Migration statuses
-HV_MIGRATION_COMPLETED = "completed"
-HV_MIGRATION_ACTIVE = "active"
-HV_MIGRATION_FAILED = "failed"
-HV_MIGRATION_CANCELLED = "cancelled"
-
-HV_MIGRATION_VALID_STATUSES = compat.UniqueFrozenset([
-  HV_MIGRATION_COMPLETED,
-  HV_MIGRATION_ACTIVE,
-  HV_MIGRATION_FAILED,
-  HV_MIGRATION_CANCELLED,
-  ])
-
-HV_MIGRATION_FAILED_STATUSES = compat.UniqueFrozenset([
-  HV_MIGRATION_FAILED,
-  HV_MIGRATION_CANCELLED,
-  ])
-
-# KVM-specific statuses
-HV_KVM_MIGRATION_VALID_STATUSES = HV_MIGRATION_VALID_STATUSES
-
-# Node info keys
-HV_NODEINFO_KEY_VERSION = "hv_version"
-
-# Hypervisor state
-HVST_MEMORY_TOTAL = "mem_total"
-HVST_MEMORY_NODE = "mem_node"
-HVST_MEMORY_HV = "mem_hv"
-HVST_CPU_TOTAL = "cpu_total"
-HVST_CPU_NODE = "cpu_node"
-
-HVST_DEFAULTS = {
-  HVST_MEMORY_TOTAL: 0,
-  HVST_MEMORY_NODE: 0,
-  HVST_MEMORY_HV: 0,
-  HVST_CPU_TOTAL: 1,
-  HVST_CPU_NODE: 1,
-  }
-
-HVSTS_PARAMETER_TYPES = {
-  HVST_MEMORY_TOTAL: VTYPE_INT,
-  HVST_MEMORY_NODE: VTYPE_INT,
-  HVST_MEMORY_HV: VTYPE_INT,
-  HVST_CPU_TOTAL: VTYPE_INT,
-  HVST_CPU_NODE: VTYPE_INT,
-  }
-
-HVSTS_PARAMETERS = frozenset(HVSTS_PARAMETER_TYPES.keys())
-
-# Disk state
-DS_DISK_TOTAL = "disk_total"
-DS_DISK_RESERVED = "disk_reserved"
-DS_DISK_OVERHEAD = "disk_overhead"
-
-DS_DEFAULTS = {
-  DS_DISK_TOTAL: 0,
-  DS_DISK_RESERVED: 0,
-  DS_DISK_OVERHEAD: 0,
-  }
-
-DSS_PARAMETER_TYPES = {
-  DS_DISK_TOTAL: VTYPE_INT,
-  DS_DISK_RESERVED: VTYPE_INT,
-  DS_DISK_OVERHEAD: VTYPE_INT,
-  }
-
-DSS_PARAMETERS = frozenset(DSS_PARAMETER_TYPES.keys())
-DS_VALID_TYPES = compat.UniqueFrozenset([DT_PLAIN])
-
-# Backend parameter names
-BE_MEMORY = "memory" # deprecated and replaced by max and min mem
-BE_MAXMEM = "maxmem"
-BE_MINMEM = "minmem"
-BE_VCPUS = "vcpus"
-BE_AUTO_BALANCE = "auto_balance"
-BE_ALWAYS_FAILOVER = "always_failover"
-BE_SPINDLE_USE = "spindle_use"
-
-BES_PARAMETER_TYPES = {
-  BE_MAXMEM: VTYPE_SIZE,
-  BE_MINMEM: VTYPE_SIZE,
-  BE_VCPUS: VTYPE_INT,
-  BE_AUTO_BALANCE: VTYPE_BOOL,
-  BE_ALWAYS_FAILOVER: VTYPE_BOOL,
-  BE_SPINDLE_USE: VTYPE_INT,
-  }
-
-BES_PARAMETER_TITLES = {
-  BE_AUTO_BALANCE: "Auto_balance",
-  BE_MAXMEM: "ConfigMaxMem",
-  BE_MINMEM: "ConfigMinMem",
-  BE_VCPUS: "ConfigVCPUs",
-  }
-
-BES_PARAMETER_COMPAT = {
-  BE_MEMORY: VTYPE_SIZE,
-  }
-BES_PARAMETER_COMPAT.update(BES_PARAMETER_TYPES)
-
-BES_PARAMETERS = frozenset(BES_PARAMETER_TYPES.keys())
-
-# instance specs
-ISPEC_MEM_SIZE = "memory-size"
-ISPEC_CPU_COUNT = "cpu-count"
-ISPEC_DISK_COUNT = "disk-count"
-ISPEC_DISK_SIZE = "disk-size"
-ISPEC_NIC_COUNT = "nic-count"
-ISPEC_SPINDLE_USE = "spindle-use"
-
-ISPECS_PARAMETER_TYPES = {
-  ISPEC_MEM_SIZE: VTYPE_INT,
-  ISPEC_CPU_COUNT: VTYPE_INT,
-  ISPEC_DISK_COUNT: VTYPE_INT,
-  ISPEC_DISK_SIZE: VTYPE_INT,
-  ISPEC_NIC_COUNT: VTYPE_INT,
-  ISPEC_SPINDLE_USE: VTYPE_INT,
-  }
-
-ISPECS_PARAMETERS = frozenset(ISPECS_PARAMETER_TYPES.keys())
-
-ISPECS_MINMAX = "minmax"
-ISPECS_MIN = "min"
-ISPECS_MAX = "max"
-ISPECS_STD = "std"
-IPOLICY_DTS = "disk-templates"
-IPOLICY_VCPU_RATIO = "vcpu-ratio"
-IPOLICY_SPINDLE_RATIO = "spindle-ratio"
-
-ISPECS_MINMAX_KEYS = compat.UniqueFrozenset([
-  ISPECS_MIN,
-  ISPECS_MAX,
-  ])
-
-IPOLICY_PARAMETERS = compat.UniqueFrozenset([
-  IPOLICY_VCPU_RATIO,
-  IPOLICY_SPINDLE_RATIO,
-  ])
-
-IPOLICY_ALL_KEYS = (IPOLICY_PARAMETERS |
-                    frozenset([ISPECS_MINMAX, ISPECS_STD, IPOLICY_DTS]))
-
-# Node parameter names
-ND_OOB_PROGRAM = "oob_program"
-ND_SPINDLE_COUNT = "spindle_count"
-ND_EXCLUSIVE_STORAGE = "exclusive_storage"
-
-NDS_PARAMETER_TYPES = {
-  ND_OOB_PROGRAM: VTYPE_STRING,
-  ND_SPINDLE_COUNT: VTYPE_INT,
-  ND_EXCLUSIVE_STORAGE: VTYPE_BOOL,
-  }
-
-NDS_PARAMETERS = frozenset(NDS_PARAMETER_TYPES.keys())
-
-NDS_PARAMETER_TITLES = {
-  ND_OOB_PROGRAM: "OutOfBandProgram",
-  ND_SPINDLE_COUNT: "SpindleCount",
-  ND_EXCLUSIVE_STORAGE: "ExclusiveStorage",
-  }
-
-# Logical Disks parameters
-LDP_RESYNC_RATE = "resync-rate"
-LDP_STRIPES = "stripes"
-LDP_BARRIERS = "disabled-barriers"
-LDP_NO_META_FLUSH = "disable-meta-flush"
-LDP_DEFAULT_METAVG = "default-metavg"
-LDP_DISK_CUSTOM = "disk-custom"
-LDP_NET_CUSTOM = "net-custom"
-LDP_PROTOCOL = "protocol"
-LDP_DYNAMIC_RESYNC = "dynamic-resync"
-LDP_PLAN_AHEAD = "c-plan-ahead"
-LDP_FILL_TARGET = "c-fill-target"
-LDP_DELAY_TARGET = "c-delay-target"
-LDP_MAX_RATE = "c-max-rate"
-LDP_MIN_RATE = "c-min-rate"
-LDP_POOL = "pool"
-DISK_LD_TYPES = {
-  LDP_RESYNC_RATE: VTYPE_INT,
-  LDP_STRIPES: VTYPE_INT,
-  LDP_BARRIERS: VTYPE_STRING,
-  LDP_NO_META_FLUSH: VTYPE_BOOL,
-  LDP_DEFAULT_METAVG: VTYPE_STRING,
-  LDP_DISK_CUSTOM: VTYPE_STRING,
-  LDP_NET_CUSTOM: VTYPE_STRING,
-  LDP_PROTOCOL: VTYPE_STRING,
-  LDP_DYNAMIC_RESYNC: VTYPE_BOOL,
-  LDP_PLAN_AHEAD: VTYPE_INT,
-  LDP_FILL_TARGET: VTYPE_INT,
-  LDP_DELAY_TARGET: VTYPE_INT,
-  LDP_MAX_RATE: VTYPE_INT,
-  LDP_MIN_RATE: VTYPE_INT,
-  LDP_POOL: VTYPE_STRING,
-  }
-DISK_LD_PARAMETERS = frozenset(DISK_LD_TYPES.keys())
-
-# Disk template parameters (can be set/changed by the user via gnt-cluster and
-# gnt-group)
-DRBD_RESYNC_RATE = "resync-rate"
-DRBD_DATA_STRIPES = "data-stripes"
-DRBD_META_STRIPES = "meta-stripes"
-DRBD_DISK_BARRIERS = "disk-barriers"
-DRBD_META_BARRIERS = "meta-barriers"
-DRBD_DEFAULT_METAVG = "metavg"
-DRBD_DISK_CUSTOM = "disk-custom"
-DRBD_NET_CUSTOM = "net-custom"
-DRBD_PROTOCOL = "protocol"
-DRBD_DYNAMIC_RESYNC = "dynamic-resync"
-DRBD_PLAN_AHEAD = "c-plan-ahead"
-DRBD_FILL_TARGET = "c-fill-target"
-DRBD_DELAY_TARGET = "c-delay-target"
-DRBD_MAX_RATE = "c-max-rate"
-DRBD_MIN_RATE = "c-min-rate"
-LV_STRIPES = "stripes"
-RBD_POOL = "pool"
-DISK_DT_TYPES = {
-  DRBD_RESYNC_RATE: VTYPE_INT,
-  DRBD_DATA_STRIPES: VTYPE_INT,
-  DRBD_META_STRIPES: VTYPE_INT,
-  DRBD_DISK_BARRIERS: VTYPE_STRING,
-  DRBD_META_BARRIERS: VTYPE_BOOL,
-  DRBD_DEFAULT_METAVG: VTYPE_STRING,
-  DRBD_DISK_CUSTOM: VTYPE_STRING,
-  DRBD_NET_CUSTOM: VTYPE_STRING,
-  DRBD_PROTOCOL: VTYPE_STRING,
-  DRBD_DYNAMIC_RESYNC: VTYPE_BOOL,
-  DRBD_PLAN_AHEAD: VTYPE_INT,
-  DRBD_FILL_TARGET: VTYPE_INT,
-  DRBD_DELAY_TARGET: VTYPE_INT,
-  DRBD_MAX_RATE: VTYPE_INT,
-  DRBD_MIN_RATE: VTYPE_INT,
-  LV_STRIPES: VTYPE_INT,
-  RBD_POOL: VTYPE_STRING,
-  }
-
-DISK_DT_PARAMETERS = frozenset(DISK_DT_TYPES.keys())
-
-# OOB supported commands
-OOB_POWER_ON = "power-on"
-OOB_POWER_OFF = "power-off"
-OOB_POWER_CYCLE = "power-cycle"
-OOB_POWER_STATUS = "power-status"
-OOB_HEALTH = "health"
-
-OOB_COMMANDS = compat.UniqueFrozenset([
-  OOB_POWER_ON,
-  OOB_POWER_OFF,
-  OOB_POWER_CYCLE,
-  OOB_POWER_STATUS,
-  OOB_HEALTH,
-  ])
-
-OOB_POWER_STATUS_POWERED = "powered"
-
-OOB_TIMEOUT = 60 # 60 seconds
-OOB_POWER_DELAY = 2.0 # 2 seconds
-
-OOB_STATUS_OK = "OK"
-OOB_STATUS_WARNING = "WARNING"
-OOB_STATUS_CRITICAL = "CRITICAL"
-OOB_STATUS_UNKNOWN = "UNKNOWN"
-
-OOB_STATUSES = compat.UniqueFrozenset([
-  OOB_STATUS_OK,
-  OOB_STATUS_WARNING,
-  OOB_STATUS_CRITICAL,
-  OOB_STATUS_UNKNOWN,
-  ])
-
-# Instance Parameters Profile
-PP_DEFAULT = "default"
-
-# NIC_* constants are used inside the ganeti config
-NIC_MODE = "mode"
-NIC_LINK = "link"
-
-NIC_MODE_BRIDGED = "bridged"
-NIC_MODE_ROUTED = "routed"
-NIC_MODE_OVS = "openvswitch"
-NIC_IP_POOL = "pool"
-
-NIC_VALID_MODES = compat.UniqueFrozenset([
-  NIC_MODE_BRIDGED,
-  NIC_MODE_ROUTED,
-  NIC_MODE_OVS,
-  ])
-
-RESERVE_ACTION = "reserve"
-RELEASE_ACTION = "release"
-
-NICS_PARAMETER_TYPES = {
-  NIC_MODE: VTYPE_STRING,
-  NIC_LINK: VTYPE_STRING,
-  }
-
-NICS_PARAMETERS = frozenset(NICS_PARAMETER_TYPES.keys())
-
-# IDISK_* constants are used in opcodes, to create/change disks
-IDISK_SIZE = "size"
-IDISK_SPINDLES = "spindles"
-IDISK_MODE = "mode"
-IDISK_ADOPT = "adopt"
-IDISK_VG = "vg"
-IDISK_METAVG = "metavg"
-IDISK_PROVIDER = "provider"
-IDISK_NAME = "name"
-IDISK_PARAMS_TYPES = {
-  IDISK_SIZE: VTYPE_SIZE,
-  IDISK_SPINDLES: VTYPE_INT,
-  IDISK_MODE: VTYPE_STRING,
-  IDISK_ADOPT: VTYPE_STRING,
-  IDISK_VG: VTYPE_STRING,
-  IDISK_METAVG: VTYPE_STRING,
-  IDISK_PROVIDER: VTYPE_STRING,
-  IDISK_NAME: VTYPE_MAYBE_STRING,
-  }
-IDISK_PARAMS = frozenset(IDISK_PARAMS_TYPES.keys())
-
-MODIFIABLE_IDISK_PARAMS_TYPES = {
-  IDISK_MODE: VTYPE_STRING,
-  IDISK_NAME: VTYPE_STRING,
-  }
-MODIFIABLE_IDISK_PARAMS = frozenset(MODIFIABLE_IDISK_PARAMS_TYPES.keys())
-
-# INIC_* constants are used in opcodes, to create/change nics
-INIC_MAC = "mac"
-INIC_IP = "ip"
-INIC_MODE = "mode"
-INIC_LINK = "link"
-INIC_NETWORK = "network"
-INIC_NAME = "name"
-INIC_PARAMS_TYPES = {
-  INIC_IP: VTYPE_MAYBE_STRING,
-  INIC_LINK: VTYPE_STRING,
-  INIC_MAC: VTYPE_STRING,
-  INIC_MODE: VTYPE_STRING,
-  INIC_NETWORK: VTYPE_MAYBE_STRING,
-  INIC_NAME: VTYPE_MAYBE_STRING,
-  }
-INIC_PARAMS = frozenset(INIC_PARAMS_TYPES.keys())
-
-# Hypervisor constants
-HT_XEN_PVM = "xen-pvm"
-HT_FAKE = "fake"
-HT_XEN_HVM = "xen-hvm"
-HT_KVM = "kvm"
-HT_CHROOT = "chroot"
-HT_LXC = "lxc"
-HYPER_TYPES = compat.UniqueFrozenset([
-  HT_XEN_PVM,
-  HT_FAKE,
-  HT_XEN_HVM,
-  HT_KVM,
-  HT_CHROOT,
-  HT_LXC,
-  ])
-HTS_REQ_PORT = compat.UniqueFrozenset([HT_XEN_HVM, HT_KVM])
-
-VNC_BASE_PORT = 5900
-VNC_DEFAULT_BIND_ADDRESS = IP4_ADDRESS_ANY
-
-# NIC types
-HT_NIC_RTL8139 = "rtl8139"
-HT_NIC_NE2K_PCI = "ne2k_pci"
-HT_NIC_NE2K_ISA = "ne2k_isa"
-HT_NIC_I82551 = "i82551"
-HT_NIC_I85557B = "i82557b"
-HT_NIC_I8259ER = "i82559er"
-HT_NIC_PCNET = "pcnet"
-HT_NIC_E1000 = "e1000"
-HT_NIC_PARAVIRTUAL = HT_DISK_PARAVIRTUAL = "paravirtual"
-
-HT_HVM_VALID_NIC_TYPES = compat.UniqueFrozenset([
-  HT_NIC_RTL8139,
-  HT_NIC_NE2K_PCI,
-  HT_NIC_E1000,
-  HT_NIC_NE2K_ISA,
-  HT_NIC_PARAVIRTUAL,
-  ])
-HT_KVM_VALID_NIC_TYPES = compat.UniqueFrozenset([
-  HT_NIC_RTL8139,
-  HT_NIC_NE2K_PCI,
-  HT_NIC_NE2K_ISA,
-  HT_NIC_I82551,
-  HT_NIC_I85557B,
-  HT_NIC_I8259ER,
-  HT_NIC_PCNET,
-  HT_NIC_E1000,
-  HT_NIC_PARAVIRTUAL,
-  ])
-
-# Vif types
-# default vif type in xen-hvm
-HT_HVM_VIF_IOEMU = "ioemu"
-HT_HVM_VIF_VIF = "vif"
-HT_HVM_VALID_VIF_TYPES = compat.UniqueFrozenset([
-  HT_HVM_VIF_IOEMU,
-  HT_HVM_VIF_VIF,
-  ])
-
-# Disk types
-HT_DISK_IOEMU = "ioemu"
-HT_DISK_IDE = "ide"
-HT_DISK_SCSI = "scsi"
-HT_DISK_SD = "sd"
-HT_DISK_MTD = "mtd"
-HT_DISK_PFLASH = "pflash"
-
-HT_CACHE_DEFAULT = "default"
-HT_CACHE_NONE = "none"
-HT_CACHE_WTHROUGH = "writethrough"
-HT_CACHE_WBACK = "writeback"
-HT_VALID_CACHE_TYPES = compat.UniqueFrozenset([
-  HT_CACHE_DEFAULT,
-  HT_CACHE_NONE,
-  HT_CACHE_WTHROUGH,
-  HT_CACHE_WBACK,
-  ])
-
-HT_HVM_VALID_DISK_TYPES = compat.UniqueFrozenset([
-  HT_DISK_PARAVIRTUAL,
-  HT_DISK_IOEMU,
-  ])
-HT_KVM_VALID_DISK_TYPES = compat.UniqueFrozenset([
-  HT_DISK_PARAVIRTUAL,
-  HT_DISK_IDE,
-  HT_DISK_SCSI,
-  HT_DISK_SD,
-  HT_DISK_MTD,
-  HT_DISK_PFLASH,
-  ])
-
-# Mouse types:
-HT_MOUSE_MOUSE = "mouse"
-HT_MOUSE_TABLET = "tablet"
-
-HT_KVM_VALID_MOUSE_TYPES = compat.UniqueFrozenset([
-  HT_MOUSE_MOUSE,
-  HT_MOUSE_TABLET,
-  ])
-
-# Boot order
-HT_BO_FLOPPY = "floppy"
-HT_BO_CDROM = "cdrom"
-HT_BO_DISK = "disk"
-HT_BO_NETWORK = "network"
-
-HT_KVM_VALID_BO_TYPES = compat.UniqueFrozenset([
-  HT_BO_FLOPPY,
-  HT_BO_CDROM,
-  HT_BO_DISK,
-  HT_BO_NETWORK,
-  ])
-
-# SPICE lossless image compression options
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ = "auto_glz"
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ = "auto_lz"
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC = "quic"
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ = "glz"
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ = "lz"
-HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF = "off"
-
-HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS = compat.UniqueFrozenset([
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ,
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ,
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC,
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ,
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ,
-  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF,
-  ])
-
-# SPICE lossy image compression options (valid for both jpeg and zlib-glz)
-HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO = "auto"
-HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER = "never"
-HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS = "always"
-
-HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS = compat.UniqueFrozenset([
-  HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO,
-  HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER,
-  HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS,
-  ])
-
-# SPICE video stream detection
-HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF = "off"
-HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL = "all"
-HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER = "filter"
-
-HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS = compat.UniqueFrozenset([
-  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF,
-  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL,
-  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER,
-  ])
-
-# Security models
-HT_SM_NONE = "none"
-HT_SM_USER = "user"
-HT_SM_POOL = "pool"
-
-HT_KVM_VALID_SM_TYPES = compat.UniqueFrozenset([
-  HT_SM_NONE,
-  HT_SM_USER,
-  HT_SM_POOL,
-  ])
-
-# Kvm flag values
-HT_KVM_ENABLED = "enabled"
-HT_KVM_DISABLED = "disabled"
-
-HT_KVM_FLAG_VALUES = compat.UniqueFrozenset([HT_KVM_ENABLED, HT_KVM_DISABLED])
-
-# Migration type
-HT_MIGRATION_LIVE = "live"
-HT_MIGRATION_NONLIVE = "non-live"
-HT_MIGRATION_MODES = compat.UniqueFrozenset([
-  HT_MIGRATION_LIVE,
-  HT_MIGRATION_NONLIVE,
-  ])
-
-# Cluster Verify steps
-VERIFY_NPLUSONE_MEM = "nplusone_mem"
-VERIFY_OPTIONAL_CHECKS = compat.UniqueFrozenset([VERIFY_NPLUSONE_MEM])
-
-# Cluster Verify error classes
-CV_TCLUSTER = "cluster"
-CV_TGROUP = "group"
-CV_TNODE = "node"
-CV_TINSTANCE = "instance"
-
-# Cluster Verify error codes and documentation
-CV_ECLUSTERCFG = \
-  (CV_TCLUSTER, "ECLUSTERCFG", "Cluster configuration verification failure")
-CV_ECLUSTERCERT = \
-  (CV_TCLUSTER, "ECLUSTERCERT",
-   "Cluster certificate files verification failure")
-CV_ECLUSTERFILECHECK = \
-  (CV_TCLUSTER, "ECLUSTERFILECHECK",
-   "Cluster configuration verification failure")
-CV_ECLUSTERDANGLINGNODES = \
-  (CV_TNODE, "ECLUSTERDANGLINGNODES",
-   "Some nodes belong to non-existing groups")
-CV_ECLUSTERDANGLINGINST = \
-  (CV_TNODE, "ECLUSTERDANGLINGINST",
-   "Some instances have a non-existing primary node")
-CV_EGROUPDIFFERENTPVSIZE = \
-  (CV_TGROUP, "EGROUPDIFFERENTPVSIZE", "PVs in the group have different sizes")
-CV_EINSTANCEBADNODE = \
-  (CV_TINSTANCE, "EINSTANCEBADNODE",
-   "Instance marked as running lives on an offline node")
-CV_EINSTANCEDOWN = \
-  (CV_TINSTANCE, "EINSTANCEDOWN", "Instance not running on its primary node")
-CV_EINSTANCELAYOUT = \
-  (CV_TINSTANCE, "EINSTANCELAYOUT", "Instance has multiple secondary nodes")
-CV_EINSTANCEMISSINGDISK = \
-  (CV_TINSTANCE, "EINSTANCEMISSINGDISK", "Missing volume on an instance")
-CV_EINSTANCEFAULTYDISK = \
-  (CV_TINSTANCE, "EINSTANCEFAULTYDISK",
-   "Impossible to retrieve status for a disk")
-CV_EINSTANCEWRONGNODE = \
-  (CV_TINSTANCE, "EINSTANCEWRONGNODE", "Instance running on the wrong node")
-CV_EINSTANCESPLITGROUPS = \
-  (CV_TINSTANCE, "EINSTANCESPLITGROUPS",
-   "Instance with primary and secondary nodes in different groups")
-CV_EINSTANCEPOLICY = \
-  (CV_TINSTANCE, "EINSTANCEPOLICY",
-   "Instance does not meet policy")
-CV_EINSTANCEUNSUITABLENODE = \
-  (CV_TINSTANCE, "EINSTANCEUNSUITABLENODE",
-   "Instance running on nodes that are not suitable for it")
-CV_EINSTANCEMISSINGCFGPARAMETER = \
-  (CV_TINSTANCE, "EINSTANCEMISSINGCFGPARAMETER",
-   "A configuration parameter for an instance is missing")
-CV_ENODEDRBD = \
-  (CV_TNODE, "ENODEDRBD", "Error parsing the DRBD status file")
-CV_ENODEDRBDVERSION = \
-  (CV_TNODE, "ENODEDRBDVERSION", "DRBD version mismatch within a node group")
-CV_ENODEDRBDHELPER = \
-  (CV_TNODE, "ENODEDRBDHELPER", "Error caused by the DRBD helper")
-CV_ENODEFILECHECK = \
-  (CV_TNODE, "ENODEFILECHECK",
-   "Error retrieving the checksum of the node files")
-CV_ENODEHOOKS = \
-  (CV_TNODE, "ENODEHOOKS", "Communication failure in hooks execution")
-CV_ENODEHV = \
-  (CV_TNODE, "ENODEHV", "Hypervisor parameters verification failure")
-CV_ENODELVM = \
-  (CV_TNODE, "ENODELVM", "LVM-related node error")
-CV_ENODEN1 = \
-  (CV_TNODE, "ENODEN1", "Not enough memory to accommodate instance failovers")
-CV_ENODENET = \
-  (CV_TNODE, "ENODENET", "Network-related node error")
-CV_ENODEOS = \
-  (CV_TNODE, "ENODEOS", "OS-related node error")
-CV_ENODEORPHANINSTANCE = \
-  (CV_TNODE, "ENODEORPHANINSTANCE", "Unknown intance running on a node")
-CV_ENODEORPHANLV = \
-  (CV_TNODE, "ENODEORPHANLV", "Unknown LVM logical volume")
-CV_ENODERPC = \
-  (CV_TNODE, "ENODERPC",
-   "Error during connection to the primary node of an instance")
-CV_ENODESSH = \
-  (CV_TNODE, "ENODESSH", "SSH-related node error")
-CV_ENODEVERSION = \
-  (CV_TNODE, "ENODEVERSION",
-   "Protocol version mismatch or Ganeti version mismatch")
-CV_ENODESETUP = \
-  (CV_TNODE, "ENODESETUP", "Node setup error")
-CV_ENODETIME = \
-  (CV_TNODE, "ENODETIME", "Node returned invalid time")
-CV_ENODEOOBPATH = \
-  (CV_TNODE, "ENODEOOBPATH", "Invalid Out Of Band path")
-CV_ENODEUSERSCRIPTS = \
-  (CV_TNODE, "ENODEUSERSCRIPTS", "User scripts not present or not executable")
-CV_ENODEFILESTORAGEPATHS = \
-  (CV_TNODE, "ENODEFILESTORAGEPATHS", "Detected bad file storage paths")
-CV_ENODEFILESTORAGEPATHUNUSABLE = \
-  (CV_TNODE, "ENODEFILESTORAGEPATHUNUSABLE", "File storage path unusable")
-CV_ENODESHAREDFILESTORAGEPATHUNUSABLE = \
-  (CV_TNODE, "ENODESHAREDFILESTORAGEPATHUNUSABLE",
-      "Shared file storage path unusable")
-
-CV_ALL_ECODES = compat.UniqueFrozenset([
-  CV_ECLUSTERCFG,
-  CV_ECLUSTERCERT,
-  CV_ECLUSTERFILECHECK,
-  CV_ECLUSTERDANGLINGNODES,
-  CV_ECLUSTERDANGLINGINST,
-  CV_EINSTANCEBADNODE,
-  CV_EINSTANCEDOWN,
-  CV_EINSTANCELAYOUT,
-  CV_EINSTANCEMISSINGDISK,
-  CV_EINSTANCEFAULTYDISK,
-  CV_EINSTANCEWRONGNODE,
-  CV_EINSTANCESPLITGROUPS,
-  CV_EINSTANCEPOLICY,
-  CV_ENODEDRBD,
-  CV_ENODEDRBDHELPER,
-  CV_ENODEFILECHECK,
-  CV_ENODEHOOKS,
-  CV_ENODEHV,
-  CV_ENODELVM,
-  CV_ENODEN1,
-  CV_ENODENET,
-  CV_ENODEOS,
-  CV_ENODEORPHANINSTANCE,
-  CV_ENODEORPHANLV,
-  CV_ENODERPC,
-  CV_ENODESSH,
-  CV_ENODEVERSION,
-  CV_ENODESETUP,
-  CV_ENODETIME,
-  CV_ENODEOOBPATH,
-  CV_ENODEUSERSCRIPTS,
-  CV_ENODEFILESTORAGEPATHS,
-  CV_ENODEFILESTORAGEPATHUNUSABLE,
-  CV_ENODESHAREDFILESTORAGEPATHUNUSABLE,
-  ])
-
-CV_ALL_ECODES_STRINGS = \
-  compat.UniqueFrozenset(estr for (_, estr, _) in CV_ALL_ECODES)
-
-# Node verify constants
-NV_BRIDGES = "bridges"
-NV_DRBDHELPER = "drbd-helper"
-NV_DRBDVERSION = "drbd-version"
-NV_DRBDLIST = "drbd-list"
-NV_EXCLUSIVEPVS = "exclusive-pvs"
-NV_FILELIST = "filelist"
-NV_ACCEPTED_STORAGE_PATHS = "allowed-file-storage-paths"
-NV_FILE_STORAGE_PATH = "file-storage-path"
-NV_SHARED_FILE_STORAGE_PATH = "shared-file-storage-path"
-NV_HVINFO = "hvinfo"
-NV_HVPARAMS = "hvparms"
-NV_HYPERVISOR = "hypervisor"
-NV_INSTANCELIST = "instancelist"
-NV_LVLIST = "lvlist"
-NV_MASTERIP = "master-ip"
-NV_NODELIST = "nodelist"
-NV_NODENETTEST = "node-net-test"
-NV_NODESETUP = "nodesetup"
-NV_OOB_PATHS = "oob-paths"
-NV_OSLIST = "oslist"
-NV_PVLIST = "pvlist"
-NV_TIME = "time"
-NV_USERSCRIPTS = "user-scripts"
-NV_VERSION = "version"
-NV_VGLIST = "vglist"
-NV_VMNODES = "vmnodes"
-
-# Instance status
-INSTST_RUNNING = "running"
-INSTST_ADMINDOWN = "ADMIN_down"
-INSTST_ADMINOFFLINE = "ADMIN_offline"
-INSTST_NODEOFFLINE = "ERROR_nodeoffline"
-INSTST_NODEDOWN = "ERROR_nodedown"
-INSTST_WRONGNODE = "ERROR_wrongnode"
-INSTST_ERRORUP = "ERROR_up"
-INSTST_ERRORDOWN = "ERROR_down"
-INSTST_ALL = compat.UniqueFrozenset([
-  INSTST_RUNNING,
-  INSTST_ADMINDOWN,
-  INSTST_ADMINOFFLINE,
-  INSTST_NODEOFFLINE,
-  INSTST_NODEDOWN,
-  INSTST_WRONGNODE,
-  INSTST_ERRORUP,
-  INSTST_ERRORDOWN,
-  ])
-
-# Admin states
-ADMINST_UP = "up"
-ADMINST_DOWN = "down"
-ADMINST_OFFLINE = "offline"
-ADMINST_ALL = compat.UniqueFrozenset([
-  ADMINST_UP,
-  ADMINST_DOWN,
-  ADMINST_OFFLINE,
-  ])
-
-# Node roles
-NR_REGULAR = "R"
-NR_MASTER = "M"
-NR_MCANDIDATE = "C"
-NR_DRAINED = "D"
-NR_OFFLINE = "O"
-NR_ALL = compat.UniqueFrozenset([
-  NR_REGULAR,
-  NR_MASTER,
-  NR_MCANDIDATE,
-  NR_DRAINED,
-  NR_OFFLINE,
-  ])
-
-# SSL certificate check constants (in days)
-SSL_CERT_EXPIRATION_WARN = 30
-SSL_CERT_EXPIRATION_ERROR = 7
-
-# Allocator framework constants
-IALLOCATOR_VERSION = 2
-IALLOCATOR_DIR_IN = "in"
-IALLOCATOR_DIR_OUT = "out"
-VALID_IALLOCATOR_DIRECTIONS = compat.UniqueFrozenset([
-  IALLOCATOR_DIR_IN,
-  IALLOCATOR_DIR_OUT,
-  ])
-IALLOCATOR_MODE_ALLOC = "allocate"
-IALLOCATOR_MODE_RELOC = "relocate"
-IALLOCATOR_MODE_CHG_GROUP = "change-group"
-IALLOCATOR_MODE_NODE_EVAC = "node-evacuate"
-IALLOCATOR_MODE_MULTI_ALLOC = "multi-allocate"
-VALID_IALLOCATOR_MODES = compat.UniqueFrozenset([
-  IALLOCATOR_MODE_ALLOC,
-  IALLOCATOR_MODE_RELOC,
-  IALLOCATOR_MODE_CHG_GROUP,
-  IALLOCATOR_MODE_NODE_EVAC,
-  IALLOCATOR_MODE_MULTI_ALLOC,
-  ])
-IALLOCATOR_SEARCH_PATH = _autoconf.IALLOCATOR_SEARCH_PATH
-DEFAULT_IALLOCATOR_SHORTCUT = "."
-
-IALLOCATOR_NEVAC_PRI = "primary-only"
-IALLOCATOR_NEVAC_SEC = "secondary-only"
-IALLOCATOR_NEVAC_ALL = "all"
-IALLOCATOR_NEVAC_MODES = compat.UniqueFrozenset([
-  IALLOCATOR_NEVAC_PRI,
-  IALLOCATOR_NEVAC_SEC,
-  IALLOCATOR_NEVAC_ALL,
-  ])
-
-# Node evacuation
-NODE_EVAC_PRI = "primary-only"
-NODE_EVAC_SEC = "secondary-only"
-NODE_EVAC_ALL = "all"
-NODE_EVAC_MODES = compat.UniqueFrozenset([
-  NODE_EVAC_PRI,
-  NODE_EVAC_SEC,
-  NODE_EVAC_ALL,
-  ])
-
-# Job queue
-JOB_QUEUE_VERSION = 1
-JOB_QUEUE_SIZE_HARD_LIMIT = 5000
-JOB_QUEUE_FILES_PERMS = 0640
-
 JOB_ID_TEMPLATE = r"\d+"
 JOB_FILE_RE = re.compile(r"^job-(%s)$" % JOB_ID_TEMPLATE)
 
-# unchanged job return
-JOB_NOTCHANGED = "nochange"
-
-# Job status
-JOB_STATUS_QUEUED = "queued"
-JOB_STATUS_WAITING = "waiting"
-JOB_STATUS_CANCELING = "canceling"
-JOB_STATUS_RUNNING = "running"
-JOB_STATUS_CANCELED = "canceled"
-JOB_STATUS_SUCCESS = "success"
-JOB_STATUS_ERROR = "error"
-JOBS_PENDING = compat.UniqueFrozenset([
-  JOB_STATUS_QUEUED,
-  JOB_STATUS_WAITING,
-  JOB_STATUS_CANCELING,
-  ])
-JOBS_FINALIZED = compat.UniqueFrozenset([
-  JOB_STATUS_CANCELED,
-  JOB_STATUS_SUCCESS,
-  JOB_STATUS_ERROR,
-  ])
-JOB_STATUS_ALL = compat.UniqueFrozenset([
-  JOB_STATUS_RUNNING,
-  ]) | JOBS_PENDING | JOBS_FINALIZED
-
-# OpCode status
-# not yet finalized
-OP_STATUS_QUEUED = "queued"
-OP_STATUS_WAITING = "waiting"
-OP_STATUS_CANCELING = "canceling"
-OP_STATUS_RUNNING = "running"
-# finalized
-OP_STATUS_CANCELED = "canceled"
-OP_STATUS_SUCCESS = "success"
-OP_STATUS_ERROR = "error"
-OPS_FINALIZED = compat.UniqueFrozenset([
-  OP_STATUS_CANCELED,
-  OP_STATUS_SUCCESS,
-  OP_STATUS_ERROR,
-  ])
-
-# OpCode priority
-OP_PRIO_LOWEST = +19
-OP_PRIO_HIGHEST = -20
-
-OP_PRIO_LOW = +10
-OP_PRIO_NORMAL = 0
-OP_PRIO_HIGH = -10
-
-OP_PRIO_SUBMIT_VALID = compat.UniqueFrozenset([
-  OP_PRIO_LOW,
-  OP_PRIO_NORMAL,
-  OP_PRIO_HIGH,
-  ])
-
-OP_PRIO_DEFAULT = OP_PRIO_NORMAL
-
-# Lock recalculate mode
-LOCKS_REPLACE = "replace"
-LOCKS_APPEND = "append"
-
-# Lock timeout (sum) before we should go into blocking acquire (still
-# can be reset by priority change); computed as max time (10 hours)
-# before we should actually go into blocking acquire given that we
-# start from default priority level; in seconds
-# TODO
-LOCK_ATTEMPTS_TIMEOUT = 10 * 3600 / (OP_PRIO_DEFAULT - OP_PRIO_HIGHEST)
-LOCK_ATTEMPTS_MAXWAIT = 15.0
-LOCK_ATTEMPTS_MINWAIT = 1.0
-
-# Execution log types
-ELOG_MESSAGE = "message"
-ELOG_REMOTE_IMPORT = "remote-import"
-ELOG_JQUEUE_TEST = "jqueue-test"
-
-# /etc/hosts modification
-ETC_HOSTS_ADD = "add"
-ETC_HOSTS_REMOVE = "remove"
-
-# Job queue test
-JQT_MSGPREFIX = "TESTMSG="
-JQT_EXPANDNAMES = "expandnames"
-JQT_EXEC = "exec"
-JQT_LOGMSG = "logmsg"
-JQT_STARTMSG = "startmsg"
-JQT_ALL = compat.UniqueFrozenset([
-  JQT_EXPANDNAMES,
-  JQT_EXEC,
-  JQT_LOGMSG,
-  JQT_STARTMSG,
-  ])
-
-# Query resources
-QR_CLUSTER = "cluster"
-QR_INSTANCE = "instance"
-QR_NODE = "node"
-QR_LOCK = "lock"
-QR_GROUP = "group"
-QR_OS = "os"
-QR_JOB = "job"
-QR_EXPORT = "export"
-QR_NETWORK = "network"
-QR_EXTSTORAGE = "extstorage"
-
-#: List of resources which can be queried using L{opcodes.OpQuery}
-QR_VIA_OP = compat.UniqueFrozenset([
-  QR_CLUSTER,
-  QR_INSTANCE,
-  QR_NODE,
-  QR_GROUP,
-  QR_OS,
-  QR_EXPORT,
-  QR_NETWORK,
-  QR_EXTSTORAGE,
-  ])
-
-#: List of resources which can be queried using Local UniX Interface
-QR_VIA_LUXI = QR_VIA_OP.union([
-  QR_LOCK,
-  QR_JOB,
-  ])
-
-#: List of resources which can be queried using RAPI
-QR_VIA_RAPI = QR_VIA_LUXI
-
-# Query field types
-QFT_UNKNOWN = "unknown"
-QFT_TEXT = "text"
-QFT_BOOL = "bool"
-QFT_NUMBER = "number"
-QFT_UNIT = "unit"
-QFT_TIMESTAMP = "timestamp"
-QFT_OTHER = "other"
-
-#: All query field types
-QFT_ALL = compat.UniqueFrozenset([
-  QFT_UNKNOWN,
-  QFT_TEXT,
-  QFT_BOOL,
-  QFT_NUMBER,
-  QFT_UNIT,
-  QFT_TIMESTAMP,
-  QFT_OTHER,
-  ])
-
-# Query result field status (don't change or reuse values as they're used by
-# clients)
-#: Normal field status
-RS_NORMAL = 0
-#: Unknown field
-RS_UNKNOWN = 1
-#: No data (e.g. RPC error), can be used instead of L{RS_OFFLINE}
-RS_NODATA = 2
-#: Value unavailable/unsupported for item; if this field is supported
-#: but we cannot get the data for the moment, RS_NODATA or
-#: RS_OFFLINE should be used
-RS_UNAVAIL = 3
-#: Resource marked offline
-RS_OFFLINE = 4
-
-RS_ALL = compat.UniqueFrozenset([
-  RS_NORMAL,
-  RS_UNKNOWN,
-  RS_NODATA,
-  RS_UNAVAIL,
-  RS_OFFLINE,
-  ])
-
-#: Dictionary with special field cases and their verbose/terse formatting
-RSS_DESCRIPTION = {
-  RS_UNKNOWN: ("(unknown)", "??"),
-  RS_NODATA: ("(nodata)", "?"),
-  RS_OFFLINE: ("(offline)", "*"),
-  RS_UNAVAIL: ("(unavail)", "-"),
-  }
-
-# max dynamic devices
-MAX_NICS = 8
-MAX_DISKS = 16
-
-# SSCONF file prefix
-SSCONF_FILEPREFIX = "ssconf_"
-# SSCONF keys
-SS_CLUSTER_NAME = "cluster_name"
-SS_CLUSTER_TAGS = "cluster_tags"
-SS_FILE_STORAGE_DIR = "file_storage_dir"
-SS_SHARED_FILE_STORAGE_DIR = "shared_file_storage_dir"
-SS_MASTER_CANDIDATES = "master_candidates"
-SS_MASTER_CANDIDATES_IPS = "master_candidates_ips"
-SS_MASTER_IP = "master_ip"
-SS_MASTER_NETDEV = "master_netdev"
-SS_MASTER_NETMASK = "master_netmask"
-SS_MASTER_NODE = "master_node"
-SS_NODE_LIST = "node_list"
-SS_NODE_PRIMARY_IPS = "node_primary_ips"
-SS_NODE_SECONDARY_IPS = "node_secondary_ips"
-SS_OFFLINE_NODES = "offline_nodes"
-SS_ONLINE_NODES = "online_nodes"
-SS_PRIMARY_IP_FAMILY = "primary_ip_family"
-SS_INSTANCE_LIST = "instance_list"
-SS_RELEASE_VERSION = "release_version"
-SS_HYPERVISOR_LIST = "hypervisor_list"
-SS_MAINTAIN_NODE_HEALTH = "maintain_node_health"
-SS_UID_POOL = "uid_pool"
-SS_NODEGROUPS = "nodegroups"
-SS_NETWORKS = "networks"
-
-# This is not a complete SSCONF key, but the prefix for the hypervisor keys
-SS_HVPARAMS_PREF = "hvparams_"
-
-# Hvparams keys:
-SS_HVPARAMS_XEN_PVM = SS_HVPARAMS_PREF + HT_XEN_PVM
-SS_HVPARAMS_XEN_FAKE = SS_HVPARAMS_PREF + HT_FAKE
-SS_HVPARAMS_XEN_HVM = SS_HVPARAMS_PREF + HT_XEN_HVM
-SS_HVPARAMS_XEN_KVM = SS_HVPARAMS_PREF + HT_KVM
-SS_HVPARAMS_XEN_CHROOT = SS_HVPARAMS_PREF + HT_CHROOT
-SS_HVPARAMS_XEN_LXC = SS_HVPARAMS_PREF + HT_LXC
-
-VALID_SS_HVPARAMS_KEYS = compat.UniqueFrozenset([
-  SS_HVPARAMS_XEN_PVM,
-  SS_HVPARAMS_XEN_FAKE,
-  SS_HVPARAMS_XEN_HVM,
-  SS_HVPARAMS_XEN_KVM,
-  SS_HVPARAMS_XEN_CHROOT,
-  SS_HVPARAMS_XEN_LXC,
-  ])
-
-SS_FILE_PERMS = 0444
-
-# cluster wide default parameters
-DEFAULT_ENABLED_HYPERVISOR = HT_XEN_PVM
-
-HVC_DEFAULTS = {
-  HT_XEN_PVM: {
-    HV_USE_BOOTLOADER: False,
-    HV_BOOTLOADER_PATH: XEN_BOOTLOADER,
-    HV_BOOTLOADER_ARGS: "",
-    HV_KERNEL_PATH: XEN_KERNEL,
-    HV_INITRD_PATH: "",
-    HV_ROOT_PATH: "/dev/xvda1",
-    HV_KERNEL_ARGS: "ro",
-    HV_MIGRATION_PORT: 8002,
-    HV_MIGRATION_MODE: HT_MIGRATION_LIVE,
-    HV_BLOCKDEV_PREFIX: "sd",
-    HV_REBOOT_BEHAVIOR: INSTANCE_REBOOT_ALLOWED,
-    HV_CPU_MASK: CPU_PINNING_ALL,
-    HV_CPU_CAP: 0,
-    HV_CPU_WEIGHT: 256,
-    HV_VIF_SCRIPT: "",
-    HV_XEN_CMD: XEN_CMD_XM,
-    },
-  HT_XEN_HVM: {
-    HV_BOOT_ORDER: "cd",
-    HV_CDROM_IMAGE_PATH: "",
-    HV_NIC_TYPE: HT_NIC_RTL8139,
-    HV_DISK_TYPE: HT_DISK_PARAVIRTUAL,
-    HV_VNC_BIND_ADDRESS: IP4_ADDRESS_ANY,
-    HV_VNC_PASSWORD_FILE: pathutils.VNC_PASSWORD_FILE,
-    HV_ACPI: True,
-    HV_PAE: True,
-    HV_KERNEL_PATH: "/usr/lib/xen/boot/hvmloader",
-    HV_DEVICE_MODEL: "/usr/lib/xen/bin/qemu-dm",
-    HV_MIGRATION_PORT: 8002,
-    HV_MIGRATION_MODE: HT_MIGRATION_NONLIVE,
-    HV_USE_LOCALTIME: False,
-    HV_BLOCKDEV_PREFIX: "hd",
-    HV_PASSTHROUGH: "",
-    HV_REBOOT_BEHAVIOR: INSTANCE_REBOOT_ALLOWED,
-    HV_CPU_MASK: CPU_PINNING_ALL,
-    HV_CPU_CAP: 0,
-    HV_CPU_WEIGHT: 256,
-    HV_VIF_TYPE: HT_HVM_VIF_IOEMU,
-    HV_VIF_SCRIPT: "",
-    HV_VIRIDIAN: False,
-    HV_XEN_CMD: XEN_CMD_XM,
-    },
-  HT_KVM: {
-    HV_KVM_PATH: KVM_PATH,
-    HV_KERNEL_PATH: KVM_KERNEL,
-    HV_INITRD_PATH: "",
-    HV_KERNEL_ARGS: "ro",
-    HV_ROOT_PATH: "/dev/vda1",
-    HV_ACPI: True,
-    HV_SERIAL_CONSOLE: True,
-    HV_SERIAL_SPEED: 38400,
-    HV_VNC_BIND_ADDRESS: "",
-    HV_VNC_TLS: False,
-    HV_VNC_X509: "",
-    HV_VNC_X509_VERIFY: False,
-    HV_VNC_PASSWORD_FILE: "",
-    HV_KVM_SPICE_BIND: "",
-    HV_KVM_SPICE_IP_VERSION: IFACE_NO_IP_VERSION_SPECIFIED,
-    HV_KVM_SPICE_PASSWORD_FILE: "",
-    HV_KVM_SPICE_LOSSLESS_IMG_COMPR: "",
-    HV_KVM_SPICE_JPEG_IMG_COMPR: "",
-    HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "",
-    HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "",
-    HV_KVM_SPICE_AUDIO_COMPR: True,
-    HV_KVM_SPICE_USE_TLS: False,
-    HV_KVM_SPICE_TLS_CIPHERS: OPENSSL_CIPHERS,
-    HV_KVM_SPICE_USE_VDAGENT: True,
-    HV_KVM_FLOPPY_IMAGE_PATH: "",
-    HV_CDROM_IMAGE_PATH: "",
-    HV_KVM_CDROM2_IMAGE_PATH: "",
-    HV_BOOT_ORDER: HT_BO_DISK,
-    HV_NIC_TYPE: HT_NIC_PARAVIRTUAL,
-    HV_DISK_TYPE: HT_DISK_PARAVIRTUAL,
-    HV_KVM_CDROM_DISK_TYPE: "",
-    HV_USB_MOUSE: "",
-    HV_KEYMAP: "",
-    HV_MIGRATION_PORT: 8102,
-    HV_MIGRATION_BANDWIDTH: 32, # MiB/s
-    HV_MIGRATION_DOWNTIME: 30,  # ms
-    HV_MIGRATION_MODE: HT_MIGRATION_LIVE,
-    HV_USE_LOCALTIME: False,
-    HV_DISK_CACHE: HT_CACHE_DEFAULT,
-    HV_SECURITY_MODEL: HT_SM_NONE,
-    HV_SECURITY_DOMAIN: "",
-    HV_KVM_FLAG: "",
-    HV_VHOST_NET: False,
-    HV_KVM_USE_CHROOT: False,
-    HV_MEM_PATH: "",
-    HV_REBOOT_BEHAVIOR: INSTANCE_REBOOT_ALLOWED,
-    HV_CPU_MASK: CPU_PINNING_ALL,
-    HV_CPU_TYPE: "",
-    HV_CPU_CORES: 0,
-    HV_CPU_THREADS: 0,
-    HV_CPU_SOCKETS: 0,
-    HV_SOUNDHW: "",
-    HV_USB_DEVICES: "",
-    HV_VGA: "",
-    HV_KVM_EXTRA: "",
-    HV_KVM_MACHINE_VERSION: "",
-    HV_VNET_HDR: True,
-    },
-  HT_FAKE: {
-    HV_MIGRATION_MODE: HT_MIGRATION_LIVE,
-  },
-  HT_CHROOT: {
-    HV_INIT_SCRIPT: "/ganeti-chroot",
-    },
-  HT_LXC: {
-    HV_CPU_MASK: "",
-    },
-  }
-
-HVC_GLOBALS = compat.UniqueFrozenset([
-  HV_MIGRATION_PORT,
-  HV_MIGRATION_BANDWIDTH,
-  HV_MIGRATION_MODE,
-  HV_XEN_CMD,
-  ])
-
-BEC_DEFAULTS = {
-  BE_MINMEM: 128,
-  BE_MAXMEM: 128,
-  BE_VCPUS: 1,
-  BE_AUTO_BALANCE: True,
-  BE_ALWAYS_FAILOVER: False,
-  BE_SPINDLE_USE: 1,
-  }
-
-NDC_DEFAULTS = {
-  ND_OOB_PROGRAM: "",
-  ND_SPINDLE_COUNT: 1,
-  ND_EXCLUSIVE_STORAGE: False,
-  }
-
-NDC_GLOBALS = compat.UniqueFrozenset([
-  ND_EXCLUSIVE_STORAGE,
-  ])
-
-DISK_LD_DEFAULTS = {
-  DT_DRBD8: {
-    LDP_RESYNC_RATE: CLASSIC_DRBD_SYNC_SPEED,
-    LDP_BARRIERS: _autoconf.DRBD_BARRIERS,
-    LDP_NO_META_FLUSH: _autoconf.DRBD_NO_META_FLUSH,
-    LDP_DEFAULT_METAVG: DEFAULT_VG,
-    LDP_DISK_CUSTOM: "",
-    LDP_NET_CUSTOM: "",
-    LDP_PROTOCOL: DRBD_DEFAULT_NET_PROTOCOL,
-    LDP_DYNAMIC_RESYNC: False,
-
-    # The default values for the DRBD dynamic resync speed algorithm
-    # are taken from the drbsetup 8.3.11 man page, except for
-    # c-plan-ahead (that we don't need to set to 0, because we have a
-    # separate option to enable it) and for c-max-rate, that we cap to
-    # the default value for the static resync rate.
-    LDP_PLAN_AHEAD: 20, # ds
-    LDP_FILL_TARGET: 0, # sectors
-    LDP_DELAY_TARGET: 1, # ds
-    LDP_MAX_RATE: CLASSIC_DRBD_SYNC_SPEED, # KiB/s
-    LDP_MIN_RATE: 4 * 1024, # KiB/s
-    },
-  DT_PLAIN: {
-    LDP_STRIPES: _autoconf.LVM_STRIPECOUNT
-    },
-  DT_FILE: {},
-  DT_SHARED_FILE: {},
-  DT_BLOCK: {},
-  DT_RBD: {
-    LDP_POOL: "rbd"
-    },
-  DT_EXT: {},
-  }
-
-# readability shortcuts
-_LV_DEFAULTS = DISK_LD_DEFAULTS[DT_PLAIN]
-_DRBD_DEFAULTS = DISK_LD_DEFAULTS[DT_DRBD8]
-
-DISK_DT_DEFAULTS = {
-  DT_PLAIN: {
-    LV_STRIPES: DISK_LD_DEFAULTS[DT_PLAIN][LDP_STRIPES],
-    },
-  DT_DRBD8: {
-    DRBD_RESYNC_RATE: _DRBD_DEFAULTS[LDP_RESYNC_RATE],
-    DRBD_DATA_STRIPES: _LV_DEFAULTS[LDP_STRIPES],
-    DRBD_META_STRIPES: _LV_DEFAULTS[LDP_STRIPES],
-    DRBD_DISK_BARRIERS: _DRBD_DEFAULTS[LDP_BARRIERS],
-    DRBD_META_BARRIERS: _DRBD_DEFAULTS[LDP_NO_META_FLUSH],
-    DRBD_DEFAULT_METAVG: _DRBD_DEFAULTS[LDP_DEFAULT_METAVG],
-    DRBD_DISK_CUSTOM: _DRBD_DEFAULTS[LDP_DISK_CUSTOM],
-    DRBD_NET_CUSTOM: _DRBD_DEFAULTS[LDP_NET_CUSTOM],
-    DRBD_PROTOCOL: _DRBD_DEFAULTS[LDP_PROTOCOL],
-    DRBD_DYNAMIC_RESYNC: _DRBD_DEFAULTS[LDP_DYNAMIC_RESYNC],
-    DRBD_PLAN_AHEAD: _DRBD_DEFAULTS[LDP_PLAN_AHEAD],
-    DRBD_FILL_TARGET: _DRBD_DEFAULTS[LDP_FILL_TARGET],
-    DRBD_DELAY_TARGET: _DRBD_DEFAULTS[LDP_DELAY_TARGET],
-    DRBD_MAX_RATE: _DRBD_DEFAULTS[LDP_MAX_RATE],
-    DRBD_MIN_RATE: _DRBD_DEFAULTS[LDP_MIN_RATE],
-    },
-  DT_DISKLESS: {},
-  DT_FILE: {},
-  DT_SHARED_FILE: {},
-  DT_BLOCK: {},
-  DT_RBD: {
-    RBD_POOL: DISK_LD_DEFAULTS[DT_RBD][LDP_POOL]
-    },
-  DT_EXT: {},
-  }
-
-# we don't want to export the shortcuts
-del _LV_DEFAULTS, _DRBD_DEFAULTS
-
-NICC_DEFAULTS = {
-  NIC_MODE: NIC_MODE_BRIDGED,
-  NIC_LINK: DEFAULT_BRIDGE,
-  }
-
-# All of the following values are quite arbitrarily - there are no
-# "good" defaults, these must be customised per-site
-ISPECS_MINMAX_DEFAULTS = {
-  ISPECS_MIN: {
-    ISPEC_MEM_SIZE: 128,
-    ISPEC_CPU_COUNT: 1,
-    ISPEC_DISK_COUNT: 1,
-    ISPEC_DISK_SIZE: 1024,
-    ISPEC_NIC_COUNT: 1,
-    ISPEC_SPINDLE_USE: 1,
-    },
-  ISPECS_MAX: {
-    ISPEC_MEM_SIZE: 32768,
-    ISPEC_CPU_COUNT: 8,
-    ISPEC_DISK_COUNT: MAX_DISKS,
-    ISPEC_DISK_SIZE: 1024 * 1024,
-    ISPEC_NIC_COUNT: MAX_NICS,
-    ISPEC_SPINDLE_USE: 12,
-    },
-  }
-IPOLICY_DEFAULTS = {
-  ISPECS_MINMAX: [ISPECS_MINMAX_DEFAULTS],
-  ISPECS_STD: {
-    ISPEC_MEM_SIZE: 128,
-    ISPEC_CPU_COUNT: 1,
-    ISPEC_DISK_COUNT: 1,
-    ISPEC_DISK_SIZE: 1024,
-    ISPEC_NIC_COUNT: 1,
-    ISPEC_SPINDLE_USE: 1,
-    },
-  IPOLICY_DTS: list(DISK_TEMPLATES),
-  IPOLICY_VCPU_RATIO: 4.0,
-  IPOLICY_SPINDLE_RATIO: 32.0,
-  }
-
-MASTER_POOL_SIZE_DEFAULT = 10
-
-# Exclusive storage:
-# Error margin used to compare physical disks
-PART_MARGIN = .01
-# Space reserved when creating instance disks
-PART_RESERVED = .02
-
-CONFD_PROTOCOL_VERSION = 1
-
-CONFD_REQ_PING = 0
-CONFD_REQ_NODE_ROLE_BYNAME = 1
-CONFD_REQ_NODE_PIP_BY_INSTANCE_IP = 2
-CONFD_REQ_CLUSTER_MASTER = 3
-CONFD_REQ_NODE_PIP_LIST = 4
-CONFD_REQ_MC_PIP_LIST = 5
-CONFD_REQ_INSTANCES_IPS_LIST = 6
-CONFD_REQ_NODE_DRBD = 7
-CONFD_REQ_NODE_INSTANCES = 8
-
-# Confd request query fields. These are used to narrow down queries.
-# These must be strings rather than integers, because json-encoding
-# converts them to strings anyway, as they're used as dict-keys.
-CONFD_REQQ_LINK = "0"
-CONFD_REQQ_IP = "1"
-CONFD_REQQ_IPLIST = "2"
-CONFD_REQQ_FIELDS = "3"
-
-CONFD_REQFIELD_NAME = "0"
-CONFD_REQFIELD_IP = "1"
-CONFD_REQFIELD_MNODE_PIP = "2"
-
-CONFD_REQS = compat.UniqueFrozenset([
-  CONFD_REQ_PING,
-  CONFD_REQ_NODE_ROLE_BYNAME,
-  CONFD_REQ_NODE_PIP_BY_INSTANCE_IP,
-  CONFD_REQ_CLUSTER_MASTER,
-  CONFD_REQ_NODE_PIP_LIST,
-  CONFD_REQ_MC_PIP_LIST,
-  CONFD_REQ_INSTANCES_IPS_LIST,
-  CONFD_REQ_NODE_DRBD,
-  ])
-
-CONFD_REPL_STATUS_OK = 0
-CONFD_REPL_STATUS_ERROR = 1
-CONFD_REPL_STATUS_NOTIMPLEMENTED = 2
-
-CONFD_REPL_STATUSES = compat.UniqueFrozenset([
-  CONFD_REPL_STATUS_OK,
-  CONFD_REPL_STATUS_ERROR,
-  CONFD_REPL_STATUS_NOTIMPLEMENTED,
-  ])
-
-(CONFD_NODE_ROLE_MASTER,
- CONFD_NODE_ROLE_CANDIDATE,
- CONFD_NODE_ROLE_OFFLINE,
- CONFD_NODE_ROLE_DRAINED,
- CONFD_NODE_ROLE_REGULAR,
- ) = range(5)
-
-# A few common errors for confd
-CONFD_ERROR_UNKNOWN_ENTRY = 1
-CONFD_ERROR_INTERNAL = 2
-CONFD_ERROR_ARGUMENT = 3
-
-# Each request is "salted" by the current timestamp.
-# This constants decides how many seconds of skew to accept.
-# TODO: make this a default and allow the value to be more configurable
-CONFD_MAX_CLOCK_SKEW = 2 * NODE_MAX_CLOCK_SKEW
-
-# When we haven't reloaded the config for more than this amount of
-# seconds, we force a test to see if inotify is betraying us. Using a
-# prime number to ensure we get less chance of 'same wakeup' with
-# other processes.
-CONFD_CONFIG_RELOAD_TIMEOUT = 17
-
-# If we receive more than one update in this amount of microseconds,
-# we move to polling every RATELIMIT seconds, rather than relying on
-# inotify, to be able to serve more requests.
-CONFD_CONFIG_RELOAD_RATELIMIT = 250000
-
-# Magic number prepended to all confd queries.
-# This allows us to distinguish different types of confd protocols and handle
-# them. For example by changing this we can move the whole payload to be
-# compressed, or move away from json.
-CONFD_MAGIC_FOURCC = "plj0"
-
-# By default a confd request is sent to the minimum between this number and all
-# MCs. 6 was chosen because even in the case of a disastrous 50% response rate,
-# we should have enough answers to be able to compare more than one.
-CONFD_DEFAULT_REQ_COVERAGE = 6
-
-# Timeout in seconds to expire pending query request in the confd client
-# library. We don't actually expect any answer more than 10 seconds after we
-# sent a request.
-CONFD_CLIENT_EXPIRE_TIMEOUT = 10
-
-# Maximum UDP datagram size.
-# On IPv4: 64K - 20 (ip header size) - 8 (udp header size) = 65507
-# On IPv6: 64K - 40 (ip6 header size) - 8 (udp header size) = 65487
-#   (assuming we can't use jumbo frames)
-# We just set this to 60K, which should be enough
-MAX_UDP_DATA_SIZE = 61440
-
-# User-id pool minimum/maximum acceptable user-ids.
-UIDPOOL_UID_MIN = 0
-UIDPOOL_UID_MAX = 2 ** 32 - 1 # Assuming 32 bit user-ids
-
-# Name or path of the pgrep command
-PGREP = "pgrep"
-
-# Name of the node group that gets created at cluster init or upgrade
-INITIAL_NODE_GROUP_NAME = "default"
-
-# Possible values for NodeGroup.alloc_policy
-ALLOC_POLICY_PREFERRED = "preferred"
-ALLOC_POLICY_LAST_RESORT = "last_resort"
-ALLOC_POLICY_UNALLOCABLE = "unallocable"
-VALID_ALLOC_POLICIES = [
-  ALLOC_POLICY_PREFERRED,
-  ALLOC_POLICY_LAST_RESORT,
-  ALLOC_POLICY_UNALLOCABLE,
-  ]
-
-# Temporary external/shared storage parameters
-BLOCKDEV_DRIVER_MANUAL = "manual"
-
-# qemu-img path, required for ovfconverter
-QEMUIMG_PATH = _autoconf.QEMUIMG_PATH
-
-# Whether htools was enabled at compilation time
-HTOOLS = _autoconf.HTOOLS
-# The hail iallocator
-IALLOC_HAIL = "hail"
-
-# Fake opcodes for functions that have hooks attached to them via
-# backend.RunLocalHooks
-FAKE_OP_MASTER_TURNUP = "OP_CLUSTER_IP_TURNUP"
-FAKE_OP_MASTER_TURNDOWN = "OP_CLUSTER_IP_TURNDOWN"
-
-# SSH key types
-SSHK_RSA = "rsa"
-SSHK_DSA = "dsa"
-SSHK_ALL = compat.UniqueFrozenset([SSHK_RSA, SSHK_DSA])
-
-# SSH authorized key types
-SSHAK_RSA = "ssh-rsa"
-SSHAK_DSS = "ssh-dss"
-SSHAK_ALL = compat.UniqueFrozenset([SSHAK_RSA, SSHAK_DSS])
-
-# SSH setup
-SSHS_CLUSTER_NAME = "cluster_name"
-SSHS_SSH_HOST_KEY = "ssh_host_key"
-SSHS_SSH_ROOT_KEY = "ssh_root_key"
-SSHS_NODE_DAEMON_CERTIFICATE = "node_daemon_certificate"
-
-#: Key files for SSH daemon
-SSH_DAEMON_KEYFILES = {
-  SSHK_RSA: (pathutils.SSH_HOST_RSA_PRIV, pathutils.SSH_HOST_RSA_PUB),
-  SSHK_DSA: (pathutils.SSH_HOST_DSA_PRIV, pathutils.SSH_HOST_DSA_PUB),
-  }
-
-# Node daemon setup
-NDS_CLUSTER_NAME = "cluster_name"
-NDS_NODE_DAEMON_CERTIFICATE = "node_daemon_certificate"
-NDS_SSCONF = "ssconf"
-NDS_START_NODE_DAEMON = "start_node_daemon"
-
-# Path generating random UUID
-RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
-
-# Regex string for verifying a UUID
-UUID_REGEX = "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
-
-# Auto-repair tag prefixes
-AUTO_REPAIR_TAG_PREFIX = "ganeti:watcher:autorepair:"
-AUTO_REPAIR_TAG_ENABLED = AUTO_REPAIR_TAG_PREFIX
-AUTO_REPAIR_TAG_SUSPENDED = AUTO_REPAIR_TAG_ENABLED + "suspend:"
-AUTO_REPAIR_TAG_PENDING = AUTO_REPAIR_TAG_PREFIX + "pending:"
-AUTO_REPAIR_TAG_RESULT = AUTO_REPAIR_TAG_PREFIX + "result:"
-
-# Auto-repair levels
-AUTO_REPAIR_FIX_STORAGE = "fix-storage"
-AUTO_REPAIR_MIGRATE = "migrate"
-AUTO_REPAIR_FAILOVER = "failover"
-AUTO_REPAIR_REINSTALL = "reinstall"
-AUTO_REPAIR_ALL_TYPES = [
-  AUTO_REPAIR_FIX_STORAGE,
-  AUTO_REPAIR_MIGRATE,
-  AUTO_REPAIR_FAILOVER,
-  AUTO_REPAIR_REINSTALL,
-]
-
-# Auto-repair results
-AUTO_REPAIR_SUCCESS = "success"
-AUTO_REPAIR_FAILURE = "failure"
-AUTO_REPAIR_ENOPERM = "enoperm"
-AUTO_REPAIR_ALL_RESULTS = frozenset([
-    AUTO_REPAIR_SUCCESS,
-    AUTO_REPAIR_FAILURE,
-    AUTO_REPAIR_ENOPERM,
-])
-
-# The version identifier for builtin data collectors
-BUILTIN_DATA_COLLECTOR_VERSION = "B"
-
-# The reason trail opcode parameter name
-OPCODE_REASON = "reason"
-
-# The source reasons for the execution of an OpCode
-OPCODE_REASON_SRC_CLIENT = "gnt:client"
-OPCODE_REASON_SRC_NODED = "gnt:daemon:noded"
-OPCODE_REASON_SRC_OPCODE = "gnt:opcode"
-OPCODE_REASON_SRC_RLIB2 = "gnt:library:rlib2"
-OPCODE_REASON_SRC_USER = "gnt:user"
-
-OPCODE_REASON_SOURCES = compat.UniqueFrozenset([
-  OPCODE_REASON_SRC_CLIENT,
-  OPCODE_REASON_SRC_NODED,
-  OPCODE_REASON_SRC_OPCODE,
-  OPCODE_REASON_SRC_RLIB2,
-  OPCODE_REASON_SRC_USER,
-  ])
-
-DISKSTATS_FILE = "/proc/diskstats"
+# HVC_DEFAULTS contains one value 'HV_VNC_PASSWORD_FILE' which is not
+# a constant because it depends on an environment variable that is
+# used for VClusters.  Therefore, it cannot be automatically generated
+# by Haskell at compilation time (given that this environment variable
+# might be different at runtime).
+HVC_DEFAULTS[HT_XEN_HVM][HV_VNC_PASSWORD_FILE] = pathutils.VNC_PASSWORD_FILE
 
 # Do not re-export imported modules
-del re, _vcsversion, _autoconf, socket, pathutils, compat
+del re, socket, pathutils, compat
diff --git a/lib/daemon.py b/lib/daemon.py
index a5cc182..e4d10bc 100644
--- a/lib/daemon.py
+++ b/lib/daemon.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module with helper classes and functions for daemons"""
@@ -457,7 +466,7 @@
       self.out_socket.send(chr(0))
 
 
-class _ShutdownCheck:
+class _ShutdownCheck(object):
   """Logic for L{Mainloop} shutdown.
 
   """
@@ -801,7 +810,23 @@
   log_filename = constants.DAEMONS_LOGFILES[daemon_name]
 
   if options.fork:
-    utils.CloseFDs()
+    # Newer GnuTLS versions (>= 3.3.0) use a library constructor for
+    # initialization and open /dev/urandom on library load time, way before we
+    # fork(). Closing /dev/urandom causes subsequent ganeti.http.client
+    # requests to fail and the process to receive a SIGABRT. As we cannot
+    # reliably detect GnuTLS's socket, we work our way around this by keeping
+    # all fds referring to /dev/urandom open.
+    noclose_fds = []
+    for fd in os.listdir("/proc/self/fd"):
+      try:
+        if os.readlink(os.path.join("/proc/self/fd", fd)) == "/dev/urandom":
+          noclose_fds.append(int(fd))
+      except EnvironmentError:
+        # The fd might have disappeared (although it shouldn't as we're running
+        # single-threaded).
+        continue
+
+    utils.CloseFDs(noclose_fds=noclose_fds)
     (wpipe, stdio_reopen_fn) = utils.Daemonize(logfile=log_filename)
   else:
     (wpipe, stdio_reopen_fn) = (None, None)
diff --git a/lib/errors.py b/lib/errors.py
index 22b7502..4a010fb 100644
--- a/lib/errors.py
+++ b/lib/errors.py
@@ -2,75 +2,50 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti exception handling.
 
 """
 
-from ganeti import compat
+from ganeti import constants
 
 
-# OpPrereqError failure types
-
-#: Resolver errors
-ECODE_RESOLVER = "resolver_error"
-
-#: Not enough resources (iallocator failure, disk space, memory, etc.)
-ECODE_NORES = "insufficient_resources"
-
-#: Temporarily out of resources; operation can be tried again
-ECODE_TEMP_NORES = "temp_insufficient_resources"
-
-#: Wrong arguments (at syntax level)
-ECODE_INVAL = "wrong_input"
-
-#: Wrong entity state
-ECODE_STATE = "wrong_state"
-
-#: Entity not found
-ECODE_NOENT = "unknown_entity"
-
-#: Entity already exists
-ECODE_EXISTS = "already_exists"
-
-#: Resource not unique (e.g. MAC or IP duplication)
-ECODE_NOTUNIQUE = "resource_not_unique"
-
-#: Internal cluster error
-ECODE_FAULT = "internal_error"
-
-#: Environment error (e.g. node disk error)
-ECODE_ENVIRON = "environment_error"
-
-#: List of all failure types
-ECODE_ALL = compat.UniqueFrozenset([
-  ECODE_RESOLVER,
-  ECODE_NORES,
-  ECODE_TEMP_NORES,
-  ECODE_INVAL,
-  ECODE_STATE,
-  ECODE_NOENT,
-  ECODE_EXISTS,
-  ECODE_NOTUNIQUE,
-  ECODE_FAULT,
-  ECODE_ENVIRON,
-  ])
+ECODE_RESOLVER = constants.ERRORS_ECODE_RESOLVER
+ECODE_NORES = constants.ERRORS_ECODE_NORES
+ECODE_TEMP_NORES = constants.ERRORS_ECODE_TEMP_NORES
+ECODE_INVAL = constants.ERRORS_ECODE_INVAL
+ECODE_STATE = constants.ERRORS_ECODE_STATE
+ECODE_NOENT = constants.ERRORS_ECODE_NOENT
+ECODE_EXISTS = constants.ERRORS_ECODE_EXISTS
+ECODE_NOTUNIQUE = constants.ERRORS_ECODE_NOTUNIQUE
+ECODE_FAULT = constants.ERRORS_ECODE_FAULT
+ECODE_ENVIRON = constants.ERRORS_ECODE_ENVIRON
+ECODE_ALL = constants.ERRORS_ECODE_ALL
 
 
 class GenericError(Exception):
@@ -102,6 +77,15 @@
   """
 
 
+class HotplugError(HypervisorError):
+  """Hotplug-related exception.
+
+  This is raised in case a hotplug action fails or is not supported.
+  It is currently used only by KVM hypervisor.
+
+  """
+
+
 class ProgrammerError(GenericError):
   """Programming-related error.
 
diff --git a/lib/hooksmaster.py b/lib/hooksmaster.py
index 515a2a8..c23d857 100644
--- a/lib/hooksmaster.py
+++ b/lib/hooksmaster.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the logic for running hooks.
@@ -46,8 +55,8 @@
 
 class HooksMaster(object):
   def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn,
-               hooks_results_adapt_fn, build_env_fn, log_fn, htype=None,
-               cluster_name=None, master_name=None):
+               hooks_results_adapt_fn, build_env_fn, prepare_post_nodes_fn,
+               log_fn, htype=None, cluster_name=None, master_name=None):
     """Base class for hooks masters.
 
     This class invokes the execution of hooks according to the behaviour
@@ -70,6 +79,11 @@
     @type build_env_fn: function that returns a dictionary having strings as
       keys
     @param build_env_fn: function that builds the environment for the hooks
+    @type prepare_post_nodes_fn: function that take a list of node UUIDs and
+      returns a list of node UUIDs
+    @param prepare_post_nodes_fn: function that is invoked right before
+      executing post hooks and can change the list of node UUIDs to run the post
+      hooks on
     @type log_fn: function that accepts a string
     @param log_fn: logging function
     @type htype: string or None
@@ -86,6 +100,7 @@
     self.hooks_execution_fn = hooks_execution_fn
     self.hooks_results_adapt_fn = hooks_results_adapt_fn
     self.build_env_fn = build_env_fn
+    self.prepare_post_nodes_fn = prepare_post_nodes_fn
     self.log_fn = log_fn
     self.htype = htype
     self.cluster_name = cluster_name
@@ -195,6 +210,8 @@
     elif phase == constants.HOOKS_PHASE_POST:
       if node_names is None:
         node_names = self.post_nodes
+        if node_names is not None and self.prepare_post_nodes_fn is not None:
+          node_names = frozenset(self.prepare_post_nodes_fn(list(node_names)))
       env = self._BuildEnv(phase)
     else:
       raise AssertionError("Unknown phase '%s'" % phase)
@@ -260,15 +277,10 @@
       nodes = (None, None)
     else:
       hooks_nodes = lu.BuildHooksNodes()
-      to_name = lambda node_uuids: frozenset(lu.cfg.GetNodeNames(node_uuids))
-      if len(hooks_nodes) == 2:
-        nodes = (to_name(hooks_nodes[0]), to_name(hooks_nodes[1]))
-      elif len(hooks_nodes) == 3:
-        nodes = (to_name(hooks_nodes[0]),
-                 to_name(hooks_nodes[1]) | frozenset(hooks_nodes[2]))
-      else:
+      if len(hooks_nodes) != 2:
         raise errors.ProgrammerError(
-          "LogicalUnit.BuildHooksNodes must return a 2- or 3-tuple")
+          "LogicalUnit.BuildHooksNodes must return a 2-tuple")
+      nodes = (frozenset(hooks_nodes[0]), frozenset(hooks_nodes[1]))
 
     master_name = cluster_name = None
     if lu.cfg:
@@ -277,4 +289,5 @@
 
     return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
                        _RpcResultsToHooksResults, lu.BuildHooksEnv,
-                       lu.LogWarning, lu.HTYPE, cluster_name, master_name)
+                       lu.PreparePostHookNodes, lu.LogWarning, lu.HTYPE,
+                       cluster_name, master_name)
diff --git a/lib/ht.py b/lib/ht.py
index a452239..556b908 100644
--- a/lib/ht.py
+++ b/lib/ht.py
@@ -2,31 +2,42 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the parameter types code."""
 
 import re
 import operator
+import ipaddr
 
 from ganeti import compat
 from ganeti import utils
 from ganeti import constants
+from ganeti import objects
 
 
 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
@@ -158,10 +169,6 @@
 NoDefault = object()
 
 
-#: The no-type (value too complex to check it in the type system)
-NoType = object()
-
-
 # Some basic types
 @WithDesc("Anything")
 def TAny(_):
@@ -359,13 +366,24 @@
 #: Maybe a list (list or None)
 TMaybeList = TMaybe(TList)
 
+
+#: a non-negative number (value > 0)
+# val_type should be TInt, TDouble (== TFloat), or TNumber
+def TNonNegative(val_type):
+  return WithDesc("EqualOrGreaterThanZero")(TAnd(val_type, lambda v: v >= 0))
+
+
+#: a positive number (value >= 0)
+# val_type should be TInt, TDouble (== TFloat), or TNumber
+def TPositive(val_type):
+  return WithDesc("GreaterThanZero")(TAnd(val_type, lambda v: v > 0))
+
+
 #: a non-negative integer (value >= 0)
-TNonNegativeInt = \
-  TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
+TNonNegativeInt = TNonNegative(TInt)
 
 #: a positive integer (value > 0)
-TPositiveInt = \
-  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
+TPositiveInt = TPositive(TInt)
 
 #: a maybe positive integer (positive integer or None)
 TMaybePositiveInt = TMaybe(TPositiveInt)
@@ -383,6 +401,9 @@
                                TRegex(re.compile("^%s$" %
                                                  constants.JOB_ID_TEMPLATE))))
 
+#: Double (== Float)
+TDouble = TFloat
+
 #: Number
 TNumber = TOr(TInt, TFloat)
 
@@ -415,6 +436,25 @@
 TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
 
 
+def TTupleOf(*val_types):
+  """Checks if a given value is a list with the proper size and its
+     elements match the given types.
+
+  """
+  desc = WithDesc("Tuple of %s" % (Parens(val_types), ))
+  return desc(TAnd(TOr(TTuple, TList), TIsLength(len(val_types)),
+                   TItems(val_types)))
+
+
+def TSetOf(val_type):
+  """Checks if a given value is a list with all elements of the same
+     type and eliminates duplicated elements.
+
+  """
+  desc = WithDesc("Set of %s" % (Parens(val_type), ))
+  return desc(lambda st: TListOf(val_type)(list(set(st))))
+
+
 def TDictOf(key_type, val_type):
   """Checks a dict type for the type of its key/values.
 
@@ -497,3 +537,197 @@
 
   return desc(lambda value: compat.all(check(i)
                                        for (check, i) in zip(items, value)))
+
+
+TAllocPolicy = TElemOf(constants.VALID_ALLOC_POLICIES)
+TCVErrorCode = TElemOf(constants.CV_ALL_ECODES_STRINGS)
+TQueryResultCode = TElemOf(constants.RS_ALL)
+TExportTarget = TOr(TNonEmptyString, TList)
+TExportMode = TElemOf(constants.EXPORT_MODES)
+TDiskIndex = TAnd(TNonNegativeInt, lambda val: val < constants.MAX_DISKS)
+TReplaceDisksMode = TElemOf(constants.REPLACE_MODES)
+TDiskTemplate = TElemOf(constants.DISK_TEMPLATES)
+TEvacMode = TElemOf(constants.NODE_EVAC_MODES)
+TIAllocatorTestDir = TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)
+TIAllocatorMode = TElemOf(constants.VALID_IALLOCATOR_MODES)
+
+
+def TSetParamsMods(fn):
+  """Generates a check for modification lists.
+
+  """
+  # Old format
+  # TODO: Remove in version 2.11 including support in LUInstanceSetParams
+  old_mod_item_fn = \
+    TAnd(TIsLength(2),
+         TItems([TOr(TElemOf(constants.DDMS_VALUES), TNonNegativeInt), fn]))
+
+  # New format, supporting adding/removing disks/NICs at arbitrary indices
+  mod_item_fn = \
+      TAnd(TIsLength(3), TItems([
+        TElemOf(constants.DDMS_VALUES_WITH_MODIFY),
+        Comment("Device index, can be negative, e.g. -1 for last disk")
+                 (TOr(TInt, TString)),
+        fn,
+        ]))
+
+  return TOr(Comment("Recommended")(TListOf(mod_item_fn)),
+             Comment("Deprecated")(TListOf(old_mod_item_fn)))
+
+
+TINicParams = \
+    Comment("NIC parameters")(TDictOf(TElemOf(constants.INIC_PARAMS),
+                                      TMaybe(TString)))
+
+TIDiskParams = \
+    Comment("Disk parameters")(TDictOf(TNonEmptyString,
+                                       TOr(TNonEmptyString, TInt)))
+
+THypervisor = TElemOf(constants.HYPER_TYPES)
+TMigrationMode = TElemOf(constants.HT_MIGRATION_MODES)
+TNICMode = TElemOf(constants.NIC_VALID_MODES)
+TInstCreateMode = TElemOf(constants.INSTANCE_CREATE_MODES)
+TRebootType = TElemOf(constants.REBOOT_TYPES)
+TFileDriver = TElemOf(constants.FILE_DRIVER)
+TOobCommand = TElemOf(constants.OOB_COMMANDS)
+TQueryTypeOp = TElemOf(constants.QR_VIA_OP)
+
+TDiskParams = \
+    Comment("Disk parameters")(TDictOf(TNonEmptyString,
+                                       TOr(TNonEmptyString, TInt)))
+
+TDiskChanges = \
+    TAnd(TIsLength(2),
+         TItems([Comment("Disk index")(TNonNegativeInt),
+                 Comment("Parameters")(TDiskParams)]))
+
+TRecreateDisksInfo = TOr(TListOf(TNonNegativeInt), TListOf(TDiskChanges))
+
+
+def TStorageType(val):
+  """Builds a function that checks if a given value is a valid storage
+  type.
+
+  """
+  return (val in constants.STORAGE_TYPES)
+
+
+TTagKind = TElemOf(constants.VALID_TAG_TYPES)
+TDdmSimple = TElemOf(constants.DDMS_VALUES)
+TVerifyOptionalChecks = TElemOf(constants.VERIFY_OPTIONAL_CHECKS)
+
+
+@WithDesc("IPv4 network")
+def _CheckCIDRNetNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@WithDesc("IPv4 address")
+def _CheckCIDRAddrNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv4Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@WithDesc("IPv6 address")
+def _CheckCIDR6AddrNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Address(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+@WithDesc("IPv6 network")
+def _CheckCIDR6NetNotation(value):
+  """Ensure a given CIDR notation type is valid.
+
+  """
+  try:
+    ipaddr.IPv6Network(value)
+  except ipaddr.AddressValueError:
+    return False
+  return True
+
+
+TIPv4Address = TAnd(TString, _CheckCIDRAddrNotation)
+TIPv6Address = TAnd(TString, _CheckCIDR6AddrNotation)
+TIPv4Network = TAnd(TString, _CheckCIDRNetNotation)
+TIPv6Network = TAnd(TString, _CheckCIDR6NetNotation)
+
+
+def TObject(val_type):
+  return TDictOf(TAny, val_type)
+
+
+def TObjectCheck(obj, fields_types):
+  """Helper to generate type checks for objects.
+
+  @param obj: The object to generate type checks
+  @param fields_types: The fields and their types as a dict
+  @return: A ht type check function
+
+  """
+  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
+    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
+  return TStrictDict(True, True, fields_types)
+
+
+TQueryFieldDef = \
+    TObjectCheck(objects.QueryFieldDefinition, {
+        "name": TNonEmptyString,
+        "title": TNonEmptyString,
+        "kind": TElemOf(constants.QFT_ALL),
+        "doc": TNonEmptyString
+    })
+
+TQueryRow = \
+    TListOf(TAnd(TIsLength(2),
+                 TItems([TElemOf(constants.RS_ALL), TAny])))
+
+TQueryResult = TListOf(TQueryRow)
+
+TQueryResponse = \
+    TObjectCheck(objects.QueryResponse, {
+        "fields": TListOf(TQueryFieldDef),
+        "data": TQueryResult
+    })
+
+TQueryFieldsResponse = \
+    TObjectCheck(objects.QueryFieldsResponse, {
+        "fields": TListOf(TQueryFieldDef)
+    })
+
+TJobIdListItem = \
+    TAnd(TIsLength(2),
+         TItems([Comment("success")(TBool),
+                 Comment("Job ID if successful, error message"
+                         " otherwise")(TOr(TString, TJobId))]))
+
+TJobIdList = TListOf(TJobIdListItem)
+
+TJobIdListOnly = TStrictDict(True, True, {
+  constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList)
+  })
+
+TInstanceMultiAllocResponse = \
+    TStrictDict(True, True, {
+      constants.JOB_IDS_KEY: Comment("List of submitted jobs")(TJobIdList),
+      constants.ALLOCATABLE_KEY: TListOf(TNonEmptyString),
+      constants.FAILED_KEY: TListOf(TNonEmptyString)
+    })
diff --git a/lib/http/__init__.py b/lib/http/__init__.py
index 0ba1e32..d1d19ae 100644
--- a/lib/http/__init__.py
+++ b/lib/http/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """HTTP module.
 
diff --git a/lib/http/auth.py b/lib/http/auth.py
index 5dec95b..35b0b32 100644
--- a/lib/http/auth.py
+++ b/lib/http/auth.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """HTTP authentication module.
 
diff --git a/lib/http/client.py b/lib/http/client.py
index 849a195..722bd33 100644
--- a/lib/http/client.py
+++ b/lib/http/client.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """HTTP client module.
 
@@ -174,7 +183,7 @@
   return _PendingRequest(curl, req, resp_buffer.getvalue)
 
 
-class _PendingRequest:
+class _PendingRequest(object):
   def __init__(self, curl, req, resp_buffer_read):
     """Initializes this class.
 
@@ -233,7 +242,7 @@
       req.completion_cb(req)
 
 
-class _NoOpRequestMonitor: # pylint: disable=W0232
+class _NoOpRequestMonitor(object): # pylint: disable=W0232
   """No-op request monitor.
 
   """
@@ -245,7 +254,7 @@
   Disable = acquire
 
 
-class _PendingRequestMonitor:
+class _PendingRequestMonitor(object):
   _LOCK = "_lock"
 
   def __init__(self, owner, pending_fn):
diff --git a/lib/http/server.py b/lib/http/server.py
index b6f0504..fdf55b2 100644
--- a/lib/http/server.py
+++ b/lib/http/server.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """HTTP server module.
 
diff --git a/lib/hypervisor/__init__.py b/lib/hypervisor/__init__.py
index e1b39ba..f4919b1 100644
--- a/lib/hypervisor/__init__.py
+++ b/lib/hypervisor/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Virtualization interface abstraction
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index fe69ab7..205c141 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Base class for all hypervisors
@@ -568,3 +577,50 @@
       return "; ".join(msgs)
     else:
       return None
+
+  # pylint: disable=R0201,W0613
+  def HotAddDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-add a device.
+
+    """
+    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
+
+  # pylint: disable=R0201,W0613
+  def HotDelDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-del a device.
+
+    """
+    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
+
+  # pylint: disable=R0201,W0613
+  def HotModDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-mod a device.
+
+    """
+    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
+
+  # pylint: disable=R0201,W0613
+  def VerifyHotplugSupport(self, instance, action, dev_type):
+    """Verifies that hotplug is supported.
+
+    Given the target device and hotplug action checks if hotplug is
+    actually supported.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance object
+    @type action: string
+    @param action: one of the supported hotplug commands
+    @type dev_type: string
+    @param dev_type: one of the supported device types to hotplug
+    @raise errors.HotplugError: if hotplugging is not supported
+
+    """
+    raise errors.HotplugError("Hotplug is not supported.")
+
+  def HotplugSupported(self, instance):
+    """Checks if hotplug is supported.
+
+    By default is not. Currently only KVM hypervisor supports it.
+
+    """
+    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py
index ea50233..dd2bb66 100644
--- a/lib/hypervisor/hv_chroot.py
+++ b/lib/hypervisor/hv_chroot.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Chroot manager hypervisor
diff --git a/lib/hypervisor/hv_fake.py b/lib/hypervisor/hv_fake.py
index 7123787..4f04ef3 100644
--- a/lib/hypervisor/hv_fake.py
+++ b/lib/hypervisor/hv_fake.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Fake hypervisor
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 5f17410..cc175b4 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """KVM hypervisor
@@ -37,10 +46,15 @@
 import socket
 import stat
 import StringIO
+from bitarray import bitarray
 try:
   import affinity   # pylint: disable=F0401
 except ImportError:
   affinity = None
+try:
+  import fdsend   # pylint: disable=F0401
+except ImportError:
+  fdsend = None
 
 from ganeti import utils
 from ganeti import constants
@@ -80,6 +94,183 @@
   constants.HV_KVM_SPICE_USE_TLS,
   ])
 
+# Constant bitarray that reflects to a free pci slot
+# Use it with bitarray.search()
+_AVAILABLE_PCI_SLOT = bitarray("0")
+
+# below constants show the format of runtime file
+# the nics are in second possition, while the disks in 4th (last)
+# moreover disk entries are stored as a list of in tuples
+# (L{objects.Disk}, link_name, uri)
+_KVM_NICS_RUNTIME_INDEX = 1
+_KVM_DISKS_RUNTIME_INDEX = 3
+_DEVICE_RUNTIME_INDEX = {
+  constants.HOTPLUG_TARGET_DISK: _KVM_DISKS_RUNTIME_INDEX,
+  constants.HOTPLUG_TARGET_NIC: _KVM_NICS_RUNTIME_INDEX
+  }
+_FIND_RUNTIME_ENTRY = {
+  constants.HOTPLUG_TARGET_NIC:
+    lambda nic, kvm_nics: [n for n in kvm_nics if n.uuid == nic.uuid],
+  constants.HOTPLUG_TARGET_DISK:
+    lambda disk, kvm_disks: [(d, l, u) for (d, l, u) in kvm_disks
+                             if d.uuid == disk.uuid]
+  }
+_RUNTIME_DEVICE = {
+  constants.HOTPLUG_TARGET_NIC: lambda d: d,
+  constants.HOTPLUG_TARGET_DISK: lambda (d, e, _): d
+  }
+_RUNTIME_ENTRY = {
+  constants.HOTPLUG_TARGET_NIC: lambda d, e: d,
+  constants.HOTPLUG_TARGET_DISK: lambda d, e: (d, e[0], e[1])
+  }
+
+
+def _GetDriveURI(disk, link, uri):
+  """Helper function to get the drive uri to be used in --drive kvm option
+
+  @type disk: L{objects.Disk}
+  @param disk: A disk configuration object
+  @type link: string
+  @param link: The device link as returned by _SymlinkBlockDev()
+  @type uri: string
+  @param uri: The drive uri as returned by _CalculateDeviceURI()
+
+  """
+  access_mode = disk.params.get(constants.LDP_ACCESS,
+                                constants.DISK_KERNELSPACE)
+  if (uri and access_mode == constants.DISK_USERSPACE):
+    drive_uri = uri
+  else:
+    drive_uri = link
+
+  return drive_uri
+
+
+def _GenerateDeviceKVMId(dev_type, dev):
+  """Helper function to generate a unique device name used by KVM
+
+  QEMU monitor commands use names to identify devices. Here we use their pci
+  slot and a part of their UUID to name them. dev.pci might be None for old
+  devices in the cluster.
+
+  @type dev_type: sting
+  @param dev_type: device type of param dev
+  @type dev: L{objects.Disk} or L{objects.NIC}
+  @param dev: the device object for which we generate a kvm name
+  @raise errors.HotplugError: in case a device has no pci slot (old devices)
+
+  """
+
+  if not dev.pci:
+    raise errors.HotplugError("Hotplug is not supported for %s with UUID %s" %
+                              (dev_type, dev.uuid))
+
+  return "%s-%s-pci-%d" % (dev_type.lower(), dev.uuid.split("-")[0], dev.pci)
+
+
+def _GetFreeSlot(slots, slot=None, reserve=False):
+  """Helper method to get first available slot in a bitarray
+
+  @type slots: bitarray
+  @param slots: the bitarray to operate on
+  @type slot: integer
+  @param slot: if given we check whether the slot is free
+  @type reserve: boolean
+  @param reserve: whether to reserve the first available slot or not
+  @return: the idx of the (first) available slot
+  @raise errors.HotplugError: If all slots in a bitarray are occupied
+    or the given slot is not free.
+
+  """
+  if slot is not None:
+    assert slot < len(slots)
+    if slots[slot]:
+      raise errors.HypervisorError("Slots %d occupied" % slot)
+
+  else:
+    avail = slots.search(_AVAILABLE_PCI_SLOT, 1)
+    if not avail:
+      raise errors.HypervisorError("All slots occupied")
+
+    slot = int(avail[0])
+
+  if reserve:
+    slots[slot] = True
+
+  return slot
+
+
+def _GetExistingDeviceInfo(dev_type, device, runtime):
+  """Helper function to get an existing device inside the runtime file
+
+  Used when an instance is running. Load kvm runtime file and search
+  for a device based on its type and uuid.
+
+  @type dev_type: sting
+  @param dev_type: device type of param dev
+  @type device: L{objects.Disk} or L{objects.NIC}
+  @param device: the device object for which we generate a kvm name
+  @type runtime: tuple (cmd, nics, hvparams, disks)
+  @param runtime: the runtime data to search for the device
+  @raise errors.HotplugError: in case the requested device does not
+    exist (e.g. device has been added without --hotplug option) or
+    device info has not pci slot (e.g. old devices in the cluster)
+
+  """
+  index = _DEVICE_RUNTIME_INDEX[dev_type]
+  found = _FIND_RUNTIME_ENTRY[dev_type](device, runtime[index])
+  if not found:
+    raise errors.HotplugError("Cannot find runtime info for %s with UUID %s" %
+                              (dev_type, device.uuid))
+
+  return found[0]
+
+
+def _UpgradeSerializedRuntime(serialized_runtime):
+  """Upgrade runtime data
+
+  Remove any deprecated fields or change the format of the data.
+  The runtime files are not upgraded when Ganeti is upgraded, so the required
+  modification have to be performed here.
+
+  @type serialized_runtime: string
+  @param serialized_runtime: raw text data read from actual runtime file
+  @return: (cmd, nic dicts, hvparams, bdev dicts)
+  @rtype: tuple
+
+  """
+  loaded_runtime = serializer.Load(serialized_runtime)
+  kvm_cmd, serialized_nics, hvparams = loaded_runtime[:3]
+  if len(loaded_runtime) >= 4:
+    serialized_disks = loaded_runtime[3]
+  else:
+    serialized_disks = []
+
+  for nic in serialized_nics:
+    # Add a dummy uuid slot if an pre-2.8 NIC is found
+    if "uuid" not in nic:
+      nic["uuid"] = utils.NewUUID()
+
+  return kvm_cmd, serialized_nics, hvparams, serialized_disks
+
+
+def _AnalyzeSerializedRuntime(serialized_runtime):
+  """Return runtime entries for a serialized runtime file
+
+  @type serialized_runtime: string
+  @param serialized_runtime: raw text data read from actual runtime file
+  @return: (cmd, nics, hvparams, bdevs)
+  @rtype: tuple
+
+  """
+  kvm_cmd, serialized_nics, hvparams, serialized_disks = \
+    _UpgradeSerializedRuntime(serialized_runtime)
+  kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
+  kvm_disks = [(objects.Disk.FromDict(sdisk), link, uri)
+               for sdisk, link, uri in serialized_disks]
+
+  return (kvm_cmd, kvm_nics, hvparams, kvm_disks)
+
 
 def _GetTunFeatures(fd, _ioctl=fcntl.ioctl):
   """Retrieves supported TUN features from file descriptor.
@@ -231,29 +422,15 @@
     return self.data == other.data
 
 
-class QmpConnection:
-  """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
-
-  """
-  _FIRST_MESSAGE_KEY = "QMP"
-  _EVENT_KEY = "event"
-  _ERROR_KEY = "error"
-  _RETURN_KEY = RETURN_KEY = "return"
-  _ACTUAL_KEY = ACTUAL_KEY = "actual"
-  _ERROR_CLASS_KEY = "class"
-  _ERROR_DESC_KEY = "desc"
-  _EXECUTE_KEY = "execute"
-  _ARGUMENTS_KEY = "arguments"
-  _CAPABILITIES_COMMAND = "qmp_capabilities"
-  _MESSAGE_END_TOKEN = "\r\n"
+class MonitorSocket(object):
   _SOCKET_TIMEOUT = 5
 
   def __init__(self, monitor_filename):
-    """Instantiates the QmpConnection object.
+    """Instantiates the MonitorSocket object.
 
     @type monitor_filename: string
     @param monitor_filename: the filename of the UNIX raw socket on which the
-                             QMP monitor is listening
+                             monitor (QMP or simple one) is listening
 
     """
     self.monitor_filename = monitor_filename
@@ -262,7 +439,6 @@
     # in a reasonable amount of time
     self.sock.settimeout(self._SOCKET_TIMEOUT)
     self._connected = False
-    self._buf = ""
 
   def _check_socket(self):
     sock_stat = None
@@ -270,29 +446,27 @@
       sock_stat = os.stat(self.monitor_filename)
     except EnvironmentError, err:
       if err.errno == errno.ENOENT:
-        raise errors.HypervisorError("No qmp socket found")
+        raise errors.HypervisorError("No monitor socket found")
       else:
-        raise errors.HypervisorError("Error checking qmp socket: %s",
+        raise errors.HypervisorError("Error checking monitor socket: %s",
                                      utils.ErrnoOrStr(err))
     if not stat.S_ISSOCK(sock_stat.st_mode):
-      raise errors.HypervisorError("Qmp socket is not a socket")
+      raise errors.HypervisorError("Monitor socket is not a socket")
 
   def _check_connection(self):
     """Make sure that the connection is established.
 
     """
     if not self._connected:
-      raise errors.ProgrammerError("To use a QmpConnection you need to first"
+      raise errors.ProgrammerError("To use a MonitorSocket you need to first"
                                    " invoke connect() on it")
 
   def connect(self):
-    """Connects to the QMP monitor.
+    """Connects to the monitor.
 
-    Connects to the UNIX socket and makes sure that we can actually send and
-    receive data to the kvm instance via QMP.
+    Connects to the UNIX socket
 
     @raise errors.HypervisorError: when there are communication errors
-    @raise errors.ProgrammerError: when there are data serialization errors
 
     """
     if self._connected:
@@ -307,6 +481,46 @@
       raise errors.HypervisorError("Can't connect to qmp socket")
     self._connected = True
 
+  def close(self):
+    """Closes the socket
+
+    It cannot be used after this call.
+
+    """
+    self.sock.close()
+
+
+class QmpConnection(MonitorSocket):
+  """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
+
+  """
+  _FIRST_MESSAGE_KEY = "QMP"
+  _EVENT_KEY = "event"
+  _ERROR_KEY = "error"
+  _RETURN_KEY = RETURN_KEY = "return"
+  _ACTUAL_KEY = ACTUAL_KEY = "actual"
+  _ERROR_CLASS_KEY = "class"
+  _ERROR_DESC_KEY = "desc"
+  _EXECUTE_KEY = "execute"
+  _ARGUMENTS_KEY = "arguments"
+  _CAPABILITIES_COMMAND = "qmp_capabilities"
+  _MESSAGE_END_TOKEN = "\r\n"
+
+  def __init__(self, monitor_filename):
+    super(QmpConnection, self).__init__(monitor_filename)
+    self._buf = ""
+
+  def connect(self):
+    """Connects to the QMP monitor.
+
+    Connects to the UNIX socket and makes sure that we can actually send and
+    receive data to the kvm instance via QMP.
+
+    @raise errors.HypervisorError: when there are communication errors
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    super(QmpConnection, self).connect()
     # Check if we receive a correct greeting message from the server
     # (As per the QEMU Protocol Specification 0.1 - section 2.2)
     greeting = self._Recv()
@@ -315,6 +529,10 @@
       raise errors.HypervisorError("kvm: QMP communication error (wrong"
                                    " server greeting")
 
+    # This is needed because QMP can return more than one greetings
+    # see https://groups.google.com/d/msg/ganeti-devel/gZYcvHKDooU/SnukC8dgS5AJ
+    self._buf = ""
+
     # Let's put the monitor in command mode using the qmp_capabilities
     # command, or else no command will be executable.
     # (As per the QEMU Protocol Specification 0.1 - section 4)
@@ -545,6 +763,7 @@
 
   _VIRTIO = "virtio"
   _VIRTIO_NET_PCI = "virtio-net-pci"
+  _VIRTIO_BLK_PCI = "virtio-blk-pci"
 
   _MIGRATION_STATUS_RE = re.compile(r"Migration\s+status:\s+(\w+)",
                                     re.M | re.I)
@@ -574,13 +793,29 @@
   _NETDEV_RE = re.compile(r"^-netdev\s", re.M)
   _DISPLAY_RE = re.compile(r"^-display\s", re.M)
   _MACHINE_RE = re.compile(r"^-machine\s", re.M)
-  _NEW_VIRTIO_RE = re.compile(r"^name \"%s\"" % _VIRTIO_NET_PCI, re.M)
+  _VIRTIO_NET_RE = re.compile(r"^name \"%s\"" % _VIRTIO_NET_PCI, re.M)
+  _VIRTIO_BLK_RE = re.compile(r"^name \"%s\"" % _VIRTIO_BLK_PCI, re.M)
   # match  -drive.*boot=on|off on different lines, but in between accept only
   # dashes not preceeded by a new line (which would mean another option
   # different than -drive is starting)
   _BOOT_RE = re.compile(r"^-drive\s([^-]|(?<!^)-)*,boot=on\|off", re.M | re.S)
   _UUID_RE = re.compile(r"^-uuid\s", re.M)
 
+  _INFO_PCI_RE = re.compile(r'Bus.*device[ ]*(\d+).*')
+  _INFO_PCI_CMD = "info pci"
+  _FIND_PCI_DEVICE_RE = \
+    staticmethod(
+      lambda pci, devid: re.compile(r'Bus.*device[ ]*%d,(.*\n){5,6}.*id "%s"' %
+                                    (pci, devid), re.M))
+
+  _INFO_VERSION_RE = \
+    re.compile(r'^QEMU (\d+)\.(\d+)(\.(\d+))?.*monitor.*', re.M)
+  _INFO_VERSION_CMD = "info version"
+
+  # Slot 0 for Host bridge, Slot 1 for ISA bridge, Slot 2 for VGA controller
+  _DEFAULT_PCI_RESERVATIONS = "11100000000000000000000000000000"
+  _SOUNDHW_WITH_PCI_SLOT = ["ac97", "es1370", "hda"]
+
   ANCILLARY_FILES = [
     _KVM_NETWORK_SCRIPT,
     ]
@@ -835,11 +1070,6 @@
     @type tap: str
 
     """
-    if instance.tags:
-      tags = " ".join(instance.tags)
-    else:
-      tags = ""
-
     env = {
       "PATH": "%s:/sbin:/usr/sbin" % os.environ["PATH"],
       "INSTANCE": instance.name,
@@ -847,15 +1077,22 @@
       "MODE": nic.nicparams[constants.NIC_MODE],
       "INTERFACE": tap,
       "INTERFACE_INDEX": str(seq),
-      "TAGS": tags,
+      "INTERFACE_UUID": nic.uuid,
+      "TAGS": " ".join(instance.GetTags()),
     }
 
     if nic.ip:
       env["IP"] = nic.ip
 
+    if nic.name:
+      env["INTERFACE_NAME"] = nic.name
+
     if nic.nicparams[constants.NIC_LINK]:
       env["LINK"] = nic.nicparams[constants.NIC_LINK]
 
+    if constants.NIC_VLAN in nic.nicparams:
+      env["VLAN"] = nic.nicparams[constants.NIC_VLAN]
+
     if nic.network:
       n = objects.Network.FromDict(nic.netinfo)
       env.update(n.HooksDict())
@@ -1033,6 +1270,94 @@
         data.append(info)
     return data
 
+  def _GenerateKVMBlockDevicesOptions(self, instance, up_hvp, kvm_disks,
+                                      kvmhelp, devlist):
+    """Generate KVM options regarding instance's block devices.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance object
+    @type up_hvp: dict
+    @param up_hvp: the instance's runtime hypervisor parameters
+    @type kvm_disks: list of tuples
+    @param kvm_disks: list of tuples [(disk, link_name, uri)..]
+    @type kvmhelp: string
+    @param kvmhelp: output of kvm --help
+    @type devlist: string
+    @param devlist: output of kvm -device ?
+    @rtype: list
+    @return: list of command line options eventually used by kvm executable
+
+    """
+    kernel_path = up_hvp[constants.HV_KERNEL_PATH]
+    if kernel_path:
+      boot_disk = False
+    else:
+      boot_disk = up_hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
+
+    # whether this is an older KVM version that uses the boot=on flag
+    # on devices
+    needs_boot_flag = self._BOOT_RE.search(kvmhelp)
+
+    dev_opts = []
+    device_driver = None
+    disk_type = up_hvp[constants.HV_DISK_TYPE]
+    if disk_type == constants.HT_DISK_PARAVIRTUAL:
+      if_val = ",if=%s" % self._VIRTIO
+      try:
+        if self._VIRTIO_BLK_RE.search(devlist):
+          if_val = ",if=none"
+          # will be passed in -device option as driver
+          device_driver = self._VIRTIO_BLK_PCI
+      except errors.HypervisorError, _:
+        pass
+    else:
+      if_val = ",if=%s" % disk_type
+    # Cache mode
+    disk_cache = up_hvp[constants.HV_DISK_CACHE]
+    if instance.disk_template in constants.DTS_EXT_MIRROR:
+      if disk_cache != "none":
+        # TODO: make this a hard error, instead of a silent overwrite
+        logging.warning("KVM: overriding disk_cache setting '%s' with 'none'"
+                        " to prevent shared storage corruption on migration",
+                        disk_cache)
+      cache_val = ",cache=none"
+    elif disk_cache != constants.HT_CACHE_DEFAULT:
+      cache_val = ",cache=%s" % disk_cache
+    else:
+      cache_val = ""
+    for cfdev, link_name, uri in kvm_disks:
+      if cfdev.mode != constants.DISK_RDWR:
+        raise errors.HypervisorError("Instance has read-only disks which"
+                                     " are not supported by KVM")
+      # TODO: handle FD_LOOP and FD_BLKTAP (?)
+      boot_val = ""
+      if boot_disk:
+        dev_opts.extend(["-boot", "c"])
+        boot_disk = False
+        if needs_boot_flag and disk_type != constants.HT_DISK_IDE:
+          boot_val = ",boot=on"
+
+      drive_uri = _GetDriveURI(cfdev, link_name, uri)
+
+      drive_val = "file=%s,format=raw%s%s%s" % \
+                  (drive_uri, if_val, boot_val, cache_val)
+
+      if device_driver:
+        # kvm_disks are the 4th entry of runtime file that did not exist in
+        # the past. That means that cfdev should always have pci slot and
+        # _GenerateDeviceKVMId() will not raise a exception.
+        kvm_devid = _GenerateDeviceKVMId(constants.HOTPLUG_TARGET_DISK, cfdev)
+        drive_val += (",id=%s" % kvm_devid)
+        drive_val += (",bus=0,unit=%d" % cfdev.pci)
+        dev_val = ("%s,drive=%s,id=%s" %
+                   (device_driver, kvm_devid, kvm_devid))
+        dev_val += ",bus=pci.0,addr=%s" % hex(cfdev.pci)
+        dev_opts.extend(["-device", dev_val])
+
+      dev_opts.extend(["-drive", drive_val])
+
+    return dev_opts
+
   def _GenerateKVMRuntime(self, instance, block_devices, startup_paused,
                           kvmhelp):
     """Generate KVM information to start an instance.
@@ -1069,7 +1394,27 @@
     kvm_cmd.extend(["-smp", ",".join(smp_list)])
 
     kvm_cmd.extend(["-pidfile", pidfile])
-    kvm_cmd.extend(["-balloon", "virtio"])
+
+    pci_reservations = bitarray(self._DEFAULT_PCI_RESERVATIONS)
+
+    # As requested by music lovers
+    if hvp[constants.HV_SOUNDHW]:
+      soundhw = hvp[constants.HV_SOUNDHW]
+      # For some reason only few sound devices require a PCI slot
+      # while the Audio controller *must* be in slot 3.
+      # That's why we bridge this option early in command line
+      if soundhw in self._SOUNDHW_WITH_PCI_SLOT:
+        _ = _GetFreeSlot(pci_reservations, reserve=True)
+      kvm_cmd.extend(["-soundhw", soundhw])
+
+    if hvp[constants.HV_DISK_TYPE] == constants.HT_DISK_SCSI:
+      # The SCSI controller requires another PCI slot.
+      _ = _GetFreeSlot(pci_reservations, reserve=True)
+
+    # Add id to ballon and place to the first available slot (3 or 4)
+    addr = _GetFreeSlot(pci_reservations, reserve=True)
+    pci_info = ",bus=pci.0,addr=%s" % hex(addr)
+    kvm_cmd.extend(["-balloon", "virtio,id=balloon%s" % pci_info])
     kvm_cmd.extend(["-daemonize"])
     if not instance.hvparams[constants.HV_ACPI]:
       kvm_cmd.extend(["-no-acpi"])
@@ -1101,9 +1446,8 @@
 
     kernel_path = hvp[constants.HV_KERNEL_PATH]
     if kernel_path:
-      boot_disk = boot_cdrom = boot_floppy = boot_network = False
+      boot_cdrom = boot_floppy = boot_network = False
     else:
-      boot_disk = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_DISK
       boot_cdrom = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_CDROM
       boot_floppy = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_FLOPPY
       boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
@@ -1119,38 +1463,6 @@
     needs_boot_flag = self._BOOT_RE.search(kvmhelp)
 
     disk_type = hvp[constants.HV_DISK_TYPE]
-    if disk_type == constants.HT_DISK_PARAVIRTUAL:
-      if_val = ",if=virtio"
-    else:
-      if_val = ",if=%s" % disk_type
-    # Cache mode
-    disk_cache = hvp[constants.HV_DISK_CACHE]
-    if instance.disk_template in constants.DTS_EXT_MIRROR:
-      if disk_cache != "none":
-        # TODO: make this a hard error, instead of a silent overwrite
-        logging.warning("KVM: overriding disk_cache setting '%s' with 'none'"
-                        " to prevent shared storage corruption on migration",
-                        disk_cache)
-      cache_val = ",cache=none"
-    elif disk_cache != constants.HT_CACHE_DEFAULT:
-      cache_val = ",cache=%s" % disk_cache
-    else:
-      cache_val = ""
-    for cfdev, dev_path in block_devices:
-      if cfdev.mode != constants.DISK_RDWR:
-        raise errors.HypervisorError("Instance has read-only disks which"
-                                     " are not supported by KVM")
-      # TODO: handle FD_LOOP and FD_BLKTAP (?)
-      boot_val = ""
-      if boot_disk:
-        kvm_cmd.extend(["-boot", "c"])
-        boot_disk = False
-        if needs_boot_flag and disk_type != constants.HT_DISK_IDE:
-          boot_val = ",boot=on"
-
-      drive_val = "file=%s,format=raw%s%s%s" % (dev_path, if_val, boot_val,
-                                                cache_val)
-      kvm_cmd.extend(["-drive", drive_val])
 
     #Now we can specify a different device type for CDROM devices.
     cdrom_disk_type = hvp[constants.HV_KVM_CDROM_DISK_TYPE]
@@ -1363,7 +1675,9 @@
       else:
         # Enable the spice agent communication channel between the host and the
         # agent.
-        kvm_cmd.extend(["-device", "virtio-serial-pci"])
+        addr = _GetFreeSlot(pci_reservations, reserve=True)
+        pci_info = ",bus=pci.0,addr=%s" % hex(addr)
+        kvm_cmd.extend(["-device", "virtio-serial-pci,id=spice%s" % pci_info])
         kvm_cmd.extend([
           "-device",
           "virtserialport,chardev=spicechannel0,name=com.redhat.spice.0",
@@ -1391,10 +1705,6 @@
     if hvp[constants.HV_CPU_TYPE]:
       kvm_cmd.extend(["-cpu", hvp[constants.HV_CPU_TYPE]])
 
-    # As requested by music lovers
-    if hvp[constants.HV_SOUNDHW]:
-      kvm_cmd.extend(["-soundhw", hvp[constants.HV_SOUNDHW]])
-
     # Pass a -vga option if requested, or if spice is used, for backwards
     # compatibility.
     if hvp[constants.HV_VGA]:
@@ -1414,12 +1724,19 @@
     if hvp[constants.HV_KVM_EXTRA]:
       kvm_cmd.extend(hvp[constants.HV_KVM_EXTRA].split(" "))
 
-    # Save the current instance nics, but defer their expansion as parameters,
-    # as we'll need to generate executable temp files for them.
-    kvm_nics = instance.nics
+    kvm_disks = []
+    for disk, link_name, uri in block_devices:
+      disk.pci = _GetFreeSlot(pci_reservations, disk.pci, True)
+      kvm_disks.append((disk, link_name, uri))
+
+    kvm_nics = []
+    for nic in instance.nics:
+      nic.pci = _GetFreeSlot(pci_reservations, nic.pci, True)
+      kvm_nics.append(nic)
+
     hvparams = hvp
 
-    return (kvm_cmd, kvm_nics, hvparams)
+    return (kvm_cmd, kvm_nics, hvparams, kvm_disks)
 
   def _WriteKVMRuntime(self, instance_name, data):
     """Write an instance's KVM runtime
@@ -1445,9 +1762,14 @@
     """Save an instance's KVM runtime
 
     """
-    kvm_cmd, kvm_nics, hvparams = kvm_runtime
+    kvm_cmd, kvm_nics, hvparams, kvm_disks = kvm_runtime
+
     serialized_nics = [nic.ToDict() for nic in kvm_nics]
-    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams))
+    serialized_disks = [(blk.ToDict(), link, uri)
+                        for blk, link, uri in kvm_disks]
+    serialized_form = serializer.Dump((kvm_cmd, serialized_nics, hvparams,
+                                      serialized_disks))
+
     self._WriteKVMRuntime(instance.name, serialized_form)
 
   def _LoadKVMRuntime(self, instance, serialized_runtime=None):
@@ -1456,10 +1778,8 @@
     """
     if not serialized_runtime:
       serialized_runtime = self._ReadKVMRuntime(instance.name)
-    loaded_runtime = serializer.Load(serialized_runtime)
-    kvm_cmd, serialized_nics, hvparams = loaded_runtime
-    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
-    return (kvm_cmd, kvm_nics, hvparams)
+
+    return _AnalyzeSerializedRuntime(serialized_runtime)
 
   def _RunKVMCmd(self, name, kvm_cmd, tap_fds=None):
     """Run the KVM cmd and check for errors
@@ -1484,6 +1804,8 @@
     if not self._InstancePidAlive(name)[2]:
       raise errors.HypervisorError("Failed to start instance %s" % name)
 
+  # too many local variables
+  # pylint: disable=R0914
   def _ExecuteKVMRuntime(self, instance, kvm_runtime, kvmhelp, incoming=None):
     """Execute a KVM cmd, after completing it with some last minute data.
 
@@ -1507,7 +1829,7 @@
 
     temp_files = []
 
-    kvm_cmd, kvm_nics, up_hvp = kvm_runtime
+    kvm_cmd, kvm_nics, up_hvp, kvm_disks = kvm_runtime
     # the first element of kvm_cmd is always the path to the kvm binary
     kvm_path = kvm_cmd[0]
     up_hvp = objects.FillDict(conf_hvp, up_hvp)
@@ -1533,6 +1855,7 @@
     # related parameters we'll use up_hvp
     tapfds = []
     taps = []
+    devlist = self._GetKVMOutput(kvm_path, self._KVMOPT_DEVICELIST)
     if not kvm_nics:
       kvm_cmd.extend(["-net", "none"])
     else:
@@ -1542,8 +1865,7 @@
       if nic_type == constants.HT_NIC_PARAVIRTUAL:
         nic_model = self._VIRTIO
         try:
-          devlist = self._GetKVMOutput(kvm_path, self._KVMOPT_DEVICELIST)
-          if self._NEW_VIRTIO_RE.search(devlist):
+          if self._VIRTIO_NET_RE.search(devlist):
             nic_model = self._VIRTIO_NET_PCI
             vnet_hdr = up_hvp[constants.HV_VNET_HDR]
         except errors.HypervisorError, _:
@@ -1568,8 +1890,18 @@
         tapfds.append(tapfd)
         taps.append(tapname)
         if kvm_supports_netdev:
-          nic_val = "%s,mac=%s,netdev=netdev%s" % (nic_model, nic.mac, nic_seq)
-          tap_val = "type=tap,id=netdev%s,fd=%d%s" % (nic_seq, tapfd, tap_extra)
+          nic_val = "%s,mac=%s" % (nic_model, nic.mac)
+          try:
+            # kvm_nics already exist in old runtime files and thus there might
+            # be some entries without pci slot (therefore try: except:)
+            kvm_devid = _GenerateDeviceKVMId(constants.HOTPLUG_TARGET_NIC, nic)
+            netdev = kvm_devid
+            nic_val += (",id=%s,bus=pci.0,addr=%s" % (kvm_devid, hex(nic.pci)))
+          except errors.HotplugError:
+            netdev = "netdev%d" % nic_seq
+          nic_val += (",netdev=%s" % netdev)
+          tap_val = ("type=tap,id=%s,fd=%d%s" %
+                     (netdev, tapfd, tap_extra))
           kvm_cmd.extend(["-netdev", tap_val, "-device", nic_val])
         else:
           nic_val = "nic,vlan=%s,macaddr=%s,model=%s" % (nic_seq,
@@ -1611,6 +1943,12 @@
         continue
       self._ConfigureNIC(instance, nic_seq, nic, taps[nic_seq])
 
+    bdev_opts = self._GenerateKVMBlockDevicesOptions(instance,
+                                                     up_hvp,
+                                                     kvm_disks,
+                                                     kvmhelp,
+                                                     devlist)
+    kvm_cmd.extend(bdev_opts)
     # CPU affinity requires kvm to start paused, so we set this flag if the
     # instance is not already paused and if we are not going to accept a
     # migrating instance. In the latter case, pausing is not needed.
@@ -1735,6 +2073,192 @@
 
     return result
 
+  def _GetFreePCISlot(self, instance, dev):
+    """Get the first available pci slot of a runnung instance.
+
+    """
+    slots = bitarray(32)
+    slots.setall(False) # pylint: disable=E1101
+    output = self._CallMonitorCommand(instance.name, self._INFO_PCI_CMD)
+    for line in output.stdout.splitlines():
+      match = self._INFO_PCI_RE.search(line)
+      if match:
+        slot = int(match.group(1))
+        slots[slot] = True
+
+    dev.pci = _GetFreeSlot(slots)
+
+  def VerifyHotplugSupport(self, instance, action, dev_type):
+    """Verifies that hotplug is supported.
+
+    Hotplug is *not* supported in case of:
+     - security models and chroot (disk hotplug)
+     - fdsend module is missing (nic hot-add)
+
+    @raise errors.HypervisorError: in one of the previous cases
+
+    """
+    if dev_type == constants.HOTPLUG_TARGET_DISK:
+      hvp = instance.hvparams
+      security_model = hvp[constants.HV_SECURITY_MODEL]
+      use_chroot = hvp[constants.HV_KVM_USE_CHROOT]
+      if action == constants.HOTPLUG_ACTION_ADD:
+        if use_chroot:
+          raise errors.HotplugError("Disk hotplug is not supported"
+                                    " in case of chroot.")
+        if security_model != constants.HT_SM_NONE:
+          raise errors.HotplugError("Disk Hotplug is not supported in case"
+                                    " security models are used.")
+
+    if (dev_type == constants.HOTPLUG_TARGET_NIC and
+        action == constants.HOTPLUG_ACTION_ADD and not fdsend):
+      raise errors.HotplugError("Cannot hot-add NIC."
+                                " fdsend python module is missing.")
+
+  def HotplugSupported(self, instance):
+    """Checks if hotplug is generally supported.
+
+    Hotplug is *not* supported in case of:
+     - qemu versions < 1.0
+     - for stopped instances
+
+    @raise errors.HypervisorError: in one of the previous cases
+
+    """
+    try:
+      output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
+    except errors.HypervisorError:
+      raise errors.HotplugError("Instance is probably down")
+
+    # TODO: search for netdev_add, drive_add, device_add.....
+    match = self._INFO_VERSION_RE.search(output.stdout)
+    if not match:
+      raise errors.HotplugError("Cannot parse qemu version via monitor")
+
+    v_major, v_min, _, _ = match.groups()
+    if (int(v_major), int(v_min)) < (1, 0):
+      raise errors.HotplugError("Hotplug not supported for qemu versions < 1.0")
+
+  def _CallHotplugCommands(self, name, cmds):
+    for c in cmds:
+      self._CallMonitorCommand(name, c)
+      time.sleep(1)
+
+  def _VerifyHotplugCommand(self, instance_name, device, dev_type,
+                            should_exist):
+    """Checks if a previous hotplug command has succeeded.
+
+    It issues info pci monitor command and checks depending on should_exist
+    value if an entry with PCI slot and device ID is found or not.
+
+    @raise errors.HypervisorError: if result is not the expected one
+
+    """
+    output = self._CallMonitorCommand(instance_name, self._INFO_PCI_CMD)
+    kvm_devid = _GenerateDeviceKVMId(dev_type, device)
+    match = \
+      self._FIND_PCI_DEVICE_RE(device.pci, kvm_devid).search(output.stdout)
+    if match and not should_exist:
+      msg = "Device %s should have been removed but is still there" % kvm_devid
+      raise errors.HypervisorError(msg)
+
+    if not match and should_exist:
+      msg = "Device %s should have been added but is missing" % kvm_devid
+      raise errors.HypervisorError(msg)
+
+    logging.info("Device %s has been correctly hot-plugged", kvm_devid)
+
+  def HotAddDevice(self, instance, dev_type, device, extra, seq):
+    """ Helper method to hot-add a new device
+
+    It gets free pci slot generates the device name and invokes the
+    device specific method.
+
+    """
+    # in case of hot-mod this is given
+    if device.pci is None:
+      self._GetFreePCISlot(instance, device)
+    kvm_devid = _GenerateDeviceKVMId(dev_type, device)
+    runtime = self._LoadKVMRuntime(instance)
+    if dev_type == constants.HOTPLUG_TARGET_DISK:
+      drive_uri = _GetDriveURI(device, extra[0], extra[1])
+      cmds = ["drive_add dummy file=%s,if=none,id=%s,format=raw" %
+                (drive_uri, kvm_devid)]
+      cmds += ["device_add virtio-blk-pci,bus=pci.0,addr=%s,drive=%s,id=%s" %
+                (hex(device.pci), kvm_devid, kvm_devid)]
+    elif dev_type == constants.HOTPLUG_TARGET_NIC:
+      (tap, fd) = _OpenTap()
+      self._ConfigureNIC(instance, seq, device, tap)
+      self._PassTapFd(instance, fd, device)
+      cmds = ["netdev_add tap,id=%s,fd=%s" % (kvm_devid, kvm_devid)]
+      args = "virtio-net-pci,bus=pci.0,addr=%s,mac=%s,netdev=%s,id=%s" % \
+               (hex(device.pci), device.mac, kvm_devid, kvm_devid)
+      cmds += ["device_add %s" % args]
+      utils.WriteFile(self._InstanceNICFile(instance.name, seq), data=tap)
+
+    self._CallHotplugCommands(instance.name, cmds)
+    self._VerifyHotplugCommand(instance.name, device, dev_type, True)
+    # update relevant entries in runtime file
+    index = _DEVICE_RUNTIME_INDEX[dev_type]
+    entry = _RUNTIME_ENTRY[dev_type](device, extra)
+    runtime[index].append(entry)
+    self._SaveKVMRuntime(instance, runtime)
+
+  def HotDelDevice(self, instance, dev_type, device, _, seq):
+    """ Helper method for hot-del device
+
+    It gets device info from runtime file, generates the device name and
+    invokes the device specific method.
+
+    """
+    runtime = self._LoadKVMRuntime(instance)
+    entry = _GetExistingDeviceInfo(dev_type, device, runtime)
+    kvm_device = _RUNTIME_DEVICE[dev_type](entry)
+    kvm_devid = _GenerateDeviceKVMId(dev_type, kvm_device)
+    if dev_type == constants.HOTPLUG_TARGET_DISK:
+      cmds = ["device_del %s" % kvm_devid]
+      cmds += ["drive_del %s" % kvm_devid]
+    elif dev_type == constants.HOTPLUG_TARGET_NIC:
+      cmds = ["device_del %s" % kvm_devid]
+      cmds += ["netdev_del %s" % kvm_devid]
+      utils.RemoveFile(self._InstanceNICFile(instance.name, seq))
+    self._CallHotplugCommands(instance.name, cmds)
+    self._VerifyHotplugCommand(instance.name, kvm_device, dev_type, False)
+    index = _DEVICE_RUNTIME_INDEX[dev_type]
+    runtime[index].remove(entry)
+    self._SaveKVMRuntime(instance, runtime)
+
+    return kvm_device.pci
+
+  def HotModDevice(self, instance, dev_type, device, _, seq):
+    """ Helper method for hot-mod device
+
+    It gets device info from runtime file, generates the device name and
+    invokes the device specific method. Currently only NICs support hot-mod
+
+    """
+    if dev_type == constants.HOTPLUG_TARGET_NIC:
+      # putting it back in the same pci slot
+      device.pci = self.HotDelDevice(instance, dev_type, device, _, seq)
+      self.HotAddDevice(instance, dev_type, device, _, seq)
+
+  def _PassTapFd(self, instance, fd, nic):
+    """Pass file descriptor to kvm process via monitor socket using SCM_RIGHTS
+
+    """
+    # TODO: factor out code related to unix sockets.
+    #       squash common parts between monitor and qmp
+    kvm_devid = _GenerateDeviceKVMId(constants.HOTPLUG_TARGET_NIC, nic)
+    command = "getfd %s\n" % kvm_devid
+    fds = [fd]
+    logging.info("%s", fds)
+    try:
+      monsock = MonitorSocket(self._InstanceMonitor(instance.name))
+      monsock.connect()
+      fdsend.sendfds(monsock.sock, command, fds=fds)
+    finally:
+      monsock.close()
+
   @classmethod
   def _ParseKVMVersion(cls, text):
     """Parse the KVM version from the --help output.
diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index 7d965ce..ac6d75b 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """LXC hypervisor
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index eeaf2ad..09b9a20 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Xen hypervisors
@@ -169,8 +178,31 @@
 
 
 def _IsInstanceRunning(instance_info):
+  """Determine whether an instance is running.
+
+  An instance is running if it is in the following Xen states:
+  running, blocked, or paused.
+
+  For some strange reason, Xen once printed 'rb----' which does not make any
+  sense because an instance cannot be both running and blocked.  Fortunately,
+  for Ganeti 'running' or 'blocked' is the same as 'running'.
+
+  A state of nothing '------' means that the domain is runnable but it is not
+  currently running.  That means it is in the queue behind other domains waiting
+  to be scheduled to run.
+  http://old-list-archives.xenproject.org/xen-users/2007-06/msg00849.html
+
+  @type instance_info: string
+  @param instance_info: Information about instance, as supplied by Xen.
+  @rtype: bool
+  @return: Whether an instance is running.
+
+  """
   return instance_info == "r-----" \
-      or instance_info == "-b----"
+      or instance_info == "rb----" \
+      or instance_info == "-b----" \
+      or instance_info == "--p---" \
+      or instance_info == "------"
 
 
 def _IsInstanceShutdown(instance_info):
@@ -297,7 +329,7 @@
 
   disk_data = []
 
-  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
+  for sd_suffix, (cfdev, dev_path, _) in zip(_letters, block_devices):
     sd_name = blockdev_prefix + sd_suffix
 
     if cfdev.mode == constants.DISK_RDWR:
@@ -306,7 +338,7 @@
       mode = "r"
 
     if cfdev.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
-      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
+      driver = _FILE_DRIVER_MAP[cfdev.logical_id[0]]
     else:
       driver = "phy"
 
@@ -315,6 +347,19 @@
   return disk_data
 
 
+def _QuoteCpuidField(data):
+  """Add quotes around the CPUID field only if necessary.
+
+  Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
+  them, and lists of XEND strings, which don't.
+
+  @param data: Either type of parameter.
+  @return: The quoted version thereof.
+
+  """
+  return "'%s'" % data if data.startswith("host") else data
+
+
 class XenHypervisor(hv_base.BaseHypervisor):
   """Xen generic hypervisor interface
 
@@ -416,12 +461,13 @@
     return utils.PathJoin(self._cfgdir, instance_name)
 
   @classmethod
-  def _WriteNICInfoFile(cls, instance_name, idx, nic):
+  def _WriteNICInfoFile(cls, instance, idx, nic):
     """Write the Xen config file for the instance.
 
     This version of the function just writes the config file from static data.
 
     """
+    instance_name = instance.name
     dirs = [(dname, constants.RUN_DIRS_MODE)
             for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
     utils.EnsureDirs(dirs)
@@ -429,26 +475,22 @@
     cfg_file = cls._InstanceNICFile(instance_name, idx)
     data = StringIO()
 
+    data.write("TAGS=%s\n" % r"\ ".join(instance.GetTags()))
     if nic.netinfo:
       netinfo = objects.Network.FromDict(nic.netinfo)
-      data.write("NETWORK_NAME=%s\n" % netinfo.name)
-      if netinfo.network:
-        data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
-      if netinfo.gateway:
-        data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
-      if netinfo.network6:
-        data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
-      if netinfo.gateway6:
-        data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
-      if netinfo.mac_prefix:
-        data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
-      if netinfo.tags:
-        data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
+      for k, v in netinfo.HooksDict().iteritems():
+        data.write("%s=%s\n" % (k, v))
 
     data.write("MAC=%s\n" % nic.mac)
-    data.write("IP=%s\n" % nic.ip)
+    if nic.ip:
+      data.write("IP=%s\n" % nic.ip)
+    data.write("INTERFACE_INDEX=%s\n" % str(idx))
+    if nic.name:
+      data.write("INTERFACE_NAME=%s\n" % nic.name)
+    data.write("INTERFACE_UUID=%s\n" % nic.uuid)
     data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
     data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
+    data.write("VLAN=%s\n" % nic.nicparams[constants.NIC_VLAN])
 
     try:
       utils.WriteFile(cfg_file, data=data.getvalue())
@@ -1041,6 +1083,8 @@
     constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
     constants.HV_XEN_CMD:
       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
+    constants.HV_XEN_CPUID: hv_base.NO_CHECK,
+    constants.HV_SOUNDHW: hv_base.NO_CHECK,
     }
 
   def _GetConfig(self, instance, startup_memory, block_devices):
@@ -1099,10 +1143,14 @@
         nic_str += ", ip=%s" % ip
       if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
         nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
+      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
+        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
+        if nic.nicparams[constants.NIC_VLAN]:
+          nic_str += "%s" % nic.nicparams[constants.NIC_VLAN]
       if hvp[constants.HV_VIF_SCRIPT]:
         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
       vif_data.append("'%s'" % nic_str)
-      self._WriteNICInfoFile(instance.name, idx, nic)
+      self._WriteNICInfoFile(instance, idx, nic)
 
     disk_data = \
       _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
@@ -1120,6 +1168,13 @@
     config.write("on_crash = 'restart'\n")
     config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
 
+    cpuid = hvp[constants.HV_XEN_CPUID]
+    if cpuid:
+      config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
+
+    if hvp[constants.HV_SOUNDHW]:
+      config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
+
     return config.getvalue()
 
 
@@ -1170,6 +1225,8 @@
     constants.HV_VIRIDIAN: hv_base.NO_CHECK,
     constants.HV_XEN_CMD:
       hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
+    constants.HV_XEN_CPUID: hv_base.NO_CHECK,
+    constants.HV_SOUNDHW: hv_base.NO_CHECK,
     }
 
   def _GetConfig(self, instance, startup_memory, block_devices):
@@ -1273,7 +1330,7 @@
       if hvp[constants.HV_VIF_SCRIPT]:
         nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
       vif_data.append("'%s'" % nic_str)
-      self._WriteNICInfoFile(instance.name, idx, nic)
+      self._WriteNICInfoFile(instance, idx, nic)
 
     config.write("vif = [%s]\n" % ",".join(vif_data))
 
@@ -1299,4 +1356,11 @@
       config.write("on_reboot = 'destroy'\n")
     config.write("on_crash = 'restart'\n")
 
+    cpuid = hvp[constants.HV_XEN_CPUID]
+    if cpuid:
+      config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
+
+    if hvp[constants.HV_SOUNDHW]:
+      config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
+
     return config.getvalue()
diff --git a/lib/impexpd/__init__.py b/lib/impexpd/__init__.py
index dd6e981..1e910bd 100644
--- a/lib/impexpd/__init__.py
+++ b/lib/impexpd/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Classes and functions for import/export daemon.
diff --git a/lib/jqueue.py b/lib/jqueue.py
index e427d9a..897375d 100644
--- a/lib/jqueue.py
+++ b/lib/jqueue.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the job queue handling.
@@ -49,6 +58,7 @@
 from ganeti import workerpool
 from ganeti import locking
 from ganeti import opcodes
+from ganeti import opcodes_base
 from ganeti import errors
 from ganeti import mcpu
 from ganeti import utils
@@ -231,7 +241,7 @@
     count = 0
     for queued_op in self.ops:
       op = queued_op.input
-      reason_src = opcodes.NameToReasonSrc(op.__class__.__name__)
+      reason_src = opcodes_base.NameToReasonSrc(op.__class__.__name__)
       reason_text = "job=%d;index=%d" % (self.id, count)
       reason = getattr(op, "reason", [])
       reason.append((reason_src, reason_text, utils.EpochNano()))
@@ -545,6 +555,7 @@
 
 
 class _OpExecCallbacks(mcpu.OpExecCbBase):
+
   def __init__(self, queue, job, op):
     """Initializes this class.
 
@@ -556,6 +567,8 @@
     @param op: OpCode
 
     """
+    super(_OpExecCallbacks, self).__init__()
+
     assert queue, "Queue is missing"
     assert job, "Job is missing"
     assert op, "Opcode is missing"
@@ -910,7 +923,7 @@
     self.summary = op.input.Summary()
 
     # Create local copy to modify
-    if getattr(op.input, opcodes.DEPEND_ATTR, None):
+    if getattr(op.input, opcodes_base.DEPEND_ATTR, None):
       self.jobdeps = op.input.depends[:]
     else:
       self.jobdeps = None
@@ -2204,11 +2217,11 @@
                                   " are %s" % (idx, op.priority, allowed))
 
       # Check job dependencies
-      dependencies = getattr(op.input, opcodes.DEPEND_ATTR, None)
-      if not opcodes.TNoRelativeJobDependencies(dependencies):
+      dependencies = getattr(op.input, opcodes_base.DEPEND_ATTR, None)
+      if not opcodes_base.TNoRelativeJobDependencies(dependencies):
         raise errors.GenericError("Opcode %s has invalid dependencies, must"
                                   " match %s: %s" %
-                                  (idx, opcodes.TNoRelativeJobDependencies,
+                                  (idx, opcodes_base.TNoRelativeJobDependencies,
                                    dependencies))
 
     # Write to disk
@@ -2236,6 +2249,19 @@
 
   @locking.ssynchronized(_LOCK)
   @_RequireOpenQueue
+  def SubmitJobToDrainedQueue(self, ops):
+    """Forcefully create and store a new job.
+
+    Do so, even if the job queue is drained.
+    @see: L{_SubmitJobUnlocked}
+
+    """
+    (job_id, ) = self._NewSerialsUnlocked(1)
+    self._EnqueueJobsUnlocked([self._SubmitJobUnlocked(job_id, ops)])
+    return job_id
+
+  @locking.ssynchronized(_LOCK)
+  @_RequireOpenQueue
   @_RequireNonDrainedQueue
   def SubmitManyJobs(self, jobs):
     """Create and store multiple jobs.
@@ -2306,7 +2332,7 @@
 
     for (idx, (job_id, ops)) in enumerate(zip(job_ids, jobs)):
       for op in ops:
-        if getattr(op, opcodes.DEPEND_ATTR, None):
+        if getattr(op, opcodes_base.DEPEND_ATTR, None):
           (status, data) = \
             self._ResolveJobDependencies(compat.partial(resolve_fn, idx),
                                          op.depends)
diff --git a/lib/jstore.py b/lib/jstore.py
index 324f91e..220b1b2 100644
--- a/lib/jstore.py
+++ b/lib/jstore.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the job queue handling."""
@@ -31,7 +40,7 @@
 from ganeti import pathutils
 
 
-JOBS_PER_ARCHIVE_DIRECTORY = 10000
+JOBS_PER_ARCHIVE_DIRECTORY = constants.JSTORE_JOBS_PER_ARCHIVE_DIRECTORY
 
 
 def _ReadNumericFile(file_name):
diff --git a/lib/locking.py b/lib/locking.py
index 7379aa0..a8e91d4 100644
--- a/lib/locking.py
+++ b/lib/locking.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Module implementing the Ganeti locking code."""
 
@@ -969,7 +978,7 @@
   """
 
 
-class LockSet:
+class LockSet(object):
   """Implements a set of locks.
 
   This abstraction implements a set of shared locks for the same resource type,
@@ -1618,7 +1627,7 @@
 NAL = "NAL"
 
 
-class GanetiLockManager:
+class GanetiLockManager(object):
   """The Ganeti Locking Library
 
   The purpose of this small library is to manage locking for ganeti clusters
diff --git a/lib/luxi.py b/lib/luxi.py
index 7f66761..99a026d 100644
--- a/lib/luxi.py
+++ b/lib/luxi.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2011, 2012, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module for the unix socket protocol
@@ -35,7 +44,6 @@
 import errno
 import logging
 
-from ganeti import compat
 from ganeti import serializer
 from ganeti import constants
 from ganeti import errors
@@ -44,62 +52,38 @@
 from ganeti import pathutils
 
 
-KEY_METHOD = "method"
-KEY_ARGS = "args"
-KEY_SUCCESS = "success"
-KEY_RESULT = "result"
-KEY_VERSION = "version"
+KEY_METHOD = constants.LUXI_KEY_METHOD
+KEY_ARGS = constants.LUXI_KEY_ARGS
+KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
+KEY_RESULT = constants.LUXI_KEY_RESULT
+KEY_VERSION = constants.LUXI_KEY_VERSION
 
-REQ_SUBMIT_JOB = "SubmitJob"
-REQ_SUBMIT_MANY_JOBS = "SubmitManyJobs"
-REQ_WAIT_FOR_JOB_CHANGE = "WaitForJobChange"
-REQ_CANCEL_JOB = "CancelJob"
-REQ_ARCHIVE_JOB = "ArchiveJob"
-REQ_CHANGE_JOB_PRIORITY = "ChangeJobPriority"
-REQ_AUTO_ARCHIVE_JOBS = "AutoArchiveJobs"
-REQ_QUERY = "Query"
-REQ_QUERY_FIELDS = "QueryFields"
-REQ_QUERY_JOBS = "QueryJobs"
-REQ_QUERY_INSTANCES = "QueryInstances"
-REQ_QUERY_NODES = "QueryNodes"
-REQ_QUERY_GROUPS = "QueryGroups"
-REQ_QUERY_NETWORKS = "QueryNetworks"
-REQ_QUERY_EXPORTS = "QueryExports"
-REQ_QUERY_CONFIG_VALUES = "QueryConfigValues"
-REQ_QUERY_CLUSTER_INFO = "QueryClusterInfo"
-REQ_QUERY_TAGS = "QueryTags"
-REQ_SET_DRAIN_FLAG = "SetDrainFlag"
-REQ_SET_WATCHER_PAUSE = "SetWatcherPause"
+REQ_SUBMIT_JOB = constants.LUXI_REQ_SUBMIT_JOB
+REQ_SUBMIT_JOB_TO_DRAINED_QUEUE = constants.LUXI_REQ_SUBMIT_JOB_TO_DRAINED_QUEUE
+REQ_SUBMIT_MANY_JOBS = constants.LUXI_REQ_SUBMIT_MANY_JOBS
+REQ_WAIT_FOR_JOB_CHANGE = constants.LUXI_REQ_WAIT_FOR_JOB_CHANGE
+REQ_CANCEL_JOB = constants.LUXI_REQ_CANCEL_JOB
+REQ_ARCHIVE_JOB = constants.LUXI_REQ_ARCHIVE_JOB
+REQ_CHANGE_JOB_PRIORITY = constants.LUXI_REQ_CHANGE_JOB_PRIORITY
+REQ_AUTO_ARCHIVE_JOBS = constants.LUXI_REQ_AUTO_ARCHIVE_JOBS
+REQ_QUERY = constants.LUXI_REQ_QUERY
+REQ_QUERY_FIELDS = constants.LUXI_REQ_QUERY_FIELDS
+REQ_QUERY_JOBS = constants.LUXI_REQ_QUERY_JOBS
+REQ_QUERY_INSTANCES = constants.LUXI_REQ_QUERY_INSTANCES
+REQ_QUERY_NODES = constants.LUXI_REQ_QUERY_NODES
+REQ_QUERY_GROUPS = constants.LUXI_REQ_QUERY_GROUPS
+REQ_QUERY_NETWORKS = constants.LUXI_REQ_QUERY_NETWORKS
+REQ_QUERY_EXPORTS = constants.LUXI_REQ_QUERY_EXPORTS
+REQ_QUERY_CONFIG_VALUES = constants.LUXI_REQ_QUERY_CONFIG_VALUES
+REQ_QUERY_CLUSTER_INFO = constants.LUXI_REQ_QUERY_CLUSTER_INFO
+REQ_QUERY_TAGS = constants.LUXI_REQ_QUERY_TAGS
+REQ_SET_DRAIN_FLAG = constants.LUXI_REQ_SET_DRAIN_FLAG
+REQ_SET_WATCHER_PAUSE = constants.LUXI_REQ_SET_WATCHER_PAUSE
+REQ_ALL = constants.LUXI_REQ_ALL
 
-#: List of all LUXI requests
-REQ_ALL = compat.UniqueFrozenset([
-  REQ_ARCHIVE_JOB,
-  REQ_AUTO_ARCHIVE_JOBS,
-  REQ_CANCEL_JOB,
-  REQ_CHANGE_JOB_PRIORITY,
-  REQ_QUERY,
-  REQ_QUERY_CLUSTER_INFO,
-  REQ_QUERY_CONFIG_VALUES,
-  REQ_QUERY_EXPORTS,
-  REQ_QUERY_FIELDS,
-  REQ_QUERY_GROUPS,
-  REQ_QUERY_INSTANCES,
-  REQ_QUERY_JOBS,
-  REQ_QUERY_NODES,
-  REQ_QUERY_NETWORKS,
-  REQ_QUERY_TAGS,
-  REQ_SET_DRAIN_FLAG,
-  REQ_SET_WATCHER_PAUSE,
-  REQ_SUBMIT_JOB,
-  REQ_SUBMIT_MANY_JOBS,
-  REQ_WAIT_FOR_JOB_CHANGE,
-  ])
-
-DEF_CTMO = 10
-DEF_RWTO = 60
-
-# WaitForJobChange timeout
-WFJC_TIMEOUT = (DEF_RWTO - 1) / 2
+DEF_CTMO = constants.LUXI_DEF_CTMO
+DEF_RWTO = constants.LUXI_DEF_RWTO
+WFJC_TIMEOUT = constants.LUXI_WFJC_TIMEOUT
 
 
 class ProtocolError(errors.LuxiError):
@@ -481,6 +465,10 @@
     ops_state = map(lambda op: op.__getstate__(), ops)
     return self.CallMethod(REQ_SUBMIT_JOB, (ops_state, ))
 
+  def SubmitJobToDrainedQueue(self, ops):
+    ops_state = map(lambda op: op.__getstate__(), ops)
+    return self.CallMethod(REQ_SUBMIT_JOB_TO_DRAINED_QUEUE, (ops_state, ))
+
   def SubmitManyJobs(self, jobs):
     jobs_state = []
     for ops in jobs:
diff --git a/lib/masterd/__init__.py b/lib/masterd/__init__.py
index 3b40b08..998df6d 100644
--- a/lib/masterd/__init__.py
+++ b/lib/masterd/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # empty file for package definition
diff --git a/lib/masterd/iallocator.py b/lib/masterd/iallocator.py
index 49cac2e..b794b09 100644
--- a/lib/masterd/iallocator.py
+++ b/lib/masterd/iallocator.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the iallocator code."""
@@ -336,7 +345,7 @@
   MODE = constants.IALLOCATOR_MODE_NODE_EVAC
   REQ_PARAMS = [
     ("instances", _STRING_LIST),
-    ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
+    ("evac_mode", ht.TEvacMode),
     ]
   REQ_RESULT = _NEVAC_RESULT
 
@@ -399,10 +408,12 @@
 
     self._BuildInputData(req)
 
-  def _ComputeClusterDataNodeInfo(self, node_list, cluster_info,
-                                   hypervisor_name):
+  def _ComputeClusterDataNodeInfo(self, disk_templates, node_list,
+                                  cluster_info, hypervisor_name):
     """Prepare and execute node info call.
 
+    @type disk_templates: list of string
+    @param disk_templates: the disk templates of the instances to be allocated
     @type node_list: list of strings
     @param node_list: list of nodes' UUIDs
     @type cluster_info: L{objects.Cluster}
@@ -413,17 +424,17 @@
     @return: the result of the node info RPC call
 
     """
-    storage_units_raw = utils.storage.GetStorageUnitsOfCluster(
-        self.cfg, include_spindles=True)
+    storage_units_raw = utils.storage.GetStorageUnits(self.cfg, disk_templates)
     storage_units = rpc.PrepareStorageUnitsForNodes(self.cfg, storage_units_raw,
                                                     node_list)
     hvspecs = [(hypervisor_name, cluster_info.hvparams[hypervisor_name])]
     return self.rpc.call_node_info(node_list, storage_units, hvspecs)
 
-  def _ComputeClusterData(self):
+  def _ComputeClusterData(self, disk_template=None):
     """Compute the generic allocator input data.
 
-    This is the data that is independent of the actual operation.
+    @type disk_template: list of string
+    @param disk_template: the disk templates of the instances to be allocated
 
     """
     cluster_info = self.cfg.GetClusterInfo()
@@ -452,9 +463,11 @@
       hypervisor_name = cluster_info.primary_hypervisor
       node_whitelist = None
 
-    has_lvm = utils.storage.IsLvmEnabled(cluster_info.enabled_disk_templates)
-    node_data = self._ComputeClusterDataNodeInfo(node_list, cluster_info,
-                                                 hypervisor_name)
+    if not disk_template:
+      disk_template = cluster_info.enabled_disk_templates[0]
+
+    node_data = self._ComputeClusterDataNodeInfo([disk_template], node_list,
+                                                 cluster_info, hypervisor_name)
 
     node_iinfo = \
       self.rpc.call_all_instances_info(node_list,
@@ -464,8 +477,8 @@
     data["nodegroups"] = self._ComputeNodeGroupData(self.cfg)
 
     config_ndata = self._ComputeBasicNodeData(self.cfg, ninfo, node_whitelist)
-    data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
-                                                 i_list, config_ndata, has_lvm)
+    data["nodes"] = self._ComputeDynamicNodeData(
+        ninfo, node_data, node_iinfo, i_list, config_ndata, disk_template)
     assert len(data["nodes"]) == len(ninfo), \
         "Incomplete node data computed"
 
@@ -548,6 +561,46 @@
     return value
 
   @staticmethod
+  def _ComputeStorageDataFromSpaceInfoByTemplate(
+      space_info, node_name, disk_template):
+    """Extract storage data from node info.
+
+    @type space_info: see result of the RPC call node info
+    @param space_info: the storage reporting part of the result of the RPC call
+      node info
+    @type node_name: string
+    @param node_name: the node's name
+    @type disk_template: string
+    @param disk_template: the disk template to report space for
+    @rtype: 4-tuple of integers
+    @return: tuple of storage info (total_disk, free_disk, total_spindles,
+       free_spindles)
+
+    """
+    storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
+    if storage_type not in constants.STS_REPORT:
+      total_disk = total_spindles = 0
+      free_disk = free_spindles = 0
+    else:
+      template_space_info = utils.storage.LookupSpaceInfoByDiskTemplate(
+          space_info, disk_template)
+      if not template_space_info:
+        raise errors.OpExecError("Node '%s' didn't return space info for disk"
+                                   "template '%s'" % (node_name, disk_template))
+      total_disk = template_space_info["storage_size"]
+      free_disk = template_space_info["storage_free"]
+
+      total_spindles = 0
+      free_spindles = 0
+      if disk_template in constants.DTS_LVM:
+        lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
+           space_info, constants.ST_LVM_PV)
+        if lvm_pv_info:
+          total_spindles = lvm_pv_info["storage_size"]
+          free_spindles = lvm_pv_info["storage_free"]
+    return (total_disk, free_disk, total_spindles, free_spindles)
+
+  @staticmethod
   def _ComputeStorageDataFromSpaceInfo(space_info, node_name, has_lvm):
     """Extract storage data from node info.
 
@@ -574,7 +627,7 @@
       free_disk = lvm_vg_info["storage_free"]
       lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
          space_info, constants.ST_LVM_PV)
-      if not lvm_vg_info:
+      if not lvm_pv_info:
         raise errors.OpExecError("Node '%s' didn't return LVM pv space info."
                                  % (node_name))
       total_spindles = lvm_pv_info["storage_size"]
@@ -616,7 +669,7 @@
     return (i_p_mem, i_p_up_mem, mem_free)
 
   def _ComputeDynamicNodeData(self, node_cfg, node_data, node_iinfo, i_list,
-                              node_results, has_lvm):
+                              node_results, disk_template):
     """Compute global node data.
 
     @param node_results: the basic node structures as filled from the config
@@ -642,8 +695,8 @@
         (i_p_mem, i_p_up_mem, mem_free) = self._ComputeInstanceMemory(
              i_list, node_iinfo, nuuid, mem_free)
         (total_disk, free_disk, total_spindles, free_spindles) = \
-            self._ComputeStorageDataFromSpaceInfo(space_info, ninfo.name,
-                                                  has_lvm)
+            self._ComputeStorageDataFromSpaceInfoByTemplate(
+                space_info, ninfo.name, disk_template)
 
         # compute memory used by instances
         pnr_dyn = {
@@ -715,9 +768,12 @@
     """Build input data structures.
 
     """
-    self._ComputeClusterData()
-
     request = req.GetRequest(self.cfg)
+    disk_template = None
+    if "disk_template" in request:
+      disk_template = request["disk_template"]
+    self._ComputeClusterData(disk_template=disk_template)
+
     request["type"] = req.MODE
     self.in_data["request"] = request
 
diff --git a/lib/masterd/instance.py b/lib/masterd/instance.py
index a4936a4..17fd3b5 100644
--- a/lib/masterd/instance.py
+++ b/lib/masterd/instance.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Instance-related functions and classes for masterd.
@@ -659,7 +668,7 @@
   return utils.CommaJoin(parts)
 
 
-class ImportExportLoop:
+class ImportExportLoop(object):
   MIN_DELAY = 1.0
   MAX_DELAY = 20.0
 
@@ -1140,7 +1149,7 @@
       finished_fn()
 
 
-class ExportInstanceHelper:
+class ExportInstanceHelper(object):
   def __init__(self, lu, feedback_fn, instance):
     """Initializes this class.
 
@@ -1165,10 +1174,11 @@
 
     instance = self._instance
     src_node = instance.primary_node
+    src_node_name = self._lu.cfg.GetNodeName(src_node)
 
     for idx, disk in enumerate(instance.disks):
       self._feedback_fn("Creating a snapshot of disk/%s on node %s" %
-                        (idx, src_node))
+                        (idx, src_node_name))
 
       # result.payload will be a snapshot of an lvm leaf of the one we
       # passed
@@ -1177,17 +1187,16 @@
       msg = result.fail_msg
       if msg:
         self._lu.LogWarning("Could not snapshot disk/%s on node %s: %s",
-                            idx, src_node, msg)
+                            idx, src_node_name, msg)
       elif (not isinstance(result.payload, (tuple, list)) or
             len(result.payload) != 2):
         self._lu.LogWarning("Could not snapshot disk/%s on node %s: invalid"
-                            " result '%s'", idx, src_node, result.payload)
+                            " result '%s'", idx, src_node_name, result.payload)
       else:
         disk_id = tuple(result.payload)
         disk_params = constants.DISK_LD_DEFAULTS[constants.DT_PLAIN].copy()
         new_dev = objects.Disk(dev_type=constants.DT_PLAIN, size=disk.size,
-                               logical_id=disk_id, physical_id=disk_id,
-                               iv_name=disk.iv_name,
+                               logical_id=disk_id, iv_name=disk.iv_name,
                                params=disk_params)
 
       self._snap_disks.append(new_dev)
@@ -1205,14 +1214,17 @@
     disk = self._snap_disks[disk_index]
     if disk and not self._removed_snaps[disk_index]:
       src_node = self._instance.primary_node
+      src_node_name = self._lu.cfg.GetNodeName(src_node)
 
       self._feedback_fn("Removing snapshot of disk/%s on node %s" %
-                        (disk_index, src_node))
+                        (disk_index, src_node_name))
 
-      result = self._lu.rpc.call_blockdev_remove(src_node, disk)
+      result = self._lu.rpc.call_blockdev_remove(src_node,
+                                                 (disk, self._instance))
       if result.fail_msg:
         self._lu.LogWarning("Could not remove snapshot for disk/%d from node"
-                            " %s: %s", disk_index, src_node, result.fail_msg)
+                            " %s: %s", disk_index, src_node_name,
+                            result.fail_msg)
       else:
         self._removed_snaps[disk_index] = True
 
@@ -1236,13 +1248,13 @@
         continue
 
       path = utils.PathJoin(pathutils.EXPORT_DIR, "%s.new" % instance.name,
-                            dev.physical_id[1])
+                            dev.logical_id[1])
 
       finished_fn = compat.partial(self._TransferFinished, idx)
 
       # FIXME: pass debug option from opcode to backend
       dt = DiskTransfer("snapshot/%s" % idx,
-                        constants.IEIO_SCRIPT, (dev, idx),
+                        constants.IEIO_SCRIPT, ((dev, instance), idx),
                         constants.IEIO_FILE, (path, ),
                         finished_fn)
       transfers.append(dt)
@@ -1255,14 +1267,22 @@
 
     assert len(dresults) == len(instance.disks)
 
-    self._feedback_fn("Finalizing export on %s" % dest_node.name)
-    result = self._lu.rpc.call_finalize_export(dest_node.uuid, instance,
-                                               self._snap_disks)
-    msg = result.fail_msg
-    fin_resu = not msg
-    if msg:
-      self._lu.LogWarning("Could not finalize export for instance %s"
-                          " on node %s: %s", instance.name, dest_node.name, msg)
+    # Finalize only if all the disks have been exported successfully
+    if all(dresults):
+      self._feedback_fn("Finalizing export on %s" % dest_node.name)
+      result = self._lu.rpc.call_finalize_export(dest_node.uuid, instance,
+                                                 self._snap_disks)
+      msg = result.fail_msg
+      fin_resu = not msg
+      if msg:
+        self._lu.LogWarning("Could not finalize export for instance %s"
+                            " on node %s: %s", instance.name, dest_node.name,
+                            msg)
+    else:
+      fin_resu = False
+      self._lu.LogWarning("Some disk exports have failed; there may be "
+                          "leftover data for instance %s on node %s",
+                          instance.name, dest_node.name)
 
     return (fin_resu, dresults)
 
@@ -1300,7 +1320,7 @@
         finished_fn = compat.partial(self._TransferFinished, idx)
         ieloop.Add(DiskExport(self._lu, instance.primary_node,
                               opts, host, port, instance, "disk%d" % idx,
-                              constants.IEIO_SCRIPT, (dev, idx),
+                              constants.IEIO_SCRIPT, ((dev, instance), idx),
                               timeouts, cbs, private=(idx, finished_fn)))
 
       ieloop.Run()
@@ -1482,7 +1502,7 @@
 
         ieloop.Add(DiskImport(lu, instance.primary_node, opts, instance,
                               "disk%d" % idx,
-                              constants.IEIO_SCRIPT, (dev, idx),
+                              constants.IEIO_SCRIPT, ((dev, instance), idx),
                               timeouts, cbs, private=(idx, )))
 
       ieloop.Run()
diff --git a/lib/mcpu.py b/lib/mcpu.py
index f27adc9..04e122d 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing the logic behind the cluster operations
@@ -36,6 +45,7 @@
 import traceback
 
 from ganeti import opcodes
+from ganeti import opcodes_base
 from ganeti import constants
 from ganeti import errors
 from ganeti import hooksmaster
@@ -138,7 +148,7 @@
     return timeout
 
 
-class OpExecCbBase: # pylint: disable=W0232
+class OpExecCbBase(object): # pylint: disable=W0232
   """Base class for OpCode execution callbacks.
 
   """
@@ -207,7 +217,7 @@
       hasattr(src, "priority")):
     dst.priority = src.priority
 
-  if not getattr(dst, opcodes.COMMENT_ATTR, None):
+  if not getattr(dst, opcodes_base.COMMENT_ATTR, None):
     dst.comment = defcomment
 
   if hasattr(src, constants.OPCODE_REASON):
@@ -473,6 +483,22 @@
 
     return result
 
+  # pylint: disable=R0201
+  def _CheckLUResult(self, op, result):
+    """Check the LU result against the contract in the opcode.
+
+    """
+    resultcheck_fn = op.OP_RESULT
+    if not (resultcheck_fn is None or resultcheck_fn(result)):
+      logging.error("Expected opcode result matching %s, got %s",
+                    resultcheck_fn, result)
+      if not getattr(op, "dry_run", False):
+        # FIXME: LUs should still behave in dry_run mode, or
+        # alternately we should have OP_DRYRUN_RESULT; in the
+        # meantime, we simply skip the OP_RESULT check in dry-run mode
+        raise errors.OpResultError("Opcode result does not match %s: %s" %
+                                   (resultcheck_fn, utils.Truncate(result, 80)))
+
   def ExecOpCode(self, op, cbs, timeout=None):
     """Execute an opcode.
 
@@ -530,16 +556,7 @@
     finally:
       self._cbs = None
 
-    resultcheck_fn = op.OP_RESULT
-    if not (resultcheck_fn is None or resultcheck_fn(result)):
-      logging.error("Expected opcode result matching %s, got %s",
-                    resultcheck_fn, result)
-      if not getattr(op, "dry_run", False):
-        # FIXME: LUs should still behave in dry_run mode, or
-        # alternately we should have OP_DRYRUN_RESULT; in the
-        # meantime, we simply skip the OP_RESULT check in dry-run mode
-        raise errors.OpResultError("Opcode result does not match %s: %s" %
-                                   (resultcheck_fn, utils.Truncate(result, 80)))
+    self._CheckLUResult(op, result)
 
     return result
 
diff --git a/lib/netutils.py b/lib/netutils.py
index f39ef77..ed7e599 100644
--- a/lib/netutils.py
+++ b/lib/netutils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti network utility module.
@@ -157,7 +166,7 @@
                                (err[0], err[2]), errors.ECODE_RESOLVER)
 
 
-class Hostname:
+class Hostname(object):
   """Class implementing resolver and hostname functionality.
 
   """
diff --git a/lib/network.py b/lib/network.py
index 0356476..445f8db 100644
--- a/lib/network.py
+++ b/lib/network.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """IP address pool management functions.
@@ -153,8 +162,6 @@
   def Validate(self):
     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.gateway in self.network
@@ -188,25 +195,40 @@
     """
     return self.all_reservations.to01().replace("1", "X").replace("0", ".")
 
-  def IsReserved(self, address):
+  def IsReserved(self, address, external=False):
     """Checks if the given IP is reserved.
 
     """
     idx = self._GetAddrIndex(address)
-    return self.all_reservations[idx]
+    if external:
+      return self.ext_reservations[idx]
+    else:
+      return self.reservations[idx]
 
   def Reserve(self, address, external=False):
     """Mark an address as used.
 
     """
-    if self.IsReserved(address):
-      raise errors.AddressPoolError("%s is already reserved" % address)
+    if self.IsReserved(address, external):
+      if external:
+        msg = "IP %s is already externally reserved" % address
+      else:
+        msg = "IP %s is already used by an instance" % address
+      raise errors.AddressPoolError(msg)
+
     self._Mark(address, external=external)
 
   def Release(self, address, external=False):
     """Release a given address reservation.
 
     """
+    if not self.IsReserved(address, external):
+      if external:
+        msg = "IP %s is not externally reserved" % address
+      else:
+        msg = "IP %s is not used by an instance" % address
+      raise errors.AddressPoolError(msg)
+
     self._Mark(address, value=False, external=external)
 
   def GetFreeAddress(self):
diff --git a/lib/objects.py b/lib/objects.py
index e5af589..d9e754f 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Transportable objects for Ganeti.
@@ -266,6 +275,10 @@
     """Implement __repr__ for ConfigObjects."""
     return repr(self.ToDict())
 
+  def __eq__(self, other):
+    """Implement __eq__ for ConfigObjects."""
+    return isinstance(other, self.__class__) and self.ToDict() == other.ToDict()
+
   def UpgradeConfig(self):
     """Fill defaults for missing configuration values.
 
@@ -447,10 +460,7 @@
       InstancePolicy.UpgradeDiskTemplates(
         nodegroup.ipolicy, self.cluster.enabled_disk_templates)
     if self.cluster.drbd_usermode_helper is None:
-      # To decide if we set an helper let's check if at least one instance has
-      # a DRBD disk. This does not cover all the possible scenarios but it
-      # gives a good approximation.
-      if self.HasAnyDiskOfType(constants.DT_DRBD8):
+      if self.cluster.IsDiskTemplateEnabled(constants.DT_DRBD8):
         self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
     if self.networks is None:
       self.networks = {}
@@ -484,7 +494,8 @@
 
 class NIC(ConfigObject):
   """Config object representing a network card."""
-  __slots__ = ["name", "mac", "ip", "network", "nicparams", "netinfo"] + _UUID
+  __slots__ = ["name", "mac", "ip", "network",
+               "nicparams", "netinfo", "pci"] + _UUID
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
@@ -507,9 +518,11 @@
 
 class Disk(ConfigObject):
   """Config object representing a block device."""
-  __slots__ = (["name", "dev_type", "logical_id", "physical_id",
-                "children", "iv_name", "size", "mode", "params", "spindles"] +
-               _UUID)
+  __slots__ = (["name", "dev_type", "logical_id", "children", "iv_name",
+                "size", "mode", "params", "spindles", "pci"] + _UUID +
+               # dynamic_params is special. It depends on the node this instance
+               # is sent to, and should not be persisted.
+               ["dynamic_params"])
 
   def CreateOnSecondary(self):
     """Test if this device needs to be created on a secondary node."""
@@ -697,51 +710,53 @@
         child.UnsetSize()
     self.size = 0
 
-  def SetPhysicalID(self, target_node_uuid, nodes_ip):
-    """Convert the logical ID to the physical ID.
+  def UpdateDynamicDiskParams(self, target_node_uuid, nodes_ip):
+    """Updates the dynamic disk params for the given node.
 
-    This is used only for drbd, which needs ip/port configuration.
-
-    The routine descends down and updates its children also, because
-    this helps when the only the top device is passed to the remote
-    node.
+    This is mainly used for drbd, which needs ip/port configuration.
 
     Arguments:
       - target_node_uuid: the node UUID we wish to configure for
       - nodes_ip: a mapping of node name to ip
 
-    The target_node must exist in in nodes_ip, and must be one of the
-    nodes in the logical ID for each of the DRBD devices encountered
-    in the disk tree.
+    The target_node must exist in nodes_ip, and should be one of the
+    nodes in the logical ID if this device is a DRBD device.
 
     """
     if self.children:
       for child in self.children:
-        child.SetPhysicalID(target_node_uuid, nodes_ip)
+        child.UpdateDynamicDiskParams(target_node_uuid, nodes_ip)
 
-    if self.logical_id is None and self.physical_id is not None:
-      return
-    if self.dev_type in constants.DTS_DRBD:
-      pnode_uuid, snode_uuid, port, pminor, sminor, secret = self.logical_id
+    dyn_disk_params = {}
+    if self.logical_id is not None and self.dev_type in constants.DTS_DRBD:
+      pnode_uuid, snode_uuid, _, pminor, sminor, _ = self.logical_id
       if target_node_uuid not in (pnode_uuid, snode_uuid):
-        raise errors.ConfigurationError("DRBD device not knowing node %s" %
-                                        target_node_uuid)
+        # disk object is being sent to neither the primary nor the secondary
+        # node. reset the dynamic parameters, the target node is not
+        # supposed to use them.
+        self.dynamic_params = dyn_disk_params
+        return
+
       pnode_ip = nodes_ip.get(pnode_uuid, None)
       snode_ip = nodes_ip.get(snode_uuid, None)
       if pnode_ip is None or snode_ip is None:
         raise errors.ConfigurationError("Can't find primary or secondary node"
                                         " for %s" % str(self))
-      p_data = (pnode_ip, port)
-      s_data = (snode_ip, port)
       if pnode_uuid == target_node_uuid:
-        self.physical_id = p_data + s_data + (pminor, secret)
+        dyn_disk_params[constants.DDP_LOCAL_IP] = pnode_ip
+        dyn_disk_params[constants.DDP_REMOTE_IP] = snode_ip
+        dyn_disk_params[constants.DDP_LOCAL_MINOR] = pminor
+        dyn_disk_params[constants.DDP_REMOTE_MINOR] = sminor
       else: # it must be secondary, we tested above
-        self.physical_id = s_data + p_data + (sminor, secret)
-    else:
-      self.physical_id = self.logical_id
-    return
+        dyn_disk_params[constants.DDP_LOCAL_IP] = snode_ip
+        dyn_disk_params[constants.DDP_REMOTE_IP] = pnode_ip
+        dyn_disk_params[constants.DDP_LOCAL_MINOR] = sminor
+        dyn_disk_params[constants.DDP_REMOTE_MINOR] = pminor
 
-  def ToDict(self):
+    self.dynamic_params = dyn_disk_params
+
+  # pylint: disable=W0221
+  def ToDict(self, include_dynamic_params=False):
     """Disk-specific conversion to standard python types.
 
     This replaces the children lists of objects with lists of
@@ -749,6 +764,8 @@
 
     """
     bo = super(Disk, self).ToDict()
+    if not include_dynamic_params and "dynamic_params" in bo:
+      del bo["dynamic_params"]
 
     for attr in ("children",):
       alist = bo.get(attr, None)
@@ -766,8 +783,6 @@
       obj.children = outils.ContainerFromDicts(obj.children, list, Disk)
     if obj.logical_id and isinstance(obj.logical_id, list):
       obj.logical_id = tuple(obj.logical_id)
-    if obj.physical_id and isinstance(obj.physical_id, list):
-      obj.physical_id = tuple(obj.physical_id)
     if obj.dev_type in constants.DTS_DRBD:
       # we need a tuple of length six here
       if len(obj.logical_id) < 6:
@@ -783,22 +798,16 @@
     elif self.dev_type in constants.DTS_DRBD:
       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
       val = "<DRBD8("
-      if self.physical_id is None:
-        phy = "unconfigured"
-      else:
-        phy = ("configured as %s:%s %s:%s" %
-               (self.physical_id[0], self.physical_id[1],
-                self.physical_id[2], self.physical_id[3]))
 
-      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
-              (node_a, minor_a, node_b, minor_b, port, phy))
+      val += ("hosts=%s/%d-%s/%d, port=%s, " %
+              (node_a, minor_a, node_b, minor_b, port))
       if self.children and self.children.count(None) == 0:
         val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
       else:
         val += "no local storage"
     else:
-      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
-             (self.dev_type, self.logical_id, self.physical_id, self.children))
+      val = ("<Disk(type=%s, logical_id=%s, children=%s" %
+             (self.dev_type, self.logical_id, self.children))
     if self.iv_name is None:
       val += ", not visible"
     else:
@@ -905,6 +914,7 @@
     elif disk_template == constants.DT_RBD:
       result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_RBD], {
         constants.LDP_POOL: dt_params[constants.RBD_POOL],
+        constants.LDP_ACCESS: dt_params[constants.RBD_ACCESS],
         }))
 
     elif disk_template == constants.DT_EXT:
@@ -1117,10 +1127,11 @@
           _Helper(nodes, child)
 
     all_nodes = set()
-    all_nodes.add(self.primary_node)
     for device in self.disks:
       _Helper(all_nodes, device)
-    return tuple(all_nodes)
+    # ensure that the primary node is always the first
+    all_nodes.discard(self.primary_node)
+    return (self.primary_node, ) + tuple(all_nodes)
 
   all_nodes = property(_ComputeAllNodes, None, None,
                        "List of names of all the nodes of the instance")
@@ -1519,6 +1530,9 @@
     if self.networks is None:
       self.networks = {}
 
+    for network, netparams in self.networks.items():
+      self.networks[network] = FillDict(constants.NICC_DEFAULTS, netparams)
+
   def FillND(self, node):
     """Return filled out ndparams for L{objects.Node}
 
@@ -1705,6 +1719,12 @@
         raise errors.ConfigurationError(msg)
       self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
 
+    # hv_state_static added in 2.7
+    if self.hv_state_static is None:
+      self.hv_state_static = {}
+    if self.disk_state_static is None:
+      self.disk_state_static = {}
+
   @property
   def primary_hypervisor(self):
     """The first hypervisor is the primary.
diff --git a/lib/opcodes.py b/lib/opcodes.py
deleted file mode 100644
index ccdb9aa..0000000
--- a/lib/opcodes.py
+++ /dev/null
@@ -1,2231 +0,0 @@
-#
-#
-
-# 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""OpCodes module
-
-This module implements the data structures which define the cluster
-operations - the so-called opcodes.
-
-Every operation which modifies the cluster state is expressed via
-opcodes.
-
-"""
-
-# this are practically structures, so disable the message about too
-# few public methods:
-# pylint: disable=R0903
-
-import logging
-import re
-import ipaddr
-
-from ganeti import constants
-from ganeti import errors
-from ganeti import ht
-from ganeti import objects
-from ganeti import outils
-
-
-# Common opcode attributes
-
-#: output fields for a query operation
-_POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
-                  "Selected output fields")
-
-#: the shutdown timeout
-_PShutdownTimeout = \
-  ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
-   "How long to wait for instance to shut down")
-
-#: the force parameter
-_PForce = ("force", False, ht.TBool, "Whether to force the operation")
-
-#: a required instance name (for single-instance LUs)
-_PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString,
-                  "Instance name")
-
-#: a instance UUID (for single-instance LUs)
-_PInstanceUuid = ("instance_uuid", None, ht.TMaybeString,
-                  "Instance UUID")
-
-#: Whether to ignore offline nodes
-_PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool,
-                        "Whether to ignore offline nodes")
-
-#: a required node name (for single-node LUs)
-_PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString, "Node name")
-
-#: a node UUID (for use with _PNodeName)
-_PNodeUuid = ("node_uuid", None, ht.TMaybeString, "Node UUID")
-
-#: a required node group name (for single-group LUs)
-_PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString, "Group name")
-
-#: Migration type (live/non-live)
-_PMigrationMode = ("mode", None,
-                   ht.TMaybe(ht.TElemOf(constants.HT_MIGRATION_MODES)),
-                   "Migration mode")
-
-#: Obsolete 'live' migration mode (boolean)
-_PMigrationLive = ("live", None, ht.TMaybeBool,
-                   "Legacy setting for live migration, do not use")
-
-#: Tag type
-_PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES),
-             "Tag kind")
-
-#: List of tag strings
-_PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
-          "List of tag names")
-
-_PForceVariant = ("force_variant", False, ht.TBool,
-                  "Whether to force an unknown OS variant")
-
-_PWaitForSync = ("wait_for_sync", True, ht.TBool,
-                 "Whether to wait for the disk to synchronize")
-
-_PWaitForSyncFalse = ("wait_for_sync", False, ht.TBool,
-                      "Whether to wait for the disk to synchronize"
-                      " (defaults to false)")
-
-_PIgnoreConsistency = ("ignore_consistency", False, ht.TBool,
-                       "Whether to ignore disk consistency")
-
-_PStorageName = ("name", ht.NoDefault, ht.TMaybeString, "Storage name")
-
-_PUseLocking = ("use_locking", False, ht.TBool,
-                "Whether to use synchronization")
-
-_PNameCheck = ("name_check", True, ht.TBool, "Whether to check name")
-
-_PNodeGroupAllocPolicy = \
-  ("alloc_policy", None,
-   ht.TMaybe(ht.TElemOf(constants.VALID_ALLOC_POLICIES)),
-   "Instance allocation policy")
-
-_PGroupNodeParams = ("ndparams", None, ht.TMaybeDict,
-                     "Default node parameters for group")
-
-_PQueryWhat = ("what", ht.NoDefault, ht.TElemOf(constants.QR_VIA_OP),
-               "Resource(s) to query for")
-
-_PEarlyRelease = ("early_release", False, ht.TBool,
-                  "Whether to release locks as soon as possible")
-
-_PIpCheckDoc = "Whether to ensure instance's IP address is inactive"
-
-#: Do not remember instance state changes
-_PNoRemember = ("no_remember", False, ht.TBool,
-                "Do not remember the state change")
-
-#: Target node for instance migration/failover
-_PMigrationTargetNode = ("target_node", None, ht.TMaybeString,
-                         "Target node for shared-storage instances")
-
-_PMigrationTargetNodeUuid = ("target_node_uuid", None, ht.TMaybeString,
-                             "Target node UUID for shared-storage instances")
-
-_PStartupPaused = ("startup_paused", False, ht.TBool,
-                   "Pause instance at startup")
-
-_PVerbose = ("verbose", False, ht.TBool, "Verbose mode")
-
-# Parameters for cluster verification
-_PDebugSimulateErrors = ("debug_simulate_errors", False, ht.TBool,
-                         "Whether to simulate errors (useful for debugging)")
-_PErrorCodes = ("error_codes", False, ht.TBool, "Error codes")
-_PSkipChecks = ("skip_checks", ht.EmptyList,
-                ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS)),
-                "Which checks to skip")
-_PIgnoreErrors = ("ignore_errors", ht.EmptyList,
-                  ht.TListOf(ht.TElemOf(constants.CV_ALL_ECODES_STRINGS)),
-                  "List of error codes that should be treated as warnings")
-
-# Disk parameters
-_PDiskParams = \
-  ("diskparams", None,
-   ht.TMaybe(ht.TDictOf(ht.TElemOf(constants.DISK_TEMPLATES), ht.TDict)),
-   "Disk templates' parameter defaults")
-
-# Parameters for node resource model
-_PHvState = ("hv_state", None, ht.TMaybeDict, "Set hypervisor states")
-_PDiskState = ("disk_state", None, ht.TMaybeDict, "Set disk states")
-
-#: Opportunistic locking
-_POpportunisticLocking = \
-  ("opportunistic_locking", False, ht.TBool,
-   ("Whether to employ opportunistic locking for nodes, meaning nodes"
-    " already locked by another opcode won't be considered for instance"
-    " allocation (only when an iallocator is used)"))
-
-_PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
-                   "Whether to ignore ipolicy violations")
-
-# Allow runtime changes while migrating
-_PAllowRuntimeChgs = ("allow_runtime_changes", True, ht.TBool,
-                      "Allow runtime changes (eg. memory ballooning)")
-
-#: IAllocator field builder
-_PIAllocFromDesc = lambda desc: ("iallocator", None, ht.TMaybeString, desc)
-
-#: a required network name
-_PNetworkName = ("network_name", ht.NoDefault, ht.TNonEmptyString,
-                 "Set network name")
-
-_PTargetGroups = \
-  ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString),
-   "Destination group names or UUIDs (defaults to \"all but current group\")")
-
-#: OP_ID conversion regular expression
-_OPID_RE = re.compile("([a-z])([A-Z])")
-
-#: Utility function for L{OpClusterSetParams}
-_TestClusterOsListItem = \
-  ht.TAnd(ht.TIsLength(2), ht.TItems([
-    ht.TElemOf(constants.DDMS_VALUES),
-    ht.TNonEmptyString,
-    ]))
-
-_TestClusterOsList = ht.TMaybeListOf(_TestClusterOsListItem)
-
-# TODO: Generate check from constants.INIC_PARAMS_TYPES
-#: Utility function for testing NIC definitions
-_TestNicDef = \
-  ht.Comment("NIC parameters")(ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS),
-                                          ht.TMaybeString))
-
-_TSetParamsResultItemItems = [
-  ht.Comment("name of changed parameter")(ht.TNonEmptyString),
-  ht.Comment("new value")(ht.TAny),
-  ]
-
-_TSetParamsResult = \
-  ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)),
-                     ht.TItems(_TSetParamsResultItemItems)))
-
-# In the disks option we can provide arbitrary parameters too, which
-# we may not be able to validate at this level, so we just check the
-# format of the dict here and the checks concerning IDISK_PARAMS will
-# happen at the LU level
-_TDiskParams = \
-  ht.Comment("Disk parameters")(ht.TDictOf(ht.TNonEmptyString,
-                                           ht.TOr(ht.TNonEmptyString, ht.TInt)))
-
-_TQueryRow = \
-  ht.TListOf(ht.TAnd(ht.TIsLength(2),
-                     ht.TItems([ht.TElemOf(constants.RS_ALL),
-                                ht.TAny])))
-
-_TQueryResult = ht.TListOf(_TQueryRow)
-
-_TOldQueryRow = ht.TListOf(ht.TAny)
-
-_TOldQueryResult = ht.TListOf(_TOldQueryRow)
-
-
-_SUMMARY_PREFIX = {
-  "CLUSTER_": "C_",
-  "GROUP_": "G_",
-  "NODE_": "N_",
-  "INSTANCE_": "I_",
-  }
-
-#: Attribute name for dependencies
-DEPEND_ATTR = "depends"
-
-#: Attribute name for comment
-COMMENT_ATTR = "comment"
-
-
-def _NameComponents(name):
-  """Split an opcode class name into its components
-
-  @type name: string
-  @param name: the class name, as OpXxxYyy
-  @rtype: array of strings
-  @return: the components of the name
-
-  """
-  assert name.startswith("Op")
-  # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
-  # consume any input, and hence we would just have all the elements
-  # in the list, one by one; but it seems that split doesn't work on
-  # non-consuming input, hence we have to process the input string a
-  # bit
-  name = _OPID_RE.sub(r"\1,\2", name)
-  elems = name.split(",")
-  return elems
-
-
-def _NameToId(name):
-  """Convert an opcode class name to an OP_ID.
-
-  @type name: string
-  @param name: the class name, as OpXxxYyy
-  @rtype: string
-  @return: the name in the OP_XXXX_YYYY format
-
-  """
-  if not name.startswith("Op"):
-    return None
-  return "_".join(n.upper() for n in _NameComponents(name))
-
-
-def NameToReasonSrc(name):
-  """Convert an opcode class name to a source string for the reason trail
-
-  @type name: string
-  @param name: the class name, as OpXxxYyy
-  @rtype: string
-  @return: the name in the OP_XXXX_YYYY format
-
-  """
-  if not name.startswith("Op"):
-    return None
-  return "%s:%s" % (constants.OPCODE_REASON_SRC_OPCODE,
-                    "_".join(n.lower() for n in _NameComponents(name)))
-
-
-def _GenerateObjectTypeCheck(obj, fields_types):
-  """Helper to generate type checks for objects.
-
-  @param obj: The object to generate type checks
-  @param fields_types: The fields and their types as a dict
-  @return: A ht type check function
-
-  """
-  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
-    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
-  return ht.TStrictDict(True, True, fields_types)
-
-
-_TQueryFieldDef = \
-  _GenerateObjectTypeCheck(objects.QueryFieldDefinition, {
-    "name": ht.TNonEmptyString,
-    "title": ht.TNonEmptyString,
-    "kind": ht.TElemOf(constants.QFT_ALL),
-    "doc": ht.TNonEmptyString,
-    })
-
-
-def _BuildDiskTemplateCheck(accept_none):
-  """Builds check for disk template.
-
-  @type accept_none: bool
-  @param accept_none: whether to accept None as a correct value
-  @rtype: callable
-
-  """
-  template_check = ht.TElemOf(constants.DISK_TEMPLATES)
-
-  if accept_none:
-    template_check = ht.TMaybe(template_check)
-
-  return template_check
-
-
-def _CheckStorageType(storage_type):
-  """Ensure a given storage type is valid.
-
-  """
-  if storage_type not in constants.STORAGE_TYPES:
-    raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
-                               errors.ECODE_INVAL)
-  return True
-
-
-#: Storage type parameter
-_PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
-                 "Storage type")
-
-
-@ht.WithDesc("IPv4 network")
-def _CheckCIDRNetNotation(value):
-  """Ensure a given CIDR notation type is valid.
-
-  """
-  try:
-    ipaddr.IPv4Network(value)
-  except ipaddr.AddressValueError:
-    return False
-  return True
-
-
-@ht.WithDesc("IPv4 address")
-def _CheckCIDRAddrNotation(value):
-  """Ensure a given CIDR notation type is valid.
-
-  """
-  try:
-    ipaddr.IPv4Address(value)
-  except ipaddr.AddressValueError:
-    return False
-  return True
-
-
-@ht.WithDesc("IPv6 address")
-def _CheckCIDR6AddrNotation(value):
-  """Ensure a given CIDR notation type is valid.
-
-  """
-  try:
-    ipaddr.IPv6Address(value)
-  except ipaddr.AddressValueError:
-    return False
-  return True
-
-
-@ht.WithDesc("IPv6 network")
-def _CheckCIDR6NetNotation(value):
-  """Ensure a given CIDR notation type is valid.
-
-  """
-  try:
-    ipaddr.IPv6Network(value)
-  except ipaddr.AddressValueError:
-    return False
-  return True
-
-
-_TIpAddress4 = ht.TAnd(ht.TString, _CheckCIDRAddrNotation)
-_TIpAddress6 = ht.TAnd(ht.TString, _CheckCIDR6AddrNotation)
-_TIpNetwork4 = ht.TAnd(ht.TString, _CheckCIDRNetNotation)
-_TIpNetwork6 = ht.TAnd(ht.TString, _CheckCIDR6NetNotation)
-_TMaybeAddr4List = ht.TMaybe(ht.TListOf(_TIpAddress4))
-
-
-class _AutoOpParamSlots(outils.AutoSlots):
-  """Meta class for opcode definitions.
-
-  """
-  def __new__(mcs, name, bases, attrs):
-    """Called when a class should be created.
-
-    @param mcs: The meta class
-    @param name: Name of created class
-    @param bases: Base classes
-    @type attrs: dict
-    @param attrs: Class attributes
-
-    """
-    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
-
-    slots = mcs._GetSlots(attrs)
-    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
-      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
-    assert ("OP_DSC_FORMATTER" not in attrs or
-            callable(attrs["OP_DSC_FORMATTER"])), \
-      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
-       (name, type(attrs["OP_DSC_FORMATTER"])))
-
-    attrs["OP_ID"] = _NameToId(name)
-
-    return outils.AutoSlots.__new__(mcs, name, bases, attrs)
-
-  @classmethod
-  def _GetSlots(mcs, attrs):
-    """Build the slots out of OP_PARAMS.
-
-    """
-    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
-    params = attrs.setdefault("OP_PARAMS", [])
-
-    # Use parameter names as slots
-    return [pname for (pname, _, _, _) in params]
-
-
-class BaseOpCode(outils.ValidatedSlots):
-  """A simple serializable object.
-
-  This object serves as a parent class for OpCode without any custom
-  field handling.
-
-  """
-  # pylint: disable=E1101
-  # as OP_ID is dynamically defined
-  __metaclass__ = _AutoOpParamSlots
-
-  def __getstate__(self):
-    """Generic serializer.
-
-    This method just returns the contents of the instance as a
-    dictionary.
-
-    @rtype:  C{dict}
-    @return: the instance attributes and their values
-
-    """
-    state = {}
-    for name in self.GetAllSlots():
-      if hasattr(self, name):
-        state[name] = getattr(self, name)
-    return state
-
-  def __setstate__(self, state):
-    """Generic unserializer.
-
-    This method just restores from the serialized state the attributes
-    of the current instance.
-
-    @param state: the serialized opcode data
-    @type state:  C{dict}
-
-    """
-    if not isinstance(state, dict):
-      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
-                       type(state))
-
-    for name in self.GetAllSlots():
-      if name not in state and hasattr(self, name):
-        delattr(self, name)
-
-    for name in state:
-      setattr(self, name, state[name])
-
-  @classmethod
-  def GetAllParams(cls):
-    """Compute list of all parameters for an opcode.
-
-    """
-    slots = []
-    for parent in cls.__mro__:
-      slots.extend(getattr(parent, "OP_PARAMS", []))
-    return slots
-
-  def Validate(self, set_defaults): # pylint: disable=W0221
-    """Validate opcode parameters, optionally setting default values.
-
-    @type set_defaults: bool
-    @param set_defaults: Whether to set default values
-    @raise errors.OpPrereqError: When a parameter value doesn't match
-                                 requirements
-
-    """
-    for (attr_name, default, test, _) in self.GetAllParams():
-      assert test == ht.NoType or callable(test)
-
-      if not hasattr(self, attr_name):
-        if default == ht.NoDefault:
-          raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
-                                     (self.OP_ID, attr_name),
-                                     errors.ECODE_INVAL)
-        elif set_defaults:
-          if callable(default):
-            dval = default()
-          else:
-            dval = default
-          setattr(self, attr_name, dval)
-
-      if test == ht.NoType:
-        # no tests here
-        continue
-
-      if set_defaults or hasattr(self, attr_name):
-        attr_val = getattr(self, attr_name)
-        if not test(attr_val):
-          logging.error("OpCode %s, parameter %s, has invalid type %s/value"
-                        " '%s' expecting type %s",
-                        self.OP_ID, attr_name, type(attr_val), attr_val, test)
-          raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
-                                     (self.OP_ID, attr_name),
-                                     errors.ECODE_INVAL)
-
-
-def _BuildJobDepCheck(relative):
-  """Builds check for job dependencies (L{DEPEND_ATTR}).
-
-  @type relative: bool
-  @param relative: Whether to accept relative job IDs (negative)
-  @rtype: callable
-
-  """
-  if relative:
-    job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
-  else:
-    job_id = ht.TJobId
-
-  job_dep = \
-    ht.TAnd(ht.TOr(ht.TList, ht.TTuple),
-            ht.TIsLength(2),
-            ht.TItems([job_id,
-                       ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
-
-  return ht.TMaybeListOf(job_dep)
-
-
-TNoRelativeJobDependencies = _BuildJobDepCheck(False)
-
-#: List of submission status and job ID as returned by C{SubmitManyJobs}
-_TJobIdListItem = \
-  ht.TAnd(ht.TIsLength(2),
-          ht.TItems([ht.Comment("success")(ht.TBool),
-                     ht.Comment("Job ID if successful, error message"
-                                " otherwise")(ht.TOr(ht.TString,
-                                                     ht.TJobId))]))
-TJobIdList = ht.TListOf(_TJobIdListItem)
-
-#: Result containing only list of submitted jobs
-TJobIdListOnly = ht.TStrictDict(True, True, {
-  constants.JOB_IDS_KEY: ht.Comment("List of submitted jobs")(TJobIdList),
-  })
-
-
-class OpCode(BaseOpCode):
-  """Abstract OpCode.
-
-  This is the root of the actual OpCode hierarchy. All clases derived
-  from this class should override OP_ID.
-
-  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
-               children of this class.
-  @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
-                      string returned by Summary(); see the docstring of that
-                      method for details).
-  @cvar OP_DSC_FORMATTER: A callable that should format the OP_DSC_FIELD; if
-                          not present, then the field will be simply converted
-                          to string
-  @cvar OP_PARAMS: List of opcode attributes, the default values they should
-                   get if not already defined, and types they must match.
-  @cvar OP_RESULT: Callable to verify opcode result
-  @cvar WITH_LU: Boolean that specifies whether this should be included in
-      mcpu's dispatch table
-  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
-                 the check steps
-  @ivar priority: Opcode priority for queue
-
-  """
-  # pylint: disable=E1101
-  # as OP_ID is dynamically defined
-  WITH_LU = True
-  OP_PARAMS = [
-    ("dry_run", None, ht.TMaybeBool, "Run checks only, don't execute"),
-    ("debug_level", None, ht.TMaybe(ht.TNonNegativeInt), "Debug level"),
-    ("priority", constants.OP_PRIO_DEFAULT,
-     ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID), "Opcode priority"),
-    (DEPEND_ATTR, None, _BuildJobDepCheck(True),
-     "Job dependencies; if used through ``SubmitManyJobs`` relative (negative)"
-     " job IDs can be used; see :doc:`design document <design-chained-jobs>`"
-     " for details"),
-    (COMMENT_ATTR, None, ht.TMaybeString,
-     "Comment describing the purpose of the opcode"),
-    (constants.OPCODE_REASON, ht.EmptyList, ht.TMaybeList,
-     "The reason trail, describing why the OpCode is executed"),
-    ]
-  OP_RESULT = None
-
-  def __getstate__(self):
-    """Specialized getstate for opcodes.
-
-    This method adds to the state dictionary the OP_ID of the class,
-    so that on unload we can identify the correct class for
-    instantiating the opcode.
-
-    @rtype:   C{dict}
-    @return:  the state as a dictionary
-
-    """
-    data = BaseOpCode.__getstate__(self)
-    data["OP_ID"] = self.OP_ID
-    return data
-
-  @classmethod
-  def LoadOpCode(cls, data):
-    """Generic load opcode method.
-
-    The method identifies the correct opcode class from the dict-form
-    by looking for a OP_ID key, if this is not found, or its value is
-    not available in this module as a child of this class, we fail.
-
-    @type data:  C{dict}
-    @param data: the serialized opcode
-
-    """
-    if not isinstance(data, dict):
-      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
-    if "OP_ID" not in data:
-      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
-    op_id = data["OP_ID"]
-    op_class = None
-    if op_id in OP_MAPPING:
-      op_class = OP_MAPPING[op_id]
-    else:
-      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
-                       op_id)
-    op = op_class()
-    new_data = data.copy()
-    del new_data["OP_ID"]
-    op.__setstate__(new_data)
-    return op
-
-  def Summary(self):
-    """Generates a summary description of this opcode.
-
-    The summary is the value of the OP_ID attribute (without the "OP_"
-    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
-    defined; this field should allow to easily identify the operation
-    (for an instance creation job, e.g., it would be the instance
-    name).
-
-    """
-    assert self.OP_ID is not None and len(self.OP_ID) > 3
-    # all OP_ID start with OP_, we remove that
-    txt = self.OP_ID[3:]
-    field_name = getattr(self, "OP_DSC_FIELD", None)
-    if field_name:
-      field_value = getattr(self, field_name, None)
-      field_formatter = getattr(self, "OP_DSC_FORMATTER", None)
-      if callable(field_formatter):
-        field_value = field_formatter(field_value)
-      elif isinstance(field_value, (list, tuple)):
-        field_value = ",".join(str(i) for i in field_value)
-      txt = "%s(%s)" % (txt, field_value)
-    return txt
-
-  def TinySummary(self):
-    """Generates a compact summary description of the opcode.
-
-    """
-    assert self.OP_ID.startswith("OP_")
-
-    text = self.OP_ID[3:]
-
-    for (prefix, supplement) in _SUMMARY_PREFIX.items():
-      if text.startswith(prefix):
-        return supplement + text[len(prefix):]
-
-    return text
-
-
-# cluster opcodes
-
-class OpClusterPostInit(OpCode):
-  """Post cluster initialization.
-
-  This opcode does not touch the cluster at all. Its purpose is to run hooks
-  after the cluster has been initialized.
-
-  """
-  OP_RESULT = ht.TBool
-
-
-class OpClusterDestroy(OpCode):
-  """Destroy the cluster.
-
-  This opcode has no other parameters. All the state is irreversibly
-  lost after the execution of this opcode.
-
-  """
-  OP_RESULT = ht.TNonEmptyString
-
-
-class OpClusterQuery(OpCode):
-  """Query cluster information."""
-  OP_RESULT = ht.TDictOf(ht.TNonEmptyString, ht.TAny)
-
-
-class OpClusterVerify(OpCode):
-  """Submits all jobs necessary to verify the cluster.
-
-  """
-  OP_PARAMS = [
-    _PDebugSimulateErrors,
-    _PErrorCodes,
-    _PSkipChecks,
-    _PIgnoreErrors,
-    _PVerbose,
-    ("group_name", None, ht.TMaybeString, "Group to verify"),
-    ]
-  OP_RESULT = TJobIdListOnly
-
-
-class OpClusterVerifyConfig(OpCode):
-  """Verify the cluster config.
-
-  """
-  OP_PARAMS = [
-    _PDebugSimulateErrors,
-    _PErrorCodes,
-    _PIgnoreErrors,
-    _PVerbose,
-    ]
-  OP_RESULT = ht.TBool
-
-
-class OpClusterVerifyGroup(OpCode):
-  """Run verify on a node group from the cluster.
-
-  @type skip_checks: C{list}
-  @ivar skip_checks: steps to be skipped from the verify process; this
-                     needs to be a subset of
-                     L{constants.VERIFY_OPTIONAL_CHECKS}; currently
-                     only L{constants.VERIFY_NPLUSONE_MEM} can be passed
-
-  """
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PDebugSimulateErrors,
-    _PErrorCodes,
-    _PSkipChecks,
-    _PIgnoreErrors,
-    _PVerbose,
-    ]
-  OP_RESULT = ht.TBool
-
-
-class OpClusterVerifyDisks(OpCode):
-  """Verify the cluster disks.
-
-  """
-  OP_RESULT = TJobIdListOnly
-
-
-class OpGroupVerifyDisks(OpCode):
-  """Verifies the status of all disks in a node group.
-
-  Result: a tuple of three elements:
-    - dict of node names with issues (values: error msg)
-    - list of instances with degraded disks (that should be activated)
-    - dict of instances with missing logical volumes (values: (node, vol)
-      pairs with details about the missing volumes)
-
-  In normal operation, all lists should be empty. A non-empty instance
-  list (3rd element of the result) is still ok (errors were fixed) but
-  non-empty node list means some node is down, and probably there are
-  unfixable drbd errors.
-
-  Note that only instances that are drbd-based are taken into
-  consideration. This might need to be revisited in the future.
-
-  """
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    ]
-  OP_RESULT = \
-    ht.TAnd(ht.TIsLength(3),
-            ht.TItems([ht.TDictOf(ht.TString, ht.TString),
-                       ht.TListOf(ht.TString),
-                       ht.TDictOf(ht.TString,
-                                  ht.TListOf(ht.TListOf(ht.TString)))]))
-
-
-class OpClusterRepairDiskSizes(OpCode):
-  """Verify the disk sizes of the instances and fixes configuration
-  mimatches.
-
-  Parameters: optional instances list, in case we want to restrict the
-  checks to only a subset of the instances.
-
-  Result: a list of tuples, (instance, disk, parameter, new-size) for changed
-  configurations.
-
-  In normal operation, the list should be empty.
-
-  @type instances: list
-  @ivar instances: the list of instances to check, or empty for all instances
-
-  """
-  OP_PARAMS = [
-    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
-    ]
-  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(4),
-                                 ht.TItems([ht.TNonEmptyString,
-                                            ht.TNonNegativeInt,
-                                            ht.TNonEmptyString,
-                                            ht.TNonNegativeInt])))
-
-
-class OpClusterConfigQuery(OpCode):
-  """Query cluster configuration values."""
-  OP_PARAMS = [
-    _POutputFields,
-    ]
-  OP_RESULT = ht.TListOf(ht.TAny)
-
-
-class OpClusterRename(OpCode):
-  """Rename the cluster.
-
-  @type name: C{str}
-  @ivar name: The new name of the cluster. The name and/or the master IP
-              address will be changed to match the new name and its IP
-              address.
-
-  """
-  OP_DSC_FIELD = "name"
-  OP_PARAMS = [
-    ("name", ht.NoDefault, ht.TNonEmptyString, None),
-    ]
-  OP_RESULT = ht.TNonEmptyString
-
-
-class OpClusterSetParams(OpCode):
-  """Change the parameters of the cluster.
-
-  @type vg_name: C{str} or C{None}
-  @ivar vg_name: The new volume group name or None to disable LVM usage.
-
-  """
-  OP_PARAMS = [
-    _PForce,
-    _PHvState,
-    _PDiskState,
-    ("vg_name", None, ht.TMaybe(ht.TString), "Volume group name"),
-    ("enabled_hypervisors", None,
-     ht.TMaybe(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)),
-                       ht.TTrue)),
-     "List of enabled hypervisors"),
-    ("hvparams", None,
-     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
-     "Cluster-wide hypervisor parameter defaults, hypervisor-dependent"),
-    ("beparams", None, ht.TMaybeDict,
-     "Cluster-wide backend parameter defaults"),
-    ("os_hvp", None, ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
-     "Cluster-wide per-OS hypervisor parameter defaults"),
-    ("osparams", None,
-     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
-     "Cluster-wide OS parameter defaults"),
-    _PDiskParams,
-    ("candidate_pool_size", None, ht.TMaybe(ht.TPositiveInt),
-     "Master candidate pool size"),
-    ("uid_pool", None, ht.NoType,
-     "Set UID pool, must be list of lists describing UID ranges (two items,"
-     " start and end inclusive)"),
-    ("add_uids", None, ht.NoType,
-     "Extend UID pool, must be list of lists describing UID ranges (two"
-     " items, start and end inclusive) to be added"),
-    ("remove_uids", None, ht.NoType,
-     "Shrink UID pool, must be list of lists describing UID ranges (two"
-     " items, start and end inclusive) to be removed"),
-    ("maintain_node_health", None, ht.TMaybeBool,
-     "Whether to automatically maintain node health"),
-    ("prealloc_wipe_disks", None, ht.TMaybeBool,
-     "Whether to wipe disks before allocating them to instances"),
-    ("nicparams", None, ht.TMaybeDict, "Cluster-wide NIC parameter defaults"),
-    ("ndparams", None, ht.TMaybeDict, "Cluster-wide node parameter defaults"),
-    ("ipolicy", None, ht.TMaybeDict,
-     "Cluster-wide :ref:`instance policy <rapi-ipolicy>` specs"),
-    ("drbd_helper", None, ht.TMaybe(ht.TString), "DRBD helper program"),
-    ("default_iallocator", None, ht.TMaybe(ht.TString),
-     "Default iallocator for cluster"),
-    ("master_netdev", None, ht.TMaybe(ht.TString),
-     "Master network device"),
-    ("master_netmask", None, ht.TMaybe(ht.TNonNegativeInt),
-     "Netmask of the master IP"),
-    ("reserved_lvs", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "List of reserved LVs"),
-    ("hidden_os", None, _TestClusterOsList,
-     "Modify list of hidden operating systems: each modification must have"
-     " two items, the operation and the OS name; the operation can be"
-     " ``%s`` or ``%s``" % (constants.DDM_ADD, constants.DDM_REMOVE)),
-    ("blacklisted_os", None, _TestClusterOsList,
-     "Modify list of blacklisted operating systems: each modification must"
-     " have two items, the operation and the OS name; the operation can be"
-     " ``%s`` or ``%s``" % (constants.DDM_ADD, constants.DDM_REMOVE)),
-    ("use_external_mip_script", None, ht.TMaybeBool,
-     "Whether to use an external master IP address setup script"),
-    ("enabled_disk_templates", None,
-     ht.TMaybe(ht.TAnd(ht.TListOf(ht.TElemOf(constants.DISK_TEMPLATES)),
-                       ht.TTrue)),
-     "List of enabled disk templates"),
-    ("modify_etc_hosts", None, ht.TMaybeBool,
-     "Whether the cluster can modify and keep in sync the /etc/hosts files"),
-    ("file_storage_dir", None, ht.TMaybe(ht.TString),
-     "Default directory for storing file-backed disks"),
-    ("shared_file_storage_dir", None, ht.TMaybe(ht.TString),
-     "Default directory for storing shared-file-backed disks"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpClusterRedistConf(OpCode):
-  """Force a full push of the cluster configuration.
-
-  """
-  OP_RESULT = ht.TNone
-
-
-class OpClusterActivateMasterIp(OpCode):
-  """Activate the master IP on the master node.
-
-  """
-  OP_RESULT = ht.TNone
-
-
-class OpClusterDeactivateMasterIp(OpCode):
-  """Deactivate the master IP on the master node.
-
-  """
-  OP_RESULT = ht.TNone
-
-
-class OpQuery(OpCode):
-  """Query for resources/items.
-
-  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
-  @ivar fields: List of fields to retrieve
-  @ivar qfilter: Query filter
-
-  """
-  OP_DSC_FIELD = "what"
-  OP_PARAMS = [
-    _PQueryWhat,
-    _PUseLocking,
-    ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
-     "Requested fields"),
-    ("qfilter", None, ht.TMaybe(ht.TList),
-     "Query filter"),
-    ]
-  OP_RESULT = \
-    _GenerateObjectTypeCheck(objects.QueryResponse, {
-      "fields": ht.TListOf(_TQueryFieldDef),
-      "data": _TQueryResult,
-      })
-
-
-class OpQueryFields(OpCode):
-  """Query for available resource/item fields.
-
-  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
-  @ivar fields: List of fields to retrieve
-
-  """
-  OP_DSC_FIELD = "what"
-  OP_PARAMS = [
-    _PQueryWhat,
-    ("fields", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "Requested fields; if not given, all are returned"),
-    ]
-  OP_RESULT = \
-    _GenerateObjectTypeCheck(objects.QueryFieldsResponse, {
-      "fields": ht.TListOf(_TQueryFieldDef),
-      })
-
-
-class OpOobCommand(OpCode):
-  """Interact with OOB."""
-  OP_PARAMS = [
-    ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "List of node names to run the OOB command against"),
-    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "List of node UUIDs to run the OOB command against"),
-    ("command", ht.NoDefault, ht.TElemOf(constants.OOB_COMMANDS),
-     "OOB command to be run"),
-    ("timeout", constants.OOB_TIMEOUT, ht.TInt,
-     "Timeout before the OOB helper will be terminated"),
-    ("ignore_status", False, ht.TBool,
-     "Ignores the node offline status for power off"),
-    ("power_delay", constants.OOB_POWER_DELAY, ht.TNonNegativeFloat,
-     "Time in seconds to wait between powering on nodes"),
-    ]
-  # Fixme: Make it more specific with all the special cases in LUOobCommand
-  OP_RESULT = _TQueryResult
-
-
-class OpRestrictedCommand(OpCode):
-  """Runs a restricted command on node(s).
-
-  """
-  OP_PARAMS = [
-    _PUseLocking,
-    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
-     "Nodes on which the command should be run (at least one)"),
-    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "Node UUIDs on which the command should be run (at least one)"),
-    ("command", ht.NoDefault, ht.TNonEmptyString,
-     "Command name (no parameters)"),
-    ]
-
-  _RESULT_ITEMS = [
-    ht.Comment("success")(ht.TBool),
-    ht.Comment("output or error message")(ht.TString),
-    ]
-
-  OP_RESULT = \
-    ht.TListOf(ht.TAnd(ht.TIsLength(len(_RESULT_ITEMS)),
-                       ht.TItems(_RESULT_ITEMS)))
-
-
-# node opcodes
-
-class OpNodeRemove(OpCode):
-  """Remove a node.
-
-  @type node_name: C{str}
-  @ivar node_name: The name of the node to remove. If the node still has
-                   instances on it, the operation will fail.
-
-  """
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNodeAdd(OpCode):
-  """Add a node to the cluster.
-
-  @type node_name: C{str}
-  @ivar node_name: The name of the node to add. This can be a short name,
-                   but it will be expanded to the FQDN.
-  @type primary_ip: IP address
-  @ivar primary_ip: The primary IP of the node. This will be ignored when the
-                    opcode is submitted, but will be filled during the node
-                    add (so it will be visible in the job query).
-  @type secondary_ip: IP address
-  @ivar secondary_ip: The secondary IP of the node. This needs to be passed
-                      if the cluster has been initialized in 'dual-network'
-                      mode, otherwise it must not be given.
-  @type readd: C{bool}
-  @ivar readd: Whether to re-add an existing node to the cluster. If
-               this is not passed, then the operation will abort if the node
-               name is already in the cluster; use this parameter to 'repair'
-               a node that had its configuration broken, or was reinstalled
-               without removal from the cluster.
-  @type group: C{str}
-  @ivar group: The node group to which this node will belong.
-  @type vm_capable: C{bool}
-  @ivar vm_capable: The vm_capable node attribute
-  @type master_capable: C{bool}
-  @ivar master_capable: The master_capable node attribute
-
-  """
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PHvState,
-    _PDiskState,
-    ("primary_ip", None, ht.NoType, "Primary IP address"),
-    ("secondary_ip", None, ht.TMaybeString, "Secondary IP address"),
-    ("readd", False, ht.TBool, "Whether node is re-added to cluster"),
-    ("group", None, ht.TMaybeString, "Initial node group"),
-    ("master_capable", None, ht.TMaybeBool,
-     "Whether node can become master or master candidate"),
-    ("vm_capable", None, ht.TMaybeBool,
-     "Whether node can host instances"),
-    ("ndparams", None, ht.TMaybeDict, "Node parameters"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNodeQuery(OpCode):
-  """Compute the list of nodes."""
-  OP_PARAMS = [
-    _POutputFields,
-    _PUseLocking,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all nodes, node names otherwise"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-class OpNodeQueryvols(OpCode):
-  """Get list of volumes on node."""
-  OP_PARAMS = [
-    _POutputFields,
-    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all nodes, node names otherwise"),
-    ]
-  OP_RESULT = ht.TListOf(ht.TAny)
-
-
-class OpNodeQueryStorage(OpCode):
-  """Get information on storage for node(s)."""
-  OP_PARAMS = [
-    _POutputFields,
-    _PStorageType,
-    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "List of nodes"),
-    ("name", None, ht.TMaybeString, "Storage name"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-class OpNodeModifyStorage(OpCode):
-  """Modifies the properies of a storage unit"""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid,
-    _PStorageType,
-    _PStorageName,
-    ("changes", ht.NoDefault, ht.TDict, "Requested changes"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpRepairNodeStorage(OpCode):
-  """Repairs the volume group on a node."""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid,
-    _PStorageType,
-    _PStorageName,
-    _PIgnoreConsistency,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNodeSetParams(OpCode):
-  """Change the parameters of a node."""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid,
-    _PForce,
-    _PHvState,
-    _PDiskState,
-    ("master_candidate", None, ht.TMaybeBool,
-     "Whether the node should become a master candidate"),
-    ("offline", None, ht.TMaybeBool,
-     "Whether the node should be marked as offline"),
-    ("drained", None, ht.TMaybeBool,
-     "Whether the node should be marked as drained"),
-    ("auto_promote", False, ht.TBool,
-     "Whether node(s) should be promoted to master candidate if necessary"),
-    ("master_capable", None, ht.TMaybeBool,
-     "Denote whether node can become master or master candidate"),
-    ("vm_capable", None, ht.TMaybeBool,
-     "Denote whether node can host instances"),
-    ("secondary_ip", None, ht.TMaybeString,
-     "Change node's secondary IP address"),
-    ("ndparams", None, ht.TMaybeDict, "Set node parameters"),
-    ("powered", None, ht.TMaybeBool,
-     "Whether the node should be marked as powered"),
-    ]
-  OP_RESULT = _TSetParamsResult
-
-
-class OpNodePowercycle(OpCode):
-  """Tries to powercycle a node."""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid,
-    _PForce,
-    ]
-  OP_RESULT = ht.TMaybeString
-
-
-class OpNodeMigrate(OpCode):
-  """Migrate all instances from a node."""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PNodeName,
-    _PNodeUuid,
-    _PMigrationMode,
-    _PMigrationLive,
-    _PMigrationTargetNode,
-    _PMigrationTargetNodeUuid,
-    _PAllowRuntimeChgs,
-    _PIgnoreIpolicy,
-    _PIAllocFromDesc("Iallocator for deciding the target node"
-                     " for shared-storage instances"),
-    ]
-  OP_RESULT = TJobIdListOnly
-
-
-class OpNodeEvacuate(OpCode):
-  """Evacuate instances off a number of nodes."""
-  OP_DSC_FIELD = "node_name"
-  OP_PARAMS = [
-    _PEarlyRelease,
-    _PNodeName,
-    _PNodeUuid,
-    ("remote_node", None, ht.TMaybeString, "New secondary node"),
-    ("remote_node_uuid", None, ht.TMaybeString, "New secondary node UUID"),
-    _PIAllocFromDesc("Iallocator for computing solution"),
-    ("mode", ht.NoDefault, ht.TElemOf(constants.NODE_EVAC_MODES),
-     "Node evacuation mode"),
-    ]
-  OP_RESULT = TJobIdListOnly
-
-
-# instance opcodes
-
-class OpInstanceCreate(OpCode):
-  """Create an instance.
-
-  @ivar instance_name: Instance name
-  @ivar mode: Instance creation mode (one of L{constants.INSTANCE_CREATE_MODES})
-  @ivar source_handshake: Signed handshake from source (remote import only)
-  @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)
-  @ivar source_instance_name: Previous name of instance (remote import only)
-  @ivar source_shutdown_timeout: Shutdown timeout used for source instance
-    (remote import only)
-
-  """
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PForceVariant,
-    _PWaitForSync,
-    _PNameCheck,
-    _PIgnoreIpolicy,
-    _POpportunisticLocking,
-    ("beparams", ht.EmptyDict, ht.TDict, "Backend parameters for instance"),
-    ("disks", ht.NoDefault, ht.TListOf(_TDiskParams),
-     "Disk descriptions, for example ``[{\"%s\": 100}, {\"%s\": 5}]``;"
-     " each disk definition must contain a ``%s`` value and"
-     " can contain an optional ``%s`` value denoting the disk access mode"
-     " (%s)" %
-     (constants.IDISK_SIZE, constants.IDISK_SIZE, constants.IDISK_SIZE,
-      constants.IDISK_MODE,
-      " or ".join("``%s``" % i for i in sorted(constants.DISK_ACCESS_SET)))),
-    ("disk_template", ht.NoDefault, _BuildDiskTemplateCheck(True),
-     "Disk template"),
-    ("file_driver", None, ht.TMaybe(ht.TElemOf(constants.FILE_DRIVER)),
-     "Driver for file-backed disks"),
-    ("file_storage_dir", None, ht.TMaybeString,
-     "Directory for storing file-backed disks"),
-    ("hvparams", ht.EmptyDict, ht.TDict,
-     "Hypervisor parameters for instance, hypervisor-dependent"),
-    ("hypervisor", None, ht.TMaybeString, "Hypervisor"),
-    _PIAllocFromDesc("Iallocator for deciding which node(s) to use"),
-    ("identify_defaults", False, ht.TBool,
-     "Reset instance parameters to default if equal"),
-    ("ip_check", True, ht.TBool, _PIpCheckDoc),
-    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
-    ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES),
-     "Instance creation mode"),
-    ("nics", ht.NoDefault, ht.TListOf(_TestNicDef),
-     "List of NIC (network interface) definitions, for example"
-     " ``[{}, {}, {\"%s\": \"198.51.100.4\"}]``; each NIC definition can"
-     " contain the optional values %s" %
-     (constants.INIC_IP,
-      ", ".join("``%s``" % i for i in sorted(constants.INIC_PARAMS)))),
-    ("no_install", None, ht.TMaybeBool,
-     "Do not install the OS (will disable automatic start)"),
-    ("osparams", ht.EmptyDict, ht.TDict, "OS parameters for instance"),
-    ("os_type", None, ht.TMaybeString, "Operating system"),
-    ("pnode", None, ht.TMaybeString, "Primary node"),
-    ("pnode_uuid", None, ht.TMaybeString, "Primary node UUID"),
-    ("snode", None, ht.TMaybeString, "Secondary node"),
-    ("snode_uuid", None, ht.TMaybeString, "Secondary node UUID"),
-    ("source_handshake", None, ht.TMaybe(ht.TList),
-     "Signed handshake from source (remote import only)"),
-    ("source_instance_name", None, ht.TMaybeString,
-     "Source instance name (remote import only)"),
-    ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
-     ht.TNonNegativeInt,
-     "How long source instance was given to shut down (remote import only)"),
-    ("source_x509_ca", None, ht.TMaybeString,
-     "Source X509 CA in PEM format (remote import only)"),
-    ("src_node", None, ht.TMaybeString, "Source node for import"),
-    ("src_node_uuid", None, ht.TMaybeString, "Source node UUID for import"),
-    ("src_path", None, ht.TMaybeString, "Source directory for import"),
-    ("start", True, ht.TBool, "Whether to start instance after creation"),
-    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Instance tags"),
-    ]
-  OP_RESULT = ht.Comment("instance nodes")(ht.TListOf(ht.TNonEmptyString))
-
-
-class OpInstanceMultiAlloc(OpCode):
-  """Allocates multiple instances.
-
-  """
-  OP_PARAMS = [
-    _POpportunisticLocking,
-    _PIAllocFromDesc("Iallocator used to allocate all the instances"),
-    ("instances", ht.EmptyList, ht.TListOf(ht.TInstanceOf(OpInstanceCreate)),
-     "List of instance create opcodes describing the instances to allocate"),
-    ]
-  _JOB_LIST = ht.Comment("List of submitted jobs")(TJobIdList)
-  ALLOCATABLE_KEY = "allocatable"
-  FAILED_KEY = "allocatable"
-  OP_RESULT = ht.TStrictDict(True, True, {
-    constants.JOB_IDS_KEY: _JOB_LIST,
-    ALLOCATABLE_KEY: ht.TListOf(ht.TNonEmptyString),
-    FAILED_KEY: ht.TListOf(ht.TNonEmptyString),
-    })
-
-  def __getstate__(self):
-    """Generic serializer.
-
-    """
-    state = OpCode.__getstate__(self)
-    if hasattr(self, "instances"):
-      # pylint: disable=E1101
-      state["instances"] = [inst.__getstate__() for inst in self.instances]
-    return state
-
-  def __setstate__(self, state):
-    """Generic unserializer.
-
-    This method just restores from the serialized state the attributes
-    of the current instance.
-
-    @param state: the serialized opcode data
-    @type state: C{dict}
-
-    """
-    if not isinstance(state, dict):
-      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
-                       type(state))
-
-    if "instances" in state:
-      state["instances"] = map(OpCode.LoadOpCode, state["instances"])
-
-    return OpCode.__setstate__(self, state)
-
-  def Validate(self, set_defaults):
-    """Validates this opcode.
-
-    We do this recursively.
-
-    """
-    OpCode.Validate(self, set_defaults)
-
-    for inst in self.instances: # pylint: disable=E1101
-      inst.Validate(set_defaults)
-
-
-class OpInstanceReinstall(OpCode):
-  """Reinstall an instance's OS."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PForceVariant,
-    ("os_type", None, ht.TMaybeString, "Instance operating system"),
-    ("osparams", None, ht.TMaybeDict, "Temporary OS parameters"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceRemove(OpCode):
-  """Remove an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PShutdownTimeout,
-    ("ignore_failures", False, ht.TBool,
-     "Whether to ignore failures during removal"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceRename(OpCode):
-  """Rename an instance."""
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PNameCheck,
-    ("new_name", ht.NoDefault, ht.TNonEmptyString, "New instance name"),
-    ("ip_check", False, ht.TBool, _PIpCheckDoc),
-    ]
-  OP_RESULT = ht.Comment("New instance name")(ht.TNonEmptyString)
-
-
-class OpInstanceStartup(OpCode):
-  """Startup an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PForce,
-    _PIgnoreOfflineNodes,
-    ("hvparams", ht.EmptyDict, ht.TDict,
-     "Temporary hypervisor parameters, hypervisor-dependent"),
-    ("beparams", ht.EmptyDict, ht.TDict, "Temporary backend parameters"),
-    _PNoRemember,
-    _PStartupPaused,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceShutdown(OpCode):
-  """Shutdown an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PForce,
-    _PIgnoreOfflineNodes,
-    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
-     "How long to wait for instance to shut down"),
-    _PNoRemember,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceReboot(OpCode):
-  """Reboot an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PShutdownTimeout,
-    ("ignore_secondaries", False, ht.TBool,
-     "Whether to start the instance even if secondary disks are failing"),
-    ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES),
-     "How to reboot instance"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceReplaceDisks(OpCode):
-  """Replace the disks of an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PEarlyRelease,
-    _PIgnoreIpolicy,
-    ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
-     "Replacement mode"),
-    ("disks", ht.EmptyList, ht.TListOf(ht.TNonNegativeInt),
-     "Disk indexes"),
-    ("remote_node", None, ht.TMaybeString, "New secondary node"),
-    ("remote_node_uuid", None, ht.TMaybeString, "New secondary node UUID"),
-    _PIAllocFromDesc("Iallocator for deciding new secondary node"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceFailover(OpCode):
-  """Failover an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PShutdownTimeout,
-    _PIgnoreConsistency,
-    _PMigrationTargetNode,
-    _PMigrationTargetNodeUuid,
-    _PIgnoreIpolicy,
-    _PIAllocFromDesc("Iallocator for deciding the target node for"
-                     " shared-storage instances"),
-    ("cleanup", False, ht.TBool,
-     "Whether a previously failed failover should be cleaned up"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceMigrate(OpCode):
-  """Migrate an instance.
-
-  This migrates (without shutting down an instance) to its secondary
-  node.
-
-  @ivar instance_name: the name of the instance
-  @ivar mode: the migration mode (live, non-live or None for auto)
-
-  """
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PMigrationMode,
-    _PMigrationLive,
-    _PMigrationTargetNode,
-    _PMigrationTargetNodeUuid,
-    _PAllowRuntimeChgs,
-    _PIgnoreIpolicy,
-    ("cleanup", False, ht.TBool,
-     "Whether a previously failed migration should be cleaned up"),
-    _PIAllocFromDesc("Iallocator for deciding the target node for"
-                     " shared-storage instances"),
-    ("allow_failover", False, ht.TBool,
-     "Whether we can fallback to failover if migration is not possible"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceMove(OpCode):
-  """Move an instance.
-
-  This move (with shutting down an instance and data copying) to an
-  arbitrary node.
-
-  @ivar instance_name: the name of the instance
-  @ivar target_node: the destination node
-
-  """
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PShutdownTimeout,
-    _PIgnoreIpolicy,
-    ("target_node", ht.NoDefault, ht.TNonEmptyString, "Target node"),
-    ("target_node_uuid", None, ht.TMaybeString, "Target node UUID"),
-    _PIgnoreConsistency,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceConsole(OpCode):
-  """Connect to an instance's console."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    ]
-  OP_RESULT = ht.TDict
-
-
-class OpInstanceActivateDisks(OpCode):
-  """Activate an instance's disks."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    ("ignore_size", False, ht.TBool, "Whether to ignore recorded size"),
-    _PWaitForSyncFalse,
-    ]
-  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(3),
-                                 ht.TItems([ht.TNonEmptyString,
-                                            ht.TNonEmptyString,
-                                            ht.TNonEmptyString])))
-
-
-class OpInstanceDeactivateDisks(OpCode):
-  """Deactivate an instance's disks."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PForce,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceRecreateDisks(OpCode):
-  """Recreate an instance's disks."""
-  _TDiskChanges = \
-    ht.TAnd(ht.TIsLength(2),
-            ht.TItems([ht.Comment("Disk index")(ht.TNonNegativeInt),
-                       ht.Comment("Parameters")(_TDiskParams)]))
-
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    ("disks", ht.EmptyList,
-     ht.TOr(ht.TListOf(ht.TNonNegativeInt), ht.TListOf(_TDiskChanges)),
-     "List of disk indexes (deprecated) or a list of tuples containing a disk"
-     " index and a possibly empty dictionary with disk parameter changes"),
-    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "New instance nodes, if relocation is desired"),
-    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "New instance node UUIDs, if relocation is desired"),
-    _PIAllocFromDesc("Iallocator for deciding new nodes"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceQuery(OpCode):
-  """Compute the list of instances."""
-  OP_PARAMS = [
-    _POutputFields,
-    _PUseLocking,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all instances, instance names otherwise"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-class OpInstanceQueryData(OpCode):
-  """Compute the run-time status of instances."""
-  OP_PARAMS = [
-    _PUseLocking,
-    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Instance names"),
-    ("static", False, ht.TBool,
-     "Whether to only return configuration data without querying"
-     " nodes"),
-    ]
-  OP_RESULT = ht.TDictOf(ht.TNonEmptyString, ht.TDict)
-
-
-def _TestInstSetParamsModList(fn):
-  """Generates a check for modification lists.
-
-  """
-  # Old format
-  # TODO: Remove in version 2.8 including support in LUInstanceSetParams
-  old_mod_item_fn = \
-    ht.TAnd(ht.TIsLength(2), ht.TItems([
-      ht.TOr(ht.TElemOf(constants.DDMS_VALUES), ht.TNonNegativeInt),
-      fn,
-      ]))
-
-  # New format, supporting adding/removing disks/NICs at arbitrary indices
-  mod_item_fn = \
-    ht.TAnd(ht.TIsLength(3), ht.TItems([
-      ht.TElemOf(constants.DDMS_VALUES_WITH_MODIFY),
-      ht.Comment("Device index, can be negative, e.g. -1 for last disk")
-                 (ht.TOr(ht.TInt, ht.TString)),
-      fn,
-      ]))
-
-  return ht.TOr(ht.Comment("Recommended")(ht.TListOf(mod_item_fn)),
-                ht.Comment("Deprecated")(ht.TListOf(old_mod_item_fn)))
-
-
-class OpInstanceSetParams(OpCode):
-  """Change the parameters of an instance.
-
-  """
-  TestNicModifications = _TestInstSetParamsModList(_TestNicDef)
-  TestDiskModifications = _TestInstSetParamsModList(_TDiskParams)
-
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PForce,
-    _PForceVariant,
-    _PIgnoreIpolicy,
-    ("nics", ht.EmptyList, TestNicModifications,
-     "List of NIC changes: each item is of the form"
-     " ``(op, identifier, settings)``, ``op`` is one of ``%s``, ``%s`` or"
-     " ``%s``, ``identifier`` can be a zero-based index number (or -1 to refer"
-     " to the last position), the NIC's UUID of the NIC's name; a"
-     " deprecated version of this parameter used the form ``(op, settings)``,"
-     " where ``op`` can be ``%s`` to add a new NIC with the specified"
-     " settings, ``%s`` to remove the last NIC or a number to modify the"
-     " settings of the NIC with that index" %
-     (constants.DDM_ADD, constants.DDM_MODIFY, constants.DDM_REMOVE,
-      constants.DDM_ADD, constants.DDM_REMOVE)),
-    ("disks", ht.EmptyList, TestDiskModifications,
-     "List of disk changes; see ``nics``"),
-    ("beparams", ht.EmptyDict, ht.TDict, "Per-instance backend parameters"),
-    ("runtime_mem", None, ht.TMaybePositiveInt, "New runtime memory"),
-    ("hvparams", ht.EmptyDict, ht.TDict,
-     "Per-instance hypervisor parameters, hypervisor-dependent"),
-    ("disk_template", None, ht.TMaybe(_BuildDiskTemplateCheck(False)),
-     "Disk template for instance"),
-    ("pnode", None, ht.TMaybeString, "New primary node"),
-    ("pnode_uuid", None, ht.TMaybeString, "New primary node UUID"),
-    ("remote_node", None, ht.TMaybeString,
-     "Secondary node (used when changing disk template)"),
-    ("remote_node_uuid", None, ht.TMaybeString,
-     "Secondary node UUID (used when changing disk template)"),
-    ("os_name", None, ht.TMaybeString,
-     "Change the instance's OS without reinstalling the instance"),
-    ("osparams", None, ht.TMaybeDict, "Per-instance OS parameters"),
-    ("wait_for_sync", True, ht.TBool,
-     "Whether to wait for the disk to synchronize, when changing template"),
-    ("offline", None, ht.TMaybeBool, "Whether to mark instance as offline"),
-    ("conflicts_check", True, ht.TBool, "Check for conflicting IPs"),
-    ]
-  OP_RESULT = _TSetParamsResult
-
-
-class OpInstanceGrowDisk(OpCode):
-  """Grow a disk of an instance."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PWaitForSync,
-    ("disk", ht.NoDefault, ht.TInt, "Disk index"),
-    ("amount", ht.NoDefault, ht.TNonNegativeInt,
-     "Amount of disk space to add (megabytes)"),
-    ("absolute", False, ht.TBool,
-     "Whether the amount parameter is an absolute target or a relative one"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpInstanceChangeGroup(OpCode):
-  """Moves an instance to another node group."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PEarlyRelease,
-    _PIAllocFromDesc("Iallocator for computing solution"),
-    _PTargetGroups,
-    ]
-  OP_RESULT = TJobIdListOnly
-
-
-# Node group opcodes
-
-class OpGroupAdd(OpCode):
-  """Add a node group to the cluster."""
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PNodeGroupAllocPolicy,
-    _PGroupNodeParams,
-    _PDiskParams,
-    _PHvState,
-    _PDiskState,
-    ("ipolicy", None, ht.TMaybeDict,
-     "Group-wide :ref:`instance policy <rapi-ipolicy>` specs"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpGroupAssignNodes(OpCode):
-  """Assign nodes to a node group."""
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PForce,
-    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
-     "List of nodes to assign"),
-    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
-     "List of node UUIDs to assign"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpGroupQuery(OpCode):
-  """Compute the list of node groups."""
-  OP_PARAMS = [
-    _POutputFields,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all groups, group names otherwise"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-class OpGroupSetParams(OpCode):
-  """Change the parameters of a node group."""
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PNodeGroupAllocPolicy,
-    _PGroupNodeParams,
-    _PDiskParams,
-    _PHvState,
-    _PDiskState,
-    ("ipolicy", None, ht.TMaybeDict, "Group-wide instance policy specs"),
-    ]
-  OP_RESULT = _TSetParamsResult
-
-
-class OpGroupRemove(OpCode):
-  """Remove a node group from the cluster."""
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpGroupRename(OpCode):
-  """Rename a node group in the cluster."""
-  OP_PARAMS = [
-    _PGroupName,
-    ("new_name", ht.NoDefault, ht.TNonEmptyString, "New group name"),
-    ]
-  OP_RESULT = ht.Comment("New group name")(ht.TNonEmptyString)
-
-
-class OpGroupEvacuate(OpCode):
-  """Evacuate a node group in the cluster."""
-  OP_DSC_FIELD = "group_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PEarlyRelease,
-    _PIAllocFromDesc("Iallocator for computing solution"),
-    _PTargetGroups,
-    ]
-  OP_RESULT = TJobIdListOnly
-
-
-# OS opcodes
-class OpOsDiagnose(OpCode):
-  """Compute the list of guest operating systems."""
-  OP_PARAMS = [
-    _POutputFields,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Which operating systems to diagnose"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-# ExtStorage opcodes
-class OpExtStorageDiagnose(OpCode):
-  """Compute the list of external storage providers."""
-  OP_PARAMS = [
-    _POutputFields,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Which ExtStorage Provider to diagnose"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-# Exports opcodes
-class OpBackupQuery(OpCode):
-  """Compute the list of exported images."""
-  OP_PARAMS = [
-    _PUseLocking,
-    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all nodes, node names otherwise"),
-    ]
-  OP_RESULT = ht.TDictOf(ht.TNonEmptyString,
-                         ht.TOr(ht.Comment("False on error")(ht.TBool),
-                                ht.TListOf(ht.TNonEmptyString)))
-
-
-class OpBackupPrepare(OpCode):
-  """Prepares an instance export.
-
-  @ivar instance_name: Instance name
-  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
-
-  """
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
-     "Export mode"),
-    ]
-  OP_RESULT = ht.TMaybeDict
-
-
-class OpBackupExport(OpCode):
-  """Export an instance.
-
-  For local exports, the export destination is the node name. For
-  remote exports, the export destination is a list of tuples, each
-  consisting of hostname/IP address, port, magic, HMAC and HMAC
-  salt. The HMAC is calculated using the cluster domain secret over
-  the value "${index}:${hostname}:${port}". The destination X509 CA
-  must be a signed certificate.
-
-  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
-  @ivar target_node: Export destination
-  @ivar x509_key_name: X509 key to use (remote export only)
-  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
-                             only)
-
-  """
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    _PShutdownTimeout,
-    # TODO: Rename target_node as it changes meaning for different export modes
-    # (e.g. "destination")
-    ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList),
-     "Destination information, depends on export mode"),
-    ("target_node_uuid", None, ht.TMaybeString,
-     "Target node UUID (if local export)"),
-    ("shutdown", True, ht.TBool, "Whether to shutdown instance before export"),
-    ("remove_instance", False, ht.TBool,
-     "Whether to remove instance after export"),
-    ("ignore_remove_failures", False, ht.TBool,
-     "Whether to ignore failures while removing instances"),
-    ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES),
-     "Export mode"),
-    ("x509_key_name", None, ht.TMaybe(ht.TList),
-     "Name of X509 key (remote export only)"),
-    ("destination_x509_ca", None, ht.TMaybeString,
-     "Destination X509 CA (remote export only)"),
-    ]
-  OP_RESULT = \
-    ht.TAnd(ht.TIsLength(2), ht.TItems([
-      ht.Comment("Finalizing status")(ht.TBool),
-      ht.Comment("Status for every exported disk")(ht.TListOf(ht.TBool)),
-      ]))
-
-
-class OpBackupRemove(OpCode):
-  """Remove an instance's export."""
-  OP_DSC_FIELD = "instance_name"
-  OP_PARAMS = [
-    _PInstanceName,
-    _PInstanceUuid,
-    ]
-  OP_RESULT = ht.TNone
-
-
-# Tags opcodes
-class OpTagsGet(OpCode):
-  """Returns the tags of the given object."""
-  OP_DSC_FIELD = "name"
-  OP_PARAMS = [
-    _PTagKind,
-    # Not using _PUseLocking as the default is different for historical reasons
-    ("use_locking", True, ht.TBool, "Whether to use synchronization"),
-    # Name is only meaningful for nodes and instances
-    ("name", ht.NoDefault, ht.TMaybeString,
-     "Name of object to retrieve tags from"),
-    ]
-  OP_RESULT = ht.TListOf(ht.TNonEmptyString)
-
-
-class OpTagsSearch(OpCode):
-  """Searches the tags in the cluster for a given pattern."""
-  OP_DSC_FIELD = "pattern"
-  OP_PARAMS = [
-    ("pattern", ht.NoDefault, ht.TNonEmptyString,
-     "Search pattern (regular expression)"),
-    ]
-  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
-    ht.TNonEmptyString,
-    ht.TNonEmptyString,
-    ])))
-
-
-class OpTagsSet(OpCode):
-  """Add a list of tags on a given object."""
-  OP_PARAMS = [
-    _PTagKind,
-    _PTags,
-    # Name is only meaningful for groups, nodes and instances
-    ("name", ht.NoDefault, ht.TMaybeString,
-     "Name of object where tag(s) should be added"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpTagsDel(OpCode):
-  """Remove a list of tags from a given object."""
-  OP_PARAMS = [
-    _PTagKind,
-    _PTags,
-    # Name is only meaningful for groups, nodes and instances
-    ("name", ht.NoDefault, ht.TMaybeString,
-     "Name of object where tag(s) should be deleted"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-# Test opcodes
-class OpTestDelay(OpCode):
-  """Sleeps for a configured amount of time.
-
-  This is used just for debugging and testing.
-
-  Parameters:
-    - duration: the time to sleep, in seconds
-    - on_master: if true, sleep on the master
-    - on_nodes: list of nodes in which to sleep
-
-  If the on_master parameter is true, it will execute a sleep on the
-  master (before any node sleep).
-
-  If the on_nodes list is not empty, it will sleep on those nodes
-  (after the sleep on the master, if that is enabled).
-
-  As an additional feature, the case of duration < 0 will be reported
-  as an execution error, so this opcode can be used as a failure
-  generator. The case of duration == 0 will not be treated specially.
-
-  """
-  OP_DSC_FIELD = "duration"
-  OP_PARAMS = [
-    ("duration", ht.NoDefault, ht.TNumber, None),
-    ("on_master", True, ht.TBool, None),
-    ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
-    ("on_node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
-    ("repeat", 0, ht.TNonNegativeInt, None),
-    ]
-
-  def OP_DSC_FORMATTER(self, value): # pylint: disable=C0103,R0201
-    """Custom formatter for duration.
-
-    """
-    try:
-      v = float(value)
-    except TypeError:
-      v = value
-    return str(v)
-
-
-class OpTestAllocator(OpCode):
-  """Allocator framework testing.
-
-  This opcode has two modes:
-    - gather and return allocator input for a given mode (allocate new
-      or replace secondary) and a given instance definition (direction
-      'in')
-    - run a selected allocator for a given operation (as above) and
-      return the allocator output (direction 'out')
-
-  """
-  OP_DSC_FIELD = "iallocator"
-  OP_PARAMS = [
-    ("direction", ht.NoDefault,
-     ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS), None),
-    ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES), None),
-    ("name", ht.NoDefault, ht.TNonEmptyString, None),
-    ("nics", ht.NoDefault,
-     ht.TMaybeListOf(ht.TDictOf(ht.TElemOf([constants.INIC_MAC,
-                                            constants.INIC_IP,
-                                            "bridge"]),
-                                ht.TMaybeString)),
-     None),
-    ("disks", ht.NoDefault, ht.TMaybe(ht.TList), None),
-    ("hypervisor", None, ht.TMaybeString, None),
-    _PIAllocFromDesc(None),
-    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
-    ("memory", None, ht.TMaybe(ht.TNonNegativeInt), None),
-    ("vcpus", None, ht.TMaybe(ht.TNonNegativeInt), None),
-    ("os", None, ht.TMaybeString, None),
-    ("disk_template", None, ht.TMaybeString, None),
-    ("instances", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
-    ("evac_mode", None,
-     ht.TMaybe(ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)), None),
-    ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
-    ("spindle_use", 1, ht.TNonNegativeInt, None),
-    ("count", 1, ht.TNonNegativeInt, None),
-    ]
-
-
-class OpTestJqueue(OpCode):
-  """Utility opcode to test some aspects of the job queue.
-
-  """
-  OP_PARAMS = [
-    ("notify_waitlock", False, ht.TBool, None),
-    ("notify_exec", False, ht.TBool, None),
-    ("log_messages", ht.EmptyList, ht.TListOf(ht.TString), None),
-    ("fail", False, ht.TBool, None),
-    ]
-
-
-class OpTestDummy(OpCode):
-  """Utility opcode used by unittests.
-
-  """
-  OP_PARAMS = [
-    ("result", ht.NoDefault, ht.NoType, None),
-    ("messages", ht.NoDefault, ht.NoType, None),
-    ("fail", ht.NoDefault, ht.NoType, None),
-    ("submit_jobs", None, ht.NoType, None),
-    ]
-  WITH_LU = False
-
-
-# Network opcodes
-# Add a new network in the cluster
-class OpNetworkAdd(OpCode):
-  """Add an IP network to the cluster."""
-  OP_DSC_FIELD = "network_name"
-  OP_PARAMS = [
-    _PNetworkName,
-    ("network", ht.NoDefault, _TIpNetwork4, "IPv4 subnet"),
-    ("gateway", None, ht.TMaybe(_TIpAddress4), "IPv4 gateway"),
-    ("network6", None, ht.TMaybe(_TIpNetwork6), "IPv6 subnet"),
-    ("gateway6", None, ht.TMaybe(_TIpAddress6), "IPv6 gateway"),
-    ("mac_prefix", None, ht.TMaybeString,
-     "MAC address prefix that overrides cluster one"),
-    ("add_reserved_ips", None, _TMaybeAddr4List,
-     "Which IP addresses to reserve"),
-    ("conflicts_check", True, ht.TBool,
-     "Whether to check for conflicting IP addresses"),
-    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Network tags"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNetworkRemove(OpCode):
-  """Remove an existing network from the cluster.
-     Must not be connected to any nodegroup.
-
-  """
-  OP_DSC_FIELD = "network_name"
-  OP_PARAMS = [
-    _PNetworkName,
-    _PForce,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNetworkSetParams(OpCode):
-  """Modify Network's parameters except for IPv4 subnet"""
-  OP_DSC_FIELD = "network_name"
-  OP_PARAMS = [
-    _PNetworkName,
-    ("gateway", None, ht.TMaybeValueNone(_TIpAddress4), "IPv4 gateway"),
-    ("network6", None, ht.TMaybeValueNone(_TIpNetwork6), "IPv6 subnet"),
-    ("gateway6", None, ht.TMaybeValueNone(_TIpAddress6), "IPv6 gateway"),
-    ("mac_prefix", None, ht.TMaybeValueNone(ht.TString),
-     "MAC address prefix that overrides cluster one"),
-    ("add_reserved_ips", None, _TMaybeAddr4List,
-     "Which external IP addresses to reserve"),
-    ("remove_reserved_ips", None, _TMaybeAddr4List,
-     "Which external IP addresses to release"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNetworkConnect(OpCode):
-  """Connect a Network to a specific Nodegroup with the defined netparams
-     (mode, link). Nics in this Network will inherit those params.
-     Produce errors if a NIC (that its not already assigned to a network)
-     has an IP that is contained in the Network this will produce error unless
-     --no-conflicts-check is passed.
-
-  """
-  OP_DSC_FIELD = "network_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PNetworkName,
-    ("network_mode", ht.NoDefault, ht.TElemOf(constants.NIC_VALID_MODES),
-     "Connectivity mode"),
-    ("network_link", ht.NoDefault, ht.TString, "Connectivity link"),
-    ("conflicts_check", True, ht.TBool, "Whether to check for conflicting IPs"),
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNetworkDisconnect(OpCode):
-  """Disconnect a Network from a Nodegroup. Produce errors if NICs are
-     present in the Network unless --no-conficts-check option is passed.
-
-  """
-  OP_DSC_FIELD = "network_name"
-  OP_PARAMS = [
-    _PGroupName,
-    _PNetworkName,
-    ]
-  OP_RESULT = ht.TNone
-
-
-class OpNetworkQuery(OpCode):
-  """Compute the list of networks."""
-  OP_PARAMS = [
-    _POutputFields,
-    _PUseLocking,
-    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "Empty list to query all groups, group names otherwise"),
-    ]
-  OP_RESULT = _TOldQueryResult
-
-
-def _GetOpList():
-  """Returns list of all defined opcodes.
-
-  Does not eliminate duplicates by C{OP_ID}.
-
-  """
-  return [v for v in globals().values()
-          if (isinstance(v, type) and issubclass(v, OpCode) and
-              hasattr(v, "OP_ID") and v is not OpCode)]
-
-
-OP_MAPPING = dict((v.OP_ID, v) for v in _GetOpList())
diff --git a/lib/opcodes.py.in_after b/lib/opcodes.py.in_after
new file mode 100644
index 0000000..4bb6b21
--- /dev/null
+++ b/lib/opcodes.py.in_after
@@ -0,0 +1,15 @@
+
+
+def _GetOpList():
+  """Returns list of all defined opcodes.
+
+  Does not eliminate duplicates by C{OP_ID}.
+
+  """
+  return [v for v in globals().values()
+          if (isinstance(v, type) and issubclass(v, OpCode) and
+              hasattr(v, "OP_ID") and v is not OpCode and
+              v.OP_ID != 'OP_INSTANCE_MULTI_ALLOC_BASE')]
+
+
+OP_MAPPING = dict((v.OP_ID, v) for v in _GetOpList())
diff --git a/lib/opcodes.py.in_before b/lib/opcodes.py.in_before
new file mode 100644
index 0000000..3cdd542
--- /dev/null
+++ b/lib/opcodes.py.in_before
@@ -0,0 +1,223 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""OpCodes module
+
+Note that this file is autogenerated using @src/hs2py@ with a header
+from @lib/opcodes.py.in_before@ and a footer from @lib/opcodes.py.in_after@.
+
+This module implements part of the data structures which define the
+cluster operations - the so-called opcodes.
+
+Every operation which modifies the cluster state is expressed via
+opcodes.
+
+"""
+
+# this are practically structures, so disable the message about too
+# few public methods:
+# pylint: disable=R0903
+# pylint: disable=C0301
+
+from ganeti import constants
+from ganeti import ht
+
+from ganeti import opcodes_base
+
+
+class OpCode(opcodes_base.BaseOpCode):
+  """Abstract OpCode.
+
+  This is the root of the actual OpCode hierarchy. All clases derived
+  from this class should override OP_ID.
+
+  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
+               children of this class.
+  @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
+                      string returned by Summary(); see the docstring of that
+                      method for details).
+  @cvar OP_DSC_FORMATTER: A callable that should format the OP_DSC_FIELD; if
+                          not present, then the field will be simply converted
+                          to string
+  @cvar OP_PARAMS: List of opcode attributes, the default values they should
+                   get if not already defined, and types they must match.
+  @cvar OP_RESULT: Callable to verify opcode result
+  @cvar WITH_LU: Boolean that specifies whether this should be included in
+      mcpu's dispatch table
+  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
+                 the check steps
+  @ivar priority: Opcode priority for queue
+
+  """
+  # pylint: disable=E1101
+  # as OP_ID is dynamically defined
+  WITH_LU = True
+  OP_PARAMS = [
+    ("dry_run", None, ht.TMaybe(ht.TBool), "Run checks only, don't execute"),
+    ("debug_level", None, ht.TMaybe(ht.TNonNegative(ht.TInt)), "Debug level"),
+    ("priority", constants.OP_PRIO_DEFAULT,
+     ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID), "Opcode priority"),
+    (opcodes_base.DEPEND_ATTR, None, opcodes_base.BuildJobDepCheck(True),
+     "Job dependencies; if used through ``SubmitManyJobs`` relative (negative)"
+     " job IDs can be used; see :doc:`design document <design-chained-jobs>`"
+     " for details"),
+    (opcodes_base.COMMENT_ATTR, None, ht.TMaybe(ht.TString),
+     "Comment describing the purpose of the opcode"),
+    (constants.OPCODE_REASON, [], ht.TMaybe(ht.TListOf(ht.TAny)),
+     "The reason trail, describing why the OpCode is executed"),
+    ]
+  OP_RESULT = None
+
+  def __getstate__(self):
+    """Specialized getstate for opcodes.
+
+    This method adds to the state dictionary the OP_ID of the class,
+    so that on unload we can identify the correct class for
+    instantiating the opcode.
+
+    @rtype:   C{dict}
+    @return:  the state as a dictionary
+
+    """
+    data = opcodes_base.BaseOpCode.__getstate__(self)
+    data["OP_ID"] = self.OP_ID
+    return data
+
+  @classmethod
+  def LoadOpCode(cls, data):
+    """Generic load opcode method.
+
+    The method identifies the correct opcode class from the dict-form
+    by looking for a OP_ID key, if this is not found, or its value is
+    not available in this module as a child of this class, we fail.
+
+    @type data:  C{dict}
+    @param data: the serialized opcode
+
+    """
+    if not isinstance(data, dict):
+      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
+    if "OP_ID" not in data:
+      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
+    op_id = data["OP_ID"]
+    op_class = None
+    if op_id in OP_MAPPING:
+      op_class = OP_MAPPING[op_id]
+    else:
+      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
+                       op_id)
+    op = op_class()
+    new_data = data.copy()
+    del new_data["OP_ID"]
+    op.__setstate__(new_data)
+    return op
+
+  def Summary(self):
+    """Generates a summary description of this opcode.
+
+    The summary is the value of the OP_ID attribute (without the "OP_"
+    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
+    defined; this field should allow to easily identify the operation
+    (for an instance creation job, e.g., it would be the instance
+    name).
+
+    """
+    assert self.OP_ID is not None and len(self.OP_ID) > 3
+    # all OP_ID start with OP_, we remove that
+    txt = self.OP_ID[3:]
+    field_name = getattr(self, "OP_DSC_FIELD", None)
+    if field_name:
+      field_value = getattr(self, field_name, None)
+      field_formatter = getattr(self, "OP_DSC_FORMATTER", None)
+      if callable(field_formatter):
+        field_value = field_formatter(field_value)
+      elif isinstance(field_value, (list, tuple)):
+        field_value = ",".join(str(i) for i in field_value)
+      txt = "%s(%s)" % (txt, field_value)
+    return txt
+
+  def TinySummary(self):
+    """Generates a compact summary description of the opcode.
+
+    """
+    assert self.OP_ID.startswith("OP_")
+
+    text = self.OP_ID[3:]
+
+    for (prefix, supplement) in opcodes_base.SUMMARY_PREFIX.items():
+      if text.startswith(prefix):
+        return supplement + text[len(prefix):]
+
+    return text
+
+
+class OpInstanceMultiAllocBase(OpCode):
+  """Allocates multiple instances.
+
+  """
+  def __getstate__(self):
+    """Generic serializer.
+
+    """
+    state = OpCode.__getstate__(self)
+    if hasattr(self, "instances"):
+      # pylint: disable=E1101
+      state["instances"] = [inst.__getstate__() for inst in self.instances]
+    return state
+
+  def __setstate__(self, state):
+    """Generic unserializer.
+
+    This method just restores from the serialized state the attributes
+    of the current instance.
+
+    @param state: the serialized opcode data
+    @type state: C{dict}
+
+    """
+    if not isinstance(state, dict):
+      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
+                       type(state))
+
+    if "instances" in state:
+      state["instances"] = map(OpCode.LoadOpCode, state["instances"])
+
+    return OpCode.__setstate__(self, state)
+
+  def Validate(self, set_defaults):
+    """Validates this opcode.
+
+    We do this recursively.
+
+    """
+    OpCode.Validate(self, set_defaults)
+
+    for inst in self.instances: # pylint: disable=E1101
+      inst.Validate(set_defaults)
diff --git a/lib/opcodes_base.py b/lib/opcodes_base.py
new file mode 100644
index 0000000..ecaf803
--- /dev/null
+++ b/lib/opcodes_base.py
@@ -0,0 +1,281 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""OpCodes base module
+
+This module implements part of the data structures which define the
+cluster operations - the so-called opcodes.
+
+Every operation which modifies the cluster state is expressed via
+opcodes.
+
+"""
+
+# this are practically structures, so disable the message about too
+# few public methods:
+# pylint: disable=R0903
+
+import copy
+import logging
+import re
+
+from ganeti import constants
+from ganeti import errors
+from ganeti import ht
+from ganeti import outils
+
+
+#: OP_ID conversion regular expression
+_OPID_RE = re.compile("([a-z])([A-Z])")
+
+SUMMARY_PREFIX = {
+  "CLUSTER_": "C_",
+  "GROUP_": "G_",
+  "NODE_": "N_",
+  "INSTANCE_": "I_",
+  }
+
+#: Attribute name for dependencies
+DEPEND_ATTR = "depends"
+
+#: Attribute name for comment
+COMMENT_ATTR = "comment"
+
+
+def _NameComponents(name):
+  """Split an opcode class name into its components
+
+  @type name: string
+  @param name: the class name, as OpXxxYyy
+  @rtype: array of strings
+  @return: the components of the name
+
+  """
+  assert name.startswith("Op")
+  # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
+  # consume any input, and hence we would just have all the elements
+  # in the list, one by one; but it seems that split doesn't work on
+  # non-consuming input, hence we have to process the input string a
+  # bit
+  name = _OPID_RE.sub(r"\1,\2", name)
+  elems = name.split(",")
+  return elems
+
+
+def _NameToId(name):
+  """Convert an opcode class name to an OP_ID.
+
+  @type name: string
+  @param name: the class name, as OpXxxYyy
+  @rtype: string
+  @return: the name in the OP_XXXX_YYYY format
+
+  """
+  if not name.startswith("Op"):
+    return None
+  return "_".join(n.upper() for n in _NameComponents(name))
+
+
+def NameToReasonSrc(name):
+  """Convert an opcode class name to a source string for the reason trail
+
+  @type name: string
+  @param name: the class name, as OpXxxYyy
+  @rtype: string
+  @return: the name in the OP_XXXX_YYYY format
+
+  """
+  if not name.startswith("Op"):
+    return None
+  return "%s:%s" % (constants.OPCODE_REASON_SRC_OPCODE,
+                    "_".join(n.lower() for n in _NameComponents(name)))
+
+
+class _AutoOpParamSlots(outils.AutoSlots):
+  """Meta class for opcode definitions.
+
+  """
+  def __new__(mcs, name, bases, attrs):
+    """Called when a class should be created.
+
+    @param mcs: The meta class
+    @param name: Name of created class
+    @param bases: Base classes
+    @type attrs: dict
+    @param attrs: Class attributes
+
+    """
+    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
+
+    slots = mcs._GetSlots(attrs)
+    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
+      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
+    assert ("OP_DSC_FORMATTER" not in attrs or
+            callable(attrs["OP_DSC_FORMATTER"])), \
+      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
+       (name, type(attrs["OP_DSC_FORMATTER"])))
+
+    attrs["OP_ID"] = _NameToId(name)
+
+    return outils.AutoSlots.__new__(mcs, name, bases, attrs)
+
+  @classmethod
+  def _GetSlots(mcs, attrs):
+    """Build the slots out of OP_PARAMS.
+
+    """
+    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
+    params = attrs.setdefault("OP_PARAMS", [])
+
+    # Use parameter names as slots
+    return [pname for (pname, _, _, _) in params]
+
+
+class BaseOpCode(outils.ValidatedSlots):
+  """A simple serializable object.
+
+  This object serves as a parent class for OpCode without any custom
+  field handling.
+
+  """
+  # pylint: disable=E1101
+  # as OP_ID is dynamically defined
+  __metaclass__ = _AutoOpParamSlots
+
+  def __getstate__(self):
+    """Generic serializer.
+
+    This method just returns the contents of the instance as a
+    dictionary.
+
+    @rtype:  C{dict}
+    @return: the instance attributes and their values
+
+    """
+    state = {}
+    for name in self.GetAllSlots():
+      if hasattr(self, name):
+        state[name] = getattr(self, name)
+    return state
+
+  def __setstate__(self, state):
+    """Generic unserializer.
+
+    This method just restores from the serialized state the attributes
+    of the current instance.
+
+    @param state: the serialized opcode data
+    @type state:  C{dict}
+
+    """
+    if not isinstance(state, dict):
+      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
+                       type(state))
+
+    for name in self.GetAllSlots():
+      if name not in state and hasattr(self, name):
+        delattr(self, name)
+
+    for name in state:
+      setattr(self, name, state[name])
+
+  @classmethod
+  def GetAllParams(cls):
+    """Compute list of all parameters for an opcode.
+
+    """
+    slots = []
+    for parent in cls.__mro__:
+      slots.extend(getattr(parent, "OP_PARAMS", []))
+    return slots
+
+  def Validate(self, set_defaults): # pylint: disable=W0221
+    """Validate opcode parameters, optionally setting default values.
+
+    @type set_defaults: bool
+    @param set_defaults: Whether to set default values
+    @raise errors.OpPrereqError: When a parameter value doesn't match
+                                 requirements
+
+    """
+    for (attr_name, default, test, _) in self.GetAllParams():
+      assert callable(test)
+
+      if hasattr(self, attr_name):
+        attr_val = getattr(self, attr_name)
+      else:
+        attr_val = copy.deepcopy(default)
+
+      if test(attr_val):
+        if set_defaults:
+          setattr(self, attr_name, attr_val)
+      elif ht.TInt(attr_val) and test(float(attr_val)):
+        if set_defaults:
+          setattr(self, attr_name, float(attr_val))
+      else:
+        logging.error("OpCode %s, parameter %s, has invalid type %s/value"
+                      " '%s' expecting type %s",
+                      self.OP_ID, attr_name, type(attr_val), attr_val, test)
+
+        if attr_val is None:
+          logging.error("OpCode %s, parameter %s, has default value None which"
+                        " is does not check against the parameter's type: this"
+                        " means this parameter is required but no value was"
+                        " given",
+                        self.OP_ID, attr_name)
+
+        raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
+                                   (self.OP_ID, attr_name),
+                                   errors.ECODE_INVAL)
+
+
+def BuildJobDepCheck(relative):
+  """Builds check for job dependencies (L{DEPEND_ATTR}).
+
+  @type relative: bool
+  @param relative: Whether to accept relative job IDs (negative)
+  @rtype: callable
+
+  """
+  if relative:
+    job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
+  else:
+    job_id = ht.TJobId
+
+  job_dep = \
+    ht.TAnd(ht.TOr(ht.TListOf(ht.TAny), ht.TTuple),
+            ht.TIsLength(2),
+            ht.TItems([job_id,
+                       ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
+
+  return ht.TMaybe(ht.TListOf(job_dep))
+
+
+TNoRelativeJobDependencies = BuildJobDepCheck(False)
diff --git a/lib/outils.py b/lib/outils.py
index f0f6558..d25c06f 100644
--- a/lib/outils.py
+++ b/lib/outils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Module for object related utils."""
 
diff --git a/lib/ovf.py b/lib/ovf.py
index adabbaa..4b65cb5 100644
--- a/lib/ovf.py
+++ b/lib/ovf.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Converter tools between ovf and ganeti config file
diff --git a/lib/pathutils.py b/lib/pathutils.py
index 735c415..14dc1ce 100644
--- a/lib/pathutils.py
+++ b/lib/pathutils.py
@@ -2,28 +2,37 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module containing constants and functions for filesystem paths.
 
 """
 
-from ganeti import _autoconf
+from ganeti import _constants
 from ganeti import compat
 from ganeti import vcluster
 
@@ -34,23 +43,29 @@
 DEFAULT_SHARED_FILE_STORAGE_DIR = "/srv/ganeti/shared-file-storage"
 DEFAULT_SHARED_FILE_STORAGE_DIR = \
     vcluster.AddNodePrefix(DEFAULT_SHARED_FILE_STORAGE_DIR)
-EXPORT_DIR = vcluster.AddNodePrefix(_autoconf.EXPORT_DIR)
-OS_SEARCH_PATH = _autoconf.OS_SEARCH_PATH
-ES_SEARCH_PATH = _autoconf.ES_SEARCH_PATH
-SSH_CONFIG_DIR = _autoconf.SSH_CONFIG_DIR
-XEN_CONFIG_DIR = vcluster.AddNodePrefix(_autoconf.XEN_CONFIG_DIR)
-SYSCONFDIR = vcluster.AddNodePrefix(_autoconf.SYSCONFDIR)
-TOOLSDIR = _autoconf.TOOLSDIR
-LOCALSTATEDIR = vcluster.AddNodePrefix(_autoconf.LOCALSTATEDIR)
+EXPORT_DIR = vcluster.AddNodePrefix(_constants.EXPORT_DIR)
+BACKUP_DIR = vcluster.AddNodePrefix(_constants.BACKUP_DIR)
+OS_SEARCH_PATH = _constants.OS_SEARCH_PATH
+ES_SEARCH_PATH = _constants.ES_SEARCH_PATH
+SSH_CONFIG_DIR = _constants.SSH_CONFIG_DIR
+XEN_CONFIG_DIR = vcluster.AddNodePrefix(_constants.XEN_CONFIG_DIR)
+SYSCONFDIR = vcluster.AddNodePrefix(_constants.SYSCONFDIR)
+TOOLSDIR = _constants.TOOLSDIR
+PKGLIBDIR = _constants.PKGLIBDIR
+SHAREDIR = _constants.SHAREDIR
+LOCALSTATEDIR = vcluster.AddNodePrefix(_constants.LOCALSTATEDIR)
 
 # Paths which don't change for a virtual cluster
-DAEMON_UTIL = _autoconf.PKGLIBDIR + "/daemon-util"
-IMPORT_EXPORT_DAEMON = _autoconf.PKGLIBDIR + "/import-export"
-KVM_CONSOLE_WRAPPER = _autoconf.PKGLIBDIR + "/tools/kvm-console-wrapper"
-KVM_IFUP = _autoconf.PKGLIBDIR + "/kvm-ifup"
-PREPARE_NODE_JOIN = _autoconf.PKGLIBDIR + "/prepare-node-join"
-NODE_DAEMON_SETUP = _autoconf.PKGLIBDIR + "/node-daemon-setup"
-XEN_CONSOLE_WRAPPER = _autoconf.PKGLIBDIR + "/tools/xen-console-wrapper"
+DAEMON_UTIL = _constants.PKGLIBDIR + "/daemon-util"
+IMPORT_EXPORT_DAEMON = _constants.PKGLIBDIR + "/import-export"
+KVM_CONSOLE_WRAPPER = _constants.PKGLIBDIR + "/tools/kvm-console-wrapper"
+KVM_IFUP = _constants.PKGLIBDIR + "/kvm-ifup"
+PREPARE_NODE_JOIN = _constants.PKGLIBDIR + "/prepare-node-join"
+NODE_DAEMON_SETUP = _constants.PKGLIBDIR + "/node-daemon-setup"
+XEN_CONSOLE_WRAPPER = _constants.PKGLIBDIR + "/tools/xen-console-wrapper"
+CFGUPGRADE = _constants.PKGLIBDIR + "/tools/cfgupgrade"
+POST_UPGRADE = _constants.PKGLIBDIR + "/tools/post-upgrade"
+ENSURE_DIRS = _constants.PKGLIBDIR + "/ensure-dirs"
 ETC_HOSTS = vcluster.ETC_HOSTS
 
 # Top-level paths
@@ -62,10 +77,10 @@
 #: Script to configure master IP address
 DEFAULT_MASTER_SETUP_SCRIPT = TOOLSDIR + "/master-ip-setup"
 
-SSH_HOST_DSA_PRIV = SSH_CONFIG_DIR + "/ssh_host_dsa_key"
-SSH_HOST_DSA_PUB = SSH_HOST_DSA_PRIV + ".pub"
-SSH_HOST_RSA_PRIV = SSH_CONFIG_DIR + "/ssh_host_rsa_key"
-SSH_HOST_RSA_PUB = SSH_HOST_RSA_PRIV + ".pub"
+SSH_HOST_DSA_PRIV = _constants.SSH_HOST_DSA_PRIV
+SSH_HOST_DSA_PUB = _constants.SSH_HOST_DSA_PUB
+SSH_HOST_RSA_PRIV = _constants.SSH_HOST_RSA_PRIV
+SSH_HOST_RSA_PUB = _constants.SSH_HOST_RSA_PUB
 
 BDEV_CACHE_DIR = RUN_DIR + "/bdev-cache"
 DISK_LINKS_DIR = RUN_DIR + "/instance-disks"
@@ -89,6 +104,7 @@
 SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts"
 RAPI_USERS_FILE = DATA_DIR + "/rapi/users"
 QUEUE_DIR = DATA_DIR + "/queue"
+INTENT_TO_UPGRADE = DATA_DIR + "/intent-to-upgrade"
 CONF_DIR = SYSCONFDIR + "/ganeti"
 USER_SCRIPTS_DIR = CONF_DIR + "/scripts"
 VNC_PASSWORD_FILE = CONF_DIR + "/vnc-cluster-password"
diff --git a/lib/qlang.py b/lib/qlang.py
index 2352391..ff50912 100644
--- a/lib/qlang.py
+++ b/lib/qlang.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module for a simple query language
@@ -32,44 +41,30 @@
 """
 
 import re
-import string # pylint: disable=W0402
 import logging
 
 import pyparsing as pyp
 
+from ganeti import constants
 from ganeti import errors
 from ganeti import utils
 from ganeti import compat
 
 
-# Logic operators with one or more operands, each of which is a filter on its
-# own
-OP_OR = "|"
-OP_AND = "&"
-
-
-# Unary operators with exactly one operand
-OP_NOT = "!"
-OP_TRUE = "?"
-
-
-# Binary operators with exactly two operands, the field name and an
-# operator-specific value
-OP_EQUAL = "="
-OP_NOT_EQUAL = "!="
-OP_LT = "<"
-OP_LE = "<="
-OP_GT = ">"
-OP_GE = ">="
-OP_REGEXP = "=~"
-OP_CONTAINS = "=[]"
-
-
-#: Characters used for detecting user-written filters (see L{_CheckFilter})
-FILTER_DETECTION_CHARS = frozenset("()=/!~'\"\\<>" + string.whitespace)
-
-#: Characters used to detect globbing filters (see L{_CheckGlobbing})
-GLOB_DETECTION_CHARS = frozenset("*?")
+OP_OR = constants.QLANG_OP_OR
+OP_AND = constants.QLANG_OP_AND
+OP_NOT = constants.QLANG_OP_NOT
+OP_TRUE = constants.QLANG_OP_TRUE
+OP_EQUAL = constants.QLANG_OP_EQUAL
+OP_NOT_EQUAL = constants.QLANG_OP_NOT_EQUAL
+OP_LT = constants.QLANG_OP_LT
+OP_LE = constants.QLANG_OP_LE
+OP_GT = constants.QLANG_OP_GT
+OP_GE = constants.QLANG_OP_GE
+OP_REGEXP = constants.QLANG_OP_REGEXP
+OP_CONTAINS = constants.QLANG_OP_CONTAINS
+FILTER_DETECTION_CHARS = constants.QLANG_FILTER_DETECTION_CHARS
+GLOB_DETECTION_CHARS = constants.QLANG_GLOB_DETECTION_CHARS
 
 
 def MakeSimpleFilter(namefield, values):
diff --git a/lib/query.py b/lib/query.py
index 1c2759b..5d908e6 100644
--- a/lib/query.py
+++ b/lib/query.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module for query operations
@@ -201,7 +210,7 @@
   return [fdef for (fdef, _, _, _) in fielddefs]
 
 
-class _FilterHints:
+class _FilterHints(object):
   """Class for filter analytics.
 
   When filters are used, the user of the L{Query} class usually doesn't know
@@ -387,7 +396,7 @@
     ]
 
 
-class _FilterCompilerHelper:
+class _FilterCompilerHelper(object):
   """Converts a query filter to a callable usable for filtering.
 
   """
@@ -655,7 +664,7 @@
   return _FilterCompilerHelper(fields)(hints, qfilter)
 
 
-class Query:
+class Query(object):
   def __init__(self, fieldlist, selected, qfilter=None, namefield=None):
     """Initializes this class.
 
@@ -1087,7 +1096,7 @@
     ]
 
 
-class NodeQueryData:
+class NodeQueryData(object):
   """Data container for node data queries.
 
   """
@@ -1406,7 +1415,7 @@
   return _PrepareFieldList(fields, [])
 
 
-class InstanceQueryData:
+class InstanceQueryData(object):
   """Data container for instance data queries.
 
   """
@@ -1722,6 +1731,24 @@
     return _FS_UNAVAIL
 
 
+def _GetInstNicVLan(ctx, index, _):
+  """Get a NIC's VLAN.
+
+  @type ctx: L{InstanceQueryData}
+  @type index: int
+  @param index: NIC index
+
+  """
+  assert len(ctx.inst_nicparams) >= index
+
+  nicparams = ctx.inst_nicparams[index]
+
+  if nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
+    return nicparams[constants.NIC_VLAN]
+  else:
+    return _FS_UNAVAIL
+
+
 def _GetInstAllNicNetworkNames(ctx, inst):
   """Get all network names for an instance.
 
@@ -1766,6 +1793,29 @@
   return result
 
 
+def _GetInstAllNicVlans(ctx, inst):
+  """Get all network VLANs of an instance.
+
+  @type ctx: L{InstanceQueryData}
+  @type inst: L{objects.Instance}
+  @param inst: Instance object
+
+  """
+  assert len(ctx.inst_nicparams) == len(inst.nics)
+
+  result = []
+
+  for nicp in ctx.inst_nicparams:
+    if nicp[constants.NIC_MODE] == constants.NIC_MODE_OVS:
+      result.append(nicp[constants.NIC_VLAN])
+    else:
+      result.append(None)
+
+  assert len(result) == len(inst.nics)
+
+  return result
+
+
 def _GetInstNicParam(name):
   """Build function for retrieving a NIC parameter.
 
@@ -1825,6 +1875,9 @@
                 "List containing each network interface's link"), IQ_CONFIG, 0,
      lambda ctx, inst: [nicp[constants.NIC_LINK]
                         for nicp in ctx.inst_nicparams]),
+    (_MakeField("nic.vlans", "NIC_VLANs", QFT_OTHER,
+                "List containing each network interface's VLAN"),
+     IQ_CONFIG, 0, _GetInstAllNicVlans),
     (_MakeField("nic.bridges", "NIC_bridges", QFT_OTHER,
                 "List containing each network interface's bridge"),
      IQ_CONFIG, 0, _GetInstAllNicBridges),
@@ -1861,6 +1914,9 @@
       (_MakeField("nic.bridge/%s" % i, "NicBridge/%s" % i, QFT_TEXT,
                   "Bridge of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicBridge)),
+      (_MakeField("nic.vlan/%s" % i, "NicVLAN/%s" % i, QFT_TEXT,
+                  "VLAN of %s network interface" % numtext),
+       IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicVLan)),
       (_MakeField("nic.network/%s" % i, "NicNetwork/%s" % i, QFT_TEXT,
                   "Network of %s network interface" % numtext),
        IQ_CONFIG, 0, _GetInstNic(i, _GetInstNicNetwork)),
@@ -2189,7 +2245,7 @@
   return _PrepareFieldList(fields, aliases)
 
 
-class LockQueryData:
+class LockQueryData(object):
   """Data container for lock data queries.
 
   """
@@ -2251,7 +2307,7 @@
     ], [])
 
 
-class GroupQueryData:
+class GroupQueryData(object):
   """Data container for node group data queries.
 
   """
@@ -2611,7 +2667,7 @@
   }
 
 
-class ClusterQueryData:
+class ClusterQueryData(object):
   def __init__(self, cluster, nodes, drain_flag, watcher_pause):
     """Initializes this class.
 
@@ -2683,7 +2739,7 @@
     ("name", "cluster_name")])
 
 
-class NetworkQueryData:
+class NetworkQueryData(object):
   """Data container for network data queries.
 
   """
diff --git a/lib/rapi/__init__.py b/lib/rapi/__init__.py
index 18c933c..ce4e843 100644
--- a/lib/rapi/__init__.py
+++ b/lib/rapi/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Ganeti RAPI module"""
 
diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py
index c396f98..17d88cb 100644
--- a/lib/rapi/baserlib.py
+++ b/lib/rapi/baserlib.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Remote API base resources library.
@@ -56,7 +65,7 @@
 
   """
   return [(method, "%s_OPCODE" % method, "%s_RENAME" % method,
-           "Get%sOpInput" % method.capitalize())
+           "%s_ALIASES" % method, "Get%sOpInput" % method.capitalize())
           for method in _SUPPORTED_METHODS]
 
 
@@ -177,13 +186,19 @@
 
   """
   try:
-    return fn(*args, **kwargs)
+    result = fn(*args, **kwargs)
   except errors.OpPrereqError, err:
     if len(err.args) == 2 and err.args[1] == errors.ECODE_NOENT:
       raise http.HttpNotFound()
 
     raise
 
+  # In case split query mechanism is used
+  if not result:
+    raise http.HttpNotFound()
+
+  return result
+
 
 def FeedbackFn(msg):
   """Feedback logging function for jobs.
@@ -406,7 +421,7 @@
 
   """
   return frozenset(filter(None, (getattr(cls, op_attr, None)
-                                 for (_, op_attr, _, _) in OPCODE_ATTRS)))
+                                 for (_, op_attr, _, _, _) in OPCODE_ATTRS)))
 
 
 def GetHandlerAccess(handler, method):
@@ -420,6 +435,22 @@
   return getattr(handler, "%s_ACCESS" % method, None)
 
 
+def GetHandler(get_fn, aliases):
+  result = get_fn()
+  if not isinstance(result, dict) or aliases is None:
+    return result
+
+  for (param, alias) in aliases.items():
+    if param in result:
+      if alias in result:
+        raise http.HttpBadRequest("Parameter '%s' has an alias of '%s', but"
+                                  " both values are present in response" %
+                                  (param, alias))
+      result[alias] = result[param]
+
+  return result
+
+
 class _MetaOpcodeResource(type):
   """Meta class for RAPI resources.
 
@@ -431,13 +462,23 @@
     # Access to private attributes of a client class, pylint: disable=W0212
     obj = type.__call__(mcs, *args, **kwargs)
 
-    for (method, op_attr, rename_attr, fn_attr) in OPCODE_ATTRS:
+    for (method, op_attr, rename_attr, aliases_attr, fn_attr) in OPCODE_ATTRS:
       if hasattr(obj, method):
-        # If the method handler is already defined, "*_RENAME" or "Get*OpInput"
-        # shouldn't be (they're only used by the automatically generated
-        # handler)
+        # If the method handler is already defined, "*_RENAME" or
+        # "Get*OpInput" shouldn't be (they're only used by the automatically
+        # generated handler)
         assert not hasattr(obj, rename_attr)
         assert not hasattr(obj, fn_attr)
+
+        # The aliases are allowed only on GET calls
+        assert not hasattr(obj, aliases_attr) or method == http.HTTP_GET
+
+        # GET methods can add aliases of values they return under a different
+        # name
+        if method == http.HTTP_GET and hasattr(obj, aliases_attr):
+          setattr(obj, method,
+                  compat.partial(GetHandler, getattr(obj, method),
+                                 getattr(obj, aliases_attr)))
       else:
         # Try to generate handler method on handler instance
         try:
@@ -462,11 +503,19 @@
   method to do their own opcode input processing (e.g. for static values). The
   C{$METHOD$_RENAME} variable defines which values are renamed (see
   L{baserlib.FillOpcode}).
+  Still default behavior cannot be totally overriden. There are opcode params
+  that are available to all opcodes, e.g. "depends". In case those params
+  (currently only "depends") are found in the original request's body, they are
+  added to the dictionary of parsed parameters and eventually passed to the
+  opcode. If the parsed body is not represented as a dictionary object, the
+  values are not added.
 
   @cvar GET_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
     automatically generate a GET handler submitting the opcode
   @cvar GET_RENAME: Set this to rename parameters in the GET handler (see
     L{baserlib.FillOpcode})
+  @cvar GET_ALIASES: Set this to duplicate return values in GET results (see
+    L{baserlib.GetHandler})
   @ivar GetGetOpInput: Define this to override the default method for
     getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
 
@@ -528,8 +577,18 @@
       }
     return common_static
 
+  def _GetDepends(self):
+    ret = {}
+    if isinstance(self.request_body, dict):
+      depends = self.getBodyParameter("depends", None)
+      if depends:
+        ret.update({"depends": depends})
+    return ret
+
   def _GenericHandler(self, opcode, rename, fn):
     (body, specific_static) = fn()
+    if isinstance(body, dict):
+      body.update(self._GetDepends())
     static = self._GetCommonStatic()
     if specific_static:
       static.update(specific_static)
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 4ad1d16..99558f2 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti RAPI client.
@@ -881,7 +890,7 @@
     return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
                              query, body)
 
-  def DeleteInstance(self, instance, dry_run=False, reason=None):
+  def DeleteInstance(self, instance, dry_run=False, reason=None, **kwargs):
     """Deletes an instance.
 
     @type instance: str
@@ -894,12 +903,14 @@
 
     """
     query = []
+    body = kwargs
+
     _AppendDryRunIf(query, dry_run)
     _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/instances/%s" %
-                              (GANETI_RAPI_VERSION, instance)), query, None)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
   def ModifyInstance(self, instance, reason=None, **kwargs):
     """Modifies an instance.
@@ -1087,7 +1098,7 @@
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
-                     dry_run=False, reason=None):
+                     dry_run=False, reason=None, **kwargs):
     """Reboots an instance.
 
     @type instance: str
@@ -1106,6 +1117,8 @@
 
     """
     query = []
+    body = kwargs
+
     _AppendDryRunIf(query, dry_run)
     _AppendIf(query, reboot_type, ("type", reboot_type))
     _AppendIf(query, ignore_secondaries is not None,
@@ -1114,7 +1127,7 @@
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/reboot" %
-                              (GANETI_RAPI_VERSION, instance)), query, None)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
   def ShutdownInstance(self, instance, dry_run=False, no_remember=False,
                        reason=None, **kwargs):
@@ -1212,7 +1225,8 @@
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
-                           remote_node=None, iallocator=None, reason=None):
+                           remote_node=None, iallocator=None, reason=None,
+                           early_release=None):
     """Replaces disks on an instance.
 
     @type instance: str
@@ -1229,6 +1243,8 @@
                        replace_auto mode)
     @type reason: string
     @param reason: the reason for executing this operation
+    @type early_release: bool
+    @param early_release: whether to release locks as soon as possible
 
     @rtype: string
     @return: job id
@@ -1247,6 +1263,8 @@
     _AppendIf(query, remote_node is not None, ("remote_node", remote_node))
     _AppendIf(query, iallocator is not None, ("iallocator", iallocator))
     _AppendReason(query, reason)
+    _AppendIf(query, early_release is not None,
+              ("early_release", early_release))
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/replace-disks" %
@@ -2013,8 +2031,8 @@
     return self._SendRequest(HTTP_POST, "/%s/networks" % GANETI_RAPI_VERSION,
                              query, body)
 
-  def ConnectNetwork(self, network_name, group_name, mode, link, dry_run=False,
-                     reason=None):
+  def ConnectNetwork(self, network_name, group_name, mode, link,
+                     vlan="", dry_run=False, reason=None):
     """Connects a Network to a NodeGroup with the given netparams
 
     """
@@ -2022,6 +2040,7 @@
       "group_name": group_name,
       "network_mode": mode,
       "network_link": link,
+      "network_vlan": vlan,
       }
 
     query = []
diff --git a/lib/rapi/client_utils.py b/lib/rapi/client_utils.py
index 51f9801..231d277 100644
--- a/lib/rapi/client_utils.py
+++ b/lib/rapi/client_utils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """RAPI client utilities.
diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index 46b89da..69d9845 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Remote API connection map.
 
@@ -43,7 +52,7 @@
 CONNECTOR = {}
 
 
-class Mapper:
+class Mapper(object):
   """Map resource to method.
 
   """
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index a3a7f19..9468774 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Remote API resource implementations.
@@ -220,6 +229,10 @@
 
   """
   GET_OPCODE = opcodes.OpClusterQuery
+  GET_ALIASES = {
+    "volume_group_name": "vg_name",
+    "drbd_usermode_helper": "drbd_helper",
+    }
 
   def GET(self):
     """Returns cluster information.
@@ -415,6 +428,9 @@
 
   """
   GET_OPCODE = opcodes.OpNodeQuery
+  GET_ALIASES = {
+    "sip": "secondary_ip",
+    }
 
   def GET(self):
     """Send information about a node.
@@ -840,6 +856,11 @@
 
   """
   PUT_OPCODE = opcodes.OpGroupSetParams
+  PUT_RENAME = {
+    "custom_ndparams": "ndparams",
+    "custom_ipolicy": "ipolicy",
+    "custom_diskparams": "diskparams",
+    }
 
   def GetPutOpInput(self):
     """Changes some parameters of node group.
@@ -1026,7 +1047,7 @@
 
     """
     assert len(self.items) == 1
-    return ({}, {
+    return (self.request_body, {
       "instance_name": self.items[0],
       "ignore_failures": False,
       "dry_run": self.dryRun(),
@@ -1065,7 +1086,7 @@
     ignore_secondaries=[False|True] parameters.
 
     """
-    return ({}, {
+    return (self.request_body, {
       "instance_name": self.items[0],
       "reboot_type":
         self.queryargs.get("type", [constants.INSTANCE_REBOOT_HARD])[0],
@@ -1353,6 +1374,10 @@
 
   """
   PUT_OPCODE = opcodes.OpInstanceSetParams
+  PUT_RENAME = {
+    "custom_beparams": "beparams",
+    "custom_hvparams": "hvparams",
+    }
 
   def GetPutOpInput(self):
     """Changes parameters of an instance.
diff --git a/lib/rapi/testutils.py b/lib/rapi/testutils.py
index cdc34dc..2a73404 100644
--- a/lib/rapi/testutils.py
+++ b/lib/rapi/testutils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Remote API test utilities.
@@ -145,7 +154,7 @@
   return "\n".join(headers)
 
 
-class FakeCurl:
+class FakeCurl(object):
   """Fake cURL object.
 
   """
@@ -206,7 +215,7 @@
       writefn(resp_body)
 
 
-class _RapiMock:
+class _RapiMock(object):
   """Mocking out the RAPI server parts.
 
   """
@@ -249,7 +258,7 @@
     return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)
 
 
-class _TestLuxiTransport:
+class _TestLuxiTransport(object):
   """Mocked LUXI transport.
 
   Raises L{errors.RapiTestResult} for all method calls, no matter the
@@ -282,7 +291,7 @@
     raise errors.RapiTestResult
 
 
-class _LuxiCallRecorder:
+class _LuxiCallRecorder(object):
   """Records all called LUXI client methods.
 
   """
@@ -327,7 +336,7 @@
     return NotImplemented
 
 
-class InputTestClient:
+class InputTestClient(object):
   """Test version of RAPI client.
 
   Instances of this class can be used to test input arguments for RAPI client
diff --git a/lib/rpc.py b/lib/rpc.py
index c934ba2..0fd31b1 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Inter-node RPC library.
@@ -126,7 +135,7 @@
   return wrapper
 
 
-def _Compress(data):
+def _Compress(_, data):
   """Compresses a string for transport over RPC.
 
   Small amounts of data are not compressed.
@@ -460,14 +469,14 @@
     self._encoder = compat.partial(self._EncodeArg, encoder_fn)
 
   @staticmethod
-  def _EncodeArg(encoder_fn, (argkind, value)):
+  def _EncodeArg(encoder_fn, node, (argkind, value)):
     """Encode argument.
 
     """
     if argkind is None:
       return value
     else:
-      return encoder_fn(argkind)(value)
+      return encoder_fn(argkind)(node, value)
 
   def _Call(self, cdef, node_list, args):
     """Entry point for automatically generated RPC wrappers.
@@ -489,18 +498,16 @@
     if len(args) != len(argdefs):
       raise errors.ProgrammerError("Number of passed arguments doesn't match")
 
-    enc_args = map(self._encoder, zip(map(compat.snd, argdefs), args))
     if prep_fn is None:
-      # for a no-op prep_fn, we serialise the body once, and then we
-      # reuse it in the dictionary values
-      body = serializer.DumpJson(enc_args)
-      pnbody = dict((n, body) for n in node_list)
-    else:
-      # for a custom prep_fn, we pass the encoded arguments and the
-      # node name to the prep_fn, and we serialise its return value
-      assert callable(prep_fn)
-      pnbody = dict((n, serializer.DumpJson(prep_fn(n, enc_args)))
-                    for n in node_list)
+      prep_fn = lambda _, args: args
+    assert callable(prep_fn)
+
+    # encode the arguments for each node individually, pass them and the node
+    # name to the prep_fn, and serialise its return value
+    encode_args_fn = lambda node: map(compat.partial(self._encoder, node),
+                                      zip(map(compat.snd, argdefs), args))
+    pnbody = dict((n, serializer.DumpJson(prep_fn(n, encode_args_fn(n))))
+                  for n in node_list)
 
     result = self._proc(node_list, procedure, pnbody, read_timeout,
                         req_resolver_opts)
@@ -512,7 +519,7 @@
       return result
 
 
-def _ObjectToDict(value):
+def _ObjectToDict(_, value):
   """Converts an object to a dictionary.
 
   @note: See L{objects}.
@@ -521,27 +528,19 @@
   return value.ToDict()
 
 
-def _ObjectListToDict(value):
+def _ObjectListToDict(node, value):
   """Converts a list of L{objects} to dictionaries.
 
   """
-  return map(_ObjectToDict, value)
+  return map(compat.partial(_ObjectToDict, node), value)
 
 
-def _EncodeNodeToDiskDict(value):
-  """Encodes a dictionary with node name as key and disk objects as values.
-
-  """
-  return dict((name, _ObjectListToDict(disks))
-              for name, disks in value.items())
-
-
-def _PrepareFileUpload(getents_fn, filename):
+def _PrepareFileUpload(getents_fn, node, filename):
   """Loads a file and prepares it for an upload to nodes.
 
   """
   statcb = utils.FileStatHelper()
-  data = _Compress(utils.ReadFile(filename, preread=statcb))
+  data = _Compress(node, utils.ReadFile(filename, preread=statcb))
   st = statcb.st
 
   if getents_fn is None:
@@ -555,7 +554,7 @@
           getents.LookupGid(st.st_gid), st.st_atime, st.st_mtime]
 
 
-def _PrepareFinalizeExportDisks(snap_disks):
+def _PrepareFinalizeExportDisks(_, snap_disks):
   """Encodes disks for finalizing export.
 
   """
@@ -570,22 +569,7 @@
   return flat_disks
 
 
-def _EncodeImportExportIO((ieio, ieioargs)):
-  """Encodes import/export I/O information.
-
-  """
-  if ieio == constants.IEIO_RAW_DISK:
-    assert len(ieioargs) == 1
-    return (ieio, (ieioargs[0].ToDict(), ))
-
-  if ieio == constants.IEIO_SCRIPT:
-    assert len(ieioargs) == 2
-    return (ieio, (ieioargs[0].ToDict(), ieioargs[1]))
-
-  return (ieio, ieioargs)
-
-
-def _EncodeBlockdevRename(value):
+def _EncodeBlockdevRename(_, value):
   """Encodes information for renaming block devices.
 
   """
@@ -611,49 +595,43 @@
     result["spindles_free"] = lvm_pv_info["storage_free"]
     result["spindles_total"] = lvm_pv_info["storage_size"]
   else:
-    raise errors.OpExecError("No spindle storage information available.")
+    result["spindles_free"] = 0
+    result["spindles_total"] = 0
 
 
-def _AddDefaultStorageInfoToLegacyNodeInfo(result, space_info):
-  """Extracts the storage space information of the default storage type from
+def _AddStorageInfoToLegacyNodeInfoByTemplate(
+    result, space_info, disk_template):
+  """Extracts the storage space information of the disk template from
   the space info and adds it to the result dictionary.
 
   @see: C{_AddSpindlesToLegacyNodeInfo} for parameter information.
 
   """
-  # Check if there is at least one row for non-spindle storage info.
-  no_defaults = (len(space_info) < 1) or \
-      (space_info[0]["type"] == constants.ST_LVM_PV and len(space_info) == 1)
-
-  default_space_info = None
-  if no_defaults:
-    logging.warning("No storage info provided for default storage type.")
+  if utils.storage.DiskTemplateSupportsSpaceReporting(disk_template):
+    disk_info = utils.storage.LookupSpaceInfoByDiskTemplate(
+        space_info, disk_template)
+    result["name"] = disk_info["name"]
+    result["storage_free"] = disk_info["storage_free"]
+    result["storage_size"] = disk_info["storage_size"]
   else:
-    default_space_info = space_info[0]
-
-  if default_space_info:
-    result["name"] = default_space_info["name"]
-    result["storage_free"] = default_space_info["storage_free"]
-    result["storage_size"] = default_space_info["storage_size"]
+    # FIXME: consider displaying '-' in this case
+    result["storage_free"] = 0
+    result["storage_size"] = 0
 
 
-def MakeLegacyNodeInfo(data, require_spindles=False):
+def MakeLegacyNodeInfo(data, disk_template):
   """Formats the data returned by L{rpc.RpcRunner.call_node_info}.
 
   Converts the data into a single dictionary. This is fine for most use cases,
   but some require information from more than one volume group or hypervisor.
 
-  @param require_spindles: add spindle storage information to the legacy node
-      info
-
   """
   (bootid, space_info, (hv_info, )) = data
 
   ret = utils.JoinDisjointDicts(hv_info, {"bootid": bootid})
 
-  if require_spindles:
-    _AddSpindlesToLegacyNodeInfo(ret, space_info)
-  _AddDefaultStorageInfoToLegacyNodeInfo(ret, space_info)
+  _AddSpindlesToLegacyNodeInfo(ret, space_info)
+  _AddStorageInfoToLegacyNodeInfoByTemplate(ret, space_info, disk_template)
 
   return ret
 
@@ -683,25 +661,26 @@
   return disk
 
 
-def AnnotateDiskParams(template, disks, disk_params):
+def AnnotateDiskParams(disks, disk_params):
   """Annotates the disk objects with the disk parameters.
 
-  @param template: The disk template used
   @param disks: The list of disks objects to annotate
-  @param disk_params: The disk paramaters for annotation
+  @param disk_params: The disk parameters for annotation
   @returns: A list of disk objects annotated
 
   """
-  ld_params = objects.Disk.ComputeLDParams(template, disk_params)
+  def AnnotateDisk(disk):
+    if disk.dev_type == constants.DT_DISKLESS:
+      return disk
 
-  if template == constants.DT_DRBD8:
-    annotation_fn = _AnnotateDParamsDRBD
-  elif template == constants.DT_DISKLESS:
-    annotation_fn = lambda disk, _: disk
-  else:
-    annotation_fn = _AnnotateDParamsGeneric
+    ld_params = objects.Disk.ComputeLDParams(disk.dev_type, disk_params)
 
-  return [annotation_fn(disk.Copy(), ld_params) for disk in disks]
+    if disk.dev_type == constants.DT_DRBD8:
+      return _AnnotateDParamsDRBD(disk, ld_params)
+    else:
+      return _AnnotateDParamsGeneric(disk, ld_params)
+
+  return [AnnotateDisk(disk.Copy()) for disk in disks]
 
 
 def _GetExclusiveStorageFlag(cfg, node_uuid):
@@ -730,8 +709,10 @@
   """
   result = []
   for (storage_type, storage_key) in storage_units:
-    if storage_type in [constants.ST_LVM_VG, constants.ST_LVM_PV]:
+    if storage_type in [constants.ST_LVM_VG]:
       result.append((storage_type, storage_key, [es_flag]))
+      if es_flag:
+        result.append((constants.ST_LVM_PV, storage_key, [es_flag]))
     else:
       result.append((storage_type, storage_key, []))
   return result
@@ -785,10 +766,8 @@
 _ENCODERS = {
   rpc_defs.ED_OBJECT_DICT: _ObjectToDict,
   rpc_defs.ED_OBJECT_DICT_LIST: _ObjectListToDict,
-  rpc_defs.ED_NODE_TO_DISK_DICT: _EncodeNodeToDiskDict,
   rpc_defs.ED_COMPRESS: _Compress,
   rpc_defs.ED_FINALIZE_EXPORT_DISKS: _PrepareFinalizeExportDisks,
-  rpc_defs.ED_IMPEXP_IO: _EncodeImportExportIO,
   rpc_defs.ED_BLOCKDEV_RENAME: _EncodeBlockdevRename,
   }
 
@@ -820,14 +799,18 @@
       rpc_defs.ED_INST_DICT_HVP_BEP_DP: self._InstDictHvpBepDp,
       rpc_defs.ED_INST_DICT_OSP_DP: self._InstDictOspDp,
       rpc_defs.ED_NIC_DICT: self._NicDict,
+      rpc_defs.ED_DEVICE_DICT: self._DeviceDict,
 
       # Encoders annotating disk parameters
       rpc_defs.ED_DISKS_DICT_DP: self._DisksDictDP,
       rpc_defs.ED_MULTI_DISKS_DICT_DP: self._MultiDiskDictDP,
       rpc_defs.ED_SINGLE_DISK_DICT_DP: self._SingleDiskDictDP,
+      rpc_defs.ED_NODE_TO_DISK_DICT_DP: self._EncodeNodeToDiskDictDP,
 
       # Encoders with special requirements
       rpc_defs.ED_FILE_DETAILS: compat.partial(_PrepareFileUpload, _getents),
+
+      rpc_defs.ED_IMPEXP_IO: self._EncodeImportExportIO,
       })
 
     # Resolver using configuration
@@ -846,7 +829,7 @@
     _generated_rpc.RpcClientDnsOnly.__init__(self)
     _generated_rpc.RpcClientDefault.__init__(self)
 
-  def _NicDict(self, nic):
+  def _NicDict(self, _, nic):
     """Convert the given nic to a dict and encapsulate netinfo
 
     """
@@ -858,7 +841,13 @@
         n.netinfo = objects.Network.ToDict(nobj)
     return n.ToDict()
 
-  def _InstDict(self, instance, hvp=None, bep=None, osp=None):
+  def _DeviceDict(self, _, (device, instance)):
+    if isinstance(device, objects.NIC):
+      return self._NicDict(None, device)
+    elif isinstance(device, objects.Disk):
+      return self._SingleDiskDictDP(None, (device, instance))
+
+  def _InstDict(self, node, instance, hvp=None, bep=None, osp=None):
     """Convert the given instance to a dict.
 
     This is done via the instance's ToDict() method and additionally
@@ -888,7 +877,7 @@
     idict["osparams"] = cluster.SimpleFillOS(instance.os, instance.osparams)
     if osp is not None:
       idict["osparams"].update(osp)
-    idict["disks"] = self._DisksDictDP((instance.disks, instance))
+    idict["disks"] = self._DisksDictDP(node, (instance.disks, instance))
     for nic in idict["nics"]:
       nic["nicparams"] = objects.FillDict(
         cluster.nicparams[constants.PP_DEFAULT],
@@ -901,42 +890,71 @@
           nic["netinfo"] = objects.Network.ToDict(nobj)
     return idict
 
-  def _InstDictHvpBepDp(self, (instance, hvp, bep)):
+  def _InstDictHvpBepDp(self, node, (instance, hvp, bep)):
     """Wrapper for L{_InstDict}.
 
     """
-    return self._InstDict(instance, hvp=hvp, bep=bep)
+    return self._InstDict(node, instance, hvp=hvp, bep=bep)
 
-  def _InstDictOspDp(self, (instance, osparams)):
+  def _InstDictOspDp(self, node, (instance, osparams)):
     """Wrapper for L{_InstDict}.
 
     """
-    return self._InstDict(instance, osp=osparams)
+    return self._InstDict(node, instance, osp=osparams)
 
-  def _DisksDictDP(self, (disks, instance)):
+  def _DisksDictDP(self, node, (disks, instance)):
     """Wrapper for L{AnnotateDiskParams}.
 
     """
     diskparams = self._cfg.GetInstanceDiskParams(instance)
-    return [disk.ToDict()
-            for disk in AnnotateDiskParams(instance.disk_template,
-                                           disks, diskparams)]
+    ret = []
+    for disk in AnnotateDiskParams(disks, diskparams):
+      disk_node_uuids = disk.GetNodes(instance.primary_node)
+      node_ips = dict((uuid, node.secondary_ip) for (uuid, node)
+                      in self._cfg.GetMultiNodeInfo(disk_node_uuids))
 
-  def _MultiDiskDictDP(self, disks_insts):
+      disk.UpdateDynamicDiskParams(node, node_ips)
+
+      ret.append(disk.ToDict(include_dynamic_params=True))
+
+    return ret
+
+  def _MultiDiskDictDP(self, node, disks_insts):
     """Wrapper for L{AnnotateDiskParams}.
 
     Supports a list of (disk, instance) tuples.
     """
     return [disk for disk_inst in disks_insts
-            for disk in self._DisksDictDP(disk_inst)]
+            for disk in self._DisksDictDP(node, disk_inst)]
 
-  def _SingleDiskDictDP(self, (disk, instance)):
+  def _SingleDiskDictDP(self, node, (disk, instance)):
     """Wrapper for L{AnnotateDiskParams}.
 
     """
-    (anno_disk,) = self._DisksDictDP(([disk], instance))
+    (anno_disk,) = self._DisksDictDP(node, ([disk], instance))
     return anno_disk
 
+  def _EncodeNodeToDiskDictDP(self, node, value):
+    """Encode dict of node name -> list of (disk, instance) tuples as values.
+
+    """
+    return dict((name, [self._SingleDiskDictDP(node, disk) for disk in disks])
+                for name, disks in value.items())
+
+  def _EncodeImportExportIO(self, node, (ieio, ieioargs)):
+    """Encodes import/export I/O information.
+
+    """
+    if ieio == constants.IEIO_RAW_DISK:
+      assert len(ieioargs) == 1
+      return (ieio, (self._SingleDiskDictDP(node, ieioargs[0]), ))
+
+    if ieio == constants.IEIO_SCRIPT:
+      assert len(ieioargs) == 2
+      return (ieio, (self._SingleDiskDictDP(node, ieioargs[0]), ieioargs[1]))
+
+    return (ieio, ieioargs)
+
 
 class JobQueueRunner(_RpcClientBase, _generated_rpc.RpcClientJobQueue):
   """RPC wrappers for job queue.
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index 95fd9e1..f2d099d 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """RPC definitions for communication between master and node daemons.
 
@@ -62,7 +71,7 @@
  ED_OBJECT_DICT_LIST,
  ED_INST_DICT,
  ED_INST_DICT_HVP_BEP_DP,
- ED_NODE_TO_DISK_DICT,
+ ED_NODE_TO_DISK_DICT_DP,
  ED_INST_DICT_OSP_DP,
  ED_IMPEXP_IO,
  ED_FILE_DETAILS,
@@ -72,7 +81,8 @@
  ED_DISKS_DICT_DP,
  ED_MULTI_DISKS_DICT_DP,
  ED_SINGLE_DISK_DICT_DP,
- ED_NIC_DICT) = range(1, 16)
+ ED_NIC_DICT,
+ ED_DEVICE_DICT) = range(1, 17)
 
 
 def _Prepare(calls):
@@ -143,11 +153,6 @@
     return args
 
 
-def _DrbdCallsPreProc(node, args):
-  """Add the target node UUID as additional field for DRBD related calls."""
-  return args + [node]
-
-
 def _OsGetPostProc(result):
   """Post-processor for L{rpc.RpcRunner.call_os_get}.
 
@@ -296,6 +301,17 @@
     ("reinstall", None, None),
     ("debug", None, None),
     ], None, None, "Starts an instance"),
+  ("hotplug_device", SINGLE, None, constants.RPC_TMO_NORMAL, [
+    ("instance", ED_INST_DICT, "Instance object"),
+    ("action", None, "Hotplug Action"),
+    ("dev_type", None, "Device type"),
+    ("device", ED_DEVICE_DICT, "Device dict"),
+    ("extra", None, "Extra info for device (dev_path for disk)"),
+    ("seq", None, "Device seq"),
+    ], None, None, "Hoplug a device to a running instance"),
+  ("hotplug_supported", SINGLE, None, constants.RPC_TMO_NORMAL, [
+    ("instance", ED_INST_DICT, "Instance object"),
+    ], None, None, "Check if hotplug is supported"),
   ]
 
 _IMPEXP_CALLS = [
@@ -351,7 +367,7 @@
     ], None, None,
    "Gets the sizes of requested block devices present on a node"),
   ("blockdev_create", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("bdev", ED_OBJECT_DICT, None),
+    ("bdev", ED_SINGLE_DISK_DICT_DP, None),
     ("size", None, None),
     ("owner", None, None),
     ("on_primary", None, None),
@@ -365,7 +381,7 @@
     ], None, None,
     "Request wipe at given offset with given size of a block device"),
   ("blockdev_remove", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("bdev", ED_OBJECT_DICT, None),
+    ("bdev", ED_SINGLE_DISK_DICT_DP, None),
     ], None, None, "Request removal of a given block device"),
   ("blockdev_pause_resume_sync", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("disks", ED_DISKS_DICT_DP, None),
@@ -373,7 +389,7 @@
     ], None, None, "Request a pause/resume of given block device"),
   ("blockdev_assemble", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("disk", ED_SINGLE_DISK_DICT_DP, None),
-    ("owner", None, None),
+    ("instance", ED_INST_DICT, None),
     ("on_primary", None, None),
     ("idx", None, None),
     ], None, None, "Request assembling of a given block device"),
@@ -382,41 +398,37 @@
     ], None, None, "Request shutdown of a given block device"),
   ("blockdev_addchildren", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("bdev", ED_SINGLE_DISK_DICT_DP, None),
-    ("ndevs", ED_OBJECT_DICT_LIST, None),
+    ("ndevs", ED_DISKS_DICT_DP, None),
     ], None, None,
    "Request adding a list of children to a (mirroring) device"),
   ("blockdev_removechildren", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("bdev", ED_OBJECT_DICT, None),
-    ("ndevs", ED_OBJECT_DICT_LIST, None),
+    ("bdev", ED_SINGLE_DISK_DICT_DP, None),
+    ("ndevs", ED_DISKS_DICT_DP, None),
     ], None, None,
    "Request removing a list of children from a (mirroring) device"),
   ("blockdev_close", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("instance_name", None, None),
-    ("disks", ED_OBJECT_DICT_LIST, None),
+    ("disks", ED_DISKS_DICT_DP, None),
     ], None, None, "Closes the given block devices"),
   ("blockdev_getdimensions", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("disks", ED_OBJECT_DICT_LIST, None),
+    ("disks", ED_MULTI_DISKS_DICT_DP, None),
     ], None, None, "Returns size and spindles of the given disks"),
   ("drbd_disconnect_net", MULTI, None, constants.RPC_TMO_NORMAL, [
-    ("nodes_ip", None, None),
-    ("disks", ED_OBJECT_DICT_LIST, None),
-    ], _DrbdCallsPreProc, None,
+    ("disks", ED_DISKS_DICT_DP, None),
+    ], None, None,
    "Disconnects the network of the given drbd devices"),
   ("drbd_attach_net", MULTI, None, constants.RPC_TMO_NORMAL, [
-    ("nodes_ip", None, None),
     ("disks", ED_DISKS_DICT_DP, None),
     ("instance_name", None, None),
     ("multimaster", None, None),
-    ], _DrbdCallsPreProc, None, "Connects the given DRBD devices"),
+    ], None, None, "Connects the given DRBD devices"),
   ("drbd_wait_sync", MULTI, None, constants.RPC_TMO_SLOW, [
-    ("nodes_ip", None, None),
     ("disks", ED_DISKS_DICT_DP, None),
-    ], _DrbdCallsPreProc, None,
+    ], None, None,
    "Waits for the synchronization of drbd devices is complete"),
   ("drbd_needs_activation", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("nodes_ip", None, None),
     ("disks", ED_MULTI_DISKS_DICT_DP, None),
-    ], _DrbdCallsPreProc, None,
+    ], None, None,
    "Returns the drbd disks which need activation"),
   ("blockdev_grow", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("cf_bdev", ED_SINGLE_DISK_DICT_DP, None),
@@ -439,7 +451,7 @@
     ("devlist", ED_BLOCKDEV_RENAME, None),
     ], None, None, "Request rename of the given block devices"),
   ("blockdev_find", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("disk", ED_OBJECT_DICT, None),
+    ("disk", ED_SINGLE_DISK_DICT_DP, None),
     ], None, _BlockdevFindPostProc,
     "Request identification of a given block device"),
   ("blockdev_getmirrorstatus", SINGLE, None, constants.RPC_TMO_NORMAL, [
@@ -447,12 +459,12 @@
     ], None, _BlockdevGetMirrorStatusPostProc,
     "Request status of a (mirroring) device"),
   ("blockdev_getmirrorstatus_multi", MULTI, None, constants.RPC_TMO_NORMAL, [
-    ("node_disks", ED_NODE_TO_DISK_DICT, None),
+    ("node_disks", ED_NODE_TO_DISK_DICT_DP, None),
     ], _BlockdevGetMirrorStatusMultiPreProc,
    _BlockdevGetMirrorStatusMultiPostProc,
     "Request status of (mirroring) devices from multiple nodes"),
   ("blockdev_setinfo", SINGLE, None, constants.RPC_TMO_NORMAL, [
-    ("disk", ED_OBJECT_DICT, None),
+    ("disk", ED_SINGLE_DISK_DICT_DP, None),
     ("info", None, None),
     ], None, None, "Sets metadata information on a given block device"),
   ]
@@ -501,6 +513,10 @@
     ("hypervisor", None, "Hypervisor type"),
     ("hvparams", None, "Hypervisor parameters"),
     ], None, None, "Tries to powercycle a node"),
+  ("node_configure_ovs", SINGLE, None, constants.RPC_TMO_NORMAL, [
+    ("ovs_name", None, "Name of the OpenvSwitch to create"),
+    ("ovs_link", None, "Link of the OpenvSwitch to the outside"),
+    ], None, None, "This will create and setup the OpenvSwitch"),
   ]
 
 _MISC_CALLS = [
diff --git a/lib/runtime.py b/lib/runtime.py
index bb586f1..8274faf 100644
--- a/lib/runtime.py
+++ b/lib/runtime.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Module implementing configuration details at runtime.
 
@@ -68,7 +77,7 @@
     raise errors.ConfigurationError("Group '%s' not found (%s)" % (group, err))
 
 
-class GetentResolver:
+class GetentResolver(object):
   """Resolves Ganeti uids and gids by name.
 
   @ivar masterd_uid: The resolved uid of the masterd user
diff --git a/lib/serializer.py b/lib/serializer.py
index f008155..1e20e2c 100644
--- a/lib/serializer.py
+++ b/lib/serializer.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Serializer abstraction module
 
diff --git a/lib/server/__init__.py b/lib/server/__init__.py
index aee6aa0..d36713a 100644
--- a/lib/server/__init__.py
+++ b/lib/server/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Empty file for package definition.
diff --git a/lib/server/masterd.py b/lib/server/masterd.py
index a41b203..62ba497 100644
--- a/lib/server/masterd.py
+++ b/lib/server/masterd.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Master daemon program.
@@ -61,6 +70,8 @@
 from ganeti import pathutils
 from ganeti import ht
 
+from ganeti.utils import version
+
 
 CLIENT_REQUEST_WORKERS = 16
 
@@ -90,7 +101,7 @@
     client_ops = ClientOps(server)
 
     try:
-      (method, args, version) = luxi.ParseRequest(message)
+      (method, args, ver) = luxi.ParseRequest(message)
     except luxi.ProtocolError, err:
       logging.error("Protocol Error: %s", err)
       client.close_log()
@@ -99,9 +110,9 @@
     success = False
     try:
       # Verify client's version if there was one in the request
-      if version is not None and version != constants.LUXI_VERSION:
+      if ver is not None and ver != constants.LUXI_VERSION:
         raise errors.LuxiError("LUXI version mismatch, server %s, request %s" %
-                               (constants.LUXI_VERSION, version))
+                               (constants.LUXI_VERSION, ver))
 
       result = client_ops.handle_request(method, args)
       success = True
@@ -141,7 +152,7 @@
     self.server.request_workers.AddTask((self.server, message, self))
 
 
-class _MasterShutdownCheck:
+class _MasterShutdownCheck(object):
   """Logic for master daemon shutdown.
 
   """
@@ -265,7 +276,7 @@
         self.context.jobqueue.Shutdown()
 
 
-class ClientOps:
+class ClientOps(object):
   """Class holding high-level client operations."""
   def __init__(self, server):
     self.server = server
@@ -293,6 +304,14 @@
       _LogNewJob(True, job_id, ops)
       return job_id
 
+    elif method == luxi.REQ_SUBMIT_JOB_TO_DRAINED_QUEUE:
+      logging.info("Forcefully receiving new job")
+      (job_def, ) = args
+      ops = [opcodes.OpCode.LoadOpCode(state) for state in job_def]
+      job_id = queue.SubmitJobToDrainedQueue(ops)
+      _LogNewJob(True, job_id, ops)
+      return job_id
+
     elif method == luxi.REQ_SUBMIT_MANY_JOBS:
       logging.info("Receiving multiple jobs")
       (job_defs, ) = args
@@ -685,8 +704,8 @@
   try:
     config.ConfigWriter()
   except errors.ConfigVersionMismatch, err:
-    v1 = "%s.%s.%s" % constants.SplitVersion(err.args[0])
-    v2 = "%s.%s.%s" % constants.SplitVersion(err.args[1])
+    v1 = "%s.%s.%s" % version.SplitVersion(err.args[0])
+    v2 = "%s.%s.%s" % version.SplitVersion(err.args[1])
     print >> sys.stderr,  \
         ("Configuration version mismatch. The current Ganeti software"
          " expects version %s, but the on-disk configuration file has"
diff --git a/lib/server/noded.py b/lib/server/noded.py
index 4266e86..e206c4b 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti node daemon"""
@@ -253,11 +262,12 @@
     """Assemble a block device.
 
     """
-    bdev_s, owner, on_primary, idx = params
+    bdev_s, idict, on_primary, idx = params
     bdev = objects.Disk.FromDict(bdev_s)
+    instance = objects.Instance.FromDict(idict)
     if bdev is None:
       raise ValueError("can't unserialize data!")
-    return backend.BlockdevAssemble(bdev, owner, on_primary, idx)
+    return backend.BlockdevAssemble(bdev, instance, on_primary, idx)
 
   @staticmethod
   def perspective_blockdev_shutdown(params):
@@ -415,9 +425,9 @@
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks, target_node_uuid = params
-    disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdDisconnectNet(target_node_uuid, nodes_ip, disks)
+    (disks,) = params
+    disks = [objects.Disk.FromDict(disk) for disk in disks]
+    return backend.DrbdDisconnectNet(disks)
 
   @staticmethod
   def perspective_drbd_attach_net(params):
@@ -427,10 +437,9 @@
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks, instance_name, multimaster, target_node_uuid = params
-    disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdAttachNet(target_node_uuid, nodes_ip, disks,
-                                 instance_name, multimaster)
+    disks, instance_name, multimaster = params
+    disks = [objects.Disk.FromDict(disk) for disk in disks]
+    return backend.DrbdAttachNet(disks, instance_name, multimaster)
 
   @staticmethod
   def perspective_drbd_wait_sync(params):
@@ -440,9 +449,9 @@
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks, target_node_uuid = params
-    disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdWaitSync(target_node_uuid, nodes_ip, disks)
+    (disks,) = params
+    disks = [objects.Disk.FromDict(disk) for disk in disks]
+    return backend.DrbdWaitSync(disks)
 
   @staticmethod
   def perspective_drbd_needs_activation(params):
@@ -452,12 +461,12 @@
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks, target_node_uuid = params
-    disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdNeedsActivation(target_node_uuid, nodes_ip, disks)
+    (disks,) = params
+    disks = [objects.Disk.FromDict(disk) for disk in disks]
+    return backend.DrbdNeedsActivation(disks)
 
   @staticmethod
-  def perspective_drbd_helper(params):
+  def perspective_drbd_helper(_):
     """Query drbd helper.
 
     """
@@ -617,6 +626,29 @@
     return backend.StartInstance(instance, startup_paused, trail)
 
   @staticmethod
+  def perspective_hotplug_device(params):
+    """Hotplugs device to a running instance.
+
+    """
+    (idict, action, dev_type, ddict, extra, seq) = params
+    instance = objects.Instance.FromDict(idict)
+    if dev_type == constants.HOTPLUG_TARGET_DISK:
+      device = objects.Disk.FromDict(ddict)
+    elif dev_type == constants.HOTPLUG_TARGET_NIC:
+      device = objects.NIC.FromDict(ddict)
+    else:
+      assert dev_type in constants.HOTPLUG_ALL_TARGETS
+    return backend.HotplugDevice(instance, action, dev_type, device, extra, seq)
+
+  @staticmethod
+  def perspective_hotplug_supported(params):
+    """Checks if hotplug is supported.
+
+    """
+    instance = objects.Instance.FromDict(params[0])
+    return backend.HotplugSupported(instance)
+
+  @staticmethod
   def perspective_migration_info(params):
     """Gather information about an instance to be migrated.
 
@@ -825,12 +857,20 @@
 
   @staticmethod
   def perspective_node_powercycle(params):
-    """Tries to powercycle the nod.
+    """Tries to powercycle the node.
 
     """
     (hypervisor_type, hvparams) = params
     return backend.PowercycleNode(hypervisor_type, hvparams)
 
+  @staticmethod
+  def perspective_node_configure_ovs(params):
+    """Sets up OpenvSwitch on the node.
+
+    """
+    (ovs_name, ovs_link) = params
+    return backend.ConfigureOVS(ovs_name, ovs_link)
+
   # cluster --------------------------
 
   @staticmethod
diff --git a/lib/server/rapi.py b/lib/server/rapi.py
index 7cdb0aa..5cfce65 100644
--- a/lib/server/rapi.py
+++ b/lib/server/rapi.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Ganeti Remote API master script.
 
@@ -205,7 +214,7 @@
     return serializer.DumpJson(result)
 
 
-class RapiUsers:
+class RapiUsers(object):
   def __init__(self):
     """Initializes this class.
 
diff --git a/lib/ssconf.py b/lib/ssconf.py
index 11af2a2..8ce5602 100644
--- a/lib/ssconf.py
+++ b/lib/ssconf.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Global Configuration data for Ganeti.
@@ -273,6 +282,14 @@
     nl = data.splitlines(False)
     return nl
 
+  def GetOnlineNodeList(self):
+    """Return the list of online cluster nodes.
+
+    """
+    data = self._ReadFile(constants.SS_ONLINE_NODES)
+    nl = data.splitlines(False)
+    return nl
+
   def GetNodePrimaryIPList(self):
     """Return the list of cluster nodes' primary IP.
 
diff --git a/lib/ssh.py b/lib/ssh.py
index c694341..be08fd2 100644
--- a/lib/ssh.py
+++ b/lib/ssh.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module encapsulating ssh functionality.
@@ -106,7 +115,7 @@
                for (kind, (privkey, pubkey, _)) in result))
 
 
-class SshRunner:
+class SshRunner(object):
   """Wrapper for SSH commands.
 
   """
diff --git a/lib/storage/__init__.py b/lib/storage/__init__.py
index 9b9e38c..a05be49 100644
--- a/lib/storage/__init__.py
+++ b/lib/storage/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Block device abstraction
diff --git a/lib/storage/base.py b/lib/storage/base.py
index 8769530..471a31c 100644
--- a/lib/storage/base.py
+++ b/lib/storage/base.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Block device abstraction - base class and utility functions"""
@@ -72,7 +81,7 @@
   after assembly we'll have our correct major/minor.
 
   """
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     self._children = children
     self.dev_path = None
     self.unique_id = unique_id
@@ -81,6 +90,7 @@
     self.attached = False
     self.size = size
     self.params = params
+    self.dyn_params = dyn_params
 
   def Assemble(self):
     """Assemble the device from its components.
@@ -109,7 +119,8 @@
     raise NotImplementedError
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create the device.
 
     If the device cannot be created, it will return None
@@ -132,6 +143,9 @@
     @param params: device-specific options/parameters
     @type excl_stor: bool
     @param excl_stor: whether exclusive_storage is active
+    @type dyn_params: dict
+    @param dyn_params: dynamic parameters of the disk only valid for this node.
+        As set by L{objects.Disk.UpdateDynamicDiskParams}.
     @rtype: L{BlockDev}
     @return: the created device, or C{None} in case of an error
 
@@ -354,6 +368,18 @@
     """
     return (self.GetActualSize(), self.GetActualSpindles())
 
+  def GetUserspaceAccessUri(self, hypervisor):
+    """Return URIs hypervisors can use to access disks in userspace mode.
+
+    @rtype: string
+    @return: userspace device URI
+    @raise errors.BlockDeviceError: if userspace access is not supported
+
+    """
+    ThrowError("Userspace access with %s block device and %s hypervisor is not "
+               "supported." % (self.__class__.__name__,
+                               hypervisor))
+
   def __repr__(self):
     return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
             (self.__class__, self.unique_id, self._children,
diff --git a/lib/storage/bdev.py b/lib/storage/bdev.py
index 005731a..6ba033d 100644
--- a/lib/storage/bdev.py
+++ b/lib/storage/bdev.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Block device abstraction"""
@@ -67,13 +76,14 @@
   _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
   _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
 
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     """Attaches to a LV device.
 
     The unique_id is a tuple (vg_name, lv_name)
 
     """
-    super(LogicalVolume, self).__init__(unique_id, children, size, params)
+    super(LogicalVolume, self).__init__(unique_id, children, size, params,
+                                        dyn_params)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self._vg_name, self._lv_name = unique_id
@@ -123,7 +133,8 @@
     return map((lambda pv: pv.name), empty_pvs)
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new logical volume.
 
     """
@@ -205,7 +216,7 @@
     if result.failed:
       base.ThrowError("LV create failed (%s): %s",
                       result.fail_reason, result.output)
-    return LogicalVolume(unique_id, children, size, params)
+    return LogicalVolume(unique_id, children, size, params, dyn_params)
 
   @staticmethod
   def _GetVolumeInfo(lvm_cmd, fields):
@@ -597,7 +608,8 @@
     snap_name = self._lv_name + ".snap"
 
     # remove existing snapshot if found
-    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
+    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params,
+                         self.dyn_params)
     base.IgnoreError(snap.Remove)
 
     vg_info = self.GetVGInfo([self._vg_name], False)
@@ -717,13 +729,14 @@
   The unique_id for the file device is a (file_driver, file_path) tuple.
 
   """
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     """Initalizes a file device backend.
 
     """
     if children:
       raise errors.BlockDeviceError("Invalid setup for file device")
-    super(FileStorage, self).__init__(unique_id, children, size, params)
+    super(FileStorage, self).__init__(unique_id, children, size, params,
+                                      dyn_params)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self.driver = unique_id[0]
@@ -836,7 +849,8 @@
       base.ThrowError("Can't stat %s: %s", self.dev_path, err)
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new file.
 
     @param size: the size of file in MiB
@@ -865,7 +879,7 @@
         base.ThrowError("File already existing: %s", dev_path)
       base.ThrowError("Error in file creation: %", str(err))
 
-    return FileStorage(unique_id, children, size, params)
+    return FileStorage(unique_id, children, size, params, dyn_params)
 
 
 class PersistentBlockDevice(base.BlockDev):
@@ -878,14 +892,14 @@
   For the time being, pathnames are required to lie under /dev.
 
   """
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     """Attaches to a static block device.
 
     The unique_id is a path under /dev.
 
     """
     super(PersistentBlockDevice, self).__init__(unique_id, children, size,
-                                                params)
+                                                params, dyn_params)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self.dev_path = unique_id[1]
@@ -904,7 +918,8 @@
     self.Attach()
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new device
 
     This is a noop, we only return a PersistentBlockDevice instance
@@ -913,7 +928,7 @@
     if excl_stor:
       raise errors.ProgrammerError("Persistent block device requested with"
                                    " exclusive_storage")
-    return PersistentBlockDevice(unique_id, children, 0, params)
+    return PersistentBlockDevice(unique_id, children, 0, params, dyn_params)
 
   def Remove(self):
     """Remove a device
@@ -946,7 +961,7 @@
       return False
 
     self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
+    self.minor = utils.osminor(st.st_rdev)
     self.attached = True
 
     return True
@@ -990,21 +1005,24 @@
   this to be functional.
 
   """
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     """Attaches to an rbd device.
 
     """
-    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
+    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params,
+                                           dyn_params)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
 
     self.driver, self.rbd_name = unique_id
+    self.rbd_pool = params[constants.LDP_POOL]
 
     self.major = self.minor = None
     self.Attach()
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new rbd device.
 
     Provision a new rbd volume inside a RADOS pool.
@@ -1027,7 +1045,7 @@
       base.ThrowError("rbd creation failed (%s): %s",
                       result.fail_reason, result.output)
 
-    return RADOSBlockDevice(unique_id, children, size, params)
+    return RADOSBlockDevice(unique_id, children, size, params, dyn_params)
 
   def Remove(self):
     """Remove the rbd device.
@@ -1079,7 +1097,7 @@
       return False
 
     self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
+    self.minor = utils.osminor(st.st_rdev)
     self.attached = True
 
     return True
@@ -1335,6 +1353,18 @@
       base.ThrowError("rbd resize failed (%s): %s",
                       result.fail_reason, result.output)
 
+  def GetUserspaceAccessUri(self, hypervisor):
+    """Generate KVM userspace URIs to be used as `-drive file` settings.
+
+    @see: L{BlockDev.GetUserspaceAccessUri}
+
+    """
+    if hypervisor == constants.HT_KVM:
+      return "rbd:" + self.rbd_pool + "/" + self.rbd_name
+    else:
+      base.ThrowError("Hypervisor %s doesn't support RBD userspace access" %
+                      hypervisor)
+
 
 class ExtStorageDevice(base.BlockDev):
   """A block device provided by an ExtStorage Provider.
@@ -1343,11 +1373,12 @@
   handling of the externally provided block devices.
 
   """
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     """Attaches to an extstorage block device.
 
     """
-    super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
+    super(ExtStorageDevice, self).__init__(unique_id, children, size, params,
+                                           dyn_params)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
 
@@ -1358,7 +1389,8 @@
     self.Attach()
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new extstorage device.
 
     Provision a new volume using an extstorage provider, which will
@@ -1377,7 +1409,7 @@
     _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
                       params, str(size))
 
-    return ExtStorageDevice(unique_id, children, size, params)
+    return ExtStorageDevice(unique_id, children, size, params, dyn_params)
 
   def Remove(self):
     """Remove the extstorage device.
@@ -1426,7 +1458,7 @@
       return False
 
     self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
+    self.minor = utils.osminor(st.st_rdev)
     self.attached = True
 
     return True
@@ -1757,8 +1789,8 @@
 
   """
   _VerifyDiskType(disk.dev_type)
-  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
-                                  disk.params)
+  device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
+                                  disk.params, disk.dynamic_params)
   if not device.attached:
     return None
   return device
@@ -1779,8 +1811,8 @@
   """
   _VerifyDiskType(disk.dev_type)
   _VerifyDiskParams(disk)
-  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
-                                  disk.params)
+  device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
+                                  disk.params, disk.dynamic_params)
   device.Assemble()
   return device
 
@@ -1801,6 +1833,7 @@
   """
   _VerifyDiskType(disk.dev_type)
   _VerifyDiskParams(disk)
-  device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
-                                         disk.spindles, disk.params, excl_stor)
+  device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size,
+                                         disk.spindles, disk.params, excl_stor,
+                                         disk.dynamic_params)
   return device
diff --git a/lib/storage/container.py b/lib/storage/container.py
index d77d80b..b4c15c8 100644
--- a/lib/storage/container.py
+++ b/lib/storage/container.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Storage container abstraction.
@@ -43,7 +52,7 @@
   return int(round(float(value), 0))
 
 
-class _Base:
+class _Base(object):
   """Base class for storage abstraction.
 
   """
@@ -95,6 +104,8 @@
     @param paths: List of file storage paths
 
     """
+    super(FileStorage, self).__init__()
+
     self._paths = paths
 
   def List(self, name, fields):
diff --git a/lib/storage/drbd.py b/lib/storage/drbd.py
index a9598c5..0c535d0 100644
--- a/lib/storage/drbd.py
+++ b/lib/storage/drbd.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """DRBD block device related functionality"""
@@ -163,10 +172,10 @@
   doesn't do anything to the supposed peer. If you need a fully
   connected DRBD pair, you need to use this class on both hosts.
 
-  The unique_id for the drbd device is a (local_ip, local_port,
-  remote_ip, remote_port, local_minor, secret) tuple, and it must have
-  two children: the data device and the meta_device. The meta device
-  is checked for valid size and is zeroed on create.
+  The unique_id for the drbd device is a (pnode_uuid, snode_uuid,
+  port, pnode_minor, lnode_minor, secret) tuple, and it must have
+  two children: the data device and the meta_device. The meta
+  device is checked for valid size and is zeroed on create.
 
   """
   _DRBD_MAJOR = 147
@@ -174,21 +183,32 @@
   # timeout constants
   _NET_RECONFIG_TIMEOUT = 60
 
-  def __init__(self, unique_id, children, size, params):
+  def __init__(self, unique_id, children, size, params, dyn_params):
     if children and children.count(None) > 0:
       children = []
     if len(children) not in (0, 2):
       raise ValueError("Invalid configuration data %s" % str(children))
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
-    (self._lhost, self._lport,
-     self._rhost, self._rport,
-     self._aminor, self._secret) = unique_id
+    if constants.DDP_LOCAL_IP not in dyn_params or \
+       constants.DDP_REMOTE_IP not in dyn_params or \
+       constants.DDP_LOCAL_MINOR not in dyn_params or \
+       constants.DDP_REMOTE_MINOR not in dyn_params:
+      raise ValueError("Invalid dynamic parameters %s" % str(dyn_params))
+
+    self._lhost = dyn_params[constants.DDP_LOCAL_IP]
+    self._lport = unique_id[2]
+    self._rhost = dyn_params[constants.DDP_REMOTE_IP]
+    self._rport = unique_id[2]
+    self._aminor = dyn_params[constants.DDP_LOCAL_MINOR]
+    self._secret = unique_id[5]
+
     if children:
       if not _CanReadDevice(children[1].dev_path):
         logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
         children = []
-    super(DRBD8Dev, self).__init__(unique_id, children, size, params)
+    super(DRBD8Dev, self).__init__(unique_id, children, size, params,
+                                   dyn_params)
     self.major = self._DRBD_MAJOR
 
     info = DRBD8.GetProcInfo()
@@ -207,8 +227,8 @@
 
     if (self._lhost is not None and self._lhost == self._rhost and
             self._lport == self._rport):
-      raise ValueError("Invalid configuration data, same local/remote %s" %
-                       (unique_id,))
+      raise ValueError("Invalid configuration data, same local/remote %s, %s" %
+                       (unique_id, dyn_params))
     self.Attach()
 
   @staticmethod
@@ -442,6 +462,21 @@
     except utils.RetryTimeout:
       base.ThrowError("drbd%d: timeout while configuring network", minor)
 
+    # Once the assembly is over, try to set the synchronization parameters
+    try:
+      # The minor may not have been set yet, requiring us to set it at least
+      # temporarily
+      old_minor = self.minor
+      self._SetFromMinor(minor)
+      sync_errors = self.SetSyncParams(self.params)
+      if sync_errors:
+        base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
+                        (self.minor, utils.CommaJoin(sync_errors)))
+    finally:
+      # Undo the change, regardless of whether it will have to be done again
+      # soon
+      self._SetFromMinor(old_minor)
+
   @staticmethod
   def _GetNetFamily(minor, lhost, rhost):
     if netutils.IP6Address.IsValid(lhost):
@@ -685,7 +720,7 @@
       base.ThrowError("drbd%d: DRBD disk missing network info in"
                       " DisconnectNet()", self.minor)
 
-    class _DisconnectStatus:
+    class _DisconnectStatus(object):
       def __init__(self, ever_disconnected):
         self.ever_disconnected = ever_disconnected
 
@@ -794,11 +829,6 @@
       # the device
       self._SlowAssemble()
 
-    sync_errors = self.SetSyncParams(self.params)
-    if sync_errors:
-      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
-                      (self.minor, utils.CommaJoin(sync_errors)))
-
   def _SlowAssemble(self):
     """Assembles the DRBD device from a (partially) configured device.
 
@@ -1005,7 +1035,8 @@
       base.ThrowError("Can't initialize meta device: %s", result.output)
 
   @classmethod
-  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
+             dyn_params):
     """Create a new DRBD8 device.
 
     Since DRBD devices are not created per se, just assembled, this
@@ -1017,8 +1048,11 @@
     if excl_stor:
       raise errors.ProgrammerError("DRBD device requested with"
                                    " exclusive_storage")
+    if constants.DDP_LOCAL_MINOR not in dyn_params:
+      raise errors.ProgrammerError("Invalid dynamic params for drbd device %s"
+                                   % dyn_params)
     # check that the minor is unused
-    aminor = unique_id[4]
+    aminor = dyn_params[constants.DDP_LOCAL_MINOR]
 
     info = DRBD8.GetProcInfo()
     if info.HasMinorStatus(aminor):
@@ -1036,7 +1070,7 @@
                       aminor, meta)
     cls._CheckMetaSize(meta.dev_path)
     cls._InitMeta(aminor, meta.dev_path)
-    return cls(unique_id, children, size, params)
+    return cls(unique_id, children, size, params, dyn_params)
 
 
 def _CanReadDevice(path):
diff --git a/lib/storage/drbd_cmdgen.py b/lib/storage/drbd_cmdgen.py
index b82aaaa..b65cf06 100644
--- a/lib/storage/drbd_cmdgen.py
+++ b/lib/storage/drbd_cmdgen.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """DRBD command generating classes"""
diff --git a/lib/storage/drbd_info.py b/lib/storage/drbd_info.py
index 92cfe13..99605f1 100644
--- a/lib/storage/drbd_info.py
+++ b/lib/storage/drbd_info.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """DRBD information parsing utilities"""
@@ -142,6 +151,11 @@
         self.sync_percent = None
       self.est_time = None
 
+  def __repr__(self):
+    return ("<%s: cstatus=%s, lrole=%s, rrole=%s, ldisk=%s, rdisk=%s>" %
+            (self.__class__, self.cstatus, self.lrole, self.rrole,
+             self.ldisk, self.rdisk))
+
 
 class DRBD8Info(object):
   """Represents information DRBD exports (usually via /proc/drbd).
diff --git a/lib/storage/filestorage.py b/lib/storage/filestorage.py
index 4b636e8..372f83b 100644
--- a/lib/storage/filestorage.py
+++ b/lib/storage/filestorage.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """File storage functions.
@@ -188,7 +197,7 @@
 
   """
   if not os.path.isdir(path):
-    raise errors.FileStoragePathError("Path '%s' is not existing or not a"
+    raise errors.FileStoragePathError("Path '%s' does not exist or is not a"
                                       " directory." % path)
   if not os.access(path, os.W_OK):
     raise errors.FileStoragePathError("Path '%s' is not writable" % path)
@@ -212,6 +221,6 @@
   except errors.FileStoragePathError as e:
     return str(e)
   if not os.path.isdir(path):
-    return "Path '%s' is not exisiting or not a directory." % path
+    return "Path '%s' is not existing or not a directory." % path
   if not os.access(path, os.W_OK):
     return "Path '%s' is not writable" % path
diff --git a/lib/tools/__init__.py b/lib/tools/__init__.py
index 520fe8f..31fcc33 100644
--- a/lib/tools/__init__.py
+++ b/lib/tools/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Common tools modules.
 
diff --git a/lib/tools/burnin.py b/lib/tools/burnin.py
index 766d93a..6068f32 100755
--- a/lib/tools/burnin.py
+++ b/lib/tools/burnin.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Burnin program
diff --git a/lib/tools/ensure_dirs.py b/lib/tools/ensure_dirs.py
index c173f43..3163331 100644
--- a/lib/tools/ensure_dirs.py
+++ b/lib/tools/ensure_dirs.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script to ensure permissions on files/dirs are accurate.
 
diff --git a/lib/tools/node_cleanup.py b/lib/tools/node_cleanup.py
index f4aa245..f8ec076 100644
--- a/lib/tools/node_cleanup.py
+++ b/lib/tools/node_cleanup.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script to configure the node daemon.
 
diff --git a/lib/tools/node_daemon_setup.py b/lib/tools/node_daemon_setup.py
index 9048ace..3d1a0d4 100644
--- a/lib/tools/node_daemon_setup.py
+++ b/lib/tools/node_daemon_setup.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script to configure the node daemon.
 
@@ -117,10 +126,18 @@
   # (no-op if that doesn't exist)
   _check_fn(cert)
 
+  key_encoded = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
+  cert_encoded = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
+                                                 cert)
+  complete_cert_encoded = key_encoded + cert_encoded
+  if not cert_pem == complete_cert_encoded:
+    logging.error("The certificate differs after being reencoded. Please"
+                  " renew the certificates cluster-wide to prevent future"
+                  " inconsistencies.")
+
   # Format for storing on disk
   buf = StringIO()
-  buf.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
-  buf.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
+  buf.write(cert_pem)
   return buf.getvalue()
 
 
diff --git a/lib/tools/prepare_node_join.py b/lib/tools/prepare_node_join.py
index c14f410..7eb1e5a 100644
--- a/lib/tools/prepare_node_join.py
+++ b/lib/tools/prepare_node_join.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script to prepare a node for joining a cluster.
 
diff --git a/lib/uidpool.py b/lib/uidpool.py
index 59401a4..778c2f5 100644
--- a/lib/uidpool.py
+++ b/lib/uidpool.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """User-id pool related functions.
diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py
index 8ca7a88..d1c44f9 100644
--- a/lib/utils/__init__.py
+++ b/lib/utils/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Ganeti utility module.
@@ -56,6 +65,7 @@
 from ganeti.utils.storage import *
 from ganeti.utils.text import *
 from ganeti.utils.wrapper import *
+from ganeti.utils.version import *
 from ganeti.utils.x509 import *
 
 
diff --git a/lib/utils/algo.py b/lib/utils/algo.py
index 745a946..877a4ad 100644
--- a/lib/utils/algo.py
+++ b/lib/utils/algo.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions with algorithms.
 
diff --git a/lib/utils/filelock.py b/lib/utils/filelock.py
index 879dfd9..138df1f 100644
--- a/lib/utils/filelock.py
+++ b/lib/utils/filelock.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for file-based locks.
 
diff --git a/lib/utils/hash.py b/lib/utils/hash.py
index 2358762..49c56be 100644
--- a/lib/utils/hash.py
+++ b/lib/utils/hash.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for hashing.
 
diff --git a/lib/utils/io.py b/lib/utils/io.py
index 808751c..cf1a403 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for I/O.
 
@@ -71,7 +80,7 @@
   return detail
 
 
-class FileStatHelper:
+class FileStatHelper(object):
   """Helper to store file handle's C{fstat}.
 
   Useful in combination with L{ReadFile}'s C{preread} parameter.
diff --git a/lib/utils/log.py b/lib/utils/log.py
index 2afbfd2..3703221 100644
--- a/lib/utils/log.py
+++ b/lib/utils/log.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for logging.
 
diff --git a/lib/utils/lvm.py b/lib/utils/lvm.py
index b1f6ff8..9645b95 100644
--- a/lib/utils/lvm.py
+++ b/lib/utils/lvm.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for LVM.
 
diff --git a/lib/utils/mlock.py b/lib/utils/mlock.py
index fc010cd..97c4de2 100644
--- a/lib/utils/mlock.py
+++ b/lib/utils/mlock.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Wrapper around mlockall(2).
 
diff --git a/lib/utils/nodesetup.py b/lib/utils/nodesetup.py
index d34741f..0ea1b36 100644
--- a/lib/utils/nodesetup.py
+++ b/lib/utils/nodesetup.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for manipulating /etc/hosts.
 
diff --git a/lib/utils/process.py b/lib/utils/process.py
index 4eef342..dccee31 100644
--- a/lib/utils/process.py
+++ b/lib/utils/process.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for processes.
 
diff --git a/lib/utils/retry.py b/lib/utils/retry.py
index 12d1014..c7567fe 100644
--- a/lib/utils/retry.py
+++ b/lib/utils/retry.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for retrying function calls with a timeout.
 
diff --git a/lib/utils/storage.py b/lib/utils/storage.py
index 273c286..485df92 100644
--- a/lib/utils/storage.py
+++ b/lib/utils/storage.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for storage.
 
@@ -34,11 +43,6 @@
           if constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[dt] == storage_type]
 
 
-def GetLvmDiskTemplates():
-  """Returns all disk templates that use LVM."""
-  return GetDiskTemplatesOfStorageType(constants.ST_LVM_VG)
-
-
 def IsDiskTemplateEnabled(disk_template, enabled_disk_templates):
   """Checks if a particular disk template is enabled.
 
@@ -62,8 +66,7 @@
 
 def IsLvmEnabled(enabled_disk_templates):
   """Check whether or not any lvm-based disk templates are enabled."""
-  return len(set(GetLvmDiskTemplates())
-             .intersection(set(enabled_disk_templates))) != 0
+  return len(constants.DTS_LVM & set(enabled_disk_templates)) != 0
 
 
 def LvmGetsEnabled(enabled_disk_templates, new_enabled_disk_templates):
@@ -73,8 +76,7 @@
   """
   if IsLvmEnabled(enabled_disk_templates):
     return False
-  return set(GetLvmDiskTemplates()).intersection(
-      set(new_enabled_disk_templates))
+  return len(constants.DTS_LVM & set(new_enabled_disk_templates)) != 0
 
 
 def _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template):
@@ -92,7 +94,7 @@
   """
   storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
   cluster = cfg.GetClusterInfo()
-  if disk_template in GetLvmDiskTemplates():
+  if disk_template in constants.DTS_LVM:
     return (storage_type, cfg.GetVGName())
   elif disk_template == constants.DT_FILE:
     return (storage_type, cluster.file_storage_dir)
@@ -102,28 +104,23 @@
     return (storage_type, None)
 
 
-def _GetDefaultStorageUnitForSpindles(cfg):
-  """Creates a 'spindle' storage unit, by retrieving the volume group
-  name and associating it to the lvm-pv storage type.
-
-  @rtype: (string, string)
-  @return: tuple (storage_type, storage_key), where storage type is
-    'lvm-pv' and storage_key the name of the default volume group
-
-  """
-  return (constants.ST_LVM_PV, cfg.GetVGName())
+def DiskTemplateSupportsSpaceReporting(disk_template):
+  """Check whether the disk template supports storage space reporting."""
+  return (constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
+          in constants.STS_REPORT)
 
 
-def GetStorageUnitsOfCluster(cfg, include_spindles=False):
-  """Examines the cluster's configuration and returns a list of storage
-  units and their storage keys, ordered by the order in which they
-  are enabled.
+def GetStorageUnits(cfg, disk_templates):
+  """Get the cluster's storage units for the given disk templates.
+
+  If any lvm-based disk template is requested, spindle information
+  is added to the request.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: Cluster configuration
-  @type include_spindles: boolean
-  @param include_spindles: flag to include an extra storage unit for physical
-    volumes
+  @type disk_templates: list of string
+  @param disk_templates: list of disk templates for which the storage
+    units will be computed
   @rtype: list of tuples (string, string)
   @return: list of storage units, each storage unit being a tuple of
     (storage_type, storage_key); storage_type is in
@@ -132,22 +129,30 @@
     name for LVM storage or a file for file storage.
 
   """
-  cluster_config = cfg.GetClusterInfo()
   storage_units = []
-  for disk_template in cluster_config.enabled_disk_templates:
-    if constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]\
-        in constants.STS_REPORT:
+  for disk_template in disk_templates:
+    if DiskTemplateSupportsSpaceReporting(disk_template):
       storage_units.append(
           _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template))
-  if include_spindles:
-    included_storage_types = set([st for (st, _) in storage_units])
-    if not constants.ST_LVM_PV in included_storage_types:
-      storage_units.append(
-          _GetDefaultStorageUnitForSpindles(cfg))
-
   return storage_units
 
 
+def LookupSpaceInfoByDiskTemplate(storage_space_info, disk_template):
+  """Looks up the storage space info for a given disk template.
+
+  @type storage_space_info: list of dicts
+  @param storage_space_info: result of C{GetNodeInfo}
+  @type disk_template: string
+  @param disk_template: disk template to get storage space info
+  @rtype: tuple
+  @return: returns the element of storage_space_info that matches the given
+    disk template
+
+  """
+  storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
+  return LookupSpaceInfoByStorageType(storage_space_info, storage_type)
+
+
 def LookupSpaceInfoByStorageType(storage_space_info, storage_type):
   """Looks up the storage space info for a given storage type.
 
@@ -175,3 +180,13 @@
         logging.warning("Storage space information requested for"
                         " ambiguous storage type '%s'.", storage_type)
   return result
+
+
+def osminor(dev):
+  """Return the device minor number from a raw device number.
+
+  This is a replacement for os.minor working around the issue that
+  Python's os.minor still has the old definition. See Ganeti issue
+  1058 for more details.
+  """
+  return (dev & 0xff) | ((dev >> 12) & ~0xff)
diff --git a/lib/utils/text.py b/lib/utils/text.py
index e768588..f870b0a 100644
--- a/lib/utils/text.py
+++ b/lib/utils/text.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for manipulating or working with text.
 
@@ -239,7 +248,14 @@
   return " ".join([ShellQuote(i) for i in args])
 
 
-class ShellWriter:
+def ShellCombineCommands(cmdlist):
+  """Out of a list of shell comands construct a single one.
+
+  """
+  return ["/bin/sh", "-c", " && ".join(ShellQuoteArgs(c) for c in cmdlist)]
+
+
+class ShellWriter(object):
   """Helper class to write scripts with indentation.
 
   """
@@ -453,6 +469,19 @@
   return rlist
 
 
+def EscapeAndJoin(slist, sep=","):
+  """Encode a list in a way parsable by UnescapeAndSplit.
+
+  @type slist: list of strings
+  @param slist: the strings to be encoded
+  @rtype: string
+  @return: the encoding of the list oas a string
+
+  """
+  return sep.join([re.sub("\\" + sep, "\\\\" + sep,
+                          re.sub(r"\\", r"\\\\", v)) for v in slist])
+
+
 def CommaJoin(names):
   """Nicely join a set of identifiers.
 
@@ -510,7 +539,7 @@
   return " ".join(parts)
 
 
-class LineSplitter:
+class LineSplitter(object):
   """Splits data chunks into lines separated by newline.
 
   Instances provide a file-like interface.
diff --git a/lib/utils/version.py b/lib/utils/version.py
new file mode 100644
index 0000000..a9cffde
--- /dev/null
+++ b/lib/utils/version.py
@@ -0,0 +1,163 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Version utilities."""
+
+import re
+
+from ganeti import constants
+
+_FULL_VERSION_RE = re.compile(r"(\d+)\.(\d+)\.(\d+)")
+_SHORT_VERSION_RE = re.compile(r"(\d+)\.(\d+)")
+
+# The first Ganeti version that supports automatic upgrades
+FIRST_UPGRADE_VERSION = (2, 10, 0)
+
+CURRENT_VERSION = (constants.VERSION_MAJOR, constants.VERSION_MINOR,
+                   constants.VERSION_REVISION)
+
+# Format for CONFIG_VERSION:
+#   01 03 0123 = 01030123
+#   ^^ ^^ ^^^^
+#   |  |  + Configuration version/revision
+#   |  + Minor version
+#   + Major version
+#
+# It is stored as an integer. Make sure not to write an octal number.
+
+# BuildVersion and SplitVersion must be in here because we can't import other
+# modules. The cfgupgrade tool must be able to read and write version numbers
+# and thus requires these functions. To avoid code duplication, they're kept in
+# here.
+
+
+def BuildVersion(major, minor, revision):
+  """Calculates int version number from major, minor and revision numbers.
+
+  Returns: int representing version number
+
+  """
+  assert isinstance(major, int)
+  assert isinstance(minor, int)
+  assert isinstance(revision, int)
+  return (1000000 * major +
+            10000 * minor +
+                1 * revision)
+
+
+def SplitVersion(version):
+  """Splits version number stored in an int.
+
+  Returns: tuple; (major, minor, revision)
+
+  """
+  assert isinstance(version, int)
+
+  (major, remainder) = divmod(version, 1000000)
+  (minor, revision) = divmod(remainder, 10000)
+
+  return (major, minor, revision)
+
+
+def ParseVersion(versionstring):
+  """Parses a version string.
+
+  @param versionstring: the version string to parse
+  @type versionstring: string
+  @rtype: tuple or None
+  @return: (major, minor, revision) if parsable, None otherwise.
+
+  """
+  m = _FULL_VERSION_RE.match(versionstring)
+  if m is not None:
+    return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
+
+  m = _SHORT_VERSION_RE.match(versionstring)
+  if m is not None:
+    return (int(m.group(1)), int(m.group(2)), 0)
+
+  return None
+
+
+def UpgradeRange(target, current=CURRENT_VERSION):
+  """Verify whether a version is within the range of automatic upgrades.
+
+  @param target: The version to upgrade to as (major, minor, revision)
+  @type target: tuple
+  @param current: The version to upgrade from as (major, minor, revision)
+  @type current: tuple
+  @rtype: string or None
+  @return: None, if within the range, and a human-readable error message
+      otherwise
+
+  """
+  if target < FIRST_UPGRADE_VERSION or current < FIRST_UPGRADE_VERSION:
+    return "automatic upgrades only supported from 2.10 onwards"
+
+  if target[0] != current[0]:
+    return "different major versions"
+
+  if target[1] < current[1] - 1:
+    return "can only downgrade one minor version at a time"
+
+  return None
+
+
+def ShouldCfgdowngrade(version, current=CURRENT_VERSION):
+  """Decide whether cfgupgrade --downgrade should be called.
+
+  Given the current version and the version to change to, decide
+  if in the transition process cfgupgrade --downgrade should
+  be called
+
+  @param version: The version to upgrade to as (major, minor, revision)
+  @type version: tuple
+  @param current: The version to upgrade from as (major, minor, revision)
+  @type current: tuple
+  @rtype: bool
+  @return: True, if cfgupgrade --downgrade should be called.
+
+  """
+  return version[0] == current[0] and version[1] == current[1] - 1
+
+
+def IsCorrectConfigVersion(targetversion, configversion):
+  """Decide whether configuration version is compatible with the target.
+
+  @param targetversion: The version to upgrade to as (major, minor, revision)
+  @type targetversion: tuple
+  @param configversion: The version of the current configuration
+  @type configversion: tuple
+  @rtype: bool
+  @return: True, if the configversion fits with the target version.
+
+  """
+  return (configversion[0] == targetversion[0] and
+          configversion[1] == targetversion[1])
diff --git a/lib/utils/wrapper.py b/lib/utils/wrapper.py
index 7c98c6f..1b19f36 100644
--- a/lib/utils/wrapper.py
+++ b/lib/utils/wrapper.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions wrapping other functions.
 
diff --git a/lib/utils/x509.py b/lib/utils/x509.py
index 8cc7af7..d9cd560 100644
--- a/lib/utils/x509.py
+++ b/lib/utils/x509.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Utility functions for X509.
 
diff --git a/lib/vcluster.py b/lib/vcluster.py
index 407c352..6c8c500 100644
--- a/lib/vcluster.py
+++ b/lib/vcluster.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module containing utilities for virtual clusters.
diff --git a/lib/watcher/__init__.py b/lib/watcher/__init__.py
index bb39b25..ed96049 100644
--- a/lib/watcher/__init__.py
+++ b/lib/watcher/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Tool to restart erroneously downed virtual machines.
@@ -158,7 +167,7 @@
     cli.SubmitOpCode(op, cl=cl)
 
 
-class Node:
+class Node(object):
   """Data container representing cluster node.
 
   """
diff --git a/lib/watcher/nodemaint.py b/lib/watcher/nodemaint.py
index 8228a7c..7bbfd4e 100644
--- a/lib/watcher/nodemaint.py
+++ b/lib/watcher/nodemaint.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module doing node maintenance for Ganeti watcher.
diff --git a/lib/watcher/state.py b/lib/watcher/state.py
index 3e8c463..e4b9245 100644
--- a/lib/watcher/state.py
+++ b/lib/watcher/state.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module keeping state for Ganeti watcher.
diff --git a/lib/workerpool.py b/lib/workerpool.py
index 6b558ce..8097923 100644
--- a/lib/workerpool.py
+++ b/lib/workerpool.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2008, 2009, 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Base classes for worker pools.
diff --git a/man/footer.rst b/man/footer.rst
index 8f03736..e6e4639 100644
--- a/man/footer.rst
+++ b/man/footer.rst
@@ -33,14 +33,31 @@
 COPYRIGHT
 ---------
 
-Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google
-Inc. Permission is granted to copy, distribute and/or modify under the
-terms of the GNU General Public License as published by the Free
-Software Foundation; either version 2 of the License, or (at your
-option) any later version.
+Copyright (C) 2006-2014 Google Inc.
+All rights reserved.
 
-On Debian systems, the complete text of the GNU General Public
-License can be found in /usr/share/common-licenses/GPL.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/man/ganeti.rst b/man/ganeti.rst
index 4024ed1..5a69c27 100644
--- a/man/ganeti.rst
+++ b/man/ganeti.rst
@@ -124,6 +124,21 @@
     parameter cannot be set on individual nodes, as its value must be
     the same within each node group.
 
+ovs
+    When this Boolean flag is enabled, OpenvSwitch will be used as the
+    network layer. This will cause the initialization of OpenvSwitch on
+    the nodes when added to the cluster. Per default this is not enabled.
+
+ovs_name
+    When ovs is enabled, this parameter will represent the name of the
+    OpenvSwitch to generate and use. This will default to `switch1`.
+
+ovs_link
+    When ovs is enabled, a OpenvSwitch will be initialized on new nodes
+    and will have this as its connection to the outside. This parameter
+    is not set per default, as it depends very much on the specific
+    setup.
+
 
 Hypervisor State Parameters
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/man/gnt-backup.rst b/man/gnt-backup.rst
index 409b955..ebf636f 100644
--- a/man/gnt-backup.rst
+++ b/man/gnt-backup.rst
@@ -47,13 +47,12 @@
 was exported. This is useful to make one last backup before
 removing the instance.
 
-The exit code of the command is 0 if all disks were backed up
-successfully, 1 if no data was backed up or if the configuration
-export failed, and 2 if just some of the disks failed to backup.
-The exact details of the failures will be shown during the command
-execution (and will be stored in the job log). It is recommended
-that for any non-zero exit code, the backup is considered invalid,
-and retried.
+Should the snapshotting or transfer of any of the instance disks
+fail, the backup will not complete and any previous backups will be
+preserved. The exact details of the failures will be shown during the
+command execution (and will be stored in the job log). It is
+recommended that for any non-zero exit code, the backup is considered
+invalid, and retried.
 
 See **ganeti**\(7) for a description of ``--submit`` and other common
 options.
diff --git a/man/gnt-cluster.rst b/man/gnt-cluster.rst
index 3b0ee1c..83dc1ef 100644
--- a/man/gnt-cluster.rst
+++ b/man/gnt-cluster.rst
@@ -84,7 +84,7 @@
 copy to be done over the replication network (only matters if the
 primary/secondary IPs are different). Example::
 
-    # gnt-cluster -n node1.example.com -n node2.example.com copyfile /tmp/test
+    # gnt-cluster copyfile -n node1.example.com -n node2.example.com /tmp/test
 
 This will copy the file /tmp/test from the current node to the two
 named nodes.
@@ -469,6 +469,21 @@
     When a new RADOS cluster is deployed, the default pool to put rbd
     volumes (Images in RADOS terminology) is 'rbd'.
 
+access
+    If 'userspace', instances will access their disks directly without
+    going through a block device, avoiding expensive context switches
+    with kernel space and the potential for deadlocks_ in low memory
+    scenarios.
+
+    The default value is 'kernelspace' and it disables this behaviour.
+    This setting may only be changed to 'userspace' if all instance
+    disks in the affected group or cluster can be accessed in userspace.
+
+    Attempts to use this feature without rbd support compiled in KVM
+    result in a "no such file or directory" error messages.
+
+.. _deadlocks: http://tracker.ceph.com/issues/3076
+
 The option ``--maintain-node-health`` allows one to enable/disable
 automatic maintenance actions on nodes. Currently these include
 automatic shutdown of instances and deactivation of DRBD devices on
@@ -788,6 +803,22 @@
 output. Otherwise it will log details about the inconsistencies in
 the configuration.
 
+UPGRADE
+~~~~~~~
+
+**upgrade** {--to *version* | --resume}
+
+This command safely switches all nodes of the cluster to a new Ganeti
+version. It is a prerequisite that the new version is already installed,
+albeit not activated, on all nodes; this requisite is checked before any
+actions are done.
+
+If called with the ``--resume`` option, any pending upgrade is
+continued, that was interrupted by a power failure or similar on
+master. It will do nothing, if not run on the master node, or if no
+upgrade was in progress.
+
+
 VERIFY
 ~~~~~~
 
diff --git a/man/gnt-group.rst b/man/gnt-group.rst
index e100715..ed248cf 100644
--- a/man/gnt-group.rst
+++ b/man/gnt-group.rst
@@ -187,7 +187,7 @@
 EVACUATE
 ~~~~~~~~
 
-| **evacuate** [\--submit] [\--print-job-id]
+| **evacuate** [\--submit] [\--print-job-id] [\--sequential] [\--force-failover]
 | [\--iallocator *NAME*] [\--to *GROUP*...] {*group*}
 
 This command will move all instances out of the given node group.
@@ -197,6 +197,14 @@
 If no specific destination groups are specified using ``--to``, all
 groups except the evacuated group are considered.
 
+The moves of the individual instances are handled as separate jobs
+to allow for maximal parallelism. If the ``--sequential`` option is
+given, the moves of the individual instances will be executed sequentially.
+This can be usefull if the link between the groups is vulnerable to
+congestion. If the ``--force-failover`` option is given, no migrations
+will be made. This might be necessary if the group being evacuated is
+too different from the other groups in the cluster.
+
 See **ganeti**\(7) for a description of ``--submit`` and other common
 options.
 
diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst
index 954c811..2cc64bb 100644
--- a/man/gnt-instance.rst
+++ b/man/gnt-instance.rst
@@ -74,7 +74,9 @@
 
 metavg
    This options specifies a different VG for the metadata device. This
-   works only for DRBD devices
+   works only for DRBD devices. If not specified, the default metavg
+   of the node-group (possibly inherited from the cluster-wide settings)
+   will be used.
 
 When creating ExtStorage disks, also arbitrary parameters can be passed,
 to the ExtStorage provider. Those parameters are passed as additional
@@ -120,7 +122,7 @@
 
 The NICs of the instances can be specified via the ``--net``
 option. By default, one NIC is created for the instance, with a
-random MAC, and set up according the the cluster level NIC
+random MAC, and set up according to the cluster level NIC
 parameters. Each NIC can take these parameters (all optional):
 
 mac
@@ -135,7 +137,10 @@
     passed in the **network** parameter if this NIC is meant to be
     connected to the said network. ``--no-conflicts-check`` can be used
     to override this check. The special value **pool** causes Ganeti to
-    select an IP from the the network the NIC is or will be connected to.
+    select an IP from the network the NIC is or will be connected to.
+    One can pick an externally reserved IP of a network along with
+    ``--no-conflict-check``. Note that this IP cannot be assigned to
+    any other instance until it gets released.
 
 mode
     specifies the connection mode for this NIC: routed, bridged or
@@ -161,6 +166,11 @@
    this option specifies a name for the NIC, which can be used as a NIC
    identifier. An instance can not have two NICs with the same name.
 
+vlan
+   in openvswitch mode specifies the VLANs that the NIC will be
+   connected to. To connect as an access port use ``n`` or ``.n`` with
+   **n** being the VLAN ID. To connect as an trunk port use ``:n[:n]``.
+   A hybrid port can be created with ``.n:n[:n]``
 
 Of these "mode" and "link" are NIC parameters, and inherit their
 default at cluster level.  Alternatively, if no network is desired for
@@ -728,11 +738,24 @@
     Number of emulated CPU sockets.
 
 soundhw
-    Valid for the KVM hypervisor.
+    Valid for the KVM and XEN hypervisors.
 
     Comma separated list of emulated sounds cards, or "all" to enable
     all the available ones.
 
+cpuid
+    Valid for the XEN hypervisor.
+
+    Modify the values returned by CPUID_ instructions run within instances.
+
+    This allows you to enable migration between nodes with different CPU
+    attributes like cores, threads, hyperthreading or SS4 support by hiding
+    the extra features where needed.
+
+    See the XEN documentation for syntax and more information.
+
+.. _CPUID: http://en.wikipedia.org/wiki/CPUID
+
 usb\_devices
     Valid for the KVM hypervisor.
 
@@ -990,6 +1013,14 @@
     # gnt-instance batch-create instances.json
     Submitted jobs 37, 38
 
+
+Note: If the allocator is used for computing suitable nodes for the
+instances, it will only take into account disk information for the
+default disk template. That means, even if other disk templates are
+specified for the instances, storage space information of these disk
+templates will not be considered in the allocation computation.
+
+
 REMOVE
 ^^^^^^
 
@@ -1126,6 +1157,8 @@
 | [\--offline \| \--online]
 | [\--submit] [\--print-job-id]
 | [\--ignore-ipolicy]
+| [\--hotplug]
+| [\--hotplug-if-possible]
 | {*instance*}
 
 Modifies the memory size, number of vcpus, ip address, MAC address
@@ -1155,14 +1188,16 @@
 instance, and ``--disk *N*:add:size=*SIZE*,[options..]`` will add a disk
 to the the instance at a specific index. The available options are the
 same as in the **add** command(``spindles``, ``mode``, ``name``, ``vg``,
-``metavg``). When adding an ExtStorage disk the ``provider=*PROVIDER*``
-option is also mandatory and specifies the ExtStorage provider. Also,
-for ExtStorage disks arbitrary parameters can be passed as additional
-comma separated options, same as in the **add** command. -The ``--disk
-remove`` option will remove the last disk of the instance. Use ``--disk
-`` *ID*``:remove`` to remove a disk by its identifier. *ID* can be the
-index of the disk, the disks's name or the disks's UUID. The ``--disk
-*ID*:modify[,options...]`` will change the options of the disk.
+``metavg``). Per default, gnt-instance waits for the disk mirror to sync.
+If you do not want this behavior, use the ``--no-wait-for-sync`` option.
+When adding an ExtStorage disk, the ``provider=*PROVIDER*`` option is
+also mandatory and specifies the ExtStorage provider. Also, for
+ExtStorage disks arbitrary parameters can be passed as additional comma
+separated options, same as in the **add** command. The ``--disk remove``
+option will remove the last disk of the instance. Use
+``--disk `` *ID*``:remove`` to remove a disk by its identifier. *ID*
+can be the index of the disk, the disks's name or the disks's UUID. The
+``--disk *ID*:modify[,options...]`` will change the options of the disk.
 Available options are:
 
 mode
@@ -1201,6 +1236,21 @@
 If ``--ignore-ipolicy`` is given any instance policy violations occuring
 during this operation are ignored.
 
+If ``--hotplug`` is given any disk and NIC modifications will take
+effect without the need of actual reboot. Please note that this feature
+is currently supported only for KVM hypervisor and there are some
+restrictions: a) KVM versions >= 1.0 support it b) instances with chroot
+or uid pool security model do not support disk hotplug c) RBD disks with
+userspace access mode can not be hotplugged (yet) d) if hotplug fails
+(for any reason) a warning is printed but execution is continued e)
+for existing NIC modification interactive verification is needed unless
+``--force`` option is passed.
+
+If ``--hotplug-if-possible`` is given then ganeti won't abort in case
+hotplug is not supported. It will continue execution and modification
+will take place after reboot. This covers use cases where instances are
+not running or hypervisor is not KVM.
+
 See **ganeti**\(7) for a description of ``--submit`` and other common
 options.
 
@@ -1906,7 +1956,9 @@
 
 This command moves an instance to another node group. The move is
 calculated by an iallocator, either given on the command line or as a
-cluster default.
+cluster default. Note that the iallocator does only consider disk
+information of the default disk template, even if the instances'
+disk templates differ from that.
 
 If no specific destination groups are specified using ``--to``, all
 groups except the one containing the instance are considered.
diff --git a/man/gnt-network.rst b/man/gnt-network.rst
index 3d71663..5b2b088 100644
--- a/man/gnt-network.rst
+++ b/man/gnt-network.rst
@@ -147,15 +147,19 @@
 
 | **connect**
 | [\--no-conflicts-check]
-| {*network*} {*mode*} {*link*} [*groups*...]
+| [{-N|\--nic-parameters} *nic-param*=*value*[,*nic-param*=*value*...]]
+| {*network*} [*groups*...]
 
 Connect a network to given node groups (all if not specified) with the
-network parameters *mode* and *link*. Every network interface will
-inherit those parameters if assigned in a network.
+network parameters defined via the ``--nic-parameters`` option. Every
+network interface will inherit those parameters if assigned to a network.
 
 The ``--no-conflicts-check`` option can be used to skip the check for
 conflicting IP addresses.
 
+Passing *mode* and *link* as possitional arguments along with
+*network* and *groups* is deprecated and not supported any more.
+
 DISCONNECT
 ~~~~~~~~~~
 
diff --git a/man/gnt-node.rst b/man/gnt-node.rst
index 72ea1d4..21dade0 100644
--- a/man/gnt-node.rst
+++ b/man/gnt-node.rst
@@ -39,7 +39,7 @@
 master.
 
 Note that the command is potentially destructive, as it will
-forcibly join the specified host the cluster, not paying attention
+forcibly join the specified host to the cluster, not paying attention
 to its current status (it could be already in a cluster, etc.)
 
 The ``-s (--secondary-ip)`` is used in dual-home clusters and
@@ -48,7 +48,7 @@
 
 In case you're readding a node after hardware failure, you can use
 the ``--readd`` parameter. In this case, you don't need to pass the
-secondary IP again, it will reused from the cluster. Also, the
+secondary IP again, it will be reused from the cluster. Also, the
 drained and offline flags of the node will be cleared before
 re-adding it.
 
@@ -123,6 +123,10 @@
   instance that the node is a secondary for.
 - when neither of the above is done a combination of the two cases is run
 
+Note that the iallocator currently only considers disk information of
+the default disk template, even if the instance's disk templates differ
+from that.
+
 See **ganeti**\(7) for a description of ``--submit`` and other common
 options.
 
@@ -223,6 +227,13 @@
 pool of memory used for instances (KVM), whereas others have separate
 memory for the node and for the instances (Xen).
 
+Note that the field 'dtotal' and 'dfree' refer to the storage type
+that is defined by the default disk template. The default disk template
+is the first on in the list of cluster-wide enabled disk templates and
+can be set with ``gnt-cluster modify``. Currently, only the disk
+templates 'plain', 'drbd', 'file', and 'sharedfile' support storage
+reporting, for all others '0' is displayed.
+
 If exactly one argument is given and it appears to be a query filter
 (see **ganeti**\(7)), the query result is filtered accordingly. For
 ambiguous cases (e.g. a single field name as a filter) the ``--filter``
diff --git a/man/hail.rst b/man/hail.rst
index 72733a2..c7fe773 100644
--- a/man/hail.rst
+++ b/man/hail.rst
@@ -27,6 +27,10 @@
 If the input file name is ``-`` (a single minus sign), then the request
 data will be read from *stdin*.
 
+Apart from input data, hail collects data over the network from all
+MonDs with the --mond option. Currently it uses only data produced by
+the CPUload collector.
+
 ALGORITHM
 ~~~~~~~~~
 
@@ -75,6 +79,25 @@
   in the JSON request itself. This is mostly used for debugging. The
   format of the file is described in the man page **htools**\(1).
 
+\--mond
+  If given the program will query all MonDs to fetch data from the
+  supported data collectors over the network.
+
+\--mond-data *datafile*
+  The name of the file holding the data provided by MonD, to override
+  quering MonDs over the network. This is mostly used for debugging. The
+  file must be in JSON format and present an array of JSON objects ,
+  one for every node, with two members. The first member named ``node``
+  is the name of the node and the second member named ``reports`` is an
+  array of report objects. The report objects must be in the same format
+  as produced by the monitoring agent.
+
+\--ignore-dynu
+  If given, all dynamic utilisation information will be ignored by
+  assuming it to be 0. This option will take precedence over any data
+  passed by the MonDs with the ``--mond`` and the ``--mond-data``
+  option.
+
 \--simulate *description*
   Backend specification: similar to the **-t** option, this allows
   overriding the cluster data with a simulated cluster. For details
diff --git a/man/hbal.rst b/man/hbal.rst
index 09e0f90..568830a 100644
--- a/man/hbal.rst
+++ b/man/hbal.rst
@@ -30,7 +30,9 @@
 **[ \--no-disk-moves ]**
 **[ \--no-instance-moves ]**
 **[ -U *util-file* ]**
+**[ \--ignore-dynu ]**
 **[ \--evac-mode ]**
+**[ \--restricted-migration ]**
 **[ \--select-instances *inst...* ]**
 **[ \--exclude-instances *inst...* ]**
 
@@ -57,6 +59,10 @@
 it is possible to make it go into a corner from which it can find no
 improvement, because it looks only one "step" ahead.
 
+The program accesses the cluster state via Rapi or Luxi. It also
+requests data over the network from all MonDs with the --mond option.
+Currently it uses only data produced by CPUload collector.
+
 By default, the program will show the solution incrementally as it is
 computed, in a somewhat cryptic format; for getting the actual Ganeti
 command list, use the **-C** option.
@@ -125,6 +131,7 @@
   spindles)
 - standard deviation of the dynamic load on the nodes, for cpus,
   memory, disk and network
+- standard deviation of the CPU load provided by MonD
 
 The free memory and free disk values help ensure that all nodes are
 somewhat balanced in their resource usage. The reserved memory helps
@@ -163,6 +170,13 @@
 that it's recommended to not have zero as the load value for any
 instance metric since then secondary instances are not well balanced.
 
+The CPUload from MonD's data collector will be used only if all MonDs
+are running, otherwise it won't affect the cluster score. Since we can't
+find the CPU load of each instance, we can assume that the CPU load of
+an instance is proportional to the number of its vcpus. With this
+heuristic, instances from nodes with high CPU load will tend to move to
+nodes with less CPU load.
+
 On a perfectly balanced cluster (all nodes the same size, all
 instances the same size and spread across the nodes equally), the
 values for all metrics would be zero. This doesn't happen too often in
@@ -293,6 +307,15 @@
   (bulk) replacement for Ganeti's own *gnt-node evacuate*, with the
   note that it doesn't guarantee full evacuation.
 
+\--restricted-migration
+  This parameter disallows any replace-primary moves (frf), as well as
+  those replace-and-failover moves (rf) where the primary node of the
+  instance is not drained. If used together with the ``--evac-mode``
+  option, the only migrations that hbal will do are migrations of
+  instances off a drained node. This can be useful if during a reinstall
+  of the base operating system migration is only possible from the old
+  OS to the new OS.
+
 \--select-instances=*instances*
   This parameter marks the given instances (as a comma-separated list)
   as the only ones being moved during the rebalance.
@@ -321,6 +344,12 @@
   metrics and thus the influence of the dynamic utilisation will be
   practically insignificant.
 
+\--ignore-dynu
+  If given, all dynamic utilisation information will be ignored by
+  assuming it to be 0. This option will take precedence over any data
+  passed by the ``-U`` option or by the MonDs with the ``--mond`` and
+  the ``--mond-data`` option.
+
 -S *filename*, \--save-cluster=*filename*
   If given, the state of the cluster before the balancing is saved to
   the given file plus the extension "original"
@@ -335,6 +364,19 @@
   other backends must be selected. The option is described in the man
   page **htools**\(1).
 
+\--mond
+  If given the program will query all MonDs to fetch data from the
+  supported data collectors over the network.
+
+\--mond-data *datafile*
+  The name of the file holding the data provided by MonD, to override
+  quering MonDs over the network. This is mostly used for debugging. The
+  file must be in JSON format and present an array of JSON objects ,
+  one for every node, with two members. The first member named ``node``
+  is the name of the node and the second member named ``reports`` is an
+  array of report objects. The report objects must be in the same format
+  as produced by the monitoring agent.
+
 -m *cluster*
   Backend specification: collect data directly from the *cluster* given
   as an argument via RAPI. The option is described in the man page
diff --git a/man/hspace.rst b/man/hspace.rst
index f80dc85..1259f25 100644
--- a/man/hspace.rst
+++ b/man/hspace.rst
@@ -16,7 +16,7 @@
 
 Backend options:
 
-{ **-m** *cluster* | **-L[** *path* **] [-X]** | **-t** *data-file* |
+{ **-m** *cluster* | **-L[** *path* **]** | **-t** *data-file* |
 **\--simulate** *spec* | **-I** *path* }
 
 
@@ -25,6 +25,7 @@
 **[ \--max-cpu *cpu-ratio* ]**
 **[ \--min-disk *disk-ratio* ]**
 **[ -O *name...* ]**
+**[ \--independent-groups ]**
 
 
 Request options:
@@ -232,6 +233,19 @@
   number. For example, specifying *disk-ratio* as **0.25** means that
   at least one quarter of disk space should be left free on nodes.
 
+\--independent-groups
+  Consider all groups independent. That is, if a node that is not N+1
+  happy is found, ignore its group, but still do allocation in the other
+  groups. The default is to not try allocation at all, if some not N+1
+  happy node is found.
+
+\--accept-existing-errors
+  This is a strengthened form of \--independent-groups. It tells hspace
+  to ignore the presence of not N+1 happy nodes and just allocate on
+  all other nodes without introducing new N+1 violations. Note that this
+  tends to overestimate the capacity, as instances still have to be
+  moved away from the existing not N+1 happy nodes.
+
 -l *rounds*, \--max-length=*rounds*
   Restrict the number of instance allocations to this length. This is
   not very useful in practice, but can be used for testing hspace
diff --git a/man/htools.rst b/man/htools.rst
index ecd5ece..37591aa 100644
--- a/man/htools.rst
+++ b/man/htools.rst
@@ -219,6 +219,25 @@
   - vcpu ratio
   - spindle ratio
 
+\--mond
+  If given the program will query all MonDs to fetch data from the
+  supported data collectors over the network.
+
+\--mond-data *datafile*
+  The name of the file holding the data provided by MonD, to override
+  quering MonDs over the network. This is mostly used for debugging. The
+  file must be in JSON format and present an array of JSON objects ,
+  one for every node, with two members. The first member named ``node``
+  is the name of the node and the second member named ``reports`` is an
+  array of report objects. The report objects must be in the same format
+  as produced by the monitoring agent.
+
+\--ignore-dynu
+  If given, all dynamic utilisation information will be ignored by
+  assuming it to be 0. This option will take precedence over any data
+  passed by the ``-U`` option (available with hbal) or by the MonDs with
+  the ``--mond`` and the ``--mond-data`` option.
+
 -m *cluster*
   Backend specification: collect data directly from the *cluster* given
   as an argument via RAPI. If the argument doesn't contain a colon (:),
diff --git a/pylintrc b/pylintrc
index 4fdec6e..7707163 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,6 +1,8 @@
 # Configuration file for pylint (http://www.logilab.org/project/pylint). See
 # http://www.logilab.org/card/pylintfeatures for more detailed variable
 # descriptions.
+#
+# NOTE: Keep this file in sync (as much as possible) with pylintrc-test!
 
 [MASTER]
 profile = no
diff --git a/pylintrc-test b/pylintrc-test
new file mode 100644
index 0000000..d61735a
--- /dev/null
+++ b/pylintrc-test
@@ -0,0 +1,113 @@
+# pylint configuration file tailored to test code.
+#
+# NOTE: Keep in sync as much as possible with the standard pylintrc file.
+# Only a few settings had to be adapted for the test code.
+
+[MASTER]
+profile = no
+ignore =
+persistent = no
+cache-size = 50000
+load-plugins =
+
+[REPORTS]
+output-format = colorized
+include-ids = yes
+files-output = no
+reports = no
+evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+comment = yes
+
+[BASIC]
+required-attributes =
+# disabling docstring checks since we have way too many without (complex
+# inheritance hierarchies)
+#no-docstring-rgx = __.*__
+no-docstring-rgx = .*
+module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+# added lower-case names
+const-rgx = ((_{0,2}[A-Za-z][A-Za-z0-9_]*)|(__.*__))$
+class-rgx = _?[A-Z][a-zA-Z0-9]+$
+# added lower-case names
+function-rgx = (_?((([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*)|main|([a-z_][a-z0-9_]*))$
+# add lower-case names, since derived classes must obey method names
+method-rgx = (_{0,2}(([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*|__.*__|runTests|setUp|tearDown|([a-z_][a-z0-9_]*))$
+attr-rgx = [a-z_][a-z0-9_]{1,30}$
+argument-rgx = [a-z_][a-z0-9_]*$
+variable-rgx = (_?([a-z_][a-z0-9_]*)|(_?[A-Z0-9_]+))$
+inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
+good-names = i,j,k,_
+bad-names = foo,bar,baz,toto,tutu,tata
+bad-functions = xrange
+
+[TYPECHECK]
+ignore-mixin-members = yes
+zope = no
+acquired-members =
+
+[VARIABLES]
+init-import = no
+dummy-variables-rgx = _
+additional-builtins =
+
+[CLASSES]
+ignore-iface-methods =
+defining-attr-methods = __init__,__new__,setUp
+
+[DESIGN]
+max-args = 15
+max-locals = 50
+max-returns = 10
+max-branchs = 80
+max-statements = 200
+max-parents = 7
+max-attributes = 20
+# zero as struct-like (PODS) classes don't export any methods
+min-public-methods = 0
+max-public-methods = 50
+
+[IMPORTS]
+deprecated-modules = regsub,string,TERMIOS,Bastion,rexec
+import-graph =
+ext-import-graph =
+int-import-graph =
+
+[FORMAT]
+max-line-length = 80
+max-module-lines = 4500
+indent-string = "  "
+
+[MISCELLANEOUS]
+notes = FIXME,XXX,TODO
+
+[SIMILARITIES]
+min-similarity-lines = 4
+ignore-comments = yes
+ignore-docstrings = yes
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+disable-checker=similarities
+
+# Enable all messages in the listed categories (IRCWEF).
+#enable-msg-cat=
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+disable-msg=W0511,R0922,W0201
+
+# The new pylint 0.21+ style (plus the similarities checker, which is no longer
+# a separate opiton, but a generic disable control)
+disable=W0511,R0922,W0201,R0922,R0801,I0011,R0201
diff --git a/qa/__init__.py b/qa/__init__.py
index f703026..28534e1 100644
--- a/qa/__init__.py
+++ b/qa/__init__.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # empty file for package definition
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 4277f9e..bcf7d1a 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for doing QA on Ganeti.
@@ -42,6 +51,7 @@
 import qa_network
 import qa_node
 import qa_os
+import qa_performance
 import qa_job
 import qa_rapi
 import qa_tags
@@ -786,6 +796,49 @@
     RunTest(qa_monitoring.TestInstStatusCollector)
 
 
+def RunPerformanceTests():
+  if not qa_config.TestEnabled("performance"):
+    ReportTestSkip("performance related tests", "performance")
+    return
+
+  if qa_config.TestEnabled("jobqueue-performance"):
+    RunTest(qa_performance.TestParallelMaxInstanceCreationPerformance)
+    RunTest(qa_performance.TestParallelNodeCountInstanceCreationPerformance)
+
+    instances = qa_performance.CreateAllInstances()
+
+    RunTest(qa_performance.TestParallelModify, instances)
+    RunTest(qa_performance.TestParallelInstanceOSOperations, instances)
+    RunTest(qa_performance.TestParallelInstanceQueries, instances)
+
+    qa_performance.RemoveAllInstances(instances)
+
+    RunTest(qa_performance.TestJobQueueSubmissionPerformance)
+
+  if qa_config.TestEnabled("parallel-performance"):
+    if qa_config.IsTemplateSupported(constants.DT_DRBD8):
+      RunTest(qa_performance.TestParallelDRBDInstanceCreationPerformance)
+    if qa_config.IsTemplateSupported(constants.DT_PLAIN):
+      RunTest(qa_performance.TestParallelPlainInstanceCreationPerformance)
+
+    if qa_config.IsTemplateSupported(constants.DT_DRBD8):
+      inodes = qa_config.AcquireManyNodes(2)
+      try:
+        instance = qa_instance.TestInstanceAddWithDrbdDisk(inodes)
+        try:
+          RunTest(qa_performance.TestParallelInstanceFailover, instance)
+          RunTest(qa_performance.TestParallelInstanceMigration, instance)
+          RunTest(qa_performance.TestParallelInstanceReplaceDisks, instance)
+          RunTest(qa_performance.TestParallelInstanceReboot, instance)
+          RunTest(qa_performance.TestParallelInstanceReinstall, instance)
+          RunTest(qa_performance.TestParallelInstanceRename, instance)
+        finally:
+          qa_instance.TestInstanceRemove(instance)
+          instance.Release()
+      finally:
+        qa_config.ReleaseManyNodes(inodes)
+
+
 def RunQa():
   """Main QA body.
 
@@ -921,6 +974,8 @@
 
   RunMonitoringTests()
 
+  RunPerformanceTests()
+
   RunTestIf("create-cluster", qa_node.TestNodeRemoveAll)
 
   RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy)
diff --git a/qa/qa-sample.json b/qa/qa-sample.json
index b096ec5..99fd9a9 100644
--- a/qa/qa-sample.json
+++ b/qa/qa-sample.json
@@ -149,6 +149,7 @@
     "os": true,
     "tags": true,
     "rapi": true,
+    "performance": true,
     "test-jobqueue": true,
     "delay": true,
 
@@ -219,9 +220,13 @@
     "instance-rename": true,
     "instance-shutdown": true,
     "instance-device-names": true,
+    "instance-device-hotplug": false,
 
     "job-list": true,
 
+    "jobqueue-performance": true,
+    "parallel-performance": true,
+
     "# cron/ganeti-watcher should be disabled for these tests": null,
     "instance-automatic-restart": false,
     "instance-consecutive-failures": false,
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index fc332b8..41b13e9 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Cluster related QA tests.
@@ -27,6 +36,7 @@
 import tempfile
 import os.path
 
+from ganeti import _constants
 from ganeti import constants
 from ganeti import compat
 from ganeti import utils
@@ -229,7 +239,13 @@
   qa_config.SetExclusiveStorage(e_s)
 
   extra_args = qa_config.get("cluster-init-args")
+
   if extra_args:
+    # This option was removed in 2.10, but in order to not break QA of older
+    # branches we remove it from the extra_args if it is in there.
+    opt_drbd_storage = "--no-drbd-storage"
+    if opt_drbd_storage in extra_args:
+      extra_args.remove(opt_drbd_storage)
     cmd.extend(extra_args)
 
   cmd.append(qa_config.get("name"))
@@ -560,8 +576,8 @@
   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
   default_disk_template = qa_config.GetDefaultDiskTemplate()
 
-  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
-                                           enabled_disk_templates)
+  _TestClusterModifyDiskTemplatesArguments(default_disk_template)
+  _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates)
   _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
 
   _RestoreEnabledDiskTemplates()
@@ -570,7 +586,6 @@
   instance_template = enabled_disk_templates[0]
   instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
 
-  _TestClusterModifyUnusedDiskTemplate(instance_template)
   _TestClusterModifyUsedDiskTemplate(instance_template,
                                      enabled_disk_templates)
 
@@ -598,8 +613,57 @@
   AssertCommand(cmd, fail=False)
 
 
-def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
-                                             enabled_disk_templates):
+def _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates):
+  """Tests argument handling of 'gnt-cluster modify' with respect to
+     the parameter '--drbd-usermode-helper'. This test is independent
+     of instances.
+
+  """
+  _RestoreEnabledDiskTemplates()
+
+  if constants.DT_DRBD8 not in enabled_disk_templates:
+    return
+  if constants.DT_PLAIN not in enabled_disk_templates:
+    return
+
+  drbd_usermode_helper = qa_config.get("drbd-usermode-helper", "/bin/true")
+  bogus_usermode_helper = "/tmp/pinkbunny"
+  for command, fail in [
+    (["gnt-cluster", "modify",
+      "--enabled-disk-templates=%s" % constants.DT_DRBD8,
+      "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=%s" % bogus_usermode_helper], True),
+    # unsetting helper when DRBD is enabled should not work
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper="], True),
+    (["gnt-cluster", "modify",
+      "--enabled-disk-templates=%s" % constants.DT_PLAIN,
+      "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper="], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=%s" % drbd_usermode_helper,
+      "--enabled-disk-templates=%s" % constants.DT_DRBD8,
+      "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=",
+      "--enabled-disk-templates=%s" % constants.DT_PLAIN,
+      "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
+    (["gnt-cluster", "modify",
+      "--drbd-usermode-helper=%s" % drbd_usermode_helper,
+      "--enabled-disk-templates=%s" % constants.DT_DRBD8,
+      "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
+    ]:
+    AssertCommand(command, fail=fail)
+  _RestoreEnabledDiskTemplates()
+
+
+def _TestClusterModifyDiskTemplatesArguments(default_disk_template):
   """Tests argument handling of 'gnt-cluster modify' with respect to
      the parameter '--enabled-disk-templates'. This test is independent
      of instances.
@@ -620,27 +684,6 @@
      "--ipolicy-disk-templates=%s" % default_disk_template],
     fail=False)
 
-  if constants.DT_DRBD8 in enabled_disk_templates:
-    # interaction with --drbd-usermode-helper option
-    drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
-    if not drbd_usermode_helper:
-      drbd_usermode_helper = "/bin/true"
-    # specifying a helper when drbd gets disabled is ok. Note that drbd still
-    # has to be installed on the nodes in this case
-    AssertCommand(["gnt-cluster", "modify",
-                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
-                   "--enabled-disk-templates=%s" % constants.DT_DISKLESS,
-                   "--ipolicy-disk-templates=%s" % constants.DT_DISKLESS],
-                   fail=False)
-    # specifying a helper when drbd is re-enabled
-    AssertCommand(["gnt-cluster", "modify",
-                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
-                   "--enabled-disk-templates=%s" %
-                     ",".join(enabled_disk_templates),
-                   "--ipolicy-disk-templates=%s" %
-                     ",".join(enabled_disk_templates)],
-                  fail=False)
-
 
 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
   """Tests argument handling of 'gnt-cluster modify' with respect to
@@ -653,10 +696,9 @@
     return
 
   # determine an LVM and a non-LVM disk template for the tests
-  non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
+  non_lvm_template = _GetOtherEnabledDiskTemplate(constants.DTS_LVM,
                                                   enabled_disk_templates)
-  lvm_template = list(set(enabled_disk_templates)
-                      .intersection(set(utils.GetLvmDiskTemplates())))[0]
+  lvm_template = list(set(enabled_disk_templates) & constants.DTS_LVM)[0]
 
   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
 
@@ -747,26 +789,6 @@
     fail=True)
 
 
-def _TestClusterModifyUnusedDiskTemplate(instance_template):
-  """Tests that unused disk templates can be disabled safely."""
-  all_disk_templates = constants.DISK_TEMPLATES
-  if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
-    all_disk_templates = list(set(all_disk_templates) -
-                              set(utils.GetLvmDiskTemplates()))
-
-  AssertCommand(
-    ["gnt-cluster", "modify",
-     "--enabled-disk-templates=%s" % ",".join(all_disk_templates),
-     "--ipolicy-disk-templates=%s" % ",".join(all_disk_templates)],
-    fail=False)
-  new_disk_templates = [instance_template]
-  AssertCommand(
-    ["gnt-cluster", "modify",
-     "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
-     "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
-    fail=False)
-
-
 def TestClusterModifyBe():
   """gnt-cluster modify -B"""
   for fail, cmd in [
@@ -1080,7 +1102,9 @@
     try:
       disks = qa_config.GetDiskOptions()
       # Run burnin
-      cmd = [script,
+      cmd = ["env",
+             "PYTHONPATH=%s" % _constants.VERSIONEDSHAREDIR,
+             script,
              "--os=%s" % qa_config.get("os"),
              "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
              "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
diff --git a/qa/qa_config.py b/qa/qa_config.py
index 212f838..da3f8d6 100644
--- a/qa/qa_config.py
+++ b/qa/qa_config.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """QA configuration.
@@ -660,7 +669,7 @@
   return GetConfig().get(name, default=default)
 
 
-class Either:
+class Either(object):
   def __init__(self, tests):
     """Initializes this class.
 
diff --git a/qa/qa_daemon.py b/qa/qa_daemon.py
index f27f48d..b63d6da 100644
--- a/qa/qa_daemon.py
+++ b/qa/qa_daemon.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Daemon related QA tests.
diff --git a/qa/qa_env.py b/qa/qa_env.py
index 34445c3..034e77f 100644
--- a/qa/qa_env.py
+++ b/qa/qa_env.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Cluster environment related QA tests.
diff --git a/qa/qa_error.py b/qa/qa_error.py
index 8b5d4b9..f0dccdc 100644
--- a/qa/qa_error.py
+++ b/qa/qa_error.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Error definitions for QA.
diff --git a/qa/qa_group.py b/qa/qa_group.py
index f11e512..c7827e1 100644
--- a/qa/qa_group.py
+++ b/qa/qa_group.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """QA tests for node groups.
diff --git a/qa/qa_instance.py b/qa/qa_instance.py
index e7d8bb4..b924d20 100644
--- a/qa/qa_instance.py
+++ b/qa/qa_instance.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Instance related QA tests.
@@ -491,6 +500,25 @@
   AssertCommand(["gnt-instance", "info", instance.name])
 
 
+def _TestKVMHotplug(instance):
+  """Tests hotplug modification commands, noting that they
+
+  """
+  args_to_try = [
+    ["--net", "-1:add", "--hotplug"],
+    ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug", "--force"],
+    ["--net", "-1:remove", "--hotplug"],
+    ["--disk", "-1:add,size=1G", "--hotplug"],
+    ["--disk", "-1:remove", "--hotplug"],
+  ]
+  for alist in args_to_try:
+    _, stdout, stderr = \
+      AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
+    if "failed" in stdout or "failed" in stderr:
+      raise qa_error.Error("Hotplugging command failed; please check output"
+                           " for further information")
+
+
 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
 def TestInstanceModify(instance):
   """gnt-instance modify"""
@@ -534,6 +562,9 @@
       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
       ])
+  elif default_hv == constants.HT_KVM and \
+    qa_config.TestEnabled("instance-device-hotplug"):
+    _TestKVMHotplug(instance)
 
   for alist in args:
     AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
@@ -545,8 +576,9 @@
   AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
                  fail=True)
 
-  # ...while making it online is ok, and should work
-  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
+  # ...while making it online fails too (needs to be offline first)
+  AssertCommand(["gnt-instance", "modify", "--online", instance.name],
+                 fail=True)
 
 
 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
diff --git a/qa/qa_instance_utils.py b/qa/qa_instance_utils.py
index 72715e9..1ae9448 100644
--- a/qa/qa_instance_utils.py
+++ b/qa/qa_instance_utils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """QA utility functions for managing instances
diff --git a/qa/qa_job.py b/qa/qa_job.py
index ed2bf4a..a9e7fe1 100644
--- a/qa/qa_job.py
+++ b/qa/qa_job.py
@@ -2,38 +2,46 @@
 #
 
 # Copyright (C) 2012, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Job-related QA tests.
 
 """
 
-from ganeti.utils import retry
-from ganeti import constants
-from ganeti import query
-
 import functools
 import re
 
+from ganeti.utils import retry
+from ganeti import constants
+from ganeti import query
 import qa_config
 import qa_error
+import qa_job_utils
 import qa_utils
-
 from qa_utils import AssertCommand, GetCommandOutput
 
 
@@ -48,20 +56,6 @@
   qa_utils.GenericQueryFieldsTest("gnt-job", query.JOB_FIELDS.keys())
 
 
-def _GetJobStatuses():
-  """ Invokes gnt-job list and extracts an id to status dictionary.
-
-  @rtype: dict of string to string
-  @return: A dictionary mapping job ids to matching statuses
-
-  """
-  master = qa_config.GetMasterNode()
-  list_output = GetCommandOutput(
-    master.primary, "gnt-job list --no-headers --output=id,status"
-  )
-  return dict(map(lambda s: s.split(), list_output.splitlines()))
-
-
 def _GetJobStatus(job_id):
   """ Retrieves the status of a job.
 
@@ -72,7 +66,7 @@
   @return: The job status, or None if not present.
 
   """
-  return _GetJobStatuses().get(job_id, None)
+  return qa_job_utils.GetJobStatuses([job_id]).get(job_id, None)
 
 
 def _RetryingFetchJobStatus(retry_status, job_id):
diff --git a/qa/qa_job_utils.py b/qa/qa_job_utils.py
new file mode 100644
index 0000000..e0c7f35
--- /dev/null
+++ b/qa/qa_job_utils.py
@@ -0,0 +1,402 @@
+#
+#
+
+# Copyright (C) 2014 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""QA utility functions for testing jobs
+
+"""
+
+import re
+import sys
+import threading
+import time
+
+# (only used in later branches)
+# from ganeti import constants
+from ganeti import locking
+from ganeti import utils
+from ganeti.utils import retry
+
+import qa_config
+import qa_error
+
+from qa_utils import AssertCommand, GetCommandOutput, GetObjectInfo
+
+
+AVAILABLE_LOCKS = [locking.LEVEL_NODE, ]
+
+
+def GetOutputFromMaster(cmd,
+                        # pylint: disable=W0613
+                        # (only in later branches required)
+                        use_multiplexer=True, log_cmd=True):
+  """ Gets the output of a command executed on master.
+
+  """
+  if isinstance(cmd, basestring):
+    cmdstr = cmd
+  else:
+    cmdstr = utils.ShellQuoteArgs(cmd)
+
+  # Necessary due to the stderr stream not being captured properly on the
+  # buildbot
+  cmdstr += " 2>&1"
+
+  return GetCommandOutput(qa_config.GetMasterNode().primary, cmdstr)
+
+
+def ExecuteJobProducingCommand(cmd):
+  """ Executes a command that contains the --submit flag, and returns a job id.
+
+  @type cmd: list of string
+  @param cmd: The command to execute, broken into constituent components.
+
+  """
+  job_id_output = GetOutputFromMaster(cmd)
+
+  # Usually, the output contains "JobID: <job_id>", but for instance related
+  # commands, the output is of the form "<job_id>: <instance_name>"
+  possible_job_ids = re.findall("JobID: ([0-9]+)", job_id_output) or \
+                     re.findall("([0-9]+): .+", job_id_output)
+  if len(possible_job_ids) != 1:
+    raise qa_error.Error("Cannot parse command output to find job id: output "
+                         "is %s" % job_id_output)
+
+  return int(possible_job_ids[0])
+
+
+def GetJobStatuses(job_ids=None):
+  """ Invokes gnt-job list and extracts an id to status dictionary.
+
+  @type job_ids: list
+  @param job_ids: list of job ids to query the status for; if C{None}, the
+                  status of all current jobs is returned
+  @rtype: dict of string to string
+  @return: A dictionary mapping job ids to matching statuses
+
+  """
+  cmd = ["gnt-job", "list", "--no-headers", "--output=id,status"]
+  if job_ids is not None:
+    cmd.extend(map(str, job_ids))
+
+  list_output = GetOutputFromMaster(cmd)
+  return dict(map(lambda s: s.split(), list_output.splitlines()))
+
+
+def _RetrieveTerminationInfo(job_id):
+  """ Retrieves the termination info from a job caused by gnt-debug delay.
+
+  @rtype: dict or None
+  @return: The termination log entry, or None if no entry was found
+
+  """
+  job_info = GetObjectInfo(["gnt-job", "info", str(job_id)])
+
+  opcodes = job_info[0]["Opcodes"]
+  if not opcodes:
+    raise qa_error.Error("Cannot retrieve a list of opcodes")
+
+  execution_logs = opcodes[0]["Execution log"]
+  if not execution_logs:
+    return None
+
+  # ELOG_DELAY_TEST constant is only introduced in later branches
+  is_termination_info_fn = \
+    lambda e: e["Content"][1] == "delay-test" # constants.ELOG_DELAY_TEST
+
+  filtered_logs = filter(is_termination_info_fn, execution_logs)
+
+  no_logs = len(filtered_logs)
+  if no_logs > 1:
+    raise qa_error.Error("Too many interruption information entries found!")
+  elif no_logs == 1:
+    return filtered_logs[0]
+  else:
+    return None
+
+
+def _StartDelayFunction(locks, timeout):
+  """ Starts the gnt-debug delay option with the given locks and timeout.
+
+  """
+  # The interruptible switch must be used
+  cmd = ["gnt-debug", "delay", "-i", "--submit", "--no-master"]
+
+  for node in locks.get(locking.LEVEL_NODE, []):
+    cmd.append("-n%s" % node)
+  cmd.append(str(timeout))
+
+  job_id = ExecuteJobProducingCommand(cmd)
+
+  # Waits until a non-empty result is returned from the function
+  log_entry = retry.SimpleRetry(lambda x: x, _RetrieveTerminationInfo, 2.0,
+                                10.0, args=[job_id])
+
+  if not log_entry:
+    raise qa_error.Error("Failure when trying to retrieve delay termination "
+                         "information")
+
+  _, _, (socket_path, ) = log_entry["Content"]
+
+  return socket_path
+
+
+def _TerminateDelayFunction(termination_socket):
+  """ Terminates the delay function by communicating with the domain socket.
+
+  """
+  AssertCommand("echo a | socat -u stdin UNIX-CLIENT:%s" % termination_socket)
+
+
+def _GetNodeUUIDMap(nodes):
+  """ Given a list of nodes, retrieves a mapping of their names to UUIDs.
+
+  @type nodes: list of string
+  @param nodes: The nodes to retrieve a map for. If empty, returns information
+                for all the nodes.
+
+  """
+  cmd = ["gnt-node", "list", "--no-header", "-o", "name,uuid"]
+  cmd.extend(nodes)
+  output = GetOutputFromMaster(cmd)
+  return dict(map(lambda x: x.split(), output.splitlines()))
+
+
+def _FindLockNames(locks):
+  """ Finds the ids and descriptions of locks that given locks can block.
+
+  @type locks: dict of locking level to list
+  @param locks: The locks that gnt-debug delay is holding.
+
+  @rtype: dict of string to string
+  @return: The lock name to entity name map.
+
+  For a given set of locks, some internal locks (e.g. ALL_SET locks) can be
+  blocked even though they were not listed explicitly. This function has to take
+  care and list all locks that can be blocked by the locks given as parameters.
+
+  """
+  lock_map = {}
+
+  if locking.LEVEL_NODE in locks:
+    node_locks = locks[locking.LEVEL_NODE]
+    if node_locks == locking.ALL_SET:
+      # Empty list retrieves all info
+      name_uuid_map = _GetNodeUUIDMap([])
+    else:
+      name_uuid_map = _GetNodeUUIDMap(node_locks)
+
+    for name in name_uuid_map:
+      lock_map["node/%s" % name_uuid_map[name]] = name
+
+    # If ALL_SET was requested explicitly, or there is at least one lock
+    # Note that locking.ALL_SET is None and hence the strange form of the if
+    if node_locks == locking.ALL_SET or node_locks:
+      lock_map["node/[lockset]"] = "joint node lock"
+
+  #TODO add other lock types here when support for these is added
+  return lock_map
+
+
+def _GetBlockingLocks():
+  """ Finds out which locks are blocking jobs by invoking "gnt-debug locks".
+
+  @rtype: list of string
+  @return: The names of the locks currently blocking any job.
+
+  """
+  # Due to mysterious issues when a SSH multiplexer is being used by two
+  # threads, we turn it off, and block most of the logging to improve the
+  # visibility of the other thread's output
+  locks_output = GetOutputFromMaster("gnt-debug locks", use_multiplexer=False,
+                                      log_cmd=False)
+
+  # The first non-empty line is the header, which we do not need
+  lock_lines = locks_output.splitlines()[1:]
+
+  blocking_locks = []
+  for lock_line in lock_lines:
+    components = lock_line.split()
+    if len(components) != 4:
+      raise qa_error.Error("Error while parsing gnt-debug locks output, "
+                           "line at fault is: %s" % lock_line)
+
+    lock_name, _, _, pending_jobs = components
+
+    if pending_jobs != '-':
+      blocking_locks.append(lock_name)
+
+  return blocking_locks
+
+
+class QAThread(threading.Thread):
+  """ An exception-preserving thread that executes a given function.
+
+  """
+  def __init__(self, fn, args, kwargs):
+    """ Constructor accepting the function to be invoked later.
+
+    """
+    threading.Thread.__init__(self)
+    self._fn = fn
+    self._args = args
+    self._kwargs = kwargs
+    self._exc_info = None
+
+  def run(self):
+    """ Executes the function, preserving exception info if necessary.
+
+    """
+    # pylint: disable=W0702
+    # We explicitly want to catch absolutely anything
+    try:
+      self._fn(*self._args, **self._kwargs)
+    except:
+      self._exc_info = sys.exc_info()
+    # pylint: enable=W0702
+
+  def reraise(self):
+    """ Reraises any exceptions that might have occured during thread execution.
+
+    """
+    if self._exc_info is not None:
+      raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
+
+
+class QAThreadGroup(object):
+  """This class manages a list of QAThreads.
+
+  """
+  def __init__(self):
+    self._threads = []
+
+  def Start(self, thread):
+    """Starts the given thread and adds it to this group.
+
+    @type thread: qa_job_utils.QAThread
+    @param thread: the thread to start and to add to this group.
+
+    """
+    thread.start()
+    self._threads.append(thread)
+
+  def JoinAndReraise(self):
+    """Joins all threads in this group and calls their C{reraise} method.
+
+    """
+    for thread in self._threads:
+      thread.join()
+      thread.reraise()
+
+
+# TODO: Can this be done as a decorator? Implement as needed.
+def RunWithLocks(fn, locks, timeout, block, *args, **kwargs):
+  """ Runs the given function, acquiring a set of locks beforehand.
+
+  @type fn: function
+  @param fn: The function to invoke.
+  @type locks: dict of string to list of string
+  @param locks: The locks to acquire, per lock category.
+  @type timeout: number
+  @param timeout: The number of seconds the locks should be held before
+                  expiring.
+  @type block: bool
+  @param block: Whether the test should block when locks are used or not.
+
+  This function allows a set of locks to be acquired in preparation for a QA
+  test, to try and see if the function can run in parallel with other
+  operations.
+
+  Locks are acquired by invoking a gnt-debug delay operation which can be
+  interrupted as needed. The QA test is then run in a separate thread, with the
+  current thread observing jobs waiting for locks. When a job is spotted waiting
+  for a lock held by the started delay operation, this is noted, and the delay
+  is interrupted, allowing the QA test to continue.
+
+  A default timeout is not provided by design - the test creator must make a
+  good conservative estimate.
+
+  """
+  if filter(lambda l_type: l_type not in AVAILABLE_LOCKS, locks):
+    raise qa_error.Error("Attempted to acquire locks that cannot yet be "
+                         "acquired in the course of a QA test.")
+
+  # The watcher may interfere by issuing its own jobs - therefore pause it
+  AssertCommand(["gnt-cluster", "watcher", "pause", "12h"])
+
+  # Find out the lock names prior to starting the delay function
+  lock_name_map = _FindLockNames(locks)
+
+  blocking_owned_locks = []
+  test_blocked = False
+
+  termination_socket = _StartDelayFunction(locks, timeout)
+  delay_fn_terminated = False
+
+  try:
+    qa_thread = QAThread(fn, args, kwargs)
+    qa_thread.start()
+
+    while qa_thread.isAlive():
+      blocking_locks = _GetBlockingLocks()
+      blocking_owned_locks = \
+        set(blocking_locks).intersection(set(lock_name_map))
+
+      if blocking_owned_locks:
+        # Set the flag first - if the termination attempt fails, we do not want
+        # to redo it in the finally block
+        delay_fn_terminated = True
+        _TerminateDelayFunction(termination_socket)
+        test_blocked = True
+        break
+
+      time.sleep(5) # Set arbitrarily
+
+    # The thread should be either finished or unblocked at this point
+    qa_thread.join()
+
+    # Raise any errors that might have occured in the thread
+    qa_thread.reraise()
+
+  finally:
+    if not delay_fn_terminated:
+      _TerminateDelayFunction(termination_socket)
+
+  blocking_lock_names = ", ".join(map(lock_name_map.get, blocking_owned_locks))
+  if not block and test_blocked:
+    raise qa_error.Error("QA test succeded, but was blocked by locks: %s" %
+                         blocking_lock_names)
+  elif block and not test_blocked:
+    raise qa_error.Error("QA test succeded, but was not blocked as it was "
+                         "expected to by locks: %s" % blocking_lock_names)
+  else:
+    pass
+
+  # Revive the watcher
+  AssertCommand(["gnt-cluster", "watcher", "continue"])
diff --git a/qa/qa_logging.py b/qa/qa_logging.py
index c1a2461..b712a98 100644
--- a/qa/qa_logging.py
+++ b/qa/qa_logging.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """ Handles the logging of messages with appropriate coloring.
 
diff --git a/qa/qa_monitoring.py b/qa/qa_monitoring.py
index 90bc1b9..bce7d01 100644
--- a/qa/qa_monitoring.py
+++ b/qa/qa_monitoring.py
@@ -2,28 +2,37 @@
 #
 
 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Monitoring related QA tests.
 
 """
 
-from ganeti import _autoconf
+from ganeti import _constants
 from ganeti import constants
 
 import qa_config
@@ -32,7 +41,7 @@
 from qa_instance_utils import CreateInstanceByDiskTemplate, \
                               RemoveInstance
 
-MON_COLLECTOR = _autoconf.PKGLIBDIR + "/mon-collector"
+MON_COLLECTOR = _constants.PKGLIBDIR + "/mon-collector"
 
 
 def TestInstStatusCollector():
diff --git a/qa/qa_network.py b/qa/qa_network.py
index aac83d5..39128fd 100644
--- a/qa/qa_network.py
+++ b/qa/qa_network.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """QA tests for networks.
@@ -80,10 +89,13 @@
     mode = default_mode
     link = default_link
 
+  nicparams = "mode=%s,link=%s" % (mode, link)
+
   AssertCommand(["gnt-group", "add", group1])
   AssertCommand(["gnt-network", "add", "--network", "192.0.2.0/24", network1])
 
-  AssertCommand(["gnt-network", "connect", network1, mode, link, group1])
+  AssertCommand(["gnt-network", "connect", "--nic-parameters", nicparams,
+                network1, group1])
   AssertCommand(["gnt-network", "disconnect", network1, group1])
 
   AssertCommand(["gnt-group", "remove", group1])
diff --git a/qa/qa_node.py b/qa/qa_node.py
index 3e0dbb3..b69b0d3 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Node-related QA tests.
@@ -129,8 +138,7 @@
 
     # Test all storage fields
     cmd = ["gnt-node", "list-storage", "--storage-type", storage_type,
-           "--output=%s" % ",".join(list(constants.VALID_STORAGE_FIELDS) +
-                                    [constants.SF_NODE, constants.SF_TYPE])]
+           "--output=%s" % ",".join(list(constants.VALID_STORAGE_FIELDS))]
     AssertCommand(cmd)
 
     # Get list of valid storage devices
diff --git a/qa/qa_os.py b/qa/qa_os.py
index f96e27e..0fa3d6a 100644
--- a/qa/qa_os.py
+++ b/qa/qa_os.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """OS related QA tests.
diff --git a/qa/qa_performance.py b/qa/qa_performance.py
new file mode 100644
index 0000000..9eba12c
--- /dev/null
+++ b/qa/qa_performance.py
@@ -0,0 +1,625 @@
+#
+#
+
+# Copyright (C) 2014 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Performance testing QA tests.
+
+"""
+
+import datetime
+import functools
+import itertools
+import threading
+import time
+
+from ganeti import constants
+
+import qa_config
+import qa_error
+from qa_instance_utils import GetGenericAddParameters
+import qa_job_utils
+import qa_logging
+import qa_utils
+
+
+MAX_JOB_SUBMISSION_DURATION = 15.0
+
+
+class _JobQueueDriver(object):
+  """This class handles polling of jobs and reacting on status changes.
+
+  Jobs are added via the L{AddJob} method, and can have callback functions
+  assigned to them. Those are called as soon as the job enters the appropriate
+  state. Callback functions can add new jobs to the driver as needed.
+
+  A call to L{WaitForCompletion} finally polls Ganeti until all jobs have
+  succeeded.
+
+  """
+
+  _UNKNOWN_STATUS = "unknown"
+
+  class _JobEntry(object):
+    """Internal class representing a job entry.
+
+    """
+    def __init__(self, job_id, running_fn, success_fn):
+      self.job_id = job_id
+      self.running_fn = running_fn
+      self.success_fn = success_fn
+
+    def __str__(self):
+      return str(self.job_id)
+
+  def __init__(self):
+    self._jobs = {}
+    self._running_notified = set()
+    self._jobs_per_status = {}
+    self._lock = threading.RLock()
+
+  def AddJob(self, job_id, running_fn=None, success_fn=None):
+    """Add a job to the driver.
+
+    @type job_id: of ints
+    @param job_id: job id to add to the driver
+    @type running_fn: function taking a L{_JobQueueDriver} and an int
+    @param running_fn: function called once when a job changes to running state
+                       (or success state, if the running state was too short)
+    @type success_fn: function taking a L{_JobQueueDriver} and an int
+    @param success_fn: function called for each successful job id
+
+    """
+    with self._lock:
+      self._jobs[job_id] = _JobQueueDriver._JobEntry(job_id,
+                                                     running_fn,
+                                                     success_fn)
+      # the status will be updated on the next call to _FetchJobStatuses
+      self._jobs_per_status.setdefault(self._UNKNOWN_STATUS, []).append(job_id)
+
+  def _FetchJobStatuses(self):
+    """Retrieves status information of the given jobs.
+
+    """
+    job_statuses = qa_job_utils.GetJobStatuses(self._GetJobIds())
+
+    new_statuses = {}
+    for job_id, status in job_statuses.items():
+      new_statuses.setdefault(status, []).append(self._jobs[int(job_id)])
+    self._jobs_per_status = new_statuses
+
+  def _GetJobIds(self):
+    return list(self._jobs.keys())
+
+  def _GetJobsInStatuses(self, statuses):
+    """Returns a list of L{_JobEntry} of all jobs in the given statuses.
+
+    @type statuses: iterable of strings
+    @param statuses: jobs in those statuses are returned
+    @rtype: list of L{_JobEntry}
+    @return: list of job entries in the requested statuses
+
+    """
+    ret = []
+    for state in statuses:
+      ret.extend(self._jobs_per_status.get(state, []))
+    return ret
+
+  def _UpdateJobStatuses(self):
+    """Retrieves job statuses from the cluster and updates internal state.
+
+    """
+    self._FetchJobStatuses()
+    error_jobs = self._GetJobsInStatuses([constants.JOB_STATUS_ERROR])
+    if error_jobs:
+      raise qa_error.Error(
+        "Jobs %s are in error state!" % [job.job_id for job in error_jobs])
+
+    for job in self._GetJobsInStatuses([constants.JOB_STATUS_RUNNING,
+                                        constants.JOB_STATUS_SUCCESS]):
+      if job.job_id not in self._running_notified:
+        if job.running_fn is not None:
+          job.running_fn(self, job.job_id)
+        self._running_notified.add(job.job_id)
+
+    for job in self._GetJobsInStatuses([constants.JOB_STATUS_SUCCESS]):
+      if job.success_fn is not None:
+        job.success_fn(self, job.job_id)
+
+      # we're done with this job
+      del self._jobs[job.job_id]
+
+  def _HasPendingJobs(self):
+    """Checks if there are still jobs pending.
+
+    @rtype: bool
+    @return: C{True} if there are still jobs which have not succeeded
+
+    """
+    with self._lock:
+      self._UpdateJobStatuses()
+      uncompleted_jobs = self._GetJobsInStatuses(
+        constants.JOB_STATUS_ALL - constants.JOBS_FINALIZED)
+      unknown_jobs = self._GetJobsInStatuses([self._UNKNOWN_STATUS])
+      return len(uncompleted_jobs) > 0 or len(unknown_jobs) > 0
+
+  def WaitForCompletion(self):
+    """Wait for the completion of all registered jobs.
+
+    """
+    while self._HasPendingJobs():
+      time.sleep(2)
+
+    with self._lock:
+      if self._jobs:
+        raise qa_error.Error(
+          "Jobs %s didn't finish in success state!" % self._GetJobIds())
+
+
+def _AcquireAllInstances():
+  """Generator for acquiring all instances in the QA config.
+
+  """
+  try:
+    while True:
+      instance = qa_config.AcquireInstance()
+      yield instance
+  except qa_error.OutOfInstancesError:
+    pass
+
+
+def _AcquireAllNodes():
+  """Generator for acquiring all nodes in the QA config.
+
+  """
+  exclude = []
+  try:
+    while True:
+      node = qa_config.AcquireNode(exclude=exclude)
+      exclude.append(node)
+      yield node
+  except qa_error.OutOfNodesError:
+    pass
+
+
+def _ExecuteJobSubmittingCmd(cmd):
+  """Executes a job submitting command and returns the resulting job ID.
+
+  This will fail if submitting the job takes longer than
+  L{MAX_JOB_SUBMISSION_DURATION}.
+
+  @type cmd: list of string or string
+  @param cmd: the job producing command to execute on the cluster
+  @rtype: int
+  @return: job-id
+
+  """
+  start = datetime.datetime.now()
+  result = qa_job_utils.ExecuteJobProducingCommand(cmd)
+  duration = qa_utils.TimedeltaToTotalSeconds(datetime.datetime.now() - start)
+  if duration > MAX_JOB_SUBMISSION_DURATION:
+    print(qa_logging.FormatWarning(
+      "Executing '%s' took %f seconds, a maximum of %f was expected" %
+      (cmd, duration, MAX_JOB_SUBMISSION_DURATION)))
+  return result
+
+
+def _SubmitInstanceCreationJob(instance, disk_template=None):
+  """Submit an instance creation job.
+
+  @type instance: L{qa_config._QaInstance}
+  @param instance: instance to submit a create command for
+  @type disk_template: string
+  @param disk_template: disk template for the new instance or C{None} which
+                        causes the default disk template to be used
+  @rtype: int
+  @return: job id of the submitted creation job
+
+  """
+  if disk_template is None:
+    disk_template = qa_config.GetDefaultDiskTemplate()
+  try:
+    cmd = (["gnt-instance", "add", "--submit",
+            "--os-type=%s" % qa_config.get("os"),
+            "--disk-template=%s" % disk_template] +
+           GetGenericAddParameters(instance, disk_template))
+    cmd.append(instance.name)
+
+    instance.SetDiskTemplate(disk_template)
+
+    return _ExecuteJobSubmittingCmd(cmd)
+  except:
+    instance.Release()
+    raise
+
+
+def _SubmitInstanceRemoveJob(instance):
+  """Submit an instance remove job.
+
+  @type instance: L{qa_config._QaInstance}
+  @param instance: the instance to remove
+  @rtype: int
+  @return: job id of the submitted remove job
+
+  """
+  try:
+    cmd = (["gnt-instance", "remove", "--submit", "-f"])
+    cmd.append(instance.name)
+
+    return _ExecuteJobSubmittingCmd(cmd)
+  finally:
+    instance.Release()
+
+
+def _TestParallelInstanceCreationAndRemoval(max_instances=None,
+                                            disk_template=None,
+                                            custom_job_driver=None):
+  """Tests parallel creation and immediate removal of instances.
+
+  @type max_instances: int
+  @param max_instances: maximum number of instances to create
+  @type disk_template: string
+  @param disk_template: disk template for the new instances or C{None} which
+                        causes the default disk template to be used
+  @type custom_job_driver: _JobQueueDriver
+  @param custom_job_driver: a custom L{_JobQueueDriver} to use if not L{None}.
+                            If one is specified, C{WaitForCompletion} is _not_
+                            called on it.
+
+  """
+  job_driver = custom_job_driver or _JobQueueDriver()
+
+  def _CreateSuccessFn(instance, job_driver, _):
+    job_id = _SubmitInstanceRemoveJob(instance)
+    job_driver.AddJob(job_id)
+
+  instance_generator = _AcquireAllInstances()
+  if max_instances is not None:
+    instance_generator = itertools.islice(instance_generator, max_instances)
+
+  for instance in instance_generator:
+    job_id = _SubmitInstanceCreationJob(instance, disk_template=disk_template)
+    job_driver.AddJob(
+      job_id, success_fn=functools.partial(_CreateSuccessFn, instance))
+
+  if custom_job_driver is None:
+    job_driver.WaitForCompletion()
+
+
+def TestParallelMaxInstanceCreationPerformance():
+  """PERFORMANCE: Parallel instance creation (instance count = max).
+
+  """
+  _TestParallelInstanceCreationAndRemoval()
+
+
+def TestParallelNodeCountInstanceCreationPerformance():
+  """PERFORMANCE: Parallel instance creation (instance count = node count).
+
+  """
+  nodes = list(_AcquireAllNodes())
+  _TestParallelInstanceCreationAndRemoval(max_instances=len(nodes))
+  qa_config.ReleaseManyNodes(nodes)
+
+
+def CreateAllInstances():
+  """Create all instances configured in QA config in the cluster.
+
+  @rtype: list of L{qa_config._QaInstance}
+  @return: list of instances created in the cluster
+
+  """
+  job_driver = _JobQueueDriver()
+  instances = list(_AcquireAllInstances())
+  for instance in instances:
+    job_id = _SubmitInstanceCreationJob(instance)
+    job_driver.AddJob(job_id)
+
+  job_driver.WaitForCompletion()
+  return instances
+
+
+def RemoveAllInstances(instances):
+  """Removes all given instances from the cluster.
+
+  @type instances: list of L{qa_config._QaInstance}
+  @param instances:
+
+  """
+  job_driver = _JobQueueDriver()
+  for instance in instances:
+    job_id = _SubmitInstanceRemoveJob(instance)
+    job_driver.AddJob(job_id)
+
+  job_driver.WaitForCompletion()
+
+
+def TestParallelModify(instances):
+  """PERFORMANCE: Parallel instance modify.
+
+  @type instances: list of L{qa_config._QaInstance}
+  @param instances: list of instances to issue modify commands against
+
+  """
+  job_driver = _JobQueueDriver()
+  # set min mem to same value as max mem
+  new_min_mem = qa_config.get(constants.BE_MAXMEM)
+  for instance in instances:
+    cmd = (["gnt-instance", "modify", "--submit",
+            "-B", "%s=%s" % (constants.BE_MINMEM, new_min_mem)])
+    cmd.append(instance.name)
+    job_driver.AddJob(_ExecuteJobSubmittingCmd(cmd))
+
+    cmd = (["gnt-instance", "modify", "--submit",
+            "-O", "fake_os_param=fake_value"])
+    cmd.append(instance.name)
+    job_driver.AddJob(_ExecuteJobSubmittingCmd(cmd))
+
+    cmd = (["gnt-instance", "modify", "--submit",
+            "-O", "fake_os_param=fake_value",
+            "-B", "%s=%s" % (constants.BE_MINMEM, new_min_mem)])
+    cmd.append(instance.name)
+    job_driver.AddJob(_ExecuteJobSubmittingCmd(cmd))
+
+  job_driver.WaitForCompletion()
+
+
+def TestParallelInstanceOSOperations(instances):
+  """PERFORMANCE: Parallel instance OS operations.
+
+  Note: This test leaves the instances either running or stopped, there's no
+  guarantee on the actual status.
+
+  @type instances: list of L{qa_config._QaInstance}
+  @param instances: list of instances to issue lifecycle commands against
+
+  """
+  OPS = ["start", "shutdown", "reboot", "reinstall"]
+  job_driver = _JobQueueDriver()
+
+  def _SubmitNextOperation(instance, start, idx, job_driver, _):
+    if idx == len(OPS):
+      return
+    op_idx = (start + idx) % len(OPS)
+
+    next_fn = functools.partial(_SubmitNextOperation, instance, start, idx + 1)
+
+    if OPS[op_idx] == "reinstall" and \
+        instance.disk_template == constants.DT_DISKLESS:
+      # no reinstall possible with diskless instances
+      next_fn(job_driver, None)
+      return
+    elif OPS[op_idx] == "reinstall":
+      # the instance has to be shut down for reinstall to work
+      shutdown_cmd = ["gnt-instance", "shutdown", "--submit", instance.name]
+      cmd = ["gnt-instance", "reinstall", "--submit", "-f", instance.name]
+
+      job_driver.AddJob(_ExecuteJobSubmittingCmd(shutdown_cmd),
+                        running_fn=lambda _, __: job_driver.AddJob(
+                          _ExecuteJobSubmittingCmd(cmd),
+                          running_fn=next_fn))
+    else:
+      cmd = ["gnt-instance", OPS[op_idx], "--submit"]
+      if OPS[op_idx] == "reinstall":
+        cmd.append("-f")
+      cmd.append(instance.name)
+
+      job_id = _ExecuteJobSubmittingCmd(cmd)
+      job_driver.AddJob(job_id, running_fn=next_fn)
+
+  for start, instance in enumerate(instances):
+    _SubmitNextOperation(instance, start % len(OPS), 0, job_driver, None)
+
+  job_driver.WaitForCompletion()
+
+
+def TestParallelInstanceQueries(instances):
+  """PERFORMANCE: Parallel instance queries.
+
+  @type instances: list of L{qa_config._QaInstance}
+  @param instances: list of instances to issue queries against
+
+  """
+  threads = qa_job_utils.QAThreadGroup()
+  for instance in instances:
+    cmd = ["gnt-instance", "info", instance.name]
+    info_thread = qa_job_utils.QAThread(qa_utils.AssertCommand, [cmd], {})
+    threads.Start(info_thread)
+
+    cmd = ["gnt-instance", "list"]
+    list_thread = qa_job_utils.QAThread(qa_utils.AssertCommand, [cmd], {})
+    threads.Start(list_thread)
+
+  threads.JoinAndReraise()
+
+
+def TestJobQueueSubmissionPerformance():
+  """PERFORMANCE: Job queue submission performance.
+
+  This test exercises the job queue and verifies that the job submission time
+  does not increase as more jobs are added.
+
+  """
+  MAX_CLUSTER_INFO_SECONDS = 15.0
+  job_driver = _JobQueueDriver()
+  submission_durations = []
+
+  def _VerifySubmissionDuration(duration_seconds):
+    # only start to verify the submission duration once we got data from the
+    # first 10 job submissions
+    if len(submission_durations) >= 10:
+      avg_duration = sum(submission_durations) / len(submission_durations)
+      max_duration = avg_duration * 1.5
+      if duration_seconds > max_duration:
+        print(qa_logging.FormatWarning(
+          "Submitting a delay job took %f seconds, max %f expected" %
+          (duration_seconds, max_duration)))
+    else:
+      submission_durations.append(duration_seconds)
+
+  def _SubmitDelayJob(count):
+    for _ in range(count):
+      cmd = ["gnt-debug", "delay", "--submit", "0.1"]
+
+      start = datetime.datetime.now()
+      job_id = _ExecuteJobSubmittingCmd(cmd)
+      duration_seconds = \
+        qa_utils.TimedeltaToTotalSeconds(datetime.datetime.now() - start)
+      _VerifySubmissionDuration(duration_seconds)
+
+      job_driver.AddJob(job_id)
+
+  threads = qa_job_utils.QAThreadGroup()
+  for i in range(10):
+    thread = qa_job_utils.QAThread(_SubmitDelayJob, [20], {})
+    threads.Start(thread)
+
+  threads.JoinAndReraise()
+
+  qa_utils.AssertCommand(["gnt-cluster", "info"],
+                         max_seconds=MAX_CLUSTER_INFO_SECONDS)
+
+  job_driver.WaitForCompletion()
+
+
+def TestParallelDRBDInstanceCreationPerformance():
+  """PERFORMANCE: Parallel DRBD backed instance creation.
+
+  """
+  assert qa_config.IsTemplateSupported(constants.DT_DRBD8)
+
+  nodes = list(_AcquireAllNodes())
+  _TestParallelInstanceCreationAndRemoval(max_instances=len(nodes) * 2,
+                                          disk_template=constants.DT_DRBD8)
+  qa_config.ReleaseManyNodes(nodes)
+
+
+def TestParallelPlainInstanceCreationPerformance():
+  """PERFORMANCE: Parallel plain backed instance creation.
+
+  """
+  assert qa_config.IsTemplateSupported(constants.DT_PLAIN)
+
+  nodes = list(_AcquireAllNodes())
+  _TestParallelInstanceCreationAndRemoval(max_instances=len(nodes) * 2,
+                                          disk_template=constants.DT_PLAIN)
+  qa_config.ReleaseManyNodes(nodes)
+
+
+def _TestInstanceOperationInParallelToInstanceCreation(*cmds):
+  """Run the given test command in parallel to an instance creation.
+
+  @type cmds: list of list of strings
+  @param cmds: commands to execute in parallel to an instance creation. Each
+               command in the list is executed once the previous job starts
+               to run.
+
+  """
+  def _SubmitNextCommand(cmd_idx, job_driver, _):
+    if cmd_idx >= len(cmds):
+      return
+    job_id = _ExecuteJobSubmittingCmd(cmds[cmd_idx])
+    job_driver.AddJob(
+      job_id, running_fn=functools.partial(_SubmitNextCommand, cmd_idx + 1))
+
+  assert qa_config.IsTemplateSupported(constants.DT_DRBD8)
+  assert len(cmds) > 0
+
+  job_driver = _JobQueueDriver()
+  _SubmitNextCommand(0, job_driver, None)
+
+  _TestParallelInstanceCreationAndRemoval(max_instances=1,
+                                          disk_template=constants.DT_DRBD8,
+                                          custom_job_driver=job_driver)
+
+  job_driver.WaitForCompletion()
+
+
+def TestParallelInstanceFailover(instance):
+  """PERFORMANCE: Instance failover with parallel instance creation.
+
+  """
+  _TestInstanceOperationInParallelToInstanceCreation(
+    ["gnt-instance", "failover", "--submit", "-f", "--shutdown-timeout=0",
+     instance.name])
+
+
+def TestParallelInstanceMigration(instance):
+  """PERFORMANCE: Instance migration with parallel instance creation.
+
+  """
+  _TestInstanceOperationInParallelToInstanceCreation(
+    ["gnt-instance", "migrate", "--submit", "-f", instance.name])
+
+
+def TestParallelInstanceReplaceDisks(instance):
+  """PERFORMANCE: Instance replace-disks with parallel instance creation.
+
+  """
+  _TestInstanceOperationInParallelToInstanceCreation(
+    ["gnt-instance", "replace-disks", "--submit", "--early-release", "-p",
+     instance.name])
+
+
+def TestParallelInstanceReboot(instance):
+  """PERFORMANCE: Instance reboot with parallel instance creation.
+
+  """
+  _TestInstanceOperationInParallelToInstanceCreation(
+    ["gnt-instance", "reboot", "--submit", instance.name])
+
+
+def TestParallelInstanceReinstall(instance):
+  """PERFORMANCE: Instance reinstall with parallel instance creation.
+
+  """
+  # instance reinstall requires the instance to be down
+  qa_utils.AssertCommand(["gnt-instance", "stop", instance.name])
+
+  _TestInstanceOperationInParallelToInstanceCreation(
+    ["gnt-instance", "reinstall", "--submit", "-f", instance.name])
+
+  qa_utils.AssertCommand(["gnt-instance", "start", instance.name])
+
+
+def TestParallelInstanceRename(instance):
+  """PERFORMANCE: Instance rename with parallel instance creation.
+
+  """
+  # instance rename requires the instance to be down
+  qa_utils.AssertCommand(["gnt-instance", "stop", instance.name])
+
+  new_instance = qa_config.AcquireInstance()
+  try:
+    _TestInstanceOperationInParallelToInstanceCreation(
+      ["gnt-instance", "rename", "--submit", instance.name, new_instance.name],
+      ["gnt-instance", "rename", "--submit", new_instance.name, instance.name])
+  finally:
+    new_instance.Release()
+
+  qa_utils.AssertCommand(["gnt-instance", "start", instance.name])
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 08bb659..c714cc3 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -1,44 +1,54 @@
 #
 #
 
-# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Remote API QA tests.
 
 """
 
-import tempfile
+import functools
+import itertools
 import random
 import re
-import itertools
-import functools
+import tempfile
 
-from ganeti import utils
+from ganeti import cli
+from ganeti import compat
 from ganeti import constants
 from ganeti import errors
-from ganeti import cli
-from ganeti import rapi
-from ganeti import objects
-from ganeti import query
-from ganeti import compat
-from ganeti import qlang
 from ganeti import pathutils
+from ganeti import objects
+from ganeti import opcodes
+from ganeti import query
+from ganeti import qlang
+from ganeti import rapi
+from ganeti import utils
 
 import ganeti.rapi.client        # pylint: disable=W0611
 import ganeti.rapi.client_utils
@@ -163,6 +173,74 @@
   return results
 
 
+# pylint: disable=W0212
+# Due to _SendRequest usage
+def _DoGetPutTests(get_uri, modify_uri, opcode_params, rapi_only_aliases=None,
+                   modify_method="PUT", exceptions=None, set_exceptions=None):
+  """ Test if all params of an object can be retrieved, and set as well.
+
+  @type get_uri: string
+  @param get_uri: The URI from which information about the object can be
+                  retrieved.
+  @type modify_uri: string
+  @param modify_uri: The URI which can be used to modify the object.
+  @type opcode_params: list of tuple
+  @param opcode_params: The parameters of the underlying opcode, used to
+                        determine which parameters are actually present.
+  @type rapi_only_aliases: list of string or None
+  @param rapi_only_aliases: Aliases for parameters which differ from the opcode,
+                            and become renamed before opcode submission.
+  @type modify_method: string
+  @param modify_method: The method to be used in the modification.
+  @type exceptions: list of string or None
+  @param exceptions: The parameters which have not been exposed and should not
+                     be tested at all.
+  @type set_exceptions: list of string or None
+  @param set_exceptions: The parameters whose setting should not be tested as a
+                         part of this test.
+
+  """
+
+  assert get_uri.startswith("/")
+  assert modify_uri.startswith("/")
+
+  if exceptions is None:
+    exceptions = []
+  if set_exceptions is None:
+    set_exceptions = []
+
+  print "Testing get/modify symmetry of %s and %s" % (get_uri, modify_uri)
+
+  # First we see if all parameters of the opcode are returned through RAPI
+  params_of_interest = map(lambda x: x[0], opcode_params)
+
+  # The RAPI-specific aliases are to be checked as well
+  if rapi_only_aliases is not None:
+    params_of_interest.extend(rapi_only_aliases)
+
+  info = _rapi_client._SendRequest("GET", get_uri, None, {})
+
+  missing_params = filter(lambda x: x not in info and x not in exceptions,
+                          params_of_interest)
+  if missing_params:
+    raise qa_error.Error("The parameters %s which can be set through the "
+                         "appropriate opcode are not present in the response "
+                         "from %s" % (','.join(missing_params), get_uri))
+
+  print "GET successful at %s" % get_uri
+
+  # Then if we can perform a set with the same values as received
+  put_payload = {}
+  for param in params_of_interest:
+    if param not in exceptions and param not in set_exceptions:
+      put_payload[param] = info[param]
+
+  _rapi_client._SendRequest(modify_method, modify_uri, None, put_payload)
+
+  print "%s successful at %s" % (modify_method, modify_uri)
+# pylint: enable=W0212
+
+
 def _VerifyReturnsJob(data):
   if not isinstance(data, int):
     AssertMatch(data, r"^\d+$")
@@ -244,6 +322,21 @@
     else:
       raise qa_error.Error("Non-implemented method didn't fail")
 
+  # Test GET/PUT symmetry
+  LEGITIMATELY_MISSING = [
+    "force",       # Standard option
+    "add_uids",    # Modifies UID pool, is not a param itself
+    "remove_uids", # Same as above
+  ]
+  NOT_EXPOSED_YET = ["hv_state", "disk_state", "modify_etc_hosts"]
+  # The nicparams are returned under the default entry, yet accepted as they
+  # are - this is a TODO to fix!
+  DEFAULT_ISSUES = ["nicparams"]
+
+  _DoGetPutTests("/2/info", "/2/modify", opcodes.OpClusterSetParams.OP_PARAMS,
+                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET),
+                 set_exceptions=DEFAULT_ISSUES)
+
 
 def TestRapiQuery():
   """Testing resource queries via remote API.
@@ -442,6 +535,20 @@
     ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
     ])
 
+  # Not parameters of the node, but controlling opcode behavior
+  LEGITIMATELY_MISSING = ["force", "powered"]
+  # Identifying the node - RAPI provides these itself
+  IDENTIFIERS = ["node_name", "node_uuid"]
+  # As the name states, these can be set but not retrieved yet
+  NOT_EXPOSED_YET = ["hv_state", "disk_state", "auto_promote"]
+
+  _DoGetPutTests("/2/nodes/%s" % node.primary,
+                 "/2/nodes/%s/modify" % node.primary,
+                 opcodes.OpNodeSetParams.OP_PARAMS,
+                 modify_method="POST",
+                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET +
+                             IDENTIFIERS))
+
 
 def _FilterTags(seq):
   """Removes unwanted tags from a sequence.
@@ -563,6 +670,27 @@
 
   _WaitForRapiJob(job_id)
 
+  # Test for get/set symmetry
+
+  # Identifying the node - RAPI provides these itself
+  IDENTIFIERS = ["group_name"]
+  # As the name states, not exposed yet
+  NOT_EXPOSED_YET = ["hv_state", "disk_state"]
+
+  # The parameters we do not want to get and set (as that sets the
+  # group-specific params to the filled ones)
+  FILLED_PARAMS = ["ndparams", "ipolicy", "diskparams"]
+
+  # The aliases that we can use to perform this test with the group-specific
+  # params
+  CUSTOM_PARAMS = ["custom_ndparams", "custom_ipolicy", "custom_diskparams"]
+
+  _DoGetPutTests("/2/groups/%s" % group3, "/2/groups/%s/modify" % group3,
+                 opcodes.OpGroupSetParams.OP_PARAMS,
+                 rapi_only_aliases=CUSTOM_PARAMS,
+                 exceptions=(IDENTIFIERS + NOT_EXPOSED_YET),
+                 set_exceptions=FILLED_PARAMS)
+
   # Delete groups
   for group in [group1, group3]:
     (job_id, ) = _DoTests([
diff --git a/qa/qa_tags.py b/qa/qa_tags.py
index 7480b16..75c82a1 100644
--- a/qa/qa_tags.py
+++ b/qa/qa_tags.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Tags related QA tests.
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 7cce741..c1a6d22 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Utilities for QA tests.
@@ -24,6 +33,7 @@
 """
 
 import copy
+import datetime
 import operator
 import os
 import random
@@ -140,19 +150,42 @@
                          (cmdstr, nodename, rcode))
 
 
-def AssertCommand(cmd, fail=False, node=None, log_cmd=True):
+def _PrintCommandOutput(stdout, stderr):
+  """Prints the output of commands, minimizing wasted space.
+
+  @type stdout: string
+  @type stderr: string
+
+  """
+  if stdout:
+    stdout_clean = stdout.rstrip('\n')
+    if stderr:
+      print "Stdout was:\n%s" % stdout_clean
+    else:
+      print stdout_clean
+
+  if stderr:
+    print "Stderr was:"
+    print >> sys.stderr, stderr.rstrip('\n')
+
+
+def AssertCommand(cmd, fail=False, node=None, log_cmd=True, max_seconds=None):
   """Checks that a remote command succeeds.
 
   @param cmd: either a string (the command to execute) or a list (to
       be converted using L{utils.ShellQuoteArgs} into a string)
-  @type fail: boolean
-  @param fail: if the command is expected to fail instead of succeeding
+  @type fail: boolean or None
+  @param fail: if the command is expected to fail instead of succeeding,
+               or None if we don't care
   @param node: if passed, it should be the node on which the command
       should be executed, instead of the master node (can be either a
       dict or a string)
   @param log_cmd: if False, the command won't be logged (simply passed to
       StartSSH)
-  @return: the return code of the command
+  @type max_seconds: double
+  @param max_seconds: fail if the command takes more than C{max_seconds}
+      seconds
+  @return: the return code, stdout and stderr of the command
   @raise qa_error.Error: if the command fails when it shouldn't or vice versa
 
   """
@@ -166,10 +199,27 @@
   else:
     cmdstr = utils.ShellQuoteArgs(cmd)
 
-  rcode = StartSSH(nodename, cmdstr, log_cmd=log_cmd).wait()
-  _AssertRetCode(rcode, fail, cmdstr, nodename)
+  start = datetime.datetime.now()
+  popen = StartSSH(nodename, cmdstr, log_cmd=log_cmd)
+  # Run the command
+  stdout, stderr = popen.communicate()
+  rcode = popen.returncode
+  duration_seconds = TimedeltaToTotalSeconds(datetime.datetime.now() - start)
 
-  return rcode
+  try:
+    if fail is not None:
+      _AssertRetCode(rcode, fail, cmdstr, nodename)
+  finally:
+    if log_cmd:
+      _PrintCommandOutput(stdout, stderr)
+
+  if max_seconds is not None:
+    if duration_seconds > max_seconds:
+      raise qa_error.Error(
+        "Cmd '%s' took %f seconds, maximum of %f was exceeded" %
+        (cmdstr, duration_seconds, max_seconds))
+
+  return rcode, stdout, stderr
 
 
 def AssertRedirectedCommand(cmd, fail=False, node=None, log_cmd=True):
@@ -271,7 +321,8 @@
 
   """
   return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
-                           _nolog_opts=True, log_cmd=log_cmd)
+                           _nolog_opts=True, log_cmd=log_cmd,
+                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 
 def StartMultiplexer(node):
@@ -528,10 +579,10 @@
                   fail=True)
 
   # Check exit code for listing unknown field
-  AssertEqual(AssertRedirectedCommand([cmd, "list",
-                                       "--output=field/does/not/exist"],
-                                      fail=True),
-              constants.EXIT_UNKNOWN_FIELD)
+  rcode, _, _ = AssertRedirectedCommand([cmd, "list",
+                                         "--output=field/does/not/exist"],
+                                        fail=True)
+  AssertEqual(rcode, constants.EXIT_UNKNOWN_FIELD)
 
 
 def GenericQueryFieldsTest(cmd, fields):
@@ -549,9 +600,9 @@
               utils.NiceSort(fields))
 
   # Check exit code for listing unknown field
-  AssertEqual(AssertCommand([cmd, "list-fields", "field/does/not/exist"],
-                            fail=True),
-              constants.EXIT_UNKNOWN_FIELD)
+  rcode, _, _ = AssertCommand([cmd, "list-fields", "field/does/not/exist"],
+                              fail=True)
+  AssertEqual(rcode, constants.EXIT_UNKNOWN_FIELD)
 
 
 def AddToEtcHosts(hostnames):
@@ -870,3 +921,19 @@
     else:
       ret_policy[key] = val
   return (ret_policy, ret_specs)
+
+
+def TimedeltaToTotalSeconds(td):
+  """Returns the total seconds in a C{datetime.timedelta} object.
+
+  This performs the same task as the C{datetime.timedelta.total_seconds()}
+  method which is present in Python 2.7 onwards.
+
+  @type td: datetime.timedelta
+  @param td: timedelta object to convert
+  @rtype float
+  @return: total seconds in the timedelta object
+
+  """
+  return ((td.microseconds + (td.seconds + td.days * 24.0 * 3600.0) * 10 ** 6) /
+          10 ** 6)
diff --git a/src/AutoConf.hs.in b/src/AutoConf.hs.in
new file mode 100644
index 0000000..dc0d082
--- /dev/null
+++ b/src/AutoConf.hs.in
@@ -0,0 +1,238 @@
+{-| Build-time configuration for Ganeti.
+
+Note that this file is autogenerated by the Makefile with a header
+from @AutoConf.hs.in@.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module AutoConf where
+
+split :: String -> [String]
+split str =
+  case span (/= ',') str of
+    (x, []) -> [x]
+    (x, _:xs) -> x:split xs
+
+packageVersion :: String
+packageVersion = "PACKAGE_VERSION"
+
+versionMajor :: Int
+versionMajor = VERSION_MAJOR
+
+versionMinor :: Int
+versionMinor = VERSION_MINOR
+
+versionRevision :: Int
+versionRevision = VERSION_REVISION
+
+versionSuffix :: String
+versionSuffix = "VERSION_SUFFIX"
+
+versionFull :: String
+versionFull = "VERSION_FULL"
+
+dirVersion :: String
+dirVersion = "DIRVERSION"
+
+localstatedir :: String
+localstatedir = "LOCALSTATEDIR"
+
+sysconfdir :: String
+sysconfdir = "SYSCONFDIR"
+
+sshConfigDir :: String
+sshConfigDir = "SSH_CONFIG_DIR"
+
+sshLoginUser :: String
+sshLoginUser = "SSH_LOGIN_USER"
+
+sshConsoleUser :: String
+sshConsoleUser = "SSH_CONSOLE_USER"
+
+exportDir :: String
+exportDir = "EXPORT_DIR"
+
+backupDir :: String
+backupDir = "BACKUP_DIR"
+
+osSearchPath :: [String]
+osSearchPath = split OS_SEARCH_PATH
+
+esSearchPath :: [String]
+esSearchPath = split ES_SEARCH_PATH
+
+xenBootloader :: String
+xenBootloader = "XEN_BOOTLOADER"
+
+xenConfigDir :: String
+xenConfigDir = "XEN_CONFIG_DIR"
+
+xenKernel :: String
+xenKernel = "XEN_KERNEL"
+
+xenInitrd :: String
+xenInitrd = "XEN_INITRD"
+
+kvmKernel :: String
+kvmKernel = "KVM_KERNEL"
+
+sharedFileStorageDir :: String
+sharedFileStorageDir = "SHARED_FILE_STORAGE_DIR"
+
+iallocatorSearchPath :: [String]
+iallocatorSearchPath = split IALLOCATOR_SEARCH_PATH
+
+kvmPath :: String
+kvmPath = "KVM_PATH"
+
+ipPath :: String
+ipPath = "IP_PATH"
+
+socatPath :: String
+socatPath = "SOCAT_PATH"
+
+socatUseEscape :: Bool
+socatUseEscape = SOCAT_USE_ESCAPE
+
+socatUseCompress :: Bool
+socatUseCompress = SOCAT_USE_COMPRESS
+
+lvmStripecount :: Int
+lvmStripecount = LVM_STRIPECOUNT
+
+toolsdir :: String
+toolsdir = "TOOLSDIR"
+
+gntScripts :: [String]
+gntScripts = GNT_SCRIPTS[]
+
+htoolsProgs :: [String]
+htoolsProgs = HS_HTOOLS_PROGS[]
+
+pkglibdir :: String
+pkglibdir = "PKGLIBDIR"
+
+sharedir :: String
+sharedir = "SHAREDIR"
+
+versionedsharedir :: String
+versionedsharedir = "VERSIONEDSHAREDIR"
+
+drbdBarriers :: String
+drbdBarriers = "DRBD_BARRIERS"
+
+drbdNoMetaFlush :: Bool
+drbdNoMetaFlush = DRBD_NO_META_FLUSH
+
+syslogUsage :: String
+syslogUsage = "SYSLOG_USAGE"
+
+daemonsGroup :: String
+daemonsGroup = "DAEMONS_GROUP"
+
+adminGroup :: String
+adminGroup = "ADMIN_GROUP"
+
+masterdUser :: String
+masterdUser = "MASTERD_USER"
+
+masterdGroup :: String
+masterdGroup = "MASTERD_GROUP"
+
+rapiUser :: String
+rapiUser = "RAPI_USER"
+
+rapiGroup :: String
+rapiGroup = "RAPI_GROUP"
+
+confdUser :: String
+confdUser = "CONFD_USER"
+
+confdGroup :: String
+confdGroup = "CONFD_GROUP"
+
+luxidUser :: String
+luxidUser = "LUXID_USER"
+
+luxidGroup :: String
+luxidGroup = "LUXID_GROUP"
+
+nodedUser :: String
+nodedUser = "NODED_USER"
+
+nodedGroup :: String
+nodedGroup = "NODED_GROUP"
+
+mondUser :: String
+mondUser = "MOND_USER"
+
+mondGroup :: String
+mondGroup = "MOND_GROUP"
+
+diskSeparator :: String
+diskSeparator = "DISK_SEPARATOR"
+
+qemuimgPath :: String
+qemuimgPath = "QEMUIMG_PATH"
+
+htools :: Bool
+htools = True
+
+enableConfd :: Bool
+enableConfd = ENABLE_CONFD
+
+xenCmd :: String
+xenCmd = "XEN_CMD"
+
+enableSplitQuery :: Bool
+enableSplitQuery = ENABLE_SPLIT_QUERY
+
+enableRestrictedCommands :: Bool
+enableRestrictedCommands = ENABLE_RESTRICTED_COMMANDS
+
+enableMond :: Bool
+enableMond = ENABLE_MOND
+
+hasGnuLn :: Bool
+hasGnuLn = HAS_GNU_LN
+
+-- Write dictionary with man page name as the key and the section
+-- number as the value
+manPages :: [(String, Int)]
+manPages = MAN_PAGES[]
+
+pyAfInet4 :: Int
+pyAfInet4 = AF_INET4
+
+pyAfInet6 :: Int
+pyAfInet6 = AF_INET6
diff --git a/src/Ganeti/BasicTypes.hs b/src/Ganeti/BasicTypes.hs
index 080c6a9..aee67ad 100644
--- a/src/Ganeti/BasicTypes.hs
+++ b/src/Ganeti/BasicTypes.hs
@@ -3,21 +3,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -34,6 +43,7 @@
   , annotateResult
   , iterateOk
   , select
+  , runListHead
   , LookupResult(..)
   , MatchPriority(..)
   , lookupName
@@ -41,6 +51,8 @@
   , goodMatchPriority
   , prefixMatch
   , compareNameComponent
+  , ListSet(..)
+  , emptyListSet
   ) where
 
 import Control.Applicative
@@ -48,6 +60,11 @@
 import Control.Monad.Trans
 import Data.Function
 import Data.List
+import Data.Maybe (listToMaybe)
+import Data.Set (Set)
+import qualified Data.Set as Set (empty)
+import Text.JSON (JSON)
+import qualified Text.JSON as JSON (readJSON, showJSON)
 
 -- | Generic monad for our error handling mechanisms.
 data GenericResult a b
@@ -151,6 +168,12 @@
        -> a            -- ^ first result which has a True condition, or default
 select def = maybe def snd . find fst
 
+-- | Apply a function to the first element of a list, return the default
+-- value, if the list is empty. This is just a convenient combination of
+-- maybe and listToMaybe.
+runListHead :: a -> (b -> a) -> [b] -> a
+runListHead a f = maybe a f . listToMaybe
+
 -- * Lookup of partial names functionality
 
 -- | The priority of a match in a lookup result.
@@ -224,3 +247,18 @@
            -> LookupResult  -- ^ Result of the lookup
 lookupName l s = foldr (chooseLookupResult s)
                        (LookupResult FailMatch s) l
+
+-- | Wrapper for a Haskell 'Set'
+--
+-- This type wraps a 'Set' and it is used in the Haskell to Python
+-- opcode generation to transform a Haskell 'Set' into a Python 'list'
+-- without duplicate elements.
+newtype ListSet a = ListSet { unListSet :: Set a }
+  deriving (Eq, Show)
+
+instance (Ord a, JSON a) => JSON (ListSet a) where
+  showJSON = JSON.showJSON . unListSet
+  readJSON = liftM ListSet . JSON.readJSON
+
+emptyListSet :: ListSet a
+emptyListSet = ListSet Set.empty
diff --git a/src/Ganeti/Common.hs b/src/Ganeti/Common.hs
index b907ead..cf70b31 100644
--- a/src/Ganeti/Common.hs
+++ b/src/Ganeti/Common.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Compat.hs b/src/Ganeti/Compat.hs
index 1ba2f24..71e97ad 100644
--- a/src/Ganeti/Compat.hs
+++ b/src/Ganeti/Compat.hs
@@ -10,21 +10,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Confd/Client.hs b/src/Ganeti/Confd/Client.hs
index 02da747..49ab5fd 100644
--- a/src/Ganeti/Confd/Client.hs
+++ b/src/Ganeti/Confd/Client.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Confd/ClientFunctions.hs b/src/Ganeti/Confd/ClientFunctions.hs
index 03f8e43..75a02a1 100644
--- a/src/Ganeti/Confd/ClientFunctions.hs
+++ b/src/Ganeti/Confd/ClientFunctions.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Confd/Server.hs b/src/Ganeti/Confd/Server.hs
index 05275c4..3e673e5 100644
--- a/src/Ganeti/Confd/Server.hs
+++ b/src/Ganeti/Confd/Server.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -259,7 +268,7 @@
       innermsg = serializeResponse (cfg >>= flip buildResponse rq)
       innerserialised = J.encodeStrict innermsg
       outermsg = signMessage hmac rsalt innerserialised
-      outerserialised = confdMagicFourcc ++ J.encodeStrict outermsg
+      outerserialised = C.confdMagicFourcc ++ J.encodeStrict outermsg
   in outerserialised
 
 -- | Main listener loop.
@@ -268,7 +277,7 @@
          -> IO ()
 listener s hmac resp = do
   (msg, _, peer) <- S.recvFrom s 4096
-  if confdMagicFourcc `isPrefixOf` msg
+  if C.confdMagicFourcc `isPrefixOf` msg
     then forkIO (resp s hmac (drop 4 msg) peer) >> return ()
     else logDebug "Invalid magic code!" >> return ()
   return ()
diff --git a/src/Ganeti/Confd/Types.hs b/src/Ganeti/Confd/Types.hs
index f503c3d..523b12e 100644
--- a/src/Ganeti/Confd/Types.hs
+++ b/src/Ganeti/Confd/Types.hs
@@ -7,40 +7,46 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.Confd.Types
-  ( C.confdProtocolVersion
-  , C.confdMaxClockSkew
-  , C.confdConfigReloadTimeout
-  , C.confdConfigReloadRatelimit
-  , C.confdMagicFourcc
-  , C.confdDefaultReqCoverage
-  , C.confdClientExpireTimeout
-  , C.maxUdpDataSize
-  , ConfdClient(..)
+  ( ConfdClient(..)
   , ConfdRequestType(..)
-  , ConfdReqQ(..)
+  , confdRequestTypeToRaw
   , ConfdReqField(..)
+  , confdReqFieldToRaw
+  , ConfdReqQ(..)
   , ConfdReplyStatus(..)
+  , confdReplyStatusToRaw
   , ConfdNodeRole(..)
+  , confdNodeRoleToRaw
   , ConfdErrorType(..)
+  , confdErrorTypeToRaw
   , ConfdRequest(..)
   , newConfdRequest
   , ConfdReply(..)
@@ -51,41 +57,28 @@
 import Text.JSON
 import qualified Network.Socket as S
 
-import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import Ganeti.Hash
 import Ganeti.THH
 import Ganeti.Utils (newUUID)
 
-{-
-   Note that we re-export as is from Constants the following simple items:
-   - confdProtocolVersion
-   - confdMaxClockSkew
-   - confdConfigReloadTimeout
-   - confdConfigReloadRatelimit
-   - confdMagicFourcc
-   - confdDefaultReqCoverage
-   - confdClientExpireTimeout
-   - maxUdpDataSize
-
--}
-
-$(declareIADT "ConfdRequestType"
-  [ ("ReqPing",             'C.confdReqPing )
-  , ("ReqNodeRoleByName",   'C.confdReqNodeRoleByname )
-  , ("ReqNodePipList",      'C.confdReqNodePipList )
-  , ("ReqNodePipByInstPip", 'C.confdReqNodePipByInstanceIp )
-  , ("ReqClusterMaster",    'C.confdReqClusterMaster )
-  , ("ReqMcPipList",        'C.confdReqMcPipList )
-  , ("ReqInstIpsList",      'C.confdReqInstancesIpsList )
-  , ("ReqNodeDrbd",         'C.confdReqNodeDrbd )
-  , ("ReqNodeInstances",    'C.confdReqNodeInstances)
+$(declareILADT "ConfdRequestType"
+  [ ("ReqPing",             0)
+  , ("ReqNodeRoleByName",   1)
+  , ("ReqNodePipByInstPip", 2)
+  , ("ReqClusterMaster",    3)
+  , ("ReqNodePipList",      4)
+  , ("ReqMcPipList",        5)
+  , ("ReqInstIpsList",      6)
+  , ("ReqNodeDrbd",         7)
+  , ("ReqNodeInstances",    8)
   ])
 $(makeJSONInstance ''ConfdRequestType)
 
-$(declareSADT "ConfdReqField"
-  [ ("ReqFieldName",     'C.confdReqfieldName )
-  , ("ReqFieldIp",       'C.confdReqfieldIp )
-  , ("ReqFieldMNodePip", 'C.confdReqfieldMnodePip )
+$(declareILADT "ConfdReqField"
+  [ ("ReqFieldName",     0)
+  , ("ReqFieldIp",       1)
+  , ("ReqFieldMNodePip", 2)
   ])
 $(makeJSONInstance ''ConfdReqField)
 
@@ -95,14 +88,17 @@
 
 $(buildObject "ConfdReqQ" "confdReqQ"
   [ renameField "Ip" .
-                optionalField $ simpleField C.confdReqqIp [t| String   |]
+    optionalField $
+    simpleField ConstantUtils.confdReqqIp [t| String |]
   , renameField "IpList" .
-                defaultField [| [] |] $
-                simpleField C.confdReqqIplist [t| [String] |]
-  , renameField "Link" . optionalField $
-                simpleField C.confdReqqLink [t| String   |]
-  , renameField "Fields" . defaultField [| [] |] $
-                simpleField C.confdReqqFields [t| [ConfdReqField] |]
+    defaultField [| [] |] $
+    simpleField ConstantUtils.confdReqqIplist [t| [String] |]
+  , renameField "Link" .
+    optionalField $
+    simpleField ConstantUtils.confdReqqLink [t| String |]
+  , renameField "Fields" .
+    defaultField [| [] |] $
+    simpleField ConstantUtils.confdReqqFields [t| [ConfdReqField] |]
   ])
 
 -- | Confd query type. This is complex enough that we can't
@@ -124,30 +120,29 @@
                   PlainQuery s -> showJSON s
                   DictQuery drq -> showJSON drq
 
-$(declareIADT "ConfdReplyStatus"
-  [ ( "ReplyStatusOk",      'C.confdReplStatusOk )
-  , ( "ReplyStatusError",   'C.confdReplStatusError )
-  , ( "ReplyStatusNotImpl", 'C.confdReplStatusNotimplemented )
+$(declareILADT "ConfdReplyStatus"
+  [ ("ReplyStatusOk",      0)
+  , ("ReplyStatusError",   1)
+  , ("ReplyStatusNotImpl", 2)
   ])
 $(makeJSONInstance ''ConfdReplyStatus)
 
-$(declareIADT "ConfdNodeRole"
-  [ ( "NodeRoleMaster",    'C.confdNodeRoleMaster )
-  , ( "NodeRoleCandidate", 'C.confdNodeRoleCandidate )
-  , ( "NodeRoleOffline",   'C.confdNodeRoleOffline )
-  , ( "NodeRoleDrained",   'C.confdNodeRoleDrained )
-  , ( "NodeRoleRegular",   'C.confdNodeRoleRegular )
+$(declareILADT "ConfdNodeRole"
+  [ ("NodeRoleMaster",    0)
+  , ("NodeRoleCandidate", 1)
+  , ("NodeRoleOffline",   2)
+  , ("NodeRoleDrained",   3)
+  , ("NodeRoleRegular",   4)
   ])
 $(makeJSONInstance ''ConfdNodeRole)
 
-
 -- Note that the next item is not a frozenset in Python, but we make
 -- it a separate type for safety
 
-$(declareIADT "ConfdErrorType"
-  [ ( "ConfdErrorUnknownEntry", 'C.confdErrorUnknownEntry )
-  , ( "ConfdErrorInternal",     'C.confdErrorInternal )
-  , ( "ConfdErrorArgument",     'C.confdErrorArgument )
+$(declareILADT "ConfdErrorType"
+  [ ("ConfdErrorUnknownEntry", 0)
+  , ("ConfdErrorInternal",     1)
+  , ("ConfdErrorArgument",     2)
   ])
 $(makeJSONInstance ''ConfdErrorType)
 
@@ -163,7 +158,7 @@
 newConfdRequest :: ConfdRequestType -> ConfdQuery -> IO ConfdRequest
 newConfdRequest reqType query = do
   rsalt <- newUUID
-  return $ ConfdRequest C.confdProtocolVersion reqType query rsalt
+  return $ ConfdRequest ConstantUtils.confdProtocolVersion reqType query rsalt
 
 $(buildObject "ConfdReply" "confdReply"
   [ simpleField "protocol" [t| Int              |]
diff --git a/src/Ganeti/Confd/Utils.hs b/src/Ganeti/Confd/Utils.hs
index 41454ce..1fdd742 100644
--- a/src/Ganeti/Confd/Utils.hs
+++ b/src/Ganeti/Confd/Utils.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Config.hs b/src/Ganeti/Config.hs
index fb0e2c6..b278663 100644
--- a/src/Ganeti/Config.hs
+++ b/src/Ganeti/Config.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/ConfigReader.hs b/src/Ganeti/ConfigReader.hs
index b40c6dc..047ac41 100644
--- a/src/Ganeti/ConfigReader.hs
+++ b/src/Ganeti/ConfigReader.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -123,7 +132,8 @@
   newcfg <- loadConfig path
   let !newdata = case newcfg of
                    Ok !cfg -> Ok cfg
-                   Bad _ -> Bad "Cannot load configuration"
+                   Bad msg -> Bad $ "Cannot load configuration from " ++ path
+                                    ++ ": " ++ msg
   save_fn newdata
   case newcfg of
     Ok cfg -> logInfo ("Loaded new config, serial " ++
@@ -329,4 +339,4 @@
   -- fork the polling timer
   unless has_inotify $ do
     _ <- forkIO $ onPollTimer inotiaction conf_file save_fn statemvar
-    return ()
\ No newline at end of file
+    return ()
diff --git a/src/Ganeti/ConstantUtils.hs b/src/Ganeti/ConstantUtils.hs
new file mode 100644
index 0000000..b12d8a3
--- /dev/null
+++ b/src/Ganeti/ConstantUtils.hs
@@ -0,0 +1,207 @@
+{-| ConstantUtils contains the helper functions for constants
+
+This module cannot be merged with 'Ganeti.Utils' because it would
+create a circular dependency if imported, for example, from
+'Ganeti.Constants'.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+module Ganeti.ConstantUtils where
+
+import Data.Char (ord)
+import Data.Set (Set)
+import qualified Data.Set as Set (difference, fromList, toList, union)
+
+import Ganeti.THH (PyValue(..))
+import Ganeti.PyValueInstances ()
+
+-- | 'PythonChar' wraps a Python 'char'
+newtype PythonChar = PythonChar { unPythonChar :: Char }
+  deriving (Show)
+
+instance PyValue PythonChar where
+  showValue c = "chr(" ++ show (ord (unPythonChar c)) ++ ")"
+
+-- | 'PythonNone' wraps Python 'None'
+data PythonNone = PythonNone
+
+instance PyValue PythonNone where
+  showValue _ = "None"
+
+-- | FrozenSet wraps a Haskell 'Set'
+--
+-- See 'PyValue' instance for 'FrozenSet'.
+newtype FrozenSet a = FrozenSet { unFrozenSet :: Set a }
+  deriving (Eq, Ord, Show)
+
+-- | Converts a Haskell 'Set' into a Python 'frozenset'
+--
+-- This instance was supposed to be for 'Set' instead of 'FrozenSet'.
+-- However, 'ghc-6.12.1' seems to be crashing with 'segmentation
+-- fault' due to the presence of more than one instance of 'Set',
+-- namely, this one and the one in 'Ganeti.OpCodes'.  For this reason,
+-- we wrap 'Set' into 'FrozenSet'.
+instance PyValue a => PyValue (FrozenSet a) where
+  showValue s = "frozenset(" ++ showValue (Set.toList (unFrozenSet s)) ++ ")"
+
+mkSet :: Ord a => [a] -> FrozenSet a
+mkSet = FrozenSet . Set.fromList
+
+toList :: FrozenSet a -> [a]
+toList = Set.toList . unFrozenSet
+
+union :: Ord a => FrozenSet a -> FrozenSet a -> FrozenSet a
+union x y = FrozenSet (unFrozenSet x `Set.union` unFrozenSet y)
+
+difference :: Ord a => FrozenSet a -> FrozenSet a -> FrozenSet a
+difference x y = FrozenSet (unFrozenSet x `Set.difference` unFrozenSet y)
+
+-- | 'Protocol' represents the protocols used by the daemons
+data Protocol = Tcp | Udp
+  deriving (Show)
+
+-- | 'PyValue' instance of 'Protocol'
+--
+-- This instance is used by the Haskell to Python constants
+instance PyValue Protocol where
+  showValue Tcp = "\"tcp\""
+  showValue Udp = "\"udp\""
+
+-- | Failure exit code
+--
+-- These are defined here and not in 'Ganeti.Constants' together with
+-- the other exit codes in order to avoid a circular dependency
+-- between 'Ganeti.Constants' and 'Ganeti.Runtime'
+exitFailure :: Int
+exitFailure = 1
+
+-- | Console device
+--
+-- This is defined here and not in 'Ganeti.Constants' order to avoid a
+-- circular dependency between 'Ganeti.Constants' and 'Ganeti.Logging'
+devConsole :: String
+devConsole = "/dev/console"
+
+-- | Random uuid generator
+--
+-- This is defined here and not in 'Ganeti.Constants' order to avoid a
+-- circular dependendy between 'Ganeti.Constants' and 'Ganeti.Types'
+randomUuidFile :: String
+randomUuidFile = "/proc/sys/kernel/random/uuid"
+
+-- * Priority levels
+--
+-- This is defined here and not in 'Ganeti.Types' in order to avoid a
+-- GHC stage restriction and because there is no suitable 'declareADT'
+-- variant that handles integer values directly.
+
+priorityLow :: Int
+priorityLow = 10
+
+priorityNormal :: Int
+priorityNormal = 0
+
+priorityHigh :: Int
+priorityHigh = -10
+
+-- | Calculates int version number from major, minor and revision
+-- numbers.
+buildVersion :: Int -> Int -> Int -> Int
+buildVersion major minor revision =
+  1000000 * major + 10000 * minor + 1 * revision
+
+-- | Confd protocol version
+--
+-- This is defined here in order to avoid a circular dependency
+-- between 'Ganeti.Confd.Types' and 'Ganeti.Constants'.
+confdProtocolVersion :: Int
+confdProtocolVersion = 1
+
+-- * Confd request query fields
+--
+-- These are defined here and not in 'Ganeti.Types' due to GHC stage
+-- restrictions concerning Template Haskell.  They are also not
+-- defined in 'Ganeti.Constants' in order to avoid a circular
+-- dependency between that module and 'Ganeti.Types'.
+
+confdReqqLink :: String
+confdReqqLink = "0"
+
+confdReqqIp :: String
+confdReqqIp = "1"
+
+confdReqqIplist :: String
+confdReqqIplist = "2"
+
+confdReqqFields :: String
+confdReqqFields = "3"
+
+-- * ISpec
+
+ispecMemSize :: String
+ispecMemSize = "memory-size"
+
+ispecCpuCount :: String
+ispecCpuCount = "cpu-count"
+
+ispecDiskCount :: String
+ispecDiskCount = "disk-count"
+
+ispecDiskSize :: String
+ispecDiskSize = "disk-size"
+
+ispecNicCount :: String
+ispecNicCount = "nic-count"
+
+ispecSpindleUse :: String
+ispecSpindleUse = "spindle-use"
+
+ispecsMinmax :: String
+ispecsMinmax = "minmax"
+
+ispecsStd :: String
+ispecsStd = "std"
+
+ipolicyDts :: String
+ipolicyDts = "disk-templates"
+
+ipolicyVcpuRatio :: String
+ipolicyVcpuRatio = "vcpu-ratio"
+
+ipolicySpindleRatio :: String
+ipolicySpindleRatio = "spindle-ratio"
+
+ipolicyDefaultsVcpuRatio :: Double
+ipolicyDefaultsVcpuRatio = 4.0
+
+ipolicyDefaultsSpindleRatio :: Double
+ipolicyDefaultsSpindleRatio = 32.0
diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs
new file mode 100644
index 0000000..d86f765
--- /dev/null
+++ b/src/Ganeti/Constants.hs
@@ -0,0 +1,4585 @@
+{-# OPTIONS -fno-warn-type-defaults #-}
+{-| Constants contains the Haskell constants
+
+The constants in this module are used in Haskell and are also
+converted to Python.
+
+Do not write any definitions in this file other than constants.  Do
+not even write helper functions.  The definitions in this module are
+automatically stripped to build the Makefile.am target
+'ListConstants.hs'.  If there are helper functions in this module,
+they will also be dragged and it will cause compilation to fail.
+Therefore, all helper functions should go to a separate module and
+imported.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+module Ganeti.Constants where
+
+import Control.Arrow ((***))
+import Data.List ((\\))
+import Data.Map (Map)
+import qualified Data.Map as Map (empty, fromList, keys, insert)
+
+import qualified AutoConf
+import Ganeti.ConstantUtils (PythonChar(..), FrozenSet, Protocol(..),
+                             buildVersion)
+import qualified Ganeti.ConstantUtils as ConstantUtils
+import Ganeti.HTools.Types (AutoRepairResult(..), AutoRepairType(..))
+import qualified Ganeti.HTools.Types as Types
+import Ganeti.Logging (SyslogUsage(..))
+import qualified Ganeti.Logging as Logging (syslogUsageToRaw)
+import qualified Ganeti.Runtime as Runtime
+import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..),
+                       ExtraLogReason(..))
+import Ganeti.THH (PyValueEx(..))
+import Ganeti.Types
+import qualified Ganeti.Types as Types
+import Ganeti.Confd.Types (ConfdRequestType(..), ConfdReqField(..),
+                           ConfdReplyStatus(..), ConfdNodeRole(..),
+                           ConfdErrorType(..))
+import qualified Ganeti.Confd.Types as Types
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
+
+-- * 'autoconf' constants for Python only ('autotools/build-bash-completion')
+
+htoolsProgs :: [String]
+htoolsProgs = AutoConf.htoolsProgs
+
+-- * 'autoconf' constants for Python only ('lib/constants.py')
+
+drbdBarriers :: String
+drbdBarriers = AutoConf.drbdBarriers
+
+drbdNoMetaFlush :: Bool
+drbdNoMetaFlush = AutoConf.drbdNoMetaFlush
+
+lvmStripecount :: Int
+lvmStripecount = AutoConf.lvmStripecount
+
+hasGnuLn :: Bool
+hasGnuLn = AutoConf.hasGnuLn
+
+-- * 'autoconf' constants for Python only ('lib/pathutils.py')
+
+-- ** Build-time constants
+
+exportDir :: String
+exportDir = AutoConf.exportDir
+
+backupDir :: String
+backupDir = AutoConf.backupDir
+
+osSearchPath :: [String]
+osSearchPath = AutoConf.osSearchPath
+
+esSearchPath :: [String]
+esSearchPath = AutoConf.esSearchPath
+
+sshConfigDir :: String
+sshConfigDir = AutoConf.sshConfigDir
+
+xenConfigDir :: String
+xenConfigDir = AutoConf.xenConfigDir
+
+sysconfdir :: String
+sysconfdir = AutoConf.sysconfdir
+
+toolsdir :: String
+toolsdir = AutoConf.toolsdir
+
+localstatedir :: String
+localstatedir = AutoConf.localstatedir
+
+-- ** Paths which don't change for a virtual cluster
+
+pkglibdir :: String
+pkglibdir = AutoConf.pkglibdir
+
+sharedir :: String
+sharedir = AutoConf.sharedir
+
+-- * 'autoconf' constants for Python only ('lib/build/sphinx_ext.py')
+
+manPages :: Map String Int
+manPages = Map.fromList AutoConf.manPages
+
+-- * 'autoconf' constants for QA cluster only ('qa/qa_cluster.py')
+
+versionedsharedir :: String
+versionedsharedir = AutoConf.versionedsharedir
+
+-- * 'autoconf' constants for Python only ('tests/py/docs_unittest.py')
+
+gntScripts :: [String]
+gntScripts = AutoConf.gntScripts
+
+-- * Various versions
+
+releaseVersion :: String
+releaseVersion = AutoConf.packageVersion
+
+versionMajor :: Int
+versionMajor = AutoConf.versionMajor
+
+versionMinor :: Int
+versionMinor = AutoConf.versionMinor
+
+versionRevision :: Int
+versionRevision = AutoConf.versionRevision
+
+dirVersion :: String
+dirVersion = AutoConf.dirVersion
+
+osApiV10 :: Int
+osApiV10 = 10
+
+osApiV15 :: Int
+osApiV15 = 15
+
+osApiV20 :: Int
+osApiV20 = 20
+
+osApiVersions :: FrozenSet Int
+osApiVersions = ConstantUtils.mkSet [osApiV10, osApiV15, osApiV20]
+
+exportVersion :: Int
+exportVersion = 0
+
+rapiVersion :: Int
+rapiVersion = 2
+
+configMajor :: Int
+configMajor = AutoConf.versionMajor
+
+configMinor :: Int
+configMinor = AutoConf.versionMinor
+
+-- | The configuration is supposed to remain stable across
+-- revisions. Therefore, the revision number is cleared to '0'.
+configRevision :: Int
+configRevision = 0
+
+configVersion :: Int
+configVersion = buildVersion configMajor configMinor configRevision
+
+-- | Similarly to the configuration (see 'configRevision'), the
+-- protocols are supposed to remain stable across revisions.
+protocolVersion :: Int
+protocolVersion = buildVersion configMajor configMinor configRevision
+
+-- * User separation
+
+daemonsGroup :: String
+daemonsGroup = Runtime.daemonGroup (ExtraGroup DaemonsGroup)
+
+adminGroup :: String
+adminGroup = Runtime.daemonGroup (ExtraGroup AdminGroup)
+
+masterdUser :: String
+masterdUser = Runtime.daemonUser GanetiMasterd
+
+masterdGroup :: String
+masterdGroup = Runtime.daemonGroup (DaemonGroup GanetiMasterd)
+
+rapiUser :: String
+rapiUser = Runtime.daemonUser GanetiRapi
+
+rapiGroup :: String
+rapiGroup = Runtime.daemonGroup (DaemonGroup GanetiRapi)
+
+confdUser :: String
+confdUser = Runtime.daemonUser GanetiConfd
+
+confdGroup :: String
+confdGroup = Runtime.daemonGroup (DaemonGroup GanetiConfd)
+
+luxidUser :: String
+luxidUser = Runtime.daemonUser GanetiLuxid
+
+luxidGroup :: String
+luxidGroup = Runtime.daemonGroup (DaemonGroup GanetiLuxid)
+
+nodedUser :: String
+nodedUser = Runtime.daemonUser GanetiNoded
+
+nodedGroup :: String
+nodedGroup = Runtime.daemonGroup (DaemonGroup GanetiNoded)
+
+mondUser :: String
+mondUser = Runtime.daemonUser GanetiMond
+
+mondGroup :: String
+mondGroup = Runtime.daemonGroup (DaemonGroup GanetiMond)
+
+sshLoginUser :: String
+sshLoginUser = AutoConf.sshLoginUser
+
+sshConsoleUser :: String
+sshConsoleUser = AutoConf.sshConsoleUser
+
+-- * Cpu pinning separators and constants
+
+cpuPinningSep :: String
+cpuPinningSep = ":"
+
+cpuPinningAll :: String
+cpuPinningAll = "all"
+
+-- | Internal representation of "all"
+cpuPinningAllVal :: Int
+cpuPinningAllVal = -1
+
+-- | One "all" entry in a CPU list means CPU pinning is off
+cpuPinningOff :: [Int]
+cpuPinningOff = [cpuPinningAllVal]
+
+-- | A Xen-specific implementation detail is that there is no way to
+-- actually say "use any cpu for pinning" in a Xen configuration file,
+-- as opposed to the command line, where you can say
+-- @
+-- xm vcpu-pin <domain> <vcpu> all
+-- @
+--
+-- The workaround used in Xen is "0-63" (see source code function
+-- "xm_vcpu_pin" in @<xen-source>/tools/python/xen/xm/main.py@).
+--
+-- To support future changes, the following constant is treated as a
+-- blackbox string that simply means "use any cpu for pinning under
+-- xen".
+cpuPinningAllXen :: String
+cpuPinningAllXen = "0-63"
+
+-- | A KVM-specific implementation detail - the following value is
+-- used to set CPU affinity to all processors (--0 through --31), per
+-- taskset man page.
+--
+-- FIXME: This only works for machines with up to 32 CPU cores
+cpuPinningAllKvm :: Int
+cpuPinningAllKvm = 0xFFFFFFFF
+
+-- * Wipe
+
+ddCmd :: String
+ddCmd = "dd"
+
+-- | 1GB
+maxWipeChunk :: Int
+maxWipeChunk = 1024
+
+minWipeChunkPercent :: Int
+minWipeChunkPercent = 10
+
+-- * Directories
+
+runDirsMode :: Int
+runDirsMode = 0o775
+
+secureDirMode :: Int
+secureDirMode = 0o700
+
+secureFileMode :: Int
+secureFileMode = 0o600
+
+adoptableBlockdevRoot :: String
+adoptableBlockdevRoot = "/dev/disk/"
+
+-- * 'autoconf' enable/disable
+
+enableConfd :: Bool
+enableConfd = AutoConf.enableConfd
+
+enableMond :: Bool
+enableMond = AutoConf.enableMond
+
+enableRestrictedCommands :: Bool
+enableRestrictedCommands = AutoConf.enableRestrictedCommands
+
+enableSplitQuery :: Bool
+enableSplitQuery = AutoConf.enableSplitQuery
+
+-- * SSH constants
+
+ssh :: String
+ssh = "ssh"
+
+scp :: String
+scp = "scp"
+
+-- * Daemons
+
+confd :: String
+confd = Runtime.daemonName GanetiConfd
+
+masterd :: String
+masterd = Runtime.daemonName GanetiMasterd
+
+mond :: String
+mond = Runtime.daemonName GanetiMond
+
+noded :: String
+noded = Runtime.daemonName GanetiNoded
+
+luxid :: String
+luxid = Runtime.daemonName GanetiLuxid
+
+rapi :: String
+rapi = Runtime.daemonName GanetiRapi
+
+daemons :: FrozenSet String
+daemons =
+  ConstantUtils.mkSet [confd,
+                       luxid,
+                       masterd,
+                       mond,
+                       noded,
+                       rapi]
+
+defaultConfdPort :: Int
+defaultConfdPort = 1814
+
+defaultMondPort :: Int
+defaultMondPort = 1815
+
+defaultNodedPort :: Int
+defaultNodedPort = 1811
+
+defaultRapiPort :: Int
+defaultRapiPort = 5080
+
+daemonsPorts :: Map String (Protocol, Int)
+daemonsPorts =
+  Map.fromList [(confd, (Udp, defaultConfdPort)),
+                (mond, (Tcp, defaultMondPort)),
+                (noded, (Tcp, defaultNodedPort)),
+                (rapi, (Tcp, defaultRapiPort)),
+                (ssh, (Tcp, 22))]
+
+firstDrbdPort :: Int
+firstDrbdPort = 11000
+
+lastDrbdPort :: Int
+lastDrbdPort = 14999
+
+daemonsLogbase :: Map String String
+daemonsLogbase =
+  Map.fromList
+  [ (Runtime.daemonName d, Runtime.daemonLogBase d) | d <- [minBound..] ]
+
+daemonsExtraLogbase :: Map String (Map String String)
+daemonsExtraLogbase =
+  Map.fromList $
+  map (Runtime.daemonName *** id)
+  [ (GanetiMond, Map.fromList
+                 [ ("access", Runtime.daemonsExtraLogbase GanetiMond AccessLog)
+                 , ("error", Runtime.daemonsExtraLogbase GanetiMond ErrorLog)
+                 ])
+  ]
+
+extraLogreasonAccess :: String
+extraLogreasonAccess = Runtime.daemonsExtraLogbase GanetiMond AccessLog
+
+extraLogreasonError :: String
+extraLogreasonError = Runtime.daemonsExtraLogbase GanetiMond ErrorLog
+
+devConsole :: String
+devConsole = ConstantUtils.devConsole
+
+procMounts :: String
+procMounts = "/proc/mounts"
+
+-- * Luxi (Local UniX Interface) related constants
+
+luxiEom :: PythonChar
+luxiEom = PythonChar '\x03'
+
+-- | Environment variable for the luxi override socket
+luxiOverride :: String
+luxiOverride = "FORCE_LUXI_SOCKET"
+
+luxiOverrideMaster :: String
+luxiOverrideMaster = "master"
+
+luxiOverrideQuery :: String
+luxiOverrideQuery = "query"
+
+luxiVersion :: Int
+luxiVersion = configVersion
+
+-- * Syslog
+
+syslogUsage :: String
+syslogUsage = AutoConf.syslogUsage
+
+syslogNo :: String
+syslogNo = Logging.syslogUsageToRaw SyslogNo
+
+syslogYes :: String
+syslogYes = Logging.syslogUsageToRaw SyslogYes
+
+syslogOnly :: String
+syslogOnly = Logging.syslogUsageToRaw SyslogOnly
+
+syslogSocket :: String
+syslogSocket = "/dev/log"
+
+exportConfFile :: String
+exportConfFile = "config.ini"
+
+-- * Xen
+
+xenBootloader :: String
+xenBootloader = AutoConf.xenBootloader
+
+xenCmdXl :: String
+xenCmdXl = "xl"
+
+xenCmdXm :: String
+xenCmdXm = "xm"
+
+xenInitrd :: String
+xenInitrd = AutoConf.xenInitrd
+
+xenKernel :: String
+xenKernel = AutoConf.xenKernel
+
+-- FIXME: perhaps rename to 'validXenCommands' for consistency with
+-- other constants
+knownXenCommands :: FrozenSet String
+knownXenCommands = ConstantUtils.mkSet [xenCmdXl, xenCmdXm]
+
+-- * KVM and socat
+
+kvmPath :: String
+kvmPath = AutoConf.kvmPath
+
+kvmKernel :: String
+kvmKernel = AutoConf.kvmKernel
+
+socatEscapeCode :: String
+socatEscapeCode = "0x1d"
+
+socatPath :: String
+socatPath = AutoConf.socatPath
+
+socatUseCompress :: Bool
+socatUseCompress = AutoConf.socatUseCompress
+
+socatUseEscape :: Bool
+socatUseEscape = AutoConf.socatUseEscape
+
+-- * Console types
+
+-- | Display a message for console access
+consMessage :: String
+consMessage = "msg"
+
+-- | Console as SPICE server
+consSpice :: String
+consSpice = "spice"
+
+-- | Console as SSH command
+consSsh :: String
+consSsh = "ssh"
+
+-- | Console as VNC server
+consVnc :: String
+consVnc = "vnc"
+
+consAll :: FrozenSet String
+consAll = ConstantUtils.mkSet [consMessage, consSpice, consSsh, consVnc]
+
+-- | RSA key bit length
+--
+-- For RSA keys more bits are better, but they also make operations
+-- more expensive. NIST SP 800-131 recommends a minimum of 2048 bits
+-- from the year 2010 on.
+rsaKeyBits :: Int
+rsaKeyBits = 2048
+
+-- | Ciphers allowed for SSL connections.
+--
+-- For the format, see ciphers(1). A better way to disable ciphers
+-- would be to use the exclamation mark (!), but socat versions below
+-- 1.5 can't parse exclamation marks in options properly. When
+-- modifying the ciphers, ensure not to accidentially add something
+-- after it's been removed. Use the "openssl" utility to check the
+-- allowed ciphers, e.g.  "openssl ciphers -v HIGH:-DES".
+opensslCiphers :: String
+opensslCiphers = "HIGH:-DES:-3DES:-EXPORT:-ADH"
+
+-- * X509
+
+-- | commonName (CN) used in certificates
+x509CertCn :: String
+x509CertCn = "ganeti.example.com"
+
+-- | Default validity of certificates in days
+x509CertDefaultValidity :: Int
+x509CertDefaultValidity = 365 * 5
+
+x509CertSignatureHeader :: String
+x509CertSignatureHeader = "X-Ganeti-Signature"
+
+-- | Digest used to sign certificates ("openssl x509" uses SHA1 by default)
+x509CertSignDigest :: String
+x509CertSignDigest = "SHA1"
+
+-- * Import/export daemon mode
+
+iemExport :: String
+iemExport = "export"
+
+iemImport :: String
+iemImport = "import"
+
+-- * Import/export transport compression
+
+iecGzip :: String
+iecGzip = "gzip"
+
+iecNone :: String
+iecNone = "none"
+
+iecAll :: [String]
+iecAll = [iecGzip, iecNone]
+
+ieCustomSize :: String
+ieCustomSize = "fd"
+
+-- * Import/export I/O
+
+-- | Direct file I/O, equivalent to a shell's I/O redirection using
+-- '<' or '>'
+ieioFile :: String
+ieioFile = "file"
+
+-- | Raw block device I/O using "dd"
+ieioRawDisk :: String
+ieioRawDisk = "raw"
+
+-- | OS definition import/export script
+ieioScript :: String
+ieioScript = "script"
+
+-- * Values
+
+valueDefault :: String
+valueDefault = "default"
+
+valueAuto :: String
+valueAuto = "auto"
+
+valueGenerate :: String
+valueGenerate = "generate"
+
+valueNone :: String
+valueNone = "none"
+
+valueTrue :: String
+valueTrue = "true"
+
+valueFalse :: String
+valueFalse = "false"
+
+-- * Hooks
+
+hooksNameCfgupdate :: String
+hooksNameCfgupdate = "config-update"
+
+hooksNameWatcher :: String
+hooksNameWatcher = "watcher"
+
+hooksPath :: String
+hooksPath = "/sbin:/bin:/usr/sbin:/usr/bin"
+
+hooksPhasePost :: String
+hooksPhasePost = "post"
+
+hooksPhasePre :: String
+hooksPhasePre = "pre"
+
+hooksVersion :: Int
+hooksVersion = 2
+
+-- * Hooks subject type (what object type does the LU deal with)
+
+htypeCluster :: String
+htypeCluster = "CLUSTER"
+
+htypeGroup :: String
+htypeGroup = "GROUP"
+
+htypeInstance :: String
+htypeInstance = "INSTANCE"
+
+htypeNetwork :: String
+htypeNetwork = "NETWORK"
+
+htypeNode :: String
+htypeNode = "NODE"
+
+-- * Hkr
+
+hkrSkip :: Int
+hkrSkip = 0
+
+hkrFail :: Int
+hkrFail = 1
+
+hkrSuccess :: Int
+hkrSuccess = 2
+
+-- * Storage types
+
+stBlock :: String
+stBlock = Types.storageTypeToRaw StorageBlock
+
+stDiskless :: String
+stDiskless = Types.storageTypeToRaw StorageDiskless
+
+stExt :: String
+stExt = Types.storageTypeToRaw StorageExt
+
+stFile :: String
+stFile = Types.storageTypeToRaw StorageFile
+
+stLvmPv :: String
+stLvmPv = Types.storageTypeToRaw StorageLvmPv
+
+stLvmVg :: String
+stLvmVg = Types.storageTypeToRaw StorageLvmVg
+
+stRados :: String
+stRados = Types.storageTypeToRaw StorageRados
+
+storageTypes :: FrozenSet String
+storageTypes = ConstantUtils.mkSet $ map Types.storageTypeToRaw [minBound..]
+
+-- | The set of storage types for which storage reporting is available
+--
+-- FIXME: Remove this, once storage reporting is available for all
+-- types.
+stsReport :: FrozenSet String
+stsReport = ConstantUtils.mkSet [stFile, stLvmPv, stLvmVg]
+
+-- * Storage fields
+-- ** First two are valid in LU context only, not passed to backend
+
+sfNode :: String
+sfNode = "node"
+
+sfType :: String
+sfType = "type"
+
+-- ** and the rest are valid in backend
+
+sfAllocatable :: String
+sfAllocatable = Types.storageFieldToRaw SFAllocatable
+
+sfFree :: String
+sfFree = Types.storageFieldToRaw SFFree
+
+sfName :: String
+sfName = Types.storageFieldToRaw SFName
+
+sfSize :: String
+sfSize = Types.storageFieldToRaw SFSize
+
+sfUsed :: String
+sfUsed = Types.storageFieldToRaw SFUsed
+
+validStorageFields :: FrozenSet String
+validStorageFields =
+  ConstantUtils.mkSet $ map Types.storageFieldToRaw [minBound..] ++
+                        [sfNode, sfType]
+
+modifiableStorageFields :: Map String (FrozenSet String)
+modifiableStorageFields =
+  Map.fromList [(Types.storageTypeToRaw StorageLvmPv,
+                 ConstantUtils.mkSet [sfAllocatable])]
+
+-- * Storage operations
+
+soFixConsistency :: String
+soFixConsistency = "fix-consistency"
+
+validStorageOperations :: Map String (FrozenSet String)
+validStorageOperations =
+  Map.fromList [(Types.storageTypeToRaw StorageLvmVg,
+                 ConstantUtils.mkSet [soFixConsistency])]
+
+-- * Volume fields
+
+vfDev :: String
+vfDev = "dev"
+
+vfInstance :: String
+vfInstance = "instance"
+
+vfName :: String
+vfName = "name"
+
+vfNode :: String
+vfNode = "node"
+
+vfPhys :: String
+vfPhys = "phys"
+
+vfSize :: String
+vfSize = "size"
+
+vfVg :: String
+vfVg = "vg"
+
+-- * Local disk status
+
+ldsFaulty :: Int
+ldsFaulty = Types.localDiskStatusToRaw DiskStatusFaulty
+
+ldsOkay :: Int
+ldsOkay = Types.localDiskStatusToRaw DiskStatusOk
+
+ldsUnknown :: Int
+ldsUnknown = Types.localDiskStatusToRaw DiskStatusUnknown
+
+ldsNames :: Map Int String
+ldsNames =
+  Map.fromList [ (Types.localDiskStatusToRaw ds,
+                  localDiskStatusName ds) | ds <- [minBound..] ]
+
+-- * Disk template types
+
+dtDiskless :: String
+dtDiskless = Types.diskTemplateToRaw DTDiskless
+
+dtFile :: String
+dtFile = Types.diskTemplateToRaw DTFile
+
+dtSharedFile :: String
+dtSharedFile = Types.diskTemplateToRaw DTSharedFile
+
+dtPlain :: String
+dtPlain = Types.diskTemplateToRaw DTPlain
+
+dtBlock :: String
+dtBlock = Types.diskTemplateToRaw DTBlock
+
+dtDrbd8 :: String
+dtDrbd8 = Types.diskTemplateToRaw DTDrbd8
+
+dtRbd :: String
+dtRbd = Types.diskTemplateToRaw DTRbd
+
+dtExt :: String
+dtExt = Types.diskTemplateToRaw DTExt
+
+-- | This is used to order determine the default disk template when
+-- the list of enabled disk templates is inferred from the current
+-- state of the cluster.  This only happens on an upgrade from a
+-- version of Ganeti that did not support the 'enabled_disk_templates'
+-- so far.
+diskTemplatePreference :: [String]
+diskTemplatePreference =
+  map Types.diskTemplateToRaw
+  [DTBlock, DTDiskless, DTDrbd8, DTExt, DTFile, DTPlain, DTRbd, DTSharedFile]
+
+diskTemplates :: FrozenSet String
+diskTemplates = ConstantUtils.mkSet $ map Types.diskTemplateToRaw [minBound..]
+
+-- | Disk templates that are enabled by default
+defaultEnabledDiskTemplates :: [String]
+defaultEnabledDiskTemplates = map Types.diskTemplateToRaw [DTDrbd8, DTPlain]
+
+-- | Mapping of disk templates to storage types
+mapDiskTemplateStorageType :: Map String String
+mapDiskTemplateStorageType =
+  Map.fromList $
+  map (Types.diskTemplateToRaw *** Types.storageTypeToRaw)
+  [(DTBlock, StorageBlock),
+   (DTDrbd8, StorageLvmVg),
+   (DTExt, StorageExt),
+   (DTSharedFile, StorageFile),
+   (DTFile, StorageFile),
+   (DTDiskless, StorageDiskless),
+   (DTPlain, StorageLvmVg),
+   (DTRbd, StorageRados)]
+
+-- | The set of network-mirrored disk templates
+dtsIntMirror :: FrozenSet String
+dtsIntMirror = ConstantUtils.mkSet [dtDrbd8]
+
+-- | 'DTDiskless' is 'trivially' externally mirrored
+dtsExtMirror :: FrozenSet String
+dtsExtMirror =
+  ConstantUtils.mkSet $
+  map Types.diskTemplateToRaw [DTDiskless, DTBlock, DTExt, DTSharedFile, DTRbd]
+
+-- | The set of non-lvm-based disk templates
+dtsNotLvm :: FrozenSet String
+dtsNotLvm =
+  ConstantUtils.mkSet $
+  map Types.diskTemplateToRaw
+  [DTSharedFile, DTDiskless, DTBlock, DTExt, DTFile, DTRbd]
+
+-- | The set of disk templates which can be grown
+dtsGrowable :: FrozenSet String
+dtsGrowable =
+  ConstantUtils.mkSet $
+  map Types.diskTemplateToRaw
+  [DTSharedFile, DTDrbd8, DTPlain, DTExt, DTFile, DTRbd]
+
+-- | The set of disk templates that allow adoption
+dtsMayAdopt :: FrozenSet String
+dtsMayAdopt =
+  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTBlock, DTPlain]
+
+-- | The set of disk templates that *must* use adoption
+dtsMustAdopt :: FrozenSet String
+dtsMustAdopt = ConstantUtils.mkSet [Types.diskTemplateToRaw DTBlock]
+
+-- | The set of disk templates that allow migrations
+dtsMirrored :: FrozenSet String
+dtsMirrored = dtsIntMirror `ConstantUtils.union` dtsExtMirror
+
+-- | The set of file based disk templates
+dtsFilebased :: FrozenSet String
+dtsFilebased =
+  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTSharedFile, DTFile]
+
+-- | The set of disk templates that can be moved by copying
+--
+-- Note: a requirement is that they're not accessed externally or
+-- shared between nodes; in particular, sharedfile is not suitable.
+dtsCopyable :: FrozenSet String
+dtsCopyable =
+  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTPlain, DTFile]
+
+-- | The set of disk templates that are supported by exclusive_storage
+dtsExclStorage :: FrozenSet String
+dtsExclStorage = ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTPlain]
+
+-- | Templates for which we don't perform checks on free space
+dtsNoFreeSpaceCheck :: FrozenSet String
+dtsNoFreeSpaceCheck =
+  ConstantUtils.mkSet $
+  map Types.diskTemplateToRaw [DTExt, DTSharedFile, DTFile, DTRbd]
+
+dtsBlock :: FrozenSet String
+dtsBlock =
+  ConstantUtils.mkSet $
+  map Types.diskTemplateToRaw [DTPlain, DTDrbd8, DTBlock, DTRbd, DTExt]
+
+-- | The set of lvm-based disk templates
+dtsLvm :: FrozenSet String
+dtsLvm = diskTemplates `ConstantUtils.difference` dtsNotLvm
+
+-- * Drbd
+
+drbdHmacAlg :: String
+drbdHmacAlg = "md5"
+
+drbdDefaultNetProtocol :: String
+drbdDefaultNetProtocol = "C"
+
+drbdMigrationNetProtocol :: String
+drbdMigrationNetProtocol = "C"
+
+drbdStatusFile :: String
+drbdStatusFile = "/proc/drbd"
+
+-- | Size of DRBD meta block device
+drbdMetaSize :: Int
+drbdMetaSize = 128
+
+-- * Drbd barrier types
+
+drbdBDiskBarriers :: String
+drbdBDiskBarriers = "b"
+
+drbdBDiskDrain :: String
+drbdBDiskDrain = "d"
+
+drbdBDiskFlush :: String
+drbdBDiskFlush = "f"
+
+drbdBNone :: String
+drbdBNone = "n"
+
+-- | Valid barrier combinations: "n" or any non-null subset of "bfd"
+drbdValidBarrierOpt :: FrozenSet (FrozenSet String)
+drbdValidBarrierOpt =
+  ConstantUtils.mkSet
+  [ ConstantUtils.mkSet [drbdBNone]
+  , ConstantUtils.mkSet [drbdBDiskBarriers]
+  , ConstantUtils.mkSet [drbdBDiskDrain]
+  , ConstantUtils.mkSet [drbdBDiskFlush]
+  , ConstantUtils.mkSet [drbdBDiskDrain, drbdBDiskFlush]
+  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskDrain]
+  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskFlush]
+  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskFlush, drbdBDiskDrain]
+  ]
+
+-- | Rbd tool command
+rbdCmd :: String
+rbdCmd = "rbd"
+
+-- * File backend driver
+
+fdBlktap :: String
+fdBlktap = Types.fileDriverToRaw FileBlktap
+
+fdBlktap2 :: String
+fdBlktap2 = Types.fileDriverToRaw FileBlktap2
+
+fdLoop :: String
+fdLoop = Types.fileDriverToRaw FileLoop
+
+fdDefault :: String
+fdDefault = fdLoop
+
+fileDriver :: FrozenSet String
+fileDriver =
+  ConstantUtils.mkSet $
+  map Types.fileDriverToRaw [minBound..]
+
+-- | The set of drbd-like disk types
+dtsDrbd :: FrozenSet String
+dtsDrbd = ConstantUtils.mkSet [Types.diskTemplateToRaw DTDrbd8]
+
+-- * Disk access mode
+
+diskRdonly :: String
+diskRdonly = Types.diskModeToRaw DiskRdOnly
+
+diskRdwr :: String
+diskRdwr = Types.diskModeToRaw DiskRdWr
+
+diskAccessSet :: FrozenSet String
+diskAccessSet = ConstantUtils.mkSet $ map Types.diskModeToRaw [minBound..]
+
+-- * Disk replacement mode
+
+replaceDiskAuto :: String
+replaceDiskAuto = Types.replaceDisksModeToRaw ReplaceAuto
+
+replaceDiskChg :: String
+replaceDiskChg = Types.replaceDisksModeToRaw ReplaceNewSecondary
+
+replaceDiskPri :: String
+replaceDiskPri = Types.replaceDisksModeToRaw ReplaceOnPrimary
+
+replaceDiskSec :: String
+replaceDiskSec = Types.replaceDisksModeToRaw ReplaceOnSecondary
+
+replaceModes :: FrozenSet String
+replaceModes =
+  ConstantUtils.mkSet $ map Types.replaceDisksModeToRaw [minBound..]
+
+-- * Instance export mode
+
+exportModeLocal :: String
+exportModeLocal = Types.exportModeToRaw ExportModeLocal
+
+exportModeRemote :: String
+exportModeRemote = Types.exportModeToRaw ExportModeRemote
+
+exportModes :: FrozenSet String
+exportModes = ConstantUtils.mkSet $ map Types.exportModeToRaw [minBound..]
+
+-- * Instance creation modes
+
+instanceCreate :: String
+instanceCreate = Types.instCreateModeToRaw InstCreate
+
+instanceImport :: String
+instanceImport = Types.instCreateModeToRaw InstImport
+
+instanceRemoteImport :: String
+instanceRemoteImport = Types.instCreateModeToRaw InstRemoteImport
+
+instanceCreateModes :: FrozenSet String
+instanceCreateModes =
+  ConstantUtils.mkSet $ map Types.instCreateModeToRaw [minBound..]
+
+-- * Remote import/export handshake message and version
+
+rieHandshake :: String
+rieHandshake = "Hi, I'm Ganeti"
+
+rieVersion :: Int
+rieVersion = 0
+
+-- | Remote import/export certificate validity (seconds)
+rieCertValidity :: Int
+rieCertValidity = 24 * 60 * 60
+
+-- | Export only: how long to wait per connection attempt (seconds)
+rieConnectAttemptTimeout :: Int
+rieConnectAttemptTimeout = 20
+
+-- | Export only: number of attempts to connect
+rieConnectRetries :: Int
+rieConnectRetries = 10
+
+-- | Overall timeout for establishing connection
+rieConnectTimeout :: Int
+rieConnectTimeout = 180
+
+-- | Give child process up to 5 seconds to exit after sending a signal
+childLingerTimeout :: Double
+childLingerTimeout = 5.0
+
+-- * Import/export config options
+
+inisectBep :: String
+inisectBep = "backend"
+
+inisectExp :: String
+inisectExp = "export"
+
+inisectHyp :: String
+inisectHyp = "hypervisor"
+
+inisectIns :: String
+inisectIns = "instance"
+
+inisectOsp :: String
+inisectOsp = "os"
+
+-- * Dynamic device modification
+
+ddmAdd :: String
+ddmAdd = Types.ddmFullToRaw DdmFullAdd
+
+ddmModify :: String
+ddmModify = Types.ddmFullToRaw DdmFullModify
+
+ddmRemove :: String
+ddmRemove = Types.ddmFullToRaw DdmFullRemove
+
+ddmsValues :: FrozenSet String
+ddmsValues = ConstantUtils.mkSet [ddmAdd, ddmRemove]
+
+ddmsValuesWithModify :: FrozenSet String
+ddmsValuesWithModify = ConstantUtils.mkSet $ map Types.ddmFullToRaw [minBound..]
+
+-- * Common exit codes
+
+exitSuccess :: Int
+exitSuccess = 0
+
+exitFailure :: Int
+exitFailure = ConstantUtils.exitFailure
+
+exitNotcluster :: Int
+exitNotcluster = 5
+
+exitNotmaster :: Int
+exitNotmaster = 11
+
+exitNodesetupError :: Int
+exitNodesetupError = 12
+
+-- | Need user confirmation
+exitConfirmation :: Int
+exitConfirmation = 13
+
+-- | Exit code for query operations with unknown fields
+exitUnknownField :: Int
+exitUnknownField = 14
+
+-- * Tags
+
+tagCluster :: String
+tagCluster = Types.tagKindToRaw TagKindCluster
+
+tagInstance :: String
+tagInstance = Types.tagKindToRaw TagKindInstance
+
+tagNetwork :: String
+tagNetwork = Types.tagKindToRaw TagKindNetwork
+
+tagNode :: String
+tagNode = Types.tagKindToRaw TagKindNode
+
+tagNodegroup :: String
+tagNodegroup = Types.tagKindToRaw TagKindGroup
+
+validTagTypes :: FrozenSet String
+validTagTypes = ConstantUtils.mkSet $ map Types.tagKindToRaw [minBound..]
+
+maxTagLen :: Int
+maxTagLen = 128
+
+maxTagsPerObj :: Int
+maxTagsPerObj = 4096
+
+-- * Others
+
+defaultBridge :: String
+defaultBridge = "xen-br0"
+
+defaultOvs :: String
+defaultOvs = "switch1"
+
+-- | 60 MiB/s, expressed in KiB/s
+classicDrbdSyncSpeed :: Int
+classicDrbdSyncSpeed = 60 * 1024
+
+ip4AddressAny :: String
+ip4AddressAny = "0.0.0.0"
+
+ip4AddressLocalhost :: String
+ip4AddressLocalhost = "127.0.0.1"
+
+ip6AddressAny :: String
+ip6AddressAny = "::"
+
+ip6AddressLocalhost :: String
+ip6AddressLocalhost = "::1"
+
+ip4Version :: Int
+ip4Version = 4
+
+ip6Version :: Int
+ip6Version = 6
+
+validIpVersions :: FrozenSet Int
+validIpVersions = ConstantUtils.mkSet [ip4Version, ip6Version]
+
+tcpPingTimeout :: Int
+tcpPingTimeout = 10
+
+defaultVg :: String
+defaultVg = "xenvg"
+
+defaultDrbdHelper :: String
+defaultDrbdHelper = "/bin/true"
+
+minVgSize :: Int
+minVgSize = 20480
+
+defaultMacPrefix :: String
+defaultMacPrefix = "aa:00:00"
+
+-- | Default maximum instance wait time (seconds)
+defaultShutdownTimeout :: Int
+defaultShutdownTimeout = 120
+
+-- | Node clock skew (seconds)
+nodeMaxClockSkew :: Int
+nodeMaxClockSkew = 150
+
+-- | Time for an intra-cluster disk transfer to wait for a connection
+diskTransferConnectTimeout :: Int
+diskTransferConnectTimeout = 60
+
+-- | Disk index separator
+diskSeparator :: String
+diskSeparator = AutoConf.diskSeparator
+
+ipCommandPath :: String
+ipCommandPath = AutoConf.ipPath
+
+-- | Key for job IDs in opcode result
+jobIdsKey :: String
+jobIdsKey = "jobs"
+
+-- * Runparts results
+
+runpartsErr :: Int
+runpartsErr = 2
+
+runpartsRun :: Int
+runpartsRun = 1
+
+runpartsSkip :: Int
+runpartsSkip = 0
+
+runpartsStatus :: [Int]
+runpartsStatus = [runpartsErr, runpartsRun, runpartsSkip]
+
+-- * RPC
+
+rpcEncodingNone :: Int
+rpcEncodingNone = 0
+
+rpcEncodingZlibBase64 :: Int
+rpcEncodingZlibBase64 = 1
+
+-- * Timeout table
+--
+-- Various time constants for the timeout table
+
+rpcTmoUrgent :: Int
+rpcTmoUrgent = Types.rpcTimeoutToRaw Urgent
+
+rpcTmoFast :: Int
+rpcTmoFast = Types.rpcTimeoutToRaw Fast
+
+rpcTmoNormal :: Int
+rpcTmoNormal = Types.rpcTimeoutToRaw Normal
+
+rpcTmoSlow :: Int
+rpcTmoSlow = Types.rpcTimeoutToRaw Slow
+
+-- | 'rpcTmo_4hrs' contains an underscore to circumvent a limitation
+-- in the 'Ganeti.THH.deCamelCase' function and generate the correct
+-- Python name.
+rpcTmo_4hrs :: Int
+rpcTmo_4hrs = Types.rpcTimeoutToRaw FourHours
+
+-- | 'rpcTmo_1day' contains an underscore to circumvent a limitation
+-- in the 'Ganeti.THH.deCamelCase' function and generate the correct
+-- Python name.
+rpcTmo_1day :: Int
+rpcTmo_1day = Types.rpcTimeoutToRaw OneDay
+
+-- | Timeout for connecting to nodes (seconds)
+rpcConnectTimeout :: Int
+rpcConnectTimeout = 5
+
+-- OS
+
+osScriptCreate :: String
+osScriptCreate = "create"
+
+osScriptExport :: String
+osScriptExport = "export"
+
+osScriptImport :: String
+osScriptImport = "import"
+
+osScriptRename :: String
+osScriptRename = "rename"
+
+osScriptVerify :: String
+osScriptVerify = "verify"
+
+osScripts :: [String]
+osScripts = [osScriptCreate, osScriptExport, osScriptImport, osScriptRename,
+             osScriptVerify]
+
+osApiFile :: String
+osApiFile = "ganeti_api_version"
+
+osVariantsFile :: String
+osVariantsFile = "variants.list"
+
+osParametersFile :: String
+osParametersFile = "parameters.list"
+
+osValidateParameters :: String
+osValidateParameters = "parameters"
+
+osValidateCalls :: FrozenSet String
+osValidateCalls = ConstantUtils.mkSet [osValidateParameters]
+
+-- | External Storage (ES) related constants
+
+esActionAttach :: String
+esActionAttach = "attach"
+
+esActionCreate :: String
+esActionCreate = "create"
+
+esActionDetach :: String
+esActionDetach = "detach"
+
+esActionGrow :: String
+esActionGrow = "grow"
+
+esActionRemove :: String
+esActionRemove = "remove"
+
+esActionSetinfo :: String
+esActionSetinfo = "setinfo"
+
+esActionVerify :: String
+esActionVerify = "verify"
+
+esScriptCreate :: String
+esScriptCreate = esActionCreate
+
+esScriptRemove :: String
+esScriptRemove = esActionRemove
+
+esScriptGrow :: String
+esScriptGrow = esActionGrow
+
+esScriptAttach :: String
+esScriptAttach = esActionAttach
+
+esScriptDetach :: String
+esScriptDetach = esActionDetach
+
+esScriptSetinfo :: String
+esScriptSetinfo = esActionSetinfo
+
+esScriptVerify :: String
+esScriptVerify = esActionVerify
+
+esScripts :: FrozenSet String
+esScripts =
+  ConstantUtils.mkSet [esScriptAttach,
+                       esScriptCreate,
+                       esScriptDetach,
+                       esScriptGrow,
+                       esScriptRemove,
+                       esScriptSetinfo,
+                       esScriptVerify]
+
+esParametersFile :: String
+esParametersFile = "parameters.list"
+
+-- * Reboot types
+
+instanceRebootSoft :: String
+instanceRebootSoft = Types.rebootTypeToRaw RebootSoft
+
+instanceRebootHard :: String
+instanceRebootHard = Types.rebootTypeToRaw RebootHard
+
+instanceRebootFull :: String
+instanceRebootFull = Types.rebootTypeToRaw RebootFull
+
+rebootTypes :: FrozenSet String
+rebootTypes = ConstantUtils.mkSet $ map Types.rebootTypeToRaw [minBound..]
+
+-- * Instance reboot behaviors
+
+instanceRebootAllowed :: String
+instanceRebootAllowed = "reboot"
+
+instanceRebootExit :: String
+instanceRebootExit = "exit"
+
+rebootBehaviors :: [String]
+rebootBehaviors = [instanceRebootAllowed, instanceRebootExit]
+
+-- * VTypes
+
+vtypeBool :: VType
+vtypeBool = VTypeBool
+
+vtypeInt :: VType
+vtypeInt = VTypeInt
+
+vtypeMaybeString :: VType
+vtypeMaybeString = VTypeMaybeString
+
+-- | Size in MiBs
+vtypeSize :: VType
+vtypeSize = VTypeSize
+
+vtypeString :: VType
+vtypeString = VTypeString
+
+enforceableTypes :: FrozenSet VType
+enforceableTypes = ConstantUtils.mkSet [minBound..]
+
+-- | Constant representing that the user does not specify any IP version
+ifaceNoIpVersionSpecified :: Int
+ifaceNoIpVersionSpecified = 0
+
+validSerialSpeeds :: [Int]
+validSerialSpeeds =
+  [75,
+   110,
+   300,
+   600,
+   1200,
+   1800,
+   2400,
+   4800,
+   9600,
+   14400,
+   19200,
+   28800,
+   38400,
+   57600,
+   115200,
+   230400,
+   345600,
+   460800]
+
+-- * HV parameter names (global namespace)
+
+hvAcpi :: String
+hvAcpi = "acpi"
+
+hvBlockdevPrefix :: String
+hvBlockdevPrefix = "blockdev_prefix"
+
+hvBootloaderArgs :: String
+hvBootloaderArgs = "bootloader_args"
+
+hvBootloaderPath :: String
+hvBootloaderPath = "bootloader_path"
+
+hvBootOrder :: String
+hvBootOrder = "boot_order"
+
+hvCdromImagePath :: String
+hvCdromImagePath = "cdrom_image_path"
+
+hvCpuCap :: String
+hvCpuCap = "cpu_cap"
+
+hvCpuCores :: String
+hvCpuCores = "cpu_cores"
+
+hvCpuMask :: String
+hvCpuMask = "cpu_mask"
+
+hvCpuSockets :: String
+hvCpuSockets = "cpu_sockets"
+
+hvCpuThreads :: String
+hvCpuThreads = "cpu_threads"
+
+hvCpuType :: String
+hvCpuType = "cpu_type"
+
+hvCpuWeight :: String
+hvCpuWeight = "cpu_weight"
+
+hvDeviceModel :: String
+hvDeviceModel = "device_model"
+
+hvDiskCache :: String
+hvDiskCache = "disk_cache"
+
+hvDiskType :: String
+hvDiskType = "disk_type"
+
+hvInitrdPath :: String
+hvInitrdPath = "initrd_path"
+
+hvInitScript :: String
+hvInitScript = "init_script"
+
+hvKernelArgs :: String
+hvKernelArgs = "kernel_args"
+
+hvKernelPath :: String
+hvKernelPath = "kernel_path"
+
+hvKeymap :: String
+hvKeymap = "keymap"
+
+hvKvmCdrom2ImagePath :: String
+hvKvmCdrom2ImagePath = "cdrom2_image_path"
+
+hvKvmCdromDiskType :: String
+hvKvmCdromDiskType = "cdrom_disk_type"
+
+hvKvmExtra :: String
+hvKvmExtra = "kvm_extra"
+
+hvKvmFlag :: String
+hvKvmFlag = "kvm_flag"
+
+hvKvmFloppyImagePath :: String
+hvKvmFloppyImagePath = "floppy_image_path"
+
+hvKvmMachineVersion :: String
+hvKvmMachineVersion = "machine_version"
+
+hvKvmPath :: String
+hvKvmPath = "kvm_path"
+
+hvKvmSpiceAudioCompr :: String
+hvKvmSpiceAudioCompr = "spice_playback_compression"
+
+hvKvmSpiceBind :: String
+hvKvmSpiceBind = "spice_bind"
+
+hvKvmSpiceIpVersion :: String
+hvKvmSpiceIpVersion = "spice_ip_version"
+
+hvKvmSpiceJpegImgCompr :: String
+hvKvmSpiceJpegImgCompr = "spice_jpeg_wan_compression"
+
+hvKvmSpiceLosslessImgCompr :: String
+hvKvmSpiceLosslessImgCompr = "spice_image_compression"
+
+hvKvmSpicePasswordFile :: String
+hvKvmSpicePasswordFile = "spice_password_file"
+
+hvKvmSpiceStreamingVideoDetection :: String
+hvKvmSpiceStreamingVideoDetection = "spice_streaming_video"
+
+hvKvmSpiceTlsCiphers :: String
+hvKvmSpiceTlsCiphers = "spice_tls_ciphers"
+
+hvKvmSpiceUseTls :: String
+hvKvmSpiceUseTls = "spice_use_tls"
+
+hvKvmSpiceUseVdagent :: String
+hvKvmSpiceUseVdagent = "spice_use_vdagent"
+
+hvKvmSpiceZlibGlzImgCompr :: String
+hvKvmSpiceZlibGlzImgCompr = "spice_zlib_glz_wan_compression"
+
+hvKvmUseChroot :: String
+hvKvmUseChroot = "use_chroot"
+
+hvMemPath :: String
+hvMemPath = "mem_path"
+
+hvMigrationBandwidth :: String
+hvMigrationBandwidth = "migration_bandwidth"
+
+hvMigrationDowntime :: String
+hvMigrationDowntime = "migration_downtime"
+
+hvMigrationMode :: String
+hvMigrationMode = "migration_mode"
+
+hvMigrationPort :: String
+hvMigrationPort = "migration_port"
+
+hvNicType :: String
+hvNicType = "nic_type"
+
+hvPae :: String
+hvPae = "pae"
+
+hvPassthrough :: String
+hvPassthrough = "pci_pass"
+
+hvRebootBehavior :: String
+hvRebootBehavior = "reboot_behavior"
+
+hvRootPath :: String
+hvRootPath = "root_path"
+
+hvSecurityDomain :: String
+hvSecurityDomain = "security_domain"
+
+hvSecurityModel :: String
+hvSecurityModel = "security_model"
+
+hvSerialConsole :: String
+hvSerialConsole = "serial_console"
+
+hvSerialSpeed :: String
+hvSerialSpeed = "serial_speed"
+
+hvSoundhw :: String
+hvSoundhw = "soundhw"
+
+hvUsbDevices :: String
+hvUsbDevices = "usb_devices"
+
+hvUsbMouse :: String
+hvUsbMouse = "usb_mouse"
+
+hvUseBootloader :: String
+hvUseBootloader = "use_bootloader"
+
+hvUseLocaltime :: String
+hvUseLocaltime = "use_localtime"
+
+hvVga :: String
+hvVga = "vga"
+
+hvVhostNet :: String
+hvVhostNet = "vhost_net"
+
+hvVifScript :: String
+hvVifScript = "vif_script"
+
+hvVifType :: String
+hvVifType = "vif_type"
+
+hvViridian :: String
+hvViridian = "viridian"
+
+hvVncBindAddress :: String
+hvVncBindAddress = "vnc_bind_address"
+
+hvVncPasswordFile :: String
+hvVncPasswordFile = "vnc_password_file"
+
+hvVncTls :: String
+hvVncTls = "vnc_tls"
+
+hvVncX509 :: String
+hvVncX509 = "vnc_x509_path"
+
+hvVncX509Verify :: String
+hvVncX509Verify = "vnc_x509_verify"
+
+hvVnetHdr :: String
+hvVnetHdr = "vnet_hdr"
+
+hvXenCmd :: String
+hvXenCmd = "xen_cmd"
+
+hvXenCpuid :: String
+hvXenCpuid = "cpuid"
+
+hvsParameterTitles :: Map String String
+hvsParameterTitles =
+  Map.fromList
+  [(hvAcpi, "ACPI"),
+   (hvBootOrder, "Boot_order"),
+   (hvCdromImagePath, "CDROM_image_path"),
+   (hvCpuType, "cpu_type"),
+   (hvDiskType, "Disk_type"),
+   (hvInitrdPath, "Initrd_path"),
+   (hvKernelPath, "Kernel_path"),
+   (hvNicType, "NIC_type"),
+   (hvPae, "PAE"),
+   (hvPassthrough, "pci_pass"),
+   (hvVncBindAddress, "VNC_bind_address")]
+
+hvsParameters :: FrozenSet String
+hvsParameters = ConstantUtils.mkSet $ Map.keys hvsParameterTypes
+
+hvsParameterTypes :: Map String VType
+hvsParameterTypes = Map.fromList
+  [ (hvAcpi,                            VTypeBool)
+  , (hvBlockdevPrefix,                  VTypeString)
+  , (hvBootloaderArgs,                  VTypeString)
+  , (hvBootloaderPath,                  VTypeString)
+  , (hvBootOrder,                       VTypeString)
+  , (hvCdromImagePath,                  VTypeString)
+  , (hvCpuCap,                          VTypeInt)
+  , (hvCpuCores,                        VTypeInt)
+  , (hvCpuMask,                         VTypeString)
+  , (hvCpuSockets,                      VTypeInt)
+  , (hvCpuThreads,                      VTypeInt)
+  , (hvCpuType,                         VTypeString)
+  , (hvCpuWeight,                       VTypeInt)
+  , (hvDeviceModel,                     VTypeString)
+  , (hvDiskCache,                       VTypeString)
+  , (hvDiskType,                        VTypeString)
+  , (hvInitrdPath,                      VTypeString)
+  , (hvInitScript,                      VTypeString)
+  , (hvKernelArgs,                      VTypeString)
+  , (hvKernelPath,                      VTypeString)
+  , (hvKeymap,                          VTypeString)
+  , (hvKvmCdrom2ImagePath,              VTypeString)
+  , (hvKvmCdromDiskType,                VTypeString)
+  , (hvKvmExtra,                        VTypeString)
+  , (hvKvmFlag,                         VTypeString)
+  , (hvKvmFloppyImagePath,              VTypeString)
+  , (hvKvmMachineVersion,               VTypeString)
+  , (hvKvmPath,                         VTypeString)
+  , (hvKvmSpiceAudioCompr,              VTypeBool)
+  , (hvKvmSpiceBind,                    VTypeString)
+  , (hvKvmSpiceIpVersion,               VTypeInt)
+  , (hvKvmSpiceJpegImgCompr,            VTypeString)
+  , (hvKvmSpiceLosslessImgCompr,        VTypeString)
+  , (hvKvmSpicePasswordFile,            VTypeString)
+  , (hvKvmSpiceStreamingVideoDetection, VTypeString)
+  , (hvKvmSpiceTlsCiphers,              VTypeString)
+  , (hvKvmSpiceUseTls,                  VTypeBool)
+  , (hvKvmSpiceUseVdagent,              VTypeBool)
+  , (hvKvmSpiceZlibGlzImgCompr,         VTypeString)
+  , (hvKvmUseChroot,                    VTypeBool)
+  , (hvMemPath,                         VTypeString)
+  , (hvMigrationBandwidth,              VTypeInt)
+  , (hvMigrationDowntime,               VTypeInt)
+  , (hvMigrationMode,                   VTypeString)
+  , (hvMigrationPort,                   VTypeInt)
+  , (hvNicType,                         VTypeString)
+  , (hvPae,                             VTypeBool)
+  , (hvPassthrough,                     VTypeString)
+  , (hvRebootBehavior,                  VTypeString)
+  , (hvRootPath,                        VTypeMaybeString)
+  , (hvSecurityDomain,                  VTypeString)
+  , (hvSecurityModel,                   VTypeString)
+  , (hvSerialConsole,                   VTypeBool)
+  , (hvSerialSpeed,                     VTypeInt)
+  , (hvSoundhw,                         VTypeString)
+  , (hvUsbDevices,                      VTypeString)
+  , (hvUsbMouse,                        VTypeString)
+  , (hvUseBootloader,                   VTypeBool)
+  , (hvUseLocaltime,                    VTypeBool)
+  , (hvVga,                             VTypeString)
+  , (hvVhostNet,                        VTypeBool)
+  , (hvVifScript,                       VTypeString)
+  , (hvVifType,                         VTypeString)
+  , (hvViridian,                        VTypeBool)
+  , (hvVncBindAddress,                  VTypeString)
+  , (hvVncPasswordFile,                 VTypeString)
+  , (hvVncTls,                          VTypeBool)
+  , (hvVncX509,                         VTypeString)
+  , (hvVncX509Verify,                   VTypeBool)
+  , (hvVnetHdr,                         VTypeBool)
+  , (hvXenCmd,                          VTypeString)
+  , (hvXenCpuid,                        VTypeString)
+  ]
+
+-- * Migration statuses
+
+hvMigrationActive :: String
+hvMigrationActive = "active"
+
+hvMigrationCancelled :: String
+hvMigrationCancelled = "cancelled"
+
+hvMigrationCompleted :: String
+hvMigrationCompleted = "completed"
+
+hvMigrationFailed :: String
+hvMigrationFailed = "failed"
+
+hvMigrationValidStatuses :: FrozenSet String
+hvMigrationValidStatuses =
+  ConstantUtils.mkSet [hvMigrationActive,
+                       hvMigrationCancelled,
+                       hvMigrationCompleted,
+                       hvMigrationFailed]
+
+hvMigrationFailedStatuses :: FrozenSet String
+hvMigrationFailedStatuses =
+  ConstantUtils.mkSet [hvMigrationFailed, hvMigrationCancelled]
+
+-- | KVM-specific statuses
+--
+-- FIXME: this constant seems unnecessary
+hvKvmMigrationValidStatuses :: FrozenSet String
+hvKvmMigrationValidStatuses = hvMigrationValidStatuses
+
+-- | Node info keys
+hvNodeinfoKeyVersion :: String
+hvNodeinfoKeyVersion = "hv_version"
+
+-- * Hypervisor state
+
+hvstCpuNode :: String
+hvstCpuNode = "cpu_node"
+
+hvstCpuTotal :: String
+hvstCpuTotal = "cpu_total"
+
+hvstMemoryHv :: String
+hvstMemoryHv = "mem_hv"
+
+hvstMemoryNode :: String
+hvstMemoryNode = "mem_node"
+
+hvstMemoryTotal :: String
+hvstMemoryTotal = "mem_total"
+
+hvstsParameters :: FrozenSet String
+hvstsParameters =
+  ConstantUtils.mkSet [hvstCpuNode,
+                       hvstCpuTotal,
+                       hvstMemoryHv,
+                       hvstMemoryNode,
+                       hvstMemoryTotal]
+
+hvstDefaults :: Map String Int
+hvstDefaults =
+  Map.fromList
+  [(hvstCpuNode, 1),
+   (hvstCpuTotal, 1),
+   (hvstMemoryHv, 0),
+   (hvstMemoryTotal, 0),
+   (hvstMemoryNode, 0)]
+
+hvstsParameterTypes :: Map String VType
+hvstsParameterTypes =
+  Map.fromList [(hvstMemoryTotal, VTypeInt),
+                (hvstMemoryNode, VTypeInt),
+                (hvstMemoryHv, VTypeInt),
+                (hvstCpuTotal, VTypeInt),
+                (hvstCpuNode, VTypeInt)]
+
+-- * Disk state
+
+dsDiskOverhead :: String
+dsDiskOverhead = "disk_overhead"
+
+dsDiskReserved :: String
+dsDiskReserved = "disk_reserved"
+
+dsDiskTotal :: String
+dsDiskTotal = "disk_total"
+
+dsDefaults :: Map String Int
+dsDefaults =
+  Map.fromList
+  [(dsDiskTotal, 0),
+   (dsDiskReserved, 0),
+   (dsDiskOverhead, 0)]
+
+dssParameterTypes :: Map String VType
+dssParameterTypes =
+  Map.fromList [(dsDiskTotal, VTypeInt),
+                (dsDiskReserved, VTypeInt),
+                (dsDiskOverhead, VTypeInt)]
+
+dssParameters :: FrozenSet String
+dssParameters =
+  ConstantUtils.mkSet [dsDiskTotal, dsDiskReserved, dsDiskOverhead]
+
+dsValidTypes :: FrozenSet String
+dsValidTypes = ConstantUtils.mkSet [Types.diskTemplateToRaw DTPlain]
+
+-- Backend parameter names
+
+beAlwaysFailover :: String
+beAlwaysFailover = "always_failover"
+
+beAutoBalance :: String
+beAutoBalance = "auto_balance"
+
+beMaxmem :: String
+beMaxmem = "maxmem"
+
+-- | Deprecated and replaced by max and min mem
+beMemory :: String
+beMemory = "memory"
+
+beMinmem :: String
+beMinmem = "minmem"
+
+beSpindleUse :: String
+beSpindleUse = "spindle_use"
+
+beVcpus :: String
+beVcpus = "vcpus"
+
+besParameterTypes :: Map String VType
+besParameterTypes =
+  Map.fromList [(beAlwaysFailover, VTypeBool),
+                (beAutoBalance, VTypeBool),
+                (beMaxmem, VTypeSize),
+                (beMinmem, VTypeSize),
+                (beSpindleUse, VTypeInt),
+                (beVcpus, VTypeInt)]
+
+besParameterTitles :: Map String String
+besParameterTitles =
+  Map.fromList [(beAutoBalance, "Auto_balance"),
+                (beMinmem, "ConfigMinMem"),
+                (beVcpus, "ConfigVCPUs"),
+                (beMaxmem, "ConfigMaxMem")]
+
+besParameterCompat :: Map String VType
+besParameterCompat = Map.insert beMemory VTypeSize besParameterTypes
+
+besParameters :: FrozenSet String
+besParameters =
+  ConstantUtils.mkSet [beAlwaysFailover,
+                       beAutoBalance,
+                       beMaxmem,
+                       beMinmem,
+                       beSpindleUse,
+                       beVcpus]
+
+-- | Instance specs
+--
+-- FIXME: these should be associated with 'Ganeti.HTools.Types.ISpec'
+
+ispecMemSize :: String
+ispecMemSize = ConstantUtils.ispecMemSize
+
+ispecCpuCount :: String
+ispecCpuCount = ConstantUtils.ispecCpuCount
+
+ispecDiskCount :: String
+ispecDiskCount = ConstantUtils.ispecDiskCount
+
+ispecDiskSize :: String
+ispecDiskSize = ConstantUtils.ispecDiskSize
+
+ispecNicCount :: String
+ispecNicCount = ConstantUtils.ispecNicCount
+
+ispecSpindleUse :: String
+ispecSpindleUse = ConstantUtils.ispecSpindleUse
+
+ispecsParameterTypes :: Map String VType
+ispecsParameterTypes =
+  Map.fromList
+  [(ConstantUtils.ispecDiskSize, VTypeInt),
+   (ConstantUtils.ispecCpuCount, VTypeInt),
+   (ConstantUtils.ispecSpindleUse, VTypeInt),
+   (ConstantUtils.ispecMemSize, VTypeInt),
+   (ConstantUtils.ispecNicCount, VTypeInt),
+   (ConstantUtils.ispecDiskCount, VTypeInt)]
+
+ispecsParameters :: FrozenSet String
+ispecsParameters =
+  ConstantUtils.mkSet [ConstantUtils.ispecCpuCount,
+                       ConstantUtils.ispecDiskCount,
+                       ConstantUtils.ispecDiskSize,
+                       ConstantUtils.ispecMemSize,
+                       ConstantUtils.ispecNicCount,
+                       ConstantUtils.ispecSpindleUse]
+
+ispecsMinmax :: String
+ispecsMinmax = ConstantUtils.ispecsMinmax
+
+ispecsMax :: String
+ispecsMax = "max"
+
+ispecsMin :: String
+ispecsMin = "min"
+
+ispecsStd :: String
+ispecsStd = ConstantUtils.ispecsStd
+
+ipolicyDts :: String
+ipolicyDts = ConstantUtils.ipolicyDts
+
+ipolicyVcpuRatio :: String
+ipolicyVcpuRatio = ConstantUtils.ipolicyVcpuRatio
+
+ipolicySpindleRatio :: String
+ipolicySpindleRatio = ConstantUtils.ipolicySpindleRatio
+
+ispecsMinmaxKeys :: FrozenSet String
+ispecsMinmaxKeys = ConstantUtils.mkSet [ispecsMax, ispecsMin]
+
+ipolicyParameters :: FrozenSet String
+ipolicyParameters =
+  ConstantUtils.mkSet [ConstantUtils.ipolicyVcpuRatio,
+                       ConstantUtils.ipolicySpindleRatio]
+
+ipolicyAllKeys :: FrozenSet String
+ipolicyAllKeys =
+  ConstantUtils.union ipolicyParameters $
+  ConstantUtils.mkSet [ConstantUtils.ipolicyDts,
+                       ConstantUtils.ispecsMinmax,
+                       ispecsStd]
+
+-- | Node parameter names
+
+ndExclusiveStorage :: String
+ndExclusiveStorage = "exclusive_storage"
+
+ndOobProgram :: String
+ndOobProgram = "oob_program"
+
+ndSpindleCount :: String
+ndSpindleCount = "spindle_count"
+
+ndOvs :: String
+ndOvs = "ovs"
+
+ndOvsLink :: String
+ndOvsLink = "ovs_link"
+
+ndOvsName :: String
+ndOvsName = "ovs_name"
+
+ndsParameterTypes :: Map String VType
+ndsParameterTypes =
+  Map.fromList
+  [(ndExclusiveStorage, VTypeBool),
+   (ndOobProgram, VTypeString),
+   (ndOvs, VTypeBool),
+   (ndOvsLink, VTypeMaybeString),
+   (ndOvsName, VTypeMaybeString),
+   (ndSpindleCount, VTypeInt)]
+
+ndsParameters :: FrozenSet String
+ndsParameters = ConstantUtils.mkSet (Map.keys ndsParameterTypes)
+
+ndsParameterTitles :: Map String String
+ndsParameterTitles =
+  Map.fromList
+  [(ndExclusiveStorage, "ExclusiveStorage"),
+   (ndOobProgram, "OutOfBandProgram"),
+   (ndOvs, "OpenvSwitch"),
+   (ndOvsLink, "OpenvSwitchLink"),
+   (ndOvsName, "OpenvSwitchName"),
+   (ndSpindleCount, "SpindleCount")]
+
+-- * Logical Disks parameters
+
+ldpAccess :: String
+ldpAccess = "access"
+
+ldpBarriers :: String
+ldpBarriers = "disabled-barriers"
+
+ldpDefaultMetavg :: String
+ldpDefaultMetavg = "default-metavg"
+
+ldpDelayTarget :: String
+ldpDelayTarget = "c-delay-target"
+
+ldpDiskCustom :: String
+ldpDiskCustom = "disk-custom"
+
+ldpDynamicResync :: String
+ldpDynamicResync = "dynamic-resync"
+
+ldpFillTarget :: String
+ldpFillTarget = "c-fill-target"
+
+ldpMaxRate :: String
+ldpMaxRate = "c-max-rate"
+
+ldpMinRate :: String
+ldpMinRate = "c-min-rate"
+
+ldpNetCustom :: String
+ldpNetCustom = "net-custom"
+
+ldpNoMetaFlush :: String
+ldpNoMetaFlush = "disable-meta-flush"
+
+ldpPlanAhead :: String
+ldpPlanAhead = "c-plan-ahead"
+
+ldpPool :: String
+ldpPool = "pool"
+
+ldpProtocol :: String
+ldpProtocol = "protocol"
+
+ldpResyncRate :: String
+ldpResyncRate = "resync-rate"
+
+ldpStripes :: String
+ldpStripes = "stripes"
+
+diskLdTypes :: Map String VType
+diskLdTypes =
+  Map.fromList
+  [(ldpAccess, VTypeString),
+   (ldpResyncRate, VTypeInt),
+   (ldpStripes, VTypeInt),
+   (ldpBarriers, VTypeString),
+   (ldpNoMetaFlush, VTypeBool),
+   (ldpDefaultMetavg, VTypeString),
+   (ldpDiskCustom, VTypeString),
+   (ldpNetCustom, VTypeString),
+   (ldpProtocol, VTypeString),
+   (ldpDynamicResync, VTypeBool),
+   (ldpPlanAhead, VTypeInt),
+   (ldpFillTarget, VTypeInt),
+   (ldpDelayTarget, VTypeInt),
+   (ldpMaxRate, VTypeInt),
+   (ldpMinRate, VTypeInt),
+   (ldpPool, VTypeString)]
+
+diskLdParameters :: FrozenSet String
+diskLdParameters = ConstantUtils.mkSet (Map.keys diskLdTypes)
+
+-- * Disk template parameters
+--
+-- Disk template parameters can be set/changed by the user via
+-- gnt-cluster and gnt-group)
+
+drbdResyncRate :: String
+drbdResyncRate = "resync-rate"
+
+drbdDataStripes :: String
+drbdDataStripes = "data-stripes"
+
+drbdMetaStripes :: String
+drbdMetaStripes = "meta-stripes"
+
+drbdDiskBarriers :: String
+drbdDiskBarriers = "disk-barriers"
+
+drbdMetaBarriers :: String
+drbdMetaBarriers = "meta-barriers"
+
+drbdDefaultMetavg :: String
+drbdDefaultMetavg = "metavg"
+
+drbdDiskCustom :: String
+drbdDiskCustom = "disk-custom"
+
+drbdNetCustom :: String
+drbdNetCustom = "net-custom"
+
+drbdProtocol :: String
+drbdProtocol = "protocol"
+
+drbdDynamicResync :: String
+drbdDynamicResync = "dynamic-resync"
+
+drbdPlanAhead :: String
+drbdPlanAhead = "c-plan-ahead"
+
+drbdFillTarget :: String
+drbdFillTarget = "c-fill-target"
+
+drbdDelayTarget :: String
+drbdDelayTarget = "c-delay-target"
+
+drbdMaxRate :: String
+drbdMaxRate = "c-max-rate"
+
+drbdMinRate :: String
+drbdMinRate = "c-min-rate"
+
+lvStripes :: String
+lvStripes = "stripes"
+
+rbdAccess :: String
+rbdAccess = "access"
+
+rbdPool :: String
+rbdPool = "pool"
+
+diskDtTypes :: Map String VType
+diskDtTypes =
+  Map.fromList [(drbdResyncRate, VTypeInt),
+                (drbdDataStripes, VTypeInt),
+                (drbdMetaStripes, VTypeInt),
+                (drbdDiskBarriers, VTypeString),
+                (drbdMetaBarriers, VTypeBool),
+                (drbdDefaultMetavg, VTypeString),
+                (drbdDiskCustom, VTypeString),
+                (drbdNetCustom, VTypeString),
+                (drbdProtocol, VTypeString),
+                (drbdDynamicResync, VTypeBool),
+                (drbdPlanAhead, VTypeInt),
+                (drbdFillTarget, VTypeInt),
+                (drbdDelayTarget, VTypeInt),
+                (drbdMaxRate, VTypeInt),
+                (drbdMinRate, VTypeInt),
+                (lvStripes, VTypeInt),
+                (rbdAccess, VTypeString),
+                (rbdPool, VTypeString)]
+
+diskDtParameters :: FrozenSet String
+diskDtParameters = ConstantUtils.mkSet (Map.keys diskDtTypes)
+
+-- * Dynamic disk parameters
+
+ddpLocalIp :: String
+ddpLocalIp = "local-ip"
+
+ddpRemoteIp :: String
+ddpRemoteIp = "remote-ip"
+
+ddpPort :: String
+ddpPort = "port"
+
+ddpLocalMinor :: String
+ddpLocalMinor = "local-minor"
+
+ddpRemoteMinor :: String
+ddpRemoteMinor = "remote-minor"
+
+-- * OOB supported commands
+
+oobPowerOn :: String
+oobPowerOn = Types.oobCommandToRaw OobPowerOn
+
+oobPowerOff :: String
+oobPowerOff = Types.oobCommandToRaw OobPowerOff
+
+oobPowerCycle :: String
+oobPowerCycle = Types.oobCommandToRaw OobPowerCycle
+
+oobPowerStatus :: String
+oobPowerStatus = Types.oobCommandToRaw OobPowerStatus
+
+oobHealth :: String
+oobHealth = Types.oobCommandToRaw OobHealth
+
+oobCommands :: FrozenSet String
+oobCommands = ConstantUtils.mkSet $ map Types.oobCommandToRaw [minBound..]
+
+oobPowerStatusPowered :: String
+oobPowerStatusPowered = "powered"
+
+-- | 60 seconds
+oobTimeout :: Int
+oobTimeout = 60
+
+-- | 2 seconds
+oobPowerDelay :: Double
+oobPowerDelay = 2.0
+
+oobStatusCritical :: String
+oobStatusCritical = Types.oobStatusToRaw OobStatusCritical
+
+oobStatusOk :: String
+oobStatusOk = Types.oobStatusToRaw OobStatusOk
+
+oobStatusUnknown :: String
+oobStatusUnknown = Types.oobStatusToRaw OobStatusUnknown
+
+oobStatusWarning :: String
+oobStatusWarning = Types.oobStatusToRaw OobStatusWarning
+
+oobStatuses :: FrozenSet String
+oobStatuses = ConstantUtils.mkSet $ map Types.oobStatusToRaw [minBound..]
+
+-- | Instance Parameters Profile
+ppDefault :: String
+ppDefault = "default"
+
+-- * nic* constants are used inside the ganeti config
+
+nicLink :: String
+nicLink = "link"
+
+nicMode :: String
+nicMode = "mode"
+
+nicVlan :: String
+nicVlan = "vlan"
+
+nicsParameterTypes :: Map String VType
+nicsParameterTypes =
+  Map.fromList [(nicMode, vtypeString),
+                (nicLink, vtypeString),
+                (nicVlan, vtypeString)]
+
+nicsParameters :: FrozenSet String
+nicsParameters = ConstantUtils.mkSet (Map.keys nicsParameterTypes)
+
+nicModeBridged :: String
+nicModeBridged = Types.nICModeToRaw NMBridged
+
+nicModeRouted :: String
+nicModeRouted = Types.nICModeToRaw NMRouted
+
+nicModeOvs :: String
+nicModeOvs = Types.nICModeToRaw NMOvs
+
+nicIpPool :: String
+nicIpPool = Types.nICModeToRaw NMPool
+
+nicValidModes :: FrozenSet String
+nicValidModes = ConstantUtils.mkSet $ map Types.nICModeToRaw [minBound..]
+
+releaseAction :: String
+releaseAction = "release"
+
+reserveAction :: String
+reserveAction = "reserve"
+
+-- * idisk* constants are used in opcodes, to create/change disks
+
+idiskAdopt :: String
+idiskAdopt = "adopt"
+
+idiskMetavg :: String
+idiskMetavg = "metavg"
+
+idiskMode :: String
+idiskMode = "mode"
+
+idiskName :: String
+idiskName = "name"
+
+idiskSize :: String
+idiskSize = "size"
+
+idiskSpindles :: String
+idiskSpindles = "spindles"
+
+idiskVg :: String
+idiskVg = "vg"
+
+idiskProvider :: String
+idiskProvider = "provider"
+
+idiskParamsTypes :: Map String VType
+idiskParamsTypes =
+  Map.fromList [(idiskSize, VTypeSize),
+                (idiskSpindles, VTypeInt),
+                (idiskMode, VTypeString),
+                (idiskAdopt, VTypeString),
+                (idiskVg, VTypeString),
+                (idiskMetavg, VTypeString),
+                (idiskProvider, VTypeString),
+                (idiskName, VTypeMaybeString)]
+
+idiskParams :: FrozenSet String
+idiskParams = ConstantUtils.mkSet (Map.keys idiskParamsTypes)
+
+modifiableIdiskParamsTypes :: Map String VType
+modifiableIdiskParamsTypes =
+  Map.fromList [(idiskMode, VTypeString),
+                (idiskName, VTypeString)]
+
+modifiableIdiskParams :: FrozenSet String
+modifiableIdiskParams =
+  ConstantUtils.mkSet (Map.keys modifiableIdiskParamsTypes)
+
+-- * inic* constants are used in opcodes, to create/change nics
+
+inicBridge :: String
+inicBridge = "bridge"
+
+inicIp :: String
+inicIp = "ip"
+
+inicLink :: String
+inicLink = "link"
+
+inicMac :: String
+inicMac = "mac"
+
+inicMode :: String
+inicMode = "mode"
+
+inicName :: String
+inicName = "name"
+
+inicNetwork :: String
+inicNetwork = "network"
+
+inicVlan :: String
+inicVlan = "vlan"
+
+inicParamsTypes :: Map String VType
+inicParamsTypes =
+  Map.fromList [(inicBridge, VTypeMaybeString),
+                (inicIp, VTypeMaybeString),
+                (inicLink, VTypeString),
+                (inicMac, VTypeString),
+                (inicMode, VTypeString),
+                (inicName, VTypeMaybeString),
+                (inicNetwork, VTypeMaybeString),
+                (inicVlan, VTypeMaybeString)]
+
+inicParams :: FrozenSet String
+inicParams = ConstantUtils.mkSet (Map.keys inicParamsTypes)
+
+-- * Hypervisor constants
+
+htXenPvm :: String
+htXenPvm = Types.hypervisorToRaw XenPvm
+
+htFake :: String
+htFake = Types.hypervisorToRaw Fake
+
+htXenHvm :: String
+htXenHvm = Types.hypervisorToRaw XenHvm
+
+htKvm :: String
+htKvm = Types.hypervisorToRaw Kvm
+
+htChroot :: String
+htChroot = Types.hypervisorToRaw Chroot
+
+htLxc :: String
+htLxc = Types.hypervisorToRaw Lxc
+
+hyperTypes :: FrozenSet String
+hyperTypes = ConstantUtils.mkSet $ map Types.hypervisorToRaw [minBound..]
+
+htsReqPort :: FrozenSet String
+htsReqPort = ConstantUtils.mkSet [htXenHvm, htKvm]
+
+vncBasePort :: Int
+vncBasePort = 5900
+
+vncDefaultBindAddress :: String
+vncDefaultBindAddress = ip4AddressAny
+
+-- * NIC types
+
+htNicE1000 :: String
+htNicE1000 = "e1000"
+
+htNicI82551 :: String
+htNicI82551 = "i82551"
+
+htNicI8259er :: String
+htNicI8259er = "i82559er"
+
+htNicI85557b :: String
+htNicI85557b = "i82557b"
+
+htNicNe2kIsa :: String
+htNicNe2kIsa = "ne2k_isa"
+
+htNicNe2kPci :: String
+htNicNe2kPci = "ne2k_pci"
+
+htNicParavirtual :: String
+htNicParavirtual = "paravirtual"
+
+htNicPcnet :: String
+htNicPcnet = "pcnet"
+
+htNicRtl8139 :: String
+htNicRtl8139 = "rtl8139"
+
+htHvmValidNicTypes :: FrozenSet String
+htHvmValidNicTypes =
+  ConstantUtils.mkSet [htNicE1000,
+                       htNicNe2kIsa,
+                       htNicNe2kPci,
+                       htNicParavirtual,
+                       htNicRtl8139]
+
+htKvmValidNicTypes :: FrozenSet String
+htKvmValidNicTypes =
+  ConstantUtils.mkSet [htNicE1000,
+                       htNicI82551,
+                       htNicI8259er,
+                       htNicI85557b,
+                       htNicNe2kIsa,
+                       htNicNe2kPci,
+                       htNicParavirtual,
+                       htNicPcnet,
+                       htNicRtl8139]
+
+-- * Vif types
+
+-- | Default vif type in xen-hvm
+htHvmVifIoemu :: String
+htHvmVifIoemu = "ioemu"
+
+htHvmVifVif :: String
+htHvmVifVif = "vif"
+
+htHvmValidVifTypes :: FrozenSet String
+htHvmValidVifTypes = ConstantUtils.mkSet [htHvmVifIoemu, htHvmVifVif]
+
+-- * Disk types
+
+htDiskIde :: String
+htDiskIde = "ide"
+
+htDiskIoemu :: String
+htDiskIoemu = "ioemu"
+
+htDiskMtd :: String
+htDiskMtd = "mtd"
+
+htDiskParavirtual :: String
+htDiskParavirtual = "paravirtual"
+
+htDiskPflash :: String
+htDiskPflash = "pflash"
+
+htDiskScsi :: String
+htDiskScsi = "scsi"
+
+htDiskSd :: String
+htDiskSd = "sd"
+
+htHvmValidDiskTypes :: FrozenSet String
+htHvmValidDiskTypes = ConstantUtils.mkSet [htDiskIoemu, htDiskParavirtual]
+
+htKvmValidDiskTypes :: FrozenSet String
+htKvmValidDiskTypes =
+  ConstantUtils.mkSet [htDiskIde,
+                       htDiskMtd,
+                       htDiskParavirtual,
+                       htDiskPflash,
+                       htDiskScsi,
+                       htDiskSd]
+
+htCacheDefault :: String
+htCacheDefault = "default"
+
+htCacheNone :: String
+htCacheNone = "none"
+
+htCacheWback :: String
+htCacheWback = "writeback"
+
+htCacheWthrough :: String
+htCacheWthrough = "writethrough"
+
+htValidCacheTypes :: FrozenSet String
+htValidCacheTypes =
+  ConstantUtils.mkSet [htCacheDefault,
+                       htCacheNone,
+                       htCacheWback,
+                       htCacheWthrough]
+
+-- * Mouse types
+
+htMouseMouse :: String
+htMouseMouse = "mouse"
+
+htMouseTablet :: String
+htMouseTablet = "tablet"
+
+htKvmValidMouseTypes :: FrozenSet String
+htKvmValidMouseTypes = ConstantUtils.mkSet [htMouseMouse, htMouseTablet]
+
+-- * Boot order
+
+htBoCdrom :: String
+htBoCdrom = "cdrom"
+
+htBoDisk :: String
+htBoDisk = "disk"
+
+htBoFloppy :: String
+htBoFloppy = "floppy"
+
+htBoNetwork :: String
+htBoNetwork = "network"
+
+htKvmValidBoTypes :: FrozenSet String
+htKvmValidBoTypes =
+  ConstantUtils.mkSet [htBoCdrom, htBoDisk, htBoFloppy, htBoNetwork]
+
+-- * SPICE lossless image compression options
+
+htKvmSpiceLosslessImgComprAutoGlz :: String
+htKvmSpiceLosslessImgComprAutoGlz = "auto_glz"
+
+htKvmSpiceLosslessImgComprAutoLz :: String
+htKvmSpiceLosslessImgComprAutoLz = "auto_lz"
+
+htKvmSpiceLosslessImgComprGlz :: String
+htKvmSpiceLosslessImgComprGlz = "glz"
+
+htKvmSpiceLosslessImgComprLz :: String
+htKvmSpiceLosslessImgComprLz = "lz"
+
+htKvmSpiceLosslessImgComprOff :: String
+htKvmSpiceLosslessImgComprOff = "off"
+
+htKvmSpiceLosslessImgComprQuic :: String
+htKvmSpiceLosslessImgComprQuic = "quic"
+
+htKvmSpiceValidLosslessImgComprOptions :: FrozenSet String
+htKvmSpiceValidLosslessImgComprOptions =
+  ConstantUtils.mkSet [htKvmSpiceLosslessImgComprAutoGlz,
+                       htKvmSpiceLosslessImgComprAutoLz,
+                       htKvmSpiceLosslessImgComprGlz,
+                       htKvmSpiceLosslessImgComprLz,
+                       htKvmSpiceLosslessImgComprOff,
+                       htKvmSpiceLosslessImgComprQuic]
+
+htKvmSpiceLossyImgComprAlways :: String
+htKvmSpiceLossyImgComprAlways = "always"
+
+htKvmSpiceLossyImgComprAuto :: String
+htKvmSpiceLossyImgComprAuto = "auto"
+
+htKvmSpiceLossyImgComprNever :: String
+htKvmSpiceLossyImgComprNever = "never"
+
+htKvmSpiceValidLossyImgComprOptions :: FrozenSet String
+htKvmSpiceValidLossyImgComprOptions =
+  ConstantUtils.mkSet [htKvmSpiceLossyImgComprAlways,
+                       htKvmSpiceLossyImgComprAuto,
+                       htKvmSpiceLossyImgComprNever]
+
+-- * SPICE video stream detection
+
+htKvmSpiceVideoStreamDetectionAll :: String
+htKvmSpiceVideoStreamDetectionAll = "all"
+
+htKvmSpiceVideoStreamDetectionFilter :: String
+htKvmSpiceVideoStreamDetectionFilter = "filter"
+
+htKvmSpiceVideoStreamDetectionOff :: String
+htKvmSpiceVideoStreamDetectionOff = "off"
+
+htKvmSpiceValidVideoStreamDetectionOptions :: FrozenSet String
+htKvmSpiceValidVideoStreamDetectionOptions =
+  ConstantUtils.mkSet [htKvmSpiceVideoStreamDetectionAll,
+                       htKvmSpiceVideoStreamDetectionFilter,
+                       htKvmSpiceVideoStreamDetectionOff]
+
+-- * Security models
+
+htSmNone :: String
+htSmNone = "none"
+
+htSmPool :: String
+htSmPool = "pool"
+
+htSmUser :: String
+htSmUser = "user"
+
+htKvmValidSmTypes :: FrozenSet String
+htKvmValidSmTypes = ConstantUtils.mkSet [htSmNone, htSmPool, htSmUser]
+
+-- * Kvm flag values
+
+htKvmDisabled :: String
+htKvmDisabled = "disabled"
+
+htKvmEnabled :: String
+htKvmEnabled = "enabled"
+
+htKvmFlagValues :: FrozenSet String
+htKvmFlagValues = ConstantUtils.mkSet [htKvmDisabled, htKvmEnabled]
+
+-- * Migration type
+
+htMigrationLive :: String
+htMigrationLive = Types.migrationModeToRaw MigrationLive
+
+htMigrationNonlive :: String
+htMigrationNonlive = Types.migrationModeToRaw MigrationNonLive
+
+htMigrationModes :: FrozenSet String
+htMigrationModes =
+  ConstantUtils.mkSet $ map Types.migrationModeToRaw [minBound..]
+
+-- * Cluster verify steps
+
+verifyNplusoneMem :: String
+verifyNplusoneMem = Types.verifyOptionalChecksToRaw VerifyNPlusOneMem
+
+verifyOptionalChecks :: FrozenSet String
+verifyOptionalChecks =
+  ConstantUtils.mkSet $ map Types.verifyOptionalChecksToRaw [minBound..]
+
+-- * Cluster Verify error classes
+
+cvTcluster :: String
+cvTcluster = "cluster"
+
+cvTgroup :: String
+cvTgroup = "group"
+
+cvTnode :: String
+cvTnode = "node"
+
+cvTinstance :: String
+cvTinstance = "instance"
+
+-- * Cluster Verify error codes and documentation
+
+cvEclustercert :: (String, String, String)
+cvEclustercert =
+  ("cluster",
+   Types.cVErrorCodeToRaw CvECLUSTERCERT,
+   "Cluster certificate files verification failure")
+
+cvEclustercfg :: (String, String, String)
+cvEclustercfg =
+  ("cluster",
+   Types.cVErrorCodeToRaw CvECLUSTERCFG,
+   "Cluster configuration verification failure")
+
+cvEclusterdanglinginst :: (String, String, String)
+cvEclusterdanglinginst =
+  ("node",
+   Types.cVErrorCodeToRaw CvECLUSTERDANGLINGINST,
+   "Some instances have a non-existing primary node")
+
+cvEclusterdanglingnodes :: (String, String, String)
+cvEclusterdanglingnodes =
+  ("node",
+   Types.cVErrorCodeToRaw CvECLUSTERDANGLINGNODES,
+   "Some nodes belong to non-existing groups")
+
+cvEclusterfilecheck :: (String, String, String)
+cvEclusterfilecheck =
+  ("cluster",
+   Types.cVErrorCodeToRaw CvECLUSTERFILECHECK,
+   "Cluster configuration verification failure")
+
+cvEgroupdifferentpvsize :: (String, String, String)
+cvEgroupdifferentpvsize =
+  ("group",
+   Types.cVErrorCodeToRaw CvEGROUPDIFFERENTPVSIZE,
+   "PVs in the group have different sizes")
+
+cvEinstancebadnode :: (String, String, String)
+cvEinstancebadnode =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEBADNODE,
+   "Instance marked as running lives on an offline node")
+
+cvEinstancedown :: (String, String, String)
+cvEinstancedown =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEDOWN,
+   "Instance not running on its primary node")
+
+cvEinstancefaultydisk :: (String, String, String)
+cvEinstancefaultydisk =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEFAULTYDISK,
+   "Impossible to retrieve status for a disk")
+
+cvEinstancelayout :: (String, String, String)
+cvEinstancelayout =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCELAYOUT,
+   "Instance has multiple secondary nodes")
+
+cvEinstancemissingcfgparameter :: (String, String, String)
+cvEinstancemissingcfgparameter =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEMISSINGCFGPARAMETER,
+   "A configuration parameter for an instance is missing")
+
+cvEinstancemissingdisk :: (String, String, String)
+cvEinstancemissingdisk =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEMISSINGDISK,
+   "Missing volume on an instance")
+
+cvEinstancepolicy :: (String, String, String)
+cvEinstancepolicy =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEPOLICY,
+   "Instance does not meet policy")
+
+cvEinstancesplitgroups :: (String, String, String)
+cvEinstancesplitgroups =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCESPLITGROUPS,
+   "Instance with primary and secondary nodes in different groups")
+
+cvEinstanceunsuitablenode :: (String, String, String)
+cvEinstanceunsuitablenode =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEUNSUITABLENODE,
+   "Instance running on nodes that are not suitable for it")
+
+cvEinstancewrongnode :: (String, String, String)
+cvEinstancewrongnode =
+  ("instance",
+   Types.cVErrorCodeToRaw CvEINSTANCEWRONGNODE,
+   "Instance running on the wrong node")
+
+cvEnodedrbd :: (String, String, String)
+cvEnodedrbd =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEDRBD,
+   "Error parsing the DRBD status file")
+
+cvEnodedrbdhelper :: (String, String, String)
+cvEnodedrbdhelper =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEDRBDHELPER,
+   "Error caused by the DRBD helper")
+
+cvEnodedrbdversion :: (String, String, String)
+cvEnodedrbdversion =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEDRBDVERSION,
+   "DRBD version mismatch within a node group")
+
+cvEnodefilecheck :: (String, String, String)
+cvEnodefilecheck =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEFILECHECK,
+   "Error retrieving the checksum of the node files")
+
+cvEnodefilestoragepaths :: (String, String, String)
+cvEnodefilestoragepaths =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEFILESTORAGEPATHS,
+   "Detected bad file storage paths")
+
+cvEnodefilestoragepathunusable :: (String, String, String)
+cvEnodefilestoragepathunusable =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEFILESTORAGEPATHUNUSABLE,
+   "File storage path unusable")
+
+cvEnodehooks :: (String, String, String)
+cvEnodehooks =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEHOOKS,
+   "Communication failure in hooks execution")
+
+cvEnodehv :: (String, String, String)
+cvEnodehv =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEHV,
+   "Hypervisor parameters verification failure")
+
+cvEnodelvm :: (String, String, String)
+cvEnodelvm =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODELVM,
+   "LVM-related node error")
+
+cvEnoden1 :: (String, String, String)
+cvEnoden1 =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEN1,
+   "Not enough memory to accommodate instance failovers")
+
+cvEnodenet :: (String, String, String)
+cvEnodenet =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODENET,
+   "Network-related node error")
+
+cvEnodeoobpath :: (String, String, String)
+cvEnodeoobpath =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEOOBPATH,
+   "Invalid Out Of Band path")
+
+cvEnodeorphaninstance :: (String, String, String)
+cvEnodeorphaninstance =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEORPHANINSTANCE,
+   "Unknown intance running on a node")
+
+cvEnodeorphanlv :: (String, String, String)
+cvEnodeorphanlv =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEORPHANLV,
+   "Unknown LVM logical volume")
+
+cvEnodeos :: (String, String, String)
+cvEnodeos =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEOS,
+   "OS-related node error")
+
+cvEnoderpc :: (String, String, String)
+cvEnoderpc =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODERPC,
+   "Error during connection to the primary node of an instance")
+
+cvEnodesetup :: (String, String, String)
+cvEnodesetup =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODESETUP,
+   "Node setup error")
+
+cvEnodesharedfilestoragepathunusable :: (String, String, String)
+cvEnodesharedfilestoragepathunusable =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODESHAREDFILESTORAGEPATHUNUSABLE,
+   "Shared file storage path unusable")
+
+cvEnodessh :: (String, String, String)
+cvEnodessh =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODESSH,
+   "SSH-related node error")
+
+cvEnodetime :: (String, String, String)
+cvEnodetime =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODETIME,
+   "Node returned invalid time")
+
+cvEnodeuserscripts :: (String, String, String)
+cvEnodeuserscripts =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEUSERSCRIPTS,
+   "User scripts not present or not executable")
+
+cvEnodeversion :: (String, String, String)
+cvEnodeversion =
+  ("node",
+   Types.cVErrorCodeToRaw CvENODEVERSION,
+   "Protocol version mismatch or Ganeti version mismatch")
+
+cvAllEcodes :: FrozenSet (String, String, String)
+cvAllEcodes =
+  ConstantUtils.mkSet
+  [cvEclustercert,
+   cvEclustercfg,
+   cvEclusterdanglinginst,
+   cvEclusterdanglingnodes,
+   cvEclusterfilecheck,
+   cvEgroupdifferentpvsize,
+   cvEinstancebadnode,
+   cvEinstancedown,
+   cvEinstancefaultydisk,
+   cvEinstancelayout,
+   cvEinstancemissingcfgparameter,
+   cvEinstancemissingdisk,
+   cvEinstancepolicy,
+   cvEinstancesplitgroups,
+   cvEinstanceunsuitablenode,
+   cvEinstancewrongnode,
+   cvEnodedrbd,
+   cvEnodedrbdhelper,
+   cvEnodedrbdversion,
+   cvEnodefilecheck,
+   cvEnodefilestoragepaths,
+   cvEnodefilestoragepathunusable,
+   cvEnodehooks,
+   cvEnodehv,
+   cvEnodelvm,
+   cvEnoden1,
+   cvEnodenet,
+   cvEnodeoobpath,
+   cvEnodeorphaninstance,
+   cvEnodeorphanlv,
+   cvEnodeos,
+   cvEnoderpc,
+   cvEnodesetup,
+   cvEnodesharedfilestoragepathunusable,
+   cvEnodessh,
+   cvEnodetime,
+   cvEnodeuserscripts,
+   cvEnodeversion]
+
+cvAllEcodesStrings :: FrozenSet String
+cvAllEcodesStrings =
+  ConstantUtils.mkSet $ map Types.cVErrorCodeToRaw [minBound..]
+
+-- * Node verify constants
+
+nvBridges :: String
+nvBridges = "bridges"
+
+nvDrbdhelper :: String
+nvDrbdhelper = "drbd-helper"
+
+nvDrbdversion :: String
+nvDrbdversion = "drbd-version"
+
+nvDrbdlist :: String
+nvDrbdlist = "drbd-list"
+
+nvExclusivepvs :: String
+nvExclusivepvs = "exclusive-pvs"
+
+nvFilelist :: String
+nvFilelist = "filelist"
+
+nvAcceptedStoragePaths :: String
+nvAcceptedStoragePaths = "allowed-file-storage-paths"
+
+nvFileStoragePath :: String
+nvFileStoragePath = "file-storage-path"
+
+nvSharedFileStoragePath :: String
+nvSharedFileStoragePath = "shared-file-storage-path"
+
+nvHvinfo :: String
+nvHvinfo = "hvinfo"
+
+nvHvparams :: String
+nvHvparams = "hvparms"
+
+nvHypervisor :: String
+nvHypervisor = "hypervisor"
+
+nvInstancelist :: String
+nvInstancelist = "instancelist"
+
+nvLvlist :: String
+nvLvlist = "lvlist"
+
+nvMasterip :: String
+nvMasterip = "master-ip"
+
+nvNodelist :: String
+nvNodelist = "nodelist"
+
+nvNodenettest :: String
+nvNodenettest = "node-net-test"
+
+nvNodesetup :: String
+nvNodesetup = "nodesetup"
+
+nvOobPaths :: String
+nvOobPaths = "oob-paths"
+
+nvOslist :: String
+nvOslist = "oslist"
+
+nvPvlist :: String
+nvPvlist = "pvlist"
+
+nvTime :: String
+nvTime = "time"
+
+nvUserscripts :: String
+nvUserscripts = "user-scripts"
+
+nvVersion :: String
+nvVersion = "version"
+
+nvVglist :: String
+nvVglist = "vglist"
+
+nvNonvmnodes :: String
+nvNonvmnodes = "nonvmnodes"
+
+-- * Instance status
+
+inststAdmindown :: String
+inststAdmindown = Types.instanceStatusToRaw StatusDown
+
+inststAdminoffline :: String
+inststAdminoffline = Types.instanceStatusToRaw StatusOffline
+
+inststErrordown :: String
+inststErrordown = Types.instanceStatusToRaw ErrorDown
+
+inststErrorup :: String
+inststErrorup = Types.instanceStatusToRaw ErrorUp
+
+inststNodedown :: String
+inststNodedown = Types.instanceStatusToRaw NodeDown
+
+inststNodeoffline :: String
+inststNodeoffline = Types.instanceStatusToRaw NodeOffline
+
+inststRunning :: String
+inststRunning = Types.instanceStatusToRaw Running
+
+inststWrongnode :: String
+inststWrongnode = Types.instanceStatusToRaw WrongNode
+
+inststAll :: FrozenSet String
+inststAll = ConstantUtils.mkSet $ map Types.instanceStatusToRaw [minBound..]
+
+-- * Admin states
+
+adminstDown :: String
+adminstDown = Types.adminStateToRaw AdminDown
+
+adminstOffline :: String
+adminstOffline = Types.adminStateToRaw AdminOffline
+
+adminstUp :: String
+adminstUp = Types.adminStateToRaw AdminUp
+
+adminstAll :: FrozenSet String
+adminstAll = ConstantUtils.mkSet $ map Types.adminStateToRaw [minBound..]
+
+-- * Node roles
+
+nrDrained :: String
+nrDrained = Types.nodeRoleToRaw NRDrained
+
+nrMaster :: String
+nrMaster = Types.nodeRoleToRaw NRMaster
+
+nrMcandidate :: String
+nrMcandidate = Types.nodeRoleToRaw NRCandidate
+
+nrOffline :: String
+nrOffline = Types.nodeRoleToRaw NROffline
+
+nrRegular :: String
+nrRegular = Types.nodeRoleToRaw NRRegular
+
+nrAll :: FrozenSet String
+nrAll = ConstantUtils.mkSet $ map Types.nodeRoleToRaw [minBound..]
+
+-- * SSL certificate check constants (in days)
+
+sslCertExpirationError :: Int
+sslCertExpirationError = 7
+
+sslCertExpirationWarn :: Int
+sslCertExpirationWarn = 30
+
+-- * Allocator framework constants
+
+iallocatorVersion :: Int
+iallocatorVersion = 2
+
+iallocatorDirIn :: String
+iallocatorDirIn = Types.iAllocatorTestDirToRaw IAllocatorDirIn
+
+iallocatorDirOut :: String
+iallocatorDirOut = Types.iAllocatorTestDirToRaw IAllocatorDirOut
+
+validIallocatorDirections :: FrozenSet String
+validIallocatorDirections =
+  ConstantUtils.mkSet $ map Types.iAllocatorTestDirToRaw [minBound..]
+
+iallocatorModeAlloc :: String
+iallocatorModeAlloc = Types.iAllocatorModeToRaw IAllocatorAlloc
+
+iallocatorModeChgGroup :: String
+iallocatorModeChgGroup = Types.iAllocatorModeToRaw IAllocatorChangeGroup
+
+iallocatorModeMultiAlloc :: String
+iallocatorModeMultiAlloc = Types.iAllocatorModeToRaw IAllocatorMultiAlloc
+
+iallocatorModeNodeEvac :: String
+iallocatorModeNodeEvac = Types.iAllocatorModeToRaw IAllocatorNodeEvac
+
+iallocatorModeReloc :: String
+iallocatorModeReloc = Types.iAllocatorModeToRaw IAllocatorReloc
+
+validIallocatorModes :: FrozenSet String
+validIallocatorModes =
+  ConstantUtils.mkSet $ map Types.iAllocatorModeToRaw [minBound..]
+
+iallocatorSearchPath :: [String]
+iallocatorSearchPath = AutoConf.iallocatorSearchPath
+
+defaultIallocatorShortcut :: String
+defaultIallocatorShortcut = "."
+
+-- * Node evacuation
+
+nodeEvacPri :: String
+nodeEvacPri = Types.evacModeToRaw ChangePrimary
+
+nodeEvacSec :: String
+nodeEvacSec = Types.evacModeToRaw ChangeSecondary
+
+nodeEvacAll :: String
+nodeEvacAll = Types.evacModeToRaw ChangeAll
+
+nodeEvacModes :: FrozenSet String
+nodeEvacModes = ConstantUtils.mkSet $ map Types.evacModeToRaw [minBound..]
+
+-- * Job queue
+
+jobQueueVersion :: Int
+jobQueueVersion = 1
+
+jobQueueSizeHardLimit :: Int
+jobQueueSizeHardLimit = 5000
+
+jobQueueFilesPerms :: Int
+jobQueueFilesPerms = 0o640
+
+-- * Unchanged job return
+
+jobNotchanged :: String
+jobNotchanged = "nochange"
+
+-- * Job status
+
+jobStatusQueued :: String
+jobStatusQueued = Types.jobStatusToRaw JOB_STATUS_QUEUED
+
+jobStatusWaiting :: String
+jobStatusWaiting = Types.jobStatusToRaw JOB_STATUS_WAITING
+
+jobStatusCanceling :: String
+jobStatusCanceling = Types.jobStatusToRaw JOB_STATUS_CANCELING
+
+jobStatusRunning :: String
+jobStatusRunning = Types.jobStatusToRaw JOB_STATUS_RUNNING
+
+jobStatusCanceled :: String
+jobStatusCanceled = Types.jobStatusToRaw JOB_STATUS_CANCELED
+
+jobStatusSuccess :: String
+jobStatusSuccess = Types.jobStatusToRaw JOB_STATUS_SUCCESS
+
+jobStatusError :: String
+jobStatusError = Types.jobStatusToRaw JOB_STATUS_ERROR
+
+jobsPending :: FrozenSet String
+jobsPending =
+  ConstantUtils.mkSet [jobStatusQueued, jobStatusWaiting, jobStatusCanceling]
+
+jobsFinalized :: FrozenSet String
+jobsFinalized =
+  ConstantUtils.mkSet $ map Types.finalizedJobStatusToRaw [minBound..]
+
+jobStatusAll :: FrozenSet String
+jobStatusAll = ConstantUtils.mkSet $ map Types.jobStatusToRaw [minBound..]
+
+-- * OpCode status
+
+-- ** Not yet finalized opcodes
+
+opStatusCanceling :: String
+opStatusCanceling = "canceling"
+
+opStatusQueued :: String
+opStatusQueued = "queued"
+
+opStatusRunning :: String
+opStatusRunning = "running"
+
+opStatusWaiting :: String
+opStatusWaiting = "waiting"
+
+-- ** Finalized opcodes
+
+opStatusCanceled :: String
+opStatusCanceled = "canceled"
+
+opStatusError :: String
+opStatusError = "error"
+
+opStatusSuccess :: String
+opStatusSuccess = "success"
+
+opsFinalized :: FrozenSet String
+opsFinalized =
+  ConstantUtils.mkSet [opStatusCanceled, opStatusError, opStatusSuccess]
+
+-- * OpCode priority
+
+opPrioLowest :: Int
+opPrioLowest = 19
+
+opPrioHighest :: Int
+opPrioHighest = -20
+
+opPrioLow :: Int
+opPrioLow = Types.opSubmitPriorityToRaw OpPrioLow
+
+opPrioNormal :: Int
+opPrioNormal = Types.opSubmitPriorityToRaw OpPrioNormal
+
+opPrioHigh :: Int
+opPrioHigh = Types.opSubmitPriorityToRaw OpPrioHigh
+
+opPrioSubmitValid :: FrozenSet Int
+opPrioSubmitValid = ConstantUtils.mkSet [opPrioLow, opPrioNormal, opPrioHigh]
+
+opPrioDefault :: Int
+opPrioDefault = opPrioNormal
+
+-- * Lock recalculate mode
+
+locksAppend :: String
+locksAppend = "append"
+
+locksReplace :: String
+locksReplace = "replace"
+
+-- * Lock timeout
+--
+-- The lock timeout (sum) before we transition into blocking acquire
+-- (this can still be reset by priority change).  Computed as max time
+-- (10 hours) before we should actually go into blocking acquire,
+-- given that we start from the default priority level.
+
+lockAttemptsMaxwait :: Double
+lockAttemptsMaxwait = 15.0
+
+lockAttemptsMinwait :: Double
+lockAttemptsMinwait = 1.0
+
+lockAttemptsTimeout :: Int
+lockAttemptsTimeout = (10 * 3600) `div` (opPrioDefault - opPrioHighest)
+
+-- * Execution log types
+
+elogMessage :: String
+elogMessage = Types.eLogTypeToRaw ELogMessage
+
+elogRemoteImport :: String
+elogRemoteImport = Types.eLogTypeToRaw ELogRemoteImport
+
+elogJqueueTest :: String
+elogJqueueTest = Types.eLogTypeToRaw ELogJqueueTest
+
+-- * /etc/hosts modification
+
+etcHostsAdd :: String
+etcHostsAdd = "add"
+
+etcHostsRemove :: String
+etcHostsRemove = "remove"
+
+-- * Job queue test
+
+jqtMsgprefix :: String
+jqtMsgprefix = "TESTMSG="
+
+jqtExec :: String
+jqtExec = "exec"
+
+jqtExpandnames :: String
+jqtExpandnames = "expandnames"
+
+jqtLogmsg :: String
+jqtLogmsg = "logmsg"
+
+jqtStartmsg :: String
+jqtStartmsg = "startmsg"
+
+jqtAll :: FrozenSet String
+jqtAll = ConstantUtils.mkSet [jqtExec, jqtExpandnames, jqtLogmsg, jqtStartmsg]
+
+-- * Query resources
+
+qrCluster :: String
+qrCluster = "cluster"
+
+qrExport :: String
+qrExport = "export"
+
+qrExtstorage :: String
+qrExtstorage = "extstorage"
+
+qrGroup :: String
+qrGroup = "group"
+
+qrInstance :: String
+qrInstance = "instance"
+
+qrJob :: String
+qrJob = "job"
+
+qrLock :: String
+qrLock = "lock"
+
+qrNetwork :: String
+qrNetwork = "network"
+
+qrNode :: String
+qrNode = "node"
+
+qrOs :: String
+qrOs = "os"
+
+-- | List of resources which can be queried using 'Ganeti.OpCodes.OpQuery'
+qrViaOp :: FrozenSet String
+qrViaOp =
+  ConstantUtils.mkSet [qrCluster,
+                       qrInstance,
+                       qrNode,
+                       qrGroup,
+                       qrOs,
+                       qrExport,
+                       qrNetwork,
+                       qrExtstorage]
+
+-- | List of resources which can be queried using Local UniX Interface
+qrViaLuxi :: FrozenSet String
+qrViaLuxi = ConstantUtils.mkSet [qrLock, qrJob]
+
+-- | List of resources which can be queried using RAPI
+qrViaRapi :: FrozenSet String
+qrViaRapi = qrViaLuxi
+
+-- * Query field types
+
+qftBool :: String
+qftBool = "bool"
+
+qftNumber :: String
+qftNumber = "number"
+
+qftOther :: String
+qftOther = "other"
+
+qftText :: String
+qftText = "text"
+
+qftTimestamp :: String
+qftTimestamp = "timestamp"
+
+qftUnit :: String
+qftUnit = "unit"
+
+qftUnknown :: String
+qftUnknown = "unknown"
+
+qftAll :: FrozenSet String
+qftAll =
+  ConstantUtils.mkSet [qftBool,
+                       qftNumber,
+                       qftOther,
+                       qftText,
+                       qftTimestamp,
+                       qftUnit,
+                       qftUnknown]
+
+-- * Query result field status
+--
+-- Don't change or reuse values as they're used by clients.
+--
+-- FIXME: link with 'Ganeti.Query.Language.ResultStatus'
+
+-- | No data (e.g. RPC error), can be used instead of 'rsOffline'
+rsNodata :: Int
+rsNodata = 2
+
+rsNormal :: Int
+rsNormal = 0
+
+-- | Resource marked offline
+rsOffline :: Int
+rsOffline = 4
+
+-- | Value unavailable/unsupported for item; if this field is
+-- supported but we cannot get the data for the moment, 'rsNodata' or
+-- 'rsOffline' should be used
+rsUnavail :: Int
+rsUnavail = 3
+
+rsUnknown :: Int
+rsUnknown = 1
+
+rsAll :: FrozenSet Int
+rsAll =
+  ConstantUtils.mkSet [rsNodata,
+                       rsNormal,
+                       rsOffline,
+                       rsUnavail,
+                       rsUnknown]
+
+-- | Special field cases and their verbose/terse formatting
+rssDescription :: Map Int (String, String)
+rssDescription =
+  Map.fromList [(rsUnknown, ("(unknown)", "??")),
+                (rsNodata, ("(nodata)", "?")),
+                (rsOffline, ("(offline)", "*")),
+                (rsUnavail, ("(unavail)", "-"))]
+
+-- * Max dynamic devices
+
+maxDisks :: Int
+maxDisks = Types.maxDisks
+
+maxNics :: Int
+maxNics = Types.maxNics
+
+-- | SSCONF file prefix
+ssconfFileprefix :: String
+ssconfFileprefix = "ssconf_"
+
+-- * SSCONF keys
+
+ssClusterName :: String
+ssClusterName = "cluster_name"
+
+ssClusterTags :: String
+ssClusterTags = "cluster_tags"
+
+ssFileStorageDir :: String
+ssFileStorageDir = "file_storage_dir"
+
+ssSharedFileStorageDir :: String
+ssSharedFileStorageDir = "shared_file_storage_dir"
+
+ssMasterCandidates :: String
+ssMasterCandidates = "master_candidates"
+
+ssMasterCandidatesIps :: String
+ssMasterCandidatesIps = "master_candidates_ips"
+
+ssMasterIp :: String
+ssMasterIp = "master_ip"
+
+ssMasterNetdev :: String
+ssMasterNetdev = "master_netdev"
+
+ssMasterNetmask :: String
+ssMasterNetmask = "master_netmask"
+
+ssMasterNode :: String
+ssMasterNode = "master_node"
+
+ssNodeList :: String
+ssNodeList = "node_list"
+
+ssNodePrimaryIps :: String
+ssNodePrimaryIps = "node_primary_ips"
+
+ssNodeSecondaryIps :: String
+ssNodeSecondaryIps = "node_secondary_ips"
+
+ssOfflineNodes :: String
+ssOfflineNodes = "offline_nodes"
+
+ssOnlineNodes :: String
+ssOnlineNodes = "online_nodes"
+
+ssPrimaryIpFamily :: String
+ssPrimaryIpFamily = "primary_ip_family"
+
+ssInstanceList :: String
+ssInstanceList = "instance_list"
+
+ssReleaseVersion :: String
+ssReleaseVersion = "release_version"
+
+ssHypervisorList :: String
+ssHypervisorList = "hypervisor_list"
+
+ssMaintainNodeHealth :: String
+ssMaintainNodeHealth = "maintain_node_health"
+
+ssUidPool :: String
+ssUidPool = "uid_pool"
+
+ssNodegroups :: String
+ssNodegroups = "nodegroups"
+
+ssNetworks :: String
+ssNetworks = "networks"
+
+-- | This is not a complete SSCONF key, but the prefix for the
+-- hypervisor keys
+ssHvparamsPref :: String
+ssHvparamsPref = "hvparams_"
+
+-- * Hvparams keys
+
+ssHvparamsXenChroot :: String
+ssHvparamsXenChroot = ssHvparamsPref ++ htChroot
+
+ssHvparamsXenFake :: String
+ssHvparamsXenFake = ssHvparamsPref ++ htFake
+
+ssHvparamsXenHvm :: String
+ssHvparamsXenHvm = ssHvparamsPref ++ htXenHvm
+
+ssHvparamsXenKvm :: String
+ssHvparamsXenKvm = ssHvparamsPref ++ htKvm
+
+ssHvparamsXenLxc :: String
+ssHvparamsXenLxc = ssHvparamsPref ++ htLxc
+
+ssHvparamsXenPvm :: String
+ssHvparamsXenPvm = ssHvparamsPref ++ htXenPvm
+
+validSsHvparamsKeys :: FrozenSet String
+validSsHvparamsKeys =
+  ConstantUtils.mkSet [ssHvparamsXenChroot,
+                       ssHvparamsXenLxc,
+                       ssHvparamsXenFake,
+                       ssHvparamsXenHvm,
+                       ssHvparamsXenKvm,
+                       ssHvparamsXenPvm]
+
+ssFilePerms :: Int
+ssFilePerms = 0o444
+
+-- | Cluster wide default parameters
+defaultEnabledHypervisor :: String
+defaultEnabledHypervisor = htXenPvm
+
+hvcDefaults :: Map Hypervisor (Map String PyValueEx)
+hvcDefaults =
+  Map.fromList
+  [ (XenPvm, Map.fromList
+             [ (hvUseBootloader,  PyValueEx False)
+             , (hvBootloaderPath, PyValueEx xenBootloader)
+             , (hvBootloaderArgs, PyValueEx "")
+             , (hvKernelPath,     PyValueEx xenKernel)
+             , (hvInitrdPath,     PyValueEx "")
+             , (hvRootPath,       PyValueEx "/dev/xvda1")
+             , (hvKernelArgs,     PyValueEx "ro")
+             , (hvMigrationPort,  PyValueEx (8002 :: Int))
+             , (hvMigrationMode,  PyValueEx htMigrationLive)
+             , (hvBlockdevPrefix, PyValueEx "sd")
+             , (hvRebootBehavior, PyValueEx instanceRebootAllowed)
+             , (hvCpuMask,        PyValueEx cpuPinningAll)
+             , (hvCpuCap,         PyValueEx (0 :: Int))
+             , (hvCpuWeight,      PyValueEx (256 :: Int))
+             , (hvVifScript,      PyValueEx "")
+             , (hvXenCmd,         PyValueEx xenCmdXm)
+             , (hvXenCpuid,       PyValueEx "")
+             , (hvSoundhw,        PyValueEx "")
+             ])
+  , (XenHvm, Map.fromList
+             [ (hvBootOrder,      PyValueEx "cd")
+             , (hvCdromImagePath, PyValueEx "")
+             , (hvNicType,        PyValueEx htNicRtl8139)
+             , (hvDiskType,       PyValueEx htDiskParavirtual)
+             , (hvVncBindAddress, PyValueEx ip4AddressAny)
+             , (hvAcpi,           PyValueEx True)
+             , (hvPae,            PyValueEx True)
+             , (hvKernelPath,     PyValueEx "/usr/lib/xen/boot/hvmloader")
+             , (hvDeviceModel,    PyValueEx "/usr/lib/xen/bin/qemu-dm")
+             , (hvMigrationPort,  PyValueEx (8002 :: Int))
+             , (hvMigrationMode,  PyValueEx htMigrationNonlive)
+             , (hvUseLocaltime,   PyValueEx False)
+             , (hvBlockdevPrefix, PyValueEx "hd")
+             , (hvPassthrough,    PyValueEx "")
+             , (hvRebootBehavior, PyValueEx instanceRebootAllowed)
+             , (hvCpuMask,        PyValueEx cpuPinningAll)
+             , (hvCpuCap,         PyValueEx (0 :: Int))
+             , (hvCpuWeight,      PyValueEx (256 :: Int))
+             , (hvVifType,        PyValueEx htHvmVifIoemu)
+             , (hvVifScript,      PyValueEx "")
+             , (hvViridian,       PyValueEx False)
+             , (hvXenCmd,         PyValueEx xenCmdXm)
+             , (hvXenCpuid,       PyValueEx "")
+             , (hvSoundhw,        PyValueEx "")
+             ])
+  , (Kvm, Map.fromList
+          [ (hvKvmPath,                         PyValueEx kvmPath)
+          , (hvKernelPath,                      PyValueEx kvmKernel)
+          , (hvInitrdPath,                      PyValueEx "")
+          , (hvKernelArgs,                      PyValueEx "ro")
+          , (hvRootPath,                        PyValueEx "/dev/vda1")
+          , (hvAcpi,                            PyValueEx True)
+          , (hvSerialConsole,                   PyValueEx True)
+          , (hvSerialSpeed,                     PyValueEx (38400 :: Int))
+          , (hvVncBindAddress,                  PyValueEx "")
+          , (hvVncTls,                          PyValueEx False)
+          , (hvVncX509,                         PyValueEx "")
+          , (hvVncX509Verify,                   PyValueEx False)
+          , (hvVncPasswordFile,                 PyValueEx "")
+          , (hvKvmSpiceBind,                    PyValueEx "")
+          , (hvKvmSpiceIpVersion,           PyValueEx ifaceNoIpVersionSpecified)
+          , (hvKvmSpicePasswordFile,            PyValueEx "")
+          , (hvKvmSpiceLosslessImgCompr,        PyValueEx "")
+          , (hvKvmSpiceJpegImgCompr,            PyValueEx "")
+          , (hvKvmSpiceZlibGlzImgCompr,         PyValueEx "")
+          , (hvKvmSpiceStreamingVideoDetection, PyValueEx "")
+          , (hvKvmSpiceAudioCompr,              PyValueEx True)
+          , (hvKvmSpiceUseTls,                  PyValueEx False)
+          , (hvKvmSpiceTlsCiphers,              PyValueEx opensslCiphers)
+          , (hvKvmSpiceUseVdagent,              PyValueEx True)
+          , (hvKvmFloppyImagePath,              PyValueEx "")
+          , (hvCdromImagePath,                  PyValueEx "")
+          , (hvKvmCdrom2ImagePath,              PyValueEx "")
+          , (hvBootOrder,                       PyValueEx htBoDisk)
+          , (hvNicType,                         PyValueEx htNicParavirtual)
+          , (hvDiskType,                        PyValueEx htDiskParavirtual)
+          , (hvKvmCdromDiskType,                PyValueEx "")
+          , (hvUsbMouse,                        PyValueEx "")
+          , (hvKeymap,                          PyValueEx "")
+          , (hvMigrationPort,                   PyValueEx (8102 :: Int))
+          , (hvMigrationBandwidth,              PyValueEx (32 :: Int))
+          , (hvMigrationDowntime,               PyValueEx (30 :: Int))
+          , (hvMigrationMode,                   PyValueEx htMigrationLive)
+          , (hvUseLocaltime,                    PyValueEx False)
+          , (hvDiskCache,                       PyValueEx htCacheDefault)
+          , (hvSecurityModel,                   PyValueEx htSmNone)
+          , (hvSecurityDomain,                  PyValueEx "")
+          , (hvKvmFlag,                         PyValueEx "")
+          , (hvVhostNet,                        PyValueEx False)
+          , (hvKvmUseChroot,                    PyValueEx False)
+          , (hvMemPath,                         PyValueEx "")
+          , (hvRebootBehavior,                  PyValueEx instanceRebootAllowed)
+          , (hvCpuMask,                         PyValueEx cpuPinningAll)
+          , (hvCpuType,                         PyValueEx "")
+          , (hvCpuCores,                        PyValueEx (0 :: Int))
+          , (hvCpuThreads,                      PyValueEx (0 :: Int))
+          , (hvCpuSockets,                      PyValueEx (0 :: Int))
+          , (hvSoundhw,                         PyValueEx "")
+          , (hvUsbDevices,                      PyValueEx "")
+          , (hvVga,                             PyValueEx "")
+          , (hvKvmExtra,                        PyValueEx "")
+          , (hvKvmMachineVersion,               PyValueEx "")
+          , (hvVnetHdr,                         PyValueEx True)])
+  , (Fake, Map.fromList [(hvMigrationMode, PyValueEx htMigrationLive)])
+  , (Chroot, Map.fromList [(hvInitScript, PyValueEx "/ganeti-chroot")])
+  , (Lxc, Map.fromList [(hvCpuMask, PyValueEx "")])
+  ]
+
+hvcGlobals :: FrozenSet String
+hvcGlobals =
+  ConstantUtils.mkSet [hvMigrationBandwidth,
+                       hvMigrationMode,
+                       hvMigrationPort,
+                       hvXenCmd]
+
+becDefaults :: Map String PyValueEx
+becDefaults =
+  Map.fromList
+  [ (beMinmem, PyValueEx (128 :: Int))
+  , (beMaxmem, PyValueEx (128 :: Int))
+  , (beVcpus, PyValueEx (1 :: Int))
+  , (beAutoBalance, PyValueEx True)
+  , (beAlwaysFailover, PyValueEx False)
+  , (beSpindleUse, PyValueEx (1 :: Int))
+  ]
+
+ndcDefaults :: Map String PyValueEx
+ndcDefaults =
+  Map.fromList
+  [ (ndOobProgram,       PyValueEx "")
+  , (ndSpindleCount,     PyValueEx (1 :: Int))
+  , (ndExclusiveStorage, PyValueEx False)
+  , (ndOvs,              PyValueEx False)
+  , (ndOvsName,          PyValueEx defaultOvs)
+  , (ndOvsLink,          PyValueEx "")
+  ]
+
+ndcGlobals :: FrozenSet String
+ndcGlobals = ConstantUtils.mkSet [ndExclusiveStorage]
+
+-- | Default delay target measured in sectors
+defaultDelayTarget :: Int
+defaultDelayTarget = 1
+
+defaultDiskCustom :: String
+defaultDiskCustom = ""
+
+defaultDiskResync :: Bool
+defaultDiskResync = False
+
+-- | Default fill target measured in sectors
+defaultFillTarget :: Int
+defaultFillTarget = 0
+
+-- | Default mininum rate measured in KiB/s
+defaultMinRate :: Int
+defaultMinRate = 4 * 1024
+
+defaultNetCustom :: String
+defaultNetCustom = ""
+
+-- | Default plan ahead measured in sectors
+--
+-- The default values for the DRBD dynamic resync speed algorithm are
+-- taken from the drbsetup 8.3.11 man page, except for c-plan-ahead
+-- (that we don't need to set to 0, because we have a separate option
+-- to enable it) and for c-max-rate, that we cap to the default value
+-- for the static resync rate.
+defaultPlanAhead :: Int
+defaultPlanAhead = 20
+
+defaultRbdPool :: String
+defaultRbdPool = "rbd"
+
+diskLdDefaults :: Map DiskTemplate (Map String PyValueEx)
+diskLdDefaults =
+  Map.fromList
+  [ (DTBlock, Map.empty)
+  , (DTDrbd8, Map.fromList
+              [ (ldpBarriers,      PyValueEx drbdBarriers)
+              , (ldpDefaultMetavg, PyValueEx defaultVg)
+              , (ldpDelayTarget,   PyValueEx defaultDelayTarget)
+              , (ldpDiskCustom,    PyValueEx defaultDiskCustom)
+              , (ldpDynamicResync, PyValueEx defaultDiskResync)
+              , (ldpFillTarget,    PyValueEx defaultFillTarget)
+              , (ldpMaxRate,       PyValueEx classicDrbdSyncSpeed)
+              , (ldpMinRate,       PyValueEx defaultMinRate)
+              , (ldpNetCustom,     PyValueEx defaultNetCustom)
+              , (ldpNoMetaFlush,   PyValueEx drbdNoMetaFlush)
+              , (ldpPlanAhead,     PyValueEx defaultPlanAhead)
+              , (ldpProtocol,      PyValueEx drbdDefaultNetProtocol)
+              , (ldpResyncRate,    PyValueEx classicDrbdSyncSpeed)
+              ])
+  , (DTExt, Map.empty)
+  , (DTFile, Map.empty)
+  , (DTPlain, Map.fromList [(ldpStripes, PyValueEx lvmStripecount)])
+  , (DTRbd, Map.fromList
+            [ (ldpPool, PyValueEx defaultRbdPool)
+            , (ldpAccess, PyValueEx diskKernelspace)
+            ])
+  , (DTSharedFile, Map.empty)
+  ]
+
+diskDtDefaults :: Map DiskTemplate (Map String PyValueEx)
+diskDtDefaults =
+  Map.fromList
+  [ (DTBlock,      Map.empty)
+  , (DTDiskless,   Map.empty)
+  , (DTDrbd8,      Map.fromList
+                   [ (drbdDataStripes,   PyValueEx lvmStripecount)
+                   , (drbdDefaultMetavg, PyValueEx defaultVg)
+                   , (drbdDelayTarget,   PyValueEx defaultDelayTarget)
+                   , (drbdDiskBarriers,  PyValueEx drbdBarriers)
+                   , (drbdDiskCustom,    PyValueEx defaultDiskCustom)
+                   , (drbdDynamicResync, PyValueEx defaultDiskResync)
+                   , (drbdFillTarget,    PyValueEx defaultFillTarget)
+                   , (drbdMaxRate,       PyValueEx classicDrbdSyncSpeed)
+                   , (drbdMetaBarriers,  PyValueEx drbdNoMetaFlush)
+                   , (drbdMetaStripes,   PyValueEx lvmStripecount)
+                   , (drbdMinRate,       PyValueEx defaultMinRate)
+                   , (drbdNetCustom,     PyValueEx defaultNetCustom)
+                   , (drbdPlanAhead,     PyValueEx defaultPlanAhead)
+                   , (drbdProtocol,      PyValueEx drbdDefaultNetProtocol)
+                   , (drbdResyncRate,    PyValueEx classicDrbdSyncSpeed)
+                   ])
+  , (DTExt,        Map.empty)
+  , (DTFile,       Map.empty)
+  , (DTPlain,      Map.fromList [(lvStripes, PyValueEx lvmStripecount)])
+  , (DTRbd,        Map.fromList
+                   [ (rbdPool, PyValueEx defaultRbdPool)
+                   , (rbdAccess, PyValueEx diskKernelspace)
+                   ])
+  , (DTSharedFile, Map.empty)
+  ]
+
+niccDefaults :: Map String PyValueEx
+niccDefaults =
+  Map.fromList
+  [ (nicMode, PyValueEx nicModeBridged)
+  , (nicLink, PyValueEx defaultBridge)
+  , (nicVlan, PyValueEx "")
+  ]
+
+-- | All of the following values are quite arbitrary - there are no
+-- "good" defaults, these must be customised per-site
+ispecsMinmaxDefaults :: Map String (Map String Int)
+ispecsMinmaxDefaults =
+  Map.fromList
+  [(ispecsMin,
+    Map.fromList
+    [(ConstantUtils.ispecMemSize, Types.iSpecMemorySize Types.defMinISpec),
+     (ConstantUtils.ispecCpuCount, Types.iSpecCpuCount Types.defMinISpec),
+     (ConstantUtils.ispecDiskCount, Types.iSpecDiskCount Types.defMinISpec),
+     (ConstantUtils.ispecDiskSize, Types.iSpecDiskSize Types.defMinISpec),
+     (ConstantUtils.ispecNicCount, Types.iSpecNicCount Types.defMinISpec),
+     (ConstantUtils.ispecSpindleUse, Types.iSpecSpindleUse Types.defMinISpec)]),
+   (ispecsMax,
+    Map.fromList
+    [(ConstantUtils.ispecMemSize, Types.iSpecMemorySize Types.defMaxISpec),
+     (ConstantUtils.ispecCpuCount, Types.iSpecCpuCount Types.defMaxISpec),
+     (ConstantUtils.ispecDiskCount, Types.iSpecDiskCount Types.defMaxISpec),
+     (ConstantUtils.ispecDiskSize, Types.iSpecDiskSize Types.defMaxISpec),
+     (ConstantUtils.ispecNicCount, Types.iSpecNicCount Types.defMaxISpec),
+     (ConstantUtils.ispecSpindleUse, Types.iSpecSpindleUse Types.defMaxISpec)])]
+
+ipolicyDefaults :: Map String PyValueEx
+ipolicyDefaults =
+  Map.fromList
+  [ (ispecsMinmax,        PyValueEx [ispecsMinmaxDefaults])
+  , (ispecsStd,           PyValueEx (Map.fromList
+                                     [ (ispecMemSize,    128)
+                                     , (ispecCpuCount,   1)
+                                     , (ispecDiskCount,  1)
+                                     , (ispecDiskSize,   1024)
+                                     , (ispecNicCount,   1)
+                                     , (ispecSpindleUse, 1)
+                                     ] :: Map String Int))
+  , (ipolicyDts,          PyValueEx (ConstantUtils.toList diskTemplates))
+  , (ipolicyVcpuRatio,    PyValueEx (4.0 :: Double))
+  , (ipolicySpindleRatio, PyValueEx (32.0 :: Double))
+  ]
+
+masterPoolSizeDefault :: Int
+masterPoolSizeDefault = 10
+
+-- * Exclusive storage
+
+-- | Error margin used to compare physical disks
+partMargin :: Double
+partMargin = 0.01
+
+-- | Space reserved when creating instance disks
+partReserved :: Double
+partReserved = 0.02
+
+-- * Confd
+
+confdProtocolVersion :: Int
+confdProtocolVersion = ConstantUtils.confdProtocolVersion
+
+-- Confd request type
+
+confdReqPing :: Int
+confdReqPing = Types.confdRequestTypeToRaw ReqPing
+
+confdReqNodeRoleByname :: Int
+confdReqNodeRoleByname = Types.confdRequestTypeToRaw ReqNodeRoleByName
+
+confdReqNodePipByInstanceIp :: Int
+confdReqNodePipByInstanceIp = Types.confdRequestTypeToRaw ReqNodePipByInstPip
+
+confdReqClusterMaster :: Int
+confdReqClusterMaster = Types.confdRequestTypeToRaw ReqClusterMaster
+
+confdReqNodePipList :: Int
+confdReqNodePipList = Types.confdRequestTypeToRaw ReqNodePipList
+
+confdReqMcPipList :: Int
+confdReqMcPipList = Types.confdRequestTypeToRaw ReqMcPipList
+
+confdReqInstancesIpsList :: Int
+confdReqInstancesIpsList = Types.confdRequestTypeToRaw ReqInstIpsList
+
+confdReqNodeDrbd :: Int
+confdReqNodeDrbd = Types.confdRequestTypeToRaw ReqNodeDrbd
+
+confdReqNodeInstances :: Int
+confdReqNodeInstances = Types.confdRequestTypeToRaw ReqNodeInstances
+
+confdReqs :: FrozenSet Int
+confdReqs =
+  ConstantUtils.mkSet .
+  map Types.confdRequestTypeToRaw $
+  [minBound..] \\ [ReqNodeInstances]
+
+-- * Confd request type
+
+confdReqfieldName :: Int
+confdReqfieldName = Types.confdReqFieldToRaw ReqFieldName
+
+confdReqfieldIp :: Int
+confdReqfieldIp = Types.confdReqFieldToRaw ReqFieldIp
+
+confdReqfieldMnodePip :: Int
+confdReqfieldMnodePip = Types.confdReqFieldToRaw ReqFieldMNodePip
+
+-- * Confd repl status
+
+confdReplStatusOk :: Int
+confdReplStatusOk = Types.confdReplyStatusToRaw ReplyStatusOk
+
+confdReplStatusError :: Int
+confdReplStatusError = Types.confdReplyStatusToRaw ReplyStatusError
+
+confdReplStatusNotimplemented :: Int
+confdReplStatusNotimplemented = Types.confdReplyStatusToRaw ReplyStatusNotImpl
+
+confdReplStatuses :: FrozenSet Int
+confdReplStatuses =
+  ConstantUtils.mkSet $ map Types.confdReplyStatusToRaw [minBound..]
+
+-- * Confd node role
+
+confdNodeRoleMaster :: Int
+confdNodeRoleMaster = Types.confdNodeRoleToRaw NodeRoleMaster
+
+confdNodeRoleCandidate :: Int
+confdNodeRoleCandidate = Types.confdNodeRoleToRaw NodeRoleCandidate
+
+confdNodeRoleOffline :: Int
+confdNodeRoleOffline = Types.confdNodeRoleToRaw NodeRoleOffline
+
+confdNodeRoleDrained :: Int
+confdNodeRoleDrained = Types.confdNodeRoleToRaw NodeRoleDrained
+
+confdNodeRoleRegular :: Int
+confdNodeRoleRegular = Types.confdNodeRoleToRaw NodeRoleRegular
+
+-- * A few common errors for confd
+
+confdErrorUnknownEntry :: Int
+confdErrorUnknownEntry = Types.confdErrorTypeToRaw ConfdErrorUnknownEntry
+
+confdErrorInternal :: Int
+confdErrorInternal = Types.confdErrorTypeToRaw ConfdErrorInternal
+
+confdErrorArgument :: Int
+confdErrorArgument = Types.confdErrorTypeToRaw ConfdErrorArgument
+
+-- * Confd request query fields
+
+confdReqqLink :: String
+confdReqqLink = ConstantUtils.confdReqqLink
+
+confdReqqIp :: String
+confdReqqIp = ConstantUtils.confdReqqIp
+
+confdReqqIplist :: String
+confdReqqIplist = ConstantUtils.confdReqqIplist
+
+confdReqqFields :: String
+confdReqqFields = ConstantUtils.confdReqqFields
+
+-- | Each request is "salted" by the current timestamp.
+--
+-- This constant decides how many seconds of skew to accept.
+--
+-- TODO: make this a default and allow the value to be more
+-- configurable
+confdMaxClockSkew :: Int
+confdMaxClockSkew = 2 * nodeMaxClockSkew
+
+-- | When we haven't reloaded the config for more than this amount of
+-- seconds, we force a test to see if inotify is betraying us. Using a
+-- prime number to ensure we get less chance of 'same wakeup' with
+-- other processes.
+confdConfigReloadTimeout :: Int
+confdConfigReloadTimeout = 17
+
+-- | If we receive more than one update in this amount of
+-- microseconds, we move to polling every RATELIMIT seconds, rather
+-- than relying on inotify, to be able to serve more requests.
+confdConfigReloadRatelimit :: Int
+confdConfigReloadRatelimit = 250000
+
+-- | Magic number prepended to all confd queries.
+--
+-- This allows us to distinguish different types of confd protocols
+-- and handle them. For example by changing this we can move the whole
+-- payload to be compressed, or move away from json.
+confdMagicFourcc :: String
+confdMagicFourcc = "plj0"
+
+-- | By default a confd request is sent to the minimum between this
+-- number and all MCs. 6 was chosen because even in the case of a
+-- disastrous 50% response rate, we should have enough answers to be
+-- able to compare more than one.
+confdDefaultReqCoverage :: Int
+confdDefaultReqCoverage = 6
+
+-- | Timeout in seconds to expire pending query request in the confd
+-- client library. We don't actually expect any answer more than 10
+-- seconds after we sent a request.
+confdClientExpireTimeout :: Int
+confdClientExpireTimeout = 10
+
+-- | Maximum UDP datagram size.
+--
+-- On IPv4: 64K - 20 (ip header size) - 8 (udp header size) = 65507
+-- On IPv6: 64K - 40 (ip6 header size) - 8 (udp header size) = 65487
+--   (assuming we can't use jumbo frames)
+-- We just set this to 60K, which should be enough
+maxUdpDataSize :: Int
+maxUdpDataSize = 61440
+
+-- * User-id pool minimum/maximum acceptable user-ids
+
+uidpoolUidMin :: Int
+uidpoolUidMin = 0
+
+-- | Assuming 32 bit user-ids
+uidpoolUidMax :: Integer
+uidpoolUidMax = 2 ^ 32 - 1
+
+-- | Name or path of the pgrep command
+pgrep :: String
+pgrep = "pgrep"
+
+-- | Name of the node group that gets created at cluster init or
+-- upgrade
+initialNodeGroupName :: String
+initialNodeGroupName = "default"
+
+-- * Possible values for NodeGroup.alloc_policy
+
+allocPolicyLastResort :: String
+allocPolicyLastResort = Types.allocPolicyToRaw AllocLastResort
+
+allocPolicyPreferred :: String
+allocPolicyPreferred = Types.allocPolicyToRaw AllocPreferred
+
+allocPolicyUnallocable :: String
+allocPolicyUnallocable = Types.allocPolicyToRaw AllocUnallocable
+
+validAllocPolicies :: [String]
+validAllocPolicies = map Types.allocPolicyToRaw [minBound..]
+
+-- | Temporary external/shared storage parameters
+blockdevDriverManual :: String
+blockdevDriverManual = Types.blockDriverToRaw BlockDrvManual
+
+-- | 'qemu-img' path, required for 'ovfconverter'
+qemuimgPath :: String
+qemuimgPath = AutoConf.qemuimgPath
+
+-- | Whether htools was enabled at compilation time
+--
+-- FIXME: this should be moved next to the other enable constants,
+-- such as, 'enableConfd', and renamed to 'enableHtools'.
+htools :: Bool
+htools = AutoConf.htools
+
+-- | The hail iallocator
+iallocHail :: String
+iallocHail = "hail"
+
+-- * Fake opcodes for functions that have hooks attached to them via
+-- backend.RunLocalHooks
+
+fakeOpMasterTurndown :: String
+fakeOpMasterTurndown = "OP_CLUSTER_IP_TURNDOWN"
+
+fakeOpMasterTurnup :: String
+fakeOpMasterTurnup = "OP_CLUSTER_IP_TURNUP"
+
+-- * SSH key types
+
+sshkDsa :: String
+sshkDsa = "dsa"
+
+sshkRsa :: String
+sshkRsa = "rsa"
+
+sshkAll :: FrozenSet String
+sshkAll = ConstantUtils.mkSet [sshkRsa, sshkDsa]
+
+-- * SSH authorized key types
+
+sshakDss :: String
+sshakDss = "ssh-dss"
+
+sshakRsa :: String
+sshakRsa = "ssh-rsa"
+
+sshakAll :: FrozenSet String
+sshakAll = ConstantUtils.mkSet [sshakDss, sshakRsa]
+
+-- * SSH setup
+
+sshsClusterName :: String
+sshsClusterName = "cluster_name"
+
+sshsSshHostKey :: String
+sshsSshHostKey = "ssh_host_key"
+
+sshsSshRootKey :: String
+sshsSshRootKey = "ssh_root_key"
+
+sshsNodeDaemonCertificate :: String
+sshsNodeDaemonCertificate = "node_daemon_certificate"
+
+-- * Key files for SSH daemon
+
+sshHostDsaPriv :: String
+sshHostDsaPriv = sshConfigDir ++ "/ssh_host_dsa_key"
+
+sshHostDsaPub :: String
+sshHostDsaPub = sshHostDsaPriv ++ ".pub"
+
+sshHostRsaPriv :: String
+sshHostRsaPriv = sshConfigDir ++ "/ssh_host_rsa_key"
+
+sshHostRsaPub :: String
+sshHostRsaPub = sshHostRsaPriv ++ ".pub"
+
+sshDaemonKeyfiles :: Map String (String, String)
+sshDaemonKeyfiles =
+  Map.fromList [ (sshkRsa, (sshHostRsaPriv, sshHostRsaPub))
+               , (sshkDsa, (sshHostDsaPriv, sshHostDsaPub))
+               ]
+
+-- * Node daemon setup
+
+ndsClusterName :: String
+ndsClusterName = "cluster_name"
+
+ndsNodeDaemonCertificate :: String
+ndsNodeDaemonCertificate = "node_daemon_certificate"
+
+ndsSsconf :: String
+ndsSsconf = "ssconf"
+
+ndsStartNodeDaemon :: String
+ndsStartNodeDaemon = "start_node_daemon"
+
+-- * The source reasons for the execution of an OpCode
+
+opcodeReasonSrcClient :: String
+opcodeReasonSrcClient = "gnt:client"
+
+opcodeReasonSrcNoded :: String
+opcodeReasonSrcNoded = "gnt:daemon:noded"
+
+opcodeReasonSrcOpcode :: String
+opcodeReasonSrcOpcode = "gnt:opcode"
+
+opcodeReasonSrcRlib2 :: String
+opcodeReasonSrcRlib2 = "gnt:library:rlib2"
+
+opcodeReasonSrcUser :: String
+opcodeReasonSrcUser = "gnt:user"
+
+opcodeReasonSources :: FrozenSet String
+opcodeReasonSources =
+  ConstantUtils.mkSet [opcodeReasonSrcClient,
+                       opcodeReasonSrcNoded,
+                       opcodeReasonSrcOpcode,
+                       opcodeReasonSrcRlib2,
+                       opcodeReasonSrcUser]
+
+-- | Path generating random UUID
+randomUuidFile :: String
+randomUuidFile = ConstantUtils.randomUuidFile
+
+-- * Auto-repair tag prefixes
+
+autoRepairTagPrefix :: String
+autoRepairTagPrefix = "ganeti:watcher:autorepair:"
+
+autoRepairTagEnabled :: String
+autoRepairTagEnabled = autoRepairTagPrefix
+
+autoRepairTagPending :: String
+autoRepairTagPending = autoRepairTagPrefix ++ "pending:"
+
+autoRepairTagResult :: String
+autoRepairTagResult = autoRepairTagPrefix ++ "result:"
+
+autoRepairTagSuspended :: String
+autoRepairTagSuspended = autoRepairTagPrefix ++ "suspend:"
+
+-- * Auto-repair levels
+
+autoRepairFailover :: String
+autoRepairFailover = Types.autoRepairTypeToRaw ArFailover
+
+autoRepairFixStorage :: String
+autoRepairFixStorage = Types.autoRepairTypeToRaw ArFixStorage
+
+autoRepairMigrate :: String
+autoRepairMigrate = Types.autoRepairTypeToRaw ArMigrate
+
+autoRepairReinstall :: String
+autoRepairReinstall = Types.autoRepairTypeToRaw ArReinstall
+
+autoRepairAllTypes :: FrozenSet String
+autoRepairAllTypes =
+  ConstantUtils.mkSet [autoRepairFailover,
+                       autoRepairFixStorage,
+                       autoRepairMigrate,
+                       autoRepairReinstall]
+
+-- * Auto-repair results
+
+autoRepairEnoperm :: String
+autoRepairEnoperm = Types.autoRepairResultToRaw ArEnoperm
+
+autoRepairFailure :: String
+autoRepairFailure = Types.autoRepairResultToRaw ArFailure
+
+autoRepairSuccess :: String
+autoRepairSuccess = Types.autoRepairResultToRaw ArSuccess
+
+autoRepairAllResults :: FrozenSet String
+autoRepairAllResults =
+  ConstantUtils.mkSet [autoRepairEnoperm, autoRepairFailure, autoRepairSuccess]
+
+-- | The version identifier for builtin data collectors
+builtinDataCollectorVersion :: String
+builtinDataCollectorVersion = "B"
+
+-- | The reason trail opcode parameter name
+opcodeReason :: String
+opcodeReason = "reason"
+
+-- | The reason trail opcode parameter name
+opcodeSequential :: String
+opcodeSequential = "sequential"
+
+diskstatsFile :: String
+diskstatsFile = "/proc/diskstats"
+
+-- *  CPU load collector
+
+statFile :: String
+statFile = "/proc/stat"
+
+cpuavgloadBufferSize :: Int
+cpuavgloadBufferSize = 150
+
+cpuavgloadWindowSize :: Int
+cpuavgloadWindowSize = 600
+
+-- * Monitoring daemon
+
+-- | Mond's variable for periodical data collection
+mondTimeInterval :: Int
+mondTimeInterval = 5
+
+-- | Mond's latest API version
+mondLatestApiVersion :: Int
+mondLatestApiVersion = 1
+
+-- * Disk access modes
+
+diskUserspace :: String
+diskUserspace = Types.diskAccessModeToRaw DiskUserspace
+
+diskKernelspace :: String
+diskKernelspace = Types.diskAccessModeToRaw DiskKernelspace
+
+diskValidAccessModes :: FrozenSet String
+diskValidAccessModes =
+  ConstantUtils.mkSet $ map Types.diskAccessModeToRaw [minBound..]
+
+-- | Timeout for queue draining in upgrades
+upgradeQueueDrainTimeout :: Int
+upgradeQueueDrainTimeout = 36 * 60 * 60 -- 1.5 days
+
+-- | Intervall at which the queue is polled during upgrades
+upgradeQueuePollInterval :: Int
+upgradeQueuePollInterval  = 10
+
+-- * Hotplug Actions
+
+hotplugActionAdd :: String
+hotplugActionAdd = Types.hotplugActionToRaw HAAdd
+
+hotplugActionRemove :: String
+hotplugActionRemove = Types.hotplugActionToRaw HARemove
+
+hotplugActionModify :: String
+hotplugActionModify = Types.hotplugActionToRaw HAMod
+
+hotplugAllActions :: FrozenSet String
+hotplugAllActions =
+  ConstantUtils.mkSet $ map Types.hotplugActionToRaw [minBound..]
+
+-- * Hotplug Device Targets
+
+hotplugTargetNic :: String
+hotplugTargetNic = Types.hotplugTargetToRaw HTNic
+
+hotplugTargetDisk :: String
+hotplugTargetDisk = Types.hotplugTargetToRaw HTDisk
+
+hotplugAllTargets :: FrozenSet String
+hotplugAllTargets =
+  ConstantUtils.mkSet $ map Types.hotplugTargetToRaw [minBound..]
+
+-- | Timeout for disk removal (seconds)
+diskRemoveRetryTimeout :: Int
+diskRemoveRetryTimeout = 30
+
+-- | Interval between disk removal retries (seconds)
+diskRemoveRetryInterval :: Int
+diskRemoveRetryInterval  = 3
+
+-- * UUID regex
+
+uuidRegex :: String
+uuidRegex = "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
+
+-- * Luxi constants
+
+luxiSocketPerms :: Int
+luxiSocketPerms = 0o660
+
+luxiKeyMethod :: String
+luxiKeyMethod = "method"
+
+luxiKeyArgs :: String
+luxiKeyArgs = "args"
+
+luxiKeySuccess :: String
+luxiKeySuccess = "success"
+
+luxiKeyResult :: String
+luxiKeyResult = "result"
+
+luxiKeyVersion :: String
+luxiKeyVersion = "version"
+
+luxiReqSubmitJob :: String
+luxiReqSubmitJob = "SubmitJob"
+
+luxiReqSubmitJobToDrainedQueue :: String
+luxiReqSubmitJobToDrainedQueue = "SubmitJobToDrainedQueue"
+
+luxiReqSubmitManyJobs :: String
+luxiReqSubmitManyJobs = "SubmitManyJobs"
+
+luxiReqWaitForJobChange :: String
+luxiReqWaitForJobChange = "WaitForJobChange"
+
+luxiReqCancelJob :: String
+luxiReqCancelJob = "CancelJob"
+
+luxiReqArchiveJob :: String
+luxiReqArchiveJob = "ArchiveJob"
+
+luxiReqChangeJobPriority :: String
+luxiReqChangeJobPriority = "ChangeJobPriority"
+
+luxiReqAutoArchiveJobs :: String
+luxiReqAutoArchiveJobs = "AutoArchiveJobs"
+
+luxiReqQuery :: String
+luxiReqQuery = "Query"
+
+luxiReqQueryFields :: String
+luxiReqQueryFields = "QueryFields"
+
+luxiReqQueryJobs :: String
+luxiReqQueryJobs = "QueryJobs"
+
+luxiReqQueryInstances :: String
+luxiReqQueryInstances = "QueryInstances"
+
+luxiReqQueryNodes :: String
+luxiReqQueryNodes = "QueryNodes"
+
+luxiReqQueryGroups :: String
+luxiReqQueryGroups = "QueryGroups"
+
+luxiReqQueryNetworks :: String
+luxiReqQueryNetworks = "QueryNetworks"
+
+luxiReqQueryExports :: String
+luxiReqQueryExports = "QueryExports"
+
+luxiReqQueryConfigValues :: String
+luxiReqQueryConfigValues = "QueryConfigValues"
+
+luxiReqQueryClusterInfo :: String
+luxiReqQueryClusterInfo = "QueryClusterInfo"
+
+luxiReqQueryTags :: String
+luxiReqQueryTags = "QueryTags"
+
+luxiReqSetDrainFlag :: String
+luxiReqSetDrainFlag = "SetDrainFlag"
+
+luxiReqSetWatcherPause :: String
+luxiReqSetWatcherPause = "SetWatcherPause"
+
+luxiReqAll :: FrozenSet String
+luxiReqAll =
+  ConstantUtils.mkSet
+  [ luxiReqArchiveJob
+  , luxiReqAutoArchiveJobs
+  , luxiReqCancelJob
+  , luxiReqChangeJobPriority
+  , luxiReqQuery
+  , luxiReqQueryClusterInfo
+  , luxiReqQueryConfigValues
+  , luxiReqQueryExports
+  , luxiReqQueryFields
+  , luxiReqQueryGroups
+  , luxiReqQueryInstances
+  , luxiReqQueryJobs
+  , luxiReqQueryNodes
+  , luxiReqQueryNetworks
+  , luxiReqQueryTags
+  , luxiReqSetDrainFlag
+  , luxiReqSetWatcherPause
+  , luxiReqSubmitJob
+  , luxiReqSubmitJobToDrainedQueue
+  , luxiReqSubmitManyJobs
+  , luxiReqWaitForJobChange
+  ]
+
+luxiDefCtmo :: Int
+luxiDefCtmo = 10
+
+luxiDefRwto :: Int
+luxiDefRwto = 60
+
+-- | 'WaitForJobChange' timeout
+luxiWfjcTimeout :: Int
+luxiWfjcTimeout = (luxiDefRwto - 1) `div` 2
+
+-- * Query language constants
+
+-- ** Logic operators with one or more operands, each of which is a
+-- filter on its own
+
+qlangOpAnd :: String
+qlangOpAnd = "&"
+
+qlangOpOr :: String
+qlangOpOr = "|"
+
+-- ** Unary operators with exactly one operand
+
+qlangOpNot :: String
+qlangOpNot = "!"
+
+qlangOpTrue :: String
+qlangOpTrue = "?"
+
+-- ** Binary operators with exactly two operands, the field name and
+-- an operator-specific value
+
+qlangOpContains :: String
+qlangOpContains = "=[]"
+
+qlangOpEqual :: String
+qlangOpEqual = "="
+
+qlangOpGe :: String
+qlangOpGe = ">="
+
+qlangOpGt :: String
+qlangOpGt = ">"
+
+qlangOpLe :: String
+qlangOpLe = "<="
+
+qlangOpLt :: String
+qlangOpLt = "<"
+
+qlangOpNotEqual :: String
+qlangOpNotEqual = "!="
+
+qlangOpRegexp :: String
+qlangOpRegexp = "=~"
+
+-- | Characters used for detecting user-written filters (see
+-- L{_CheckFilter})
+
+qlangFilterDetectionChars :: FrozenSet String
+qlangFilterDetectionChars =
+  ConstantUtils.mkSet ["!", " ", "\"", "\'",
+                       ")", "(", "\x0b", "\n",
+                       "\r", "\x0c", "/", "<",
+                       "\t", ">", "=", "\\", "~"]
+
+-- | Characters used to detect globbing filters
+qlangGlobDetectionChars :: FrozenSet String
+qlangGlobDetectionChars = ConstantUtils.mkSet ["*", "?"]
+
+-- * Error related constants
+--
+-- 'OpPrereqError' failure types
+
+-- | Environment error (e.g. node disk error)
+errorsEcodeEnviron :: String
+errorsEcodeEnviron = "environment_error"
+
+-- | Entity already exists
+errorsEcodeExists :: String
+errorsEcodeExists = "already_exists"
+
+-- | Internal cluster error
+errorsEcodeFault :: String
+errorsEcodeFault = "internal_error"
+
+-- | Wrong arguments (at syntax level)
+errorsEcodeInval :: String
+errorsEcodeInval = "wrong_input"
+
+-- | Entity not found
+errorsEcodeNoent :: String
+errorsEcodeNoent = "unknown_entity"
+
+-- | Not enough resources (iallocator failure, disk space, memory, etc)
+errorsEcodeNores :: String
+errorsEcodeNores = "insufficient_resources"
+
+-- | Resource not unique (e.g. MAC or IP duplication)
+errorsEcodeNotunique :: String
+errorsEcodeNotunique = "resource_not_unique"
+
+-- | Resolver errors
+errorsEcodeResolver :: String
+errorsEcodeResolver = "resolver_error"
+
+-- | Wrong entity state
+errorsEcodeState :: String
+errorsEcodeState = "wrong_state"
+
+-- | Temporarily out of resources; operation can be tried again
+errorsEcodeTempNores :: String
+errorsEcodeTempNores = "temp_insufficient_resources"
+
+errorsEcodeAll :: FrozenSet String
+errorsEcodeAll =
+  ConstantUtils.mkSet [ errorsEcodeNores
+                      , errorsEcodeExists
+                      , errorsEcodeState
+                      , errorsEcodeNotunique
+                      , errorsEcodeTempNores
+                      , errorsEcodeNoent
+                      , errorsEcodeFault
+                      , errorsEcodeResolver
+                      , errorsEcodeInval
+                      , errorsEcodeEnviron
+                      ]
+
+-- * Jstore related constants
+
+jstoreJobsPerArchiveDirectory :: Int
+jstoreJobsPerArchiveDirectory = 10000
diff --git a/src/Ganeti/Constants.hs.in b/src/Ganeti/Constants.hs.in
deleted file mode 100644
index 7a52a9d..0000000
--- a/src/Ganeti/Constants.hs.in
+++ /dev/null
@@ -1,30 +0,0 @@
-{-| Ganeti constants.
-
-These are duplicated from the Python code. Note that this file is
-autogenerated using @autotools/convert_constants@ script with a header
-from @Constants.hs.in@.
-
--}
-
-{-
-
-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
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
-
--}
-
-module Ganeti.Constants where
diff --git a/src/Ganeti/Cpu/LoadParser.hs b/src/Ganeti/Cpu/LoadParser.hs
new file mode 100644
index 0000000..7be0759
--- /dev/null
+++ b/src/Ganeti/Cpu/LoadParser.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-| /proc/stat file parser
+
+This module holds the definition of the parser that extracts information
+about the CPU load of the system from the @/proc/stat@ file.
+
+-}
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+module Ganeti.Cpu.LoadParser (cpustatParser) where
+
+import Control.Applicative ((<*>), (<*), (*>), (<$>), (<|>))
+import qualified Data.Attoparsec.Text as A
+import qualified Data.Attoparsec.Combinator as AC
+import Data.Attoparsec.Text (Parser)
+
+import Ganeti.Parsers
+import Ganeti.Cpu.Types
+
+-- * Parser implementation
+
+-- | The parser for one line of the CPU status file.
+oneCPUstatParser :: Parser CPUstat
+oneCPUstatParser =
+  let nameP = stringP
+      userP = numberP
+      niceP = numberP
+      systemP = numberP
+      idleP = numberP
+      iowaitP = numberP
+      irqP = numberP
+      softirqP = numberP
+      stealP = numberP
+      guestP = numberP
+      guest_niceP = numberP
+  in
+    CPUstat <$> nameP <*> userP <*> niceP <*> systemP <*> idleP <*> iowaitP
+            <*> irqP <*> softirqP <*> stealP <*> guestP <*> guest_niceP
+            <* A.endOfLine
+
+-- | When this is satisfied all the lines containing information about
+-- the CPU load are parsed.
+intrFound :: Parser ()
+intrFound = (A.string "intr" *> return ())
+             <|> (A.string "page" *> return ())
+             <|> (A.string "swap" *> return ())
+
+-- | The parser for the fragment of CPU status file containing
+-- information about the CPU load.
+cpustatParser :: Parser [CPUstat]
+cpustatParser = oneCPUstatParser `AC.manyTill` intrFound
diff --git a/src/Ganeti/Cpu/Types.hs b/src/Ganeti/Cpu/Types.hs
new file mode 100644
index 0000000..cc67e4d
--- /dev/null
+++ b/src/Ganeti/Cpu/Types.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-| CPUload data types
+
+This module holds the definition of the data types describing the CPU
+load according to information collected periodically from @/proc/stat@.
+
+-}
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+module Ganeti.Cpu.Types
+  ( CPUstat(..)
+  , CPUavgload(..)
+  ) where
+
+import Ganeti.THH
+
+-- | This is the format of the report produced by the cpu load
+-- collector.
+$(buildObject "CPUavgload" "cav"
+  [ simpleField "cpu_number" [t| Int |]
+  , simpleField "cpus"       [t| [Double] |]
+  , simpleField "cpu_total"  [t| Double |]
+  ])
+
+-- | This is the format of the data parsed by the input file.
+$(buildObject "CPUstat" "cs"
+  [ simpleField "name"       [t| String |]
+  , simpleField "user"       [t| Int |]
+  , simpleField "nice"       [t| Int |]
+  , simpleField "system"     [t| Int |]
+  , simpleField "idle"       [t| Int |]
+  , simpleField "iowait"     [t| Int |]
+  , simpleField "irq"        [t| Int |]
+  , simpleField "softirq"    [t| Int |]
+  , simpleField "steal"      [t| Int |]
+  , simpleField "guest"      [t| Int |]
+  , simpleField "guest_nice" [t| Int |]
+  ])
diff --git a/src/Ganeti/Curl/Internal.hsc b/src/Ganeti/Curl/Internal.hsc
index b3a5160..5f7754c 100644
--- a/src/Ganeti/Curl/Internal.hsc
+++ b/src/Ganeti/Curl/Internal.hsc
@@ -11,21 +11,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Curl/Multi.hs b/src/Ganeti/Curl/Multi.hs
index d096502..5d7e766 100644
--- a/src/Ganeti/Curl/Multi.hs
+++ b/src/Ganeti/Curl/Multi.hs
@@ -13,21 +13,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Daemon.hs b/src/Ganeti/Daemon.hs
index a427c9c..9cc11ad 100644
--- a/src/Ganeti/Daemon.hs
+++ b/src/Ganeti/Daemon.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -298,10 +307,11 @@
 vClusterHostNameEnvVar :: String
 vClusterHostNameEnvVar = "GANETI_HOSTNAME"
 
-getFQDN :: IO String
-getFQDN = do
+-- | Get the real full qualified host name.
+getFQDN' :: Maybe Socket.AddrInfo -> IO String
+getFQDN' hints = do
   hostname <- getHostName
-  addrInfos <- Socket.getAddrInfo Nothing (Just hostname) Nothing
+  addrInfos <- Socket.getAddrInfo hints (Just hostname) Nothing
   let address = listToMaybe addrInfos >>= (Just . Socket.addrAddress)
   case address of
     Just a -> do
@@ -309,17 +319,32 @@
       return (fromMaybe hostname fqdn)
     Nothing -> return hostname
 
--- | Returns if the current node is the master node.
-isMaster :: IO Bool
-isMaster = do
+-- | Return the full qualified host name, honoring the vcluster setup
+-- and hints on the preferred socket type or protocol.
+getFQDNwithHints :: Maybe Socket.AddrInfo -> IO String
+getFQDNwithHints hints = do
   let ioErrorToNothing :: IOError -> IO (Maybe String)
       ioErrorToNothing _ = return Nothing
   vcluster_node <- Control.Exception.catch
                      (liftM Just (getEnv vClusterHostNameEnvVar))
                      ioErrorToNothing
-  curNode <- case vcluster_node of
+  case vcluster_node of
     Just node_name -> return node_name
-    Nothing -> getFQDN
+    Nothing -> getFQDN' hints
+
+-- | Return the full qualified host name, honoring the vcluster setup.
+getFQDN :: IO String
+getFQDN = do
+  familyresult <- Ssconf.getPrimaryIPFamily Nothing
+  getFQDNwithHints
+    $ genericResult (const Nothing)
+        (\family -> Just $ Socket.defaultHints { Socket.addrFamily = family })
+        familyresult
+
+-- | Returns if the current node is the master node.
+isMaster :: IO Bool
+isMaster = do
+  curNode <- getFQDN
   masterNode <- Ssconf.getMasterNode Nothing
   case masterNode of
     Ok n -> return (curNode == n)
diff --git a/src/Ganeti/DataCollectors/CLI.hs b/src/Ganeti/DataCollectors/CLI.hs
index d807d07..6f5b035 100644
--- a/src/Ganeti/DataCollectors/CLI.hs
+++ b/src/Ganeti/DataCollectors/CLI.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/CPUload.hs b/src/Ganeti/DataCollectors/CPUload.hs
new file mode 100644
index 0000000..e79b87d
--- /dev/null
+++ b/src/Ganeti/DataCollectors/CPUload.hs
@@ -0,0 +1,192 @@
+{-| @/proc/stat@ data collector.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Ganeti.DataCollectors.CPUload
+  ( dcName
+  , dcVersion
+  , dcFormatVersion
+  , dcCategory
+  , dcKind
+  , dcReport
+  , dcUpdate
+  ) where
+
+import qualified Control.Exception as E
+import Data.Attoparsec.Text.Lazy as A
+import Data.Text.Lazy (pack, unpack)
+import qualified Text.JSON as J
+import qualified Data.Sequence as Seq
+import System.Posix.Unistd (getSysVar, SysVar(ClockTick))
+
+import qualified Ganeti.BasicTypes as BT
+import qualified Ganeti.Constants as C
+import Ganeti.Cpu.LoadParser(cpustatParser)
+import Ganeti.DataCollectors.Types
+import Ganeti.Utils
+import Ganeti.Cpu.Types
+
+-- | The default path of the CPU status file.
+-- It is hardcoded because it is not likely to change.
+defaultFile :: FilePath
+defaultFile = C.statFile
+
+-- | The buffer size of the values kept in the map.
+bufferSize :: Int
+bufferSize = C.cpuavgloadBufferSize
+
+-- | The window size of the values that will export the average load.
+windowSize :: Integer
+windowSize = toInteger C.cpuavgloadWindowSize
+
+-- | The default setting for the maximum amount of not parsed character to
+-- print in case of error.
+-- It is set to use most of the screen estate on a standard 80x25 terminal.
+-- TODO: add the possibility to set this with a command line parameter.
+defaultCharNum :: Int
+defaultCharNum = 80*20
+
+-- | The name of this data collector.
+dcName :: String
+dcName = "cpu-avg-load"
+
+-- | The version of this data collector.
+dcVersion :: DCVersion
+dcVersion = DCVerBuiltin
+
+-- | The version number for the data format of this data collector.
+dcFormatVersion :: Int
+dcFormatVersion = 1
+
+-- | The category of this data collector.
+dcCategory :: Maybe DCCategory
+dcCategory = Nothing
+
+-- | The kind of this data collector.
+dcKind :: DCKind
+dcKind = DCKPerf
+
+-- | The data exported by the data collector, taken from the default location.
+dcReport :: Maybe CollectorData -> IO DCReport
+dcReport colData =
+  let cpuLoadData =
+        case colData of
+          Nothing -> Seq.empty
+          Just colData' ->
+            case colData' of
+              CPULoadData v -> v
+  in buildDCReport cpuLoadData
+-- | Data stored by the collector in mond's memory.
+type Buffer = Seq.Seq (Integer, [Int])
+
+-- | Compute the load from a CPU.
+computeLoad :: CPUstat -> Int
+computeLoad cpuData =
+  csUser cpuData + csNice cpuData + csSystem cpuData
+  + csIowait cpuData + csIrq cpuData + csSoftirq cpuData
+  + csSteal cpuData + csGuest cpuData + csGuestNice cpuData
+
+-- | Reads and Computes the load for each CPU.
+dcCollectFromFile :: FilePath -> IO (Integer, [Int])
+dcCollectFromFile inputFile = do
+  contents <-
+    ((E.try $ readFile inputFile) :: IO (Either IOError String)) >>=
+      exitIfBad "reading from file" . either (BT.Bad . show) BT.Ok
+  cpustatData <-
+    case A.parse cpustatParser $ pack contents of
+      A.Fail unparsedText contexts errorMessage -> exitErr $
+        show (Prelude.take defaultCharNum $ unpack unparsedText) ++ "\n"
+          ++ show contexts ++ "\n" ++ errorMessage
+      A.Done _ cpustatD -> return cpustatD
+  now <- getCurrentTime
+  let timestamp = now :: Integer
+  return (timestamp, map computeLoad cpustatData)
+
+-- | Returns the collected data in the appropriate type.
+dcCollect :: IO Buffer
+dcCollect  = do
+  l <- dcCollectFromFile defaultFile
+  return (Seq.singleton l)
+
+-- | Formats data for JSON transformation.
+formatData :: [Double] -> CPUavgload
+formatData [] = CPUavgload (0 :: Int) [] (0 :: Double)
+formatData l@(x:xs) = CPUavgload (length l - 1) xs x
+
+-- | Update a Map Entry.
+updateEntry :: Buffer -> Buffer -> Buffer
+updateEntry newBuffer mapEntry =
+  (Seq.><) newBuffer
+  (if Seq.length mapEntry < bufferSize
+    then mapEntry
+    else Seq.drop 1 mapEntry)
+
+-- | Updates the given Collector data.
+dcUpdate :: Maybe CollectorData -> IO CollectorData
+dcUpdate mcd = do
+  v <- dcCollect
+  let new_v =
+        case mcd of
+          Nothing -> v
+          Just cd ->
+            case cd of
+              CPULoadData old_v -> updateEntry v old_v
+  new_v `seq` return $ CPULoadData new_v
+
+-- | Computes the average load for every CPU and the overall from data read
+-- from the map.
+computeAverage :: Buffer -> Integer -> Integer -> [Double]
+computeAverage s w ticks =
+  let window = Seq.takeWhileL ((> w) . fst) s
+      go Seq.EmptyL          _                    = []
+      go _                   Seq.EmptyR           = []
+      go (leftmost Seq.:< _) (_ Seq.:> rightmost) = do
+        let (timestampL, listL) = leftmost
+            (timestampR, listR) = rightmost
+            work = zipWith (-) listL listR
+            overall = (timestampL - timestampR) * ticks
+        map (\x -> fromIntegral x / fromIntegral overall) work
+  in go (Seq.viewl window) (Seq.viewr window)
+
+-- | This function computes the JSON representation of the CPU load.
+buildJsonReport :: Buffer -> IO J.JSValue
+buildJsonReport v = do
+  ticks <- getSysVar ClockTick
+  let res = computeAverage v windowSize ticks
+  return . J.showJSON $ formatData res
+
+-- | This function computes the DCReport for the CPU load.
+buildDCReport :: Buffer -> IO DCReport
+buildDCReport v  =
+  buildJsonReport v >>=
+    buildReport dcName dcVersion dcFormatVersion dcCategory dcKind
diff --git a/src/Ganeti/DataCollectors/Diskstats.hs b/src/Ganeti/DataCollectors/Diskstats.hs
index 92d7ac3..b46f988 100644
--- a/src/Ganeti/DataCollectors/Diskstats.hs
+++ b/src/Ganeti/DataCollectors/Diskstats.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/Drbd.hs b/src/Ganeti/DataCollectors/Drbd.hs
index 4be9ddb..6d363cd 100644
--- a/src/Ganeti/DataCollectors/Drbd.hs
+++ b/src/Ganeti/DataCollectors/Drbd.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/InstStatus.hs b/src/Ganeti/DataCollectors/InstStatus.hs
index ed898d9..3eded25 100644
--- a/src/Ganeti/DataCollectors/InstStatus.hs
+++ b/src/Ganeti/DataCollectors/InstStatus.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/InstStatusTypes.hs b/src/Ganeti/DataCollectors/InstStatusTypes.hs
index 5803c3d..2271612 100644
--- a/src/Ganeti/DataCollectors/InstStatusTypes.hs
+++ b/src/Ganeti/DataCollectors/InstStatusTypes.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -32,7 +41,6 @@
 
 import Ganeti.DataCollectors.Types
 import Ganeti.Hypervisor.Xen.Types
-import Ganeti.Objects
 import Ganeti.THH
 import Ganeti.Types
 
diff --git a/src/Ganeti/DataCollectors/Lv.hs b/src/Ganeti/DataCollectors/Lv.hs
index d296b0f..abcf0e6 100644
--- a/src/Ganeti/DataCollectors/Lv.hs
+++ b/src/Ganeti/DataCollectors/Lv.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/Program.hs b/src/Ganeti/DataCollectors/Program.hs
index 7005535..775f486 100644
--- a/src/Ganeti/DataCollectors/Program.hs
+++ b/src/Ganeti/DataCollectors/Program.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/DataCollectors/Types.hs b/src/Ganeti/DataCollectors/Types.hs
index 80df40c..c2ab6ad 100644
--- a/src/Ganeti/DataCollectors/Types.hs
+++ b/src/Ganeti/DataCollectors/Types.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -33,11 +42,19 @@
   , DCStatus(..)
   , DCStatusCode(..)
   , DCVersion(..)
+  , CollectorData(..)
+  , CollectorMap
   , buildReport
   , mergeStatuses
+  , getCategoryName
   ) where
 
+import Control.DeepSeq (NFData, rnf)
+import Control.Seq (using, seqFoldable, rdeepseq)
 import Data.Char
+import Data.Ratio
+import qualified Data.Map as Map
+import qualified Data.Sequence as Seq
 import Text.JSON
 
 import Ganeti.Constants as C
@@ -46,13 +63,27 @@
 
 -- | The possible classes a data collector can belong to.
 data DCCategory = DCInstance | DCStorage | DCDaemon | DCHypervisor
-  deriving (Show, Eq)
+  deriving (Show, Eq, Read)
+
+-- | Get the category name and return it as a string.
+getCategoryName :: DCCategory -> String
+getCategoryName dcc = map toLower . drop 2 . show $ dcc
+
+categoryNames :: Map.Map String DCCategory
+categoryNames =
+  let l = [DCInstance, DCStorage, DCDaemon, DCHypervisor]
+  in Map.fromList $ zip (map getCategoryName l) l
 
 -- | The JSON instance for DCCategory.
 instance JSON DCCategory where
-  showJSON = showJSON . map toLower . drop 2 . show
-  readJSON =
-    error "JSON read instance not implemented for type DCCategory"
+  showJSON = showJSON . getCategoryName
+  readJSON (JSString s) =
+    let s' = fromJSString s
+    in case Map.lookup s' categoryNames of
+         Just category -> Ok category
+         Nothing -> fail $ "Invalid category name " ++ s' ++ " for type\
+                           \ DCCategory"
+  readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for type DCCategory"
 
 -- | The possible status codes of a data collector.
 data DCStatusCode = DCSCOk      -- ^ Everything is OK
@@ -84,7 +115,15 @@
 instance JSON DCKind where
   showJSON DCKPerf   = showJSON (0 :: Int)
   showJSON DCKStatus = showJSON (1 :: Int)
-  readJSON = error "JSON read instance not implemented for type DCKind"
+  readJSON (JSRational _ x) =
+    if denominator x /= 1
+    then fail $ "Invalid JSON value " ++ show x ++ " for type DCKind"
+    else
+      let x' = (fromIntegral . numerator $ x) :: Int
+      in if x' == 0 then Ok DCKPerf
+         else if x' == 1 then Ok DCKStatus
+         else fail $ "Invalid JSON value " ++ show x' ++ " for type DCKind"
+  readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for type DCKind"
 
 -- | Type representing the version number of a data collector.
 data DCVersion = DCVerBuiltin | DCVersion String deriving (Show, Eq)
@@ -93,7 +132,35 @@
 instance JSON DCVersion where
   showJSON DCVerBuiltin = showJSON C.builtinDataCollectorVersion
   showJSON (DCVersion v) = showJSON v
-  readJSON = error "JSON read instance not implemented for type DCVersion"
+  readJSON (JSString s) =
+    if fromJSString s == C.builtinDataCollectorVersion
+    then Ok DCVerBuiltin else Ok . DCVersion $ fromJSString s
+  readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for type DCVersion"
+
+-- | Type for the value field of the `CollectorMap` below.
+data CollectorData = CPULoadData (Seq.Seq (Integer, [Int]))
+
+{-
+
+Naturally, we want to make CollectorData an instance of NFData as
+follows.
+
+instance NFData CollectorData where
+  rnf (CPULoadData x) = rnf x
+
+However, Seq.Seq only became an instance of NFData in version 0.5.0.0
+of containers (Released 2012). So, for the moment, we use a generic
+way to reduce to normal form. In later versions of Ganeti, where we
+have the infra structure to do so, we will choose implementation depending
+on the version of the containers library available.
+
+-}
+
+instance NFData CollectorData where
+  rnf (CPULoadData x) =  (x `using` seqFoldable rdeepseq) `seq` ()
+
+-- | Type for the map storing the data of the statefull DataCollectors.
+type CollectorMap = Map.Map String CollectorData
 
 -- | This is the format of the report produced by each data collector.
 $(buildObject "DCReport" "dcReport"
diff --git a/src/Ganeti/Errors.hs b/src/Ganeti/Errors.hs
index 1eedf6c..2123edd 100644
--- a/src/Ganeti/Errors.hs
+++ b/src/Ganeti/Errors.hs
@@ -11,21 +11,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Backend/IAlloc.hs b/src/Ganeti/HTools/Backend/IAlloc.hs
index 4c670ac..da547b0 100644
--- a/src/Ganeti/HTools/Backend/IAlloc.hs
+++ b/src/Ganeti/HTools/Backend/IAlloc.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -50,6 +59,8 @@
 import Ganeti.HTools.Loader
 import Ganeti.HTools.Types
 import Ganeti.JSON
+import Ganeti.Types ( EvacMode(ChangePrimary, ChangeSecondary)
+                    , adminStateFromRaw, AdminState(..))
 import Ganeti.Utils
 
 {-# ANN module "HLint: ignore Eta reduce" #-}
@@ -97,9 +108,13 @@
   su    <- extract "spindle_use"
   nics  <- extract "nics" >>= toArray >>= asObjectList >>=
            mapM (parseNic n . fromJSObject)
-  return
-    (n,
-     Instance.create n mem disk disks vcpus Running tags True 0 0 dt su nics)
+  state <- (tryFromObj errorMessage a "admin_state" >>= adminStateFromRaw)
+           `mplus` Ok AdminUp
+  let getRunSt AdminOffline = StatusOffline
+      getRunSt AdminDown = StatusDown
+      getRunSt AdminUp = Running
+  return (n, Instance.create n mem disk disks vcpus (getRunSt state) tags
+             True 0 0 dt su nics)
 
 -- | Parses an instance as found in the cluster instance list.
 parseInstance :: NameAssoc -- ^ The node name-to-index association list
diff --git a/src/Ganeti/HTools/Backend/Luxi.hs b/src/Ganeti/HTools/Backend/Luxi.hs
index cf29e60..771fcc5 100644
--- a/src/Ganeti/HTools/Backend/Luxi.hs
+++ b/src/Ganeti/HTools/Backend/Luxi.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -227,8 +236,12 @@
   xmtotal <- lvconvert 0.0 "mtotal" mtotal
   xmnode <- lvconvert 0 "mnode" mnode
   xmfree <- lvconvert 0 "mfree" mfree
-  xdtotal <- lvconvert 0.0 "dtotal" dtotal
-  xdfree <- lvconvert 0 "dfree" dfree
+  let xdtotal = genericResult (const 0.0) id
+                  $ lvconvert 0.0 "dtotal" dtotal
+      xdfree = genericResult (const 0) id
+                 $ lvconvert 0 "dfree" dfree
+      -- "dtotal" and "dfree" might be missing, e.g., if sharedfile
+      -- is the only supported disk template
   xctotal <- lvconvert 0.0 "ctotal" ctotal
   xcnos <- lvconvert 0 "cnos" cnos
   let node = flip Node.setNodeTags xtags $
diff --git a/src/Ganeti/HTools/Backend/Rapi.hs b/src/Ganeti/HTools/Backend/Rapi.hs
index 4455a83..e829fc4 100644
--- a/src/Ganeti/HTools/Backend/Rapi.hs
+++ b/src/Ganeti/HTools/Backend/Rapi.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Backend/Simu.hs b/src/Ganeti/HTools/Backend/Simu.hs
index 822809e..24e3386 100644
--- a/src/Ganeti/HTools/Backend/Simu.hs
+++ b/src/Ganeti/HTools/Backend/Simu.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Backend/Text.hs b/src/Ganeti/HTools/Backend/Text.hs
index c53b488..8c49c43 100644
--- a/src/Ganeti/HTools/Backend/Text.hs
+++ b/src/Ganeti/HTools/Backend/Text.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/CLI.hs b/src/Ganeti/HTools/CLI.hs
index 314fb79..6202d4a 100644
--- a/src/Ganeti/HTools/CLI.hs
+++ b/src/Ganeti/HTools/CLI.hs
@@ -9,21 +9,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -48,7 +57,10 @@
   , oDiskTemplate
   , oSpindleUse
   , oDynuFile
+  , oMonD
+  , oMonDDataFile
   , oEvacMode
+  , oRestrictedMigrate
   , oExInst
   , oExTags
   , oExecJobs
@@ -56,7 +68,10 @@
   , oFullEvacuation
   , oGroup
   , oIAllocSrc
+  , oIgnoreDyn 
   , oIgnoreNonRedundant
+  , oIndependentGroups
+  , oAcceptExisting
   , oInstMoves
   , oJobDelay
   , genOLuxiSocket
@@ -121,7 +136,14 @@
   , optDiskTemplate :: Maybe DiskTemplate  -- ^ Override for the disk template
   , optSpindleUse  :: Maybe Int      -- ^ Override for the spindle usage
   , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
+  , optIgnoreDynu  :: Bool           -- ^ Do not use dynamic use data
+  , optIndependentGroups :: Bool     -- ^ consider groups independently
+  , optAcceptExisting :: Bool        -- ^ accept existing N+1 violations
+  , optMonD        :: Bool           -- ^ Query MonDs
+  , optMonDFile    :: Maybe FilePath -- ^ Optional file with data provided
+                                     -- by MonDs
   , optEvacMode    :: Bool           -- ^ Enable evacuation mode
+  , optRestrictedMigrate :: Bool     -- ^ Disallow replace-primary moves
   , optExInst      :: [String]       -- ^ Instances to be excluded
   , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
   , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
@@ -172,10 +194,16 @@
   { optDataFile    = Nothing
   , optDiskMoves   = True
   , optInstMoves   = True
+  , optIndependentGroups = False
+  , optAcceptExisting = False
   , optDiskTemplate = Nothing
   , optSpindleUse  = Nothing
+  , optIgnoreDynu  = False
   , optDynuFile    = Nothing
+  , optMonD        = False
+  , optMonDFile = Nothing
   , optEvacMode    = False
+  , optRestrictedMigrate = False
   , optExInst      = []
   , optExTags      = Nothing
   , optExecJobs    = False
@@ -277,6 +305,20 @@
    \ thus allowing only the 'cheap' failover/migrate operations",
    OptComplNone)
 
+oMonD :: OptType
+oMonD =
+  (Option "" ["mond"]
+   (NoArg (\ opts -> Ok opts {optMonD = True}))
+   "Query MonDs",
+   OptComplNone)
+
+oMonDDataFile :: OptType
+oMonDDataFile =
+  (Option "" ["mond-data"]
+   (ReqArg (\ f opts -> Ok opts { optMonDFile = Just f }) "FILE")
+   "Import data provided by MonDs from the given FILE",
+   OptComplFile)
+
 oDiskTemplate :: OptType
 oDiskTemplate =
   (Option "" ["disk-template"]
@@ -320,6 +362,27 @@
    "Import dynamic utilisation data from the given FILE",
    OptComplFile)
 
+oIgnoreDyn :: OptType
+oIgnoreDyn =
+  (Option "" ["ignore-dynu"]
+   (NoArg (\ opts -> Ok opts {optIgnoreDynu = True}))
+   "Ignore any dynamic utilisation information",
+   OptComplNone)
+
+oIndependentGroups :: OptType
+oIndependentGroups =
+  (Option "" ["independent-groups"]
+   (NoArg (\ opts -> Ok opts {optIndependentGroups = True}))
+   "Consider groups independently",
+   OptComplNone)
+
+oAcceptExisting :: OptType
+oAcceptExisting =
+  (Option "" ["accept-existing-errors"]
+   (NoArg (\ opts -> Ok opts {optAcceptExisting = True}))
+   "Accept existing N+1 violations; just don't add new ones",
+   OptComplNone)
+
 oEvacMode :: OptType
 oEvacMode =
   (Option "E" ["evac-mode"]
@@ -328,6 +391,14 @@
    \ instances away from offline and drained nodes",
    OptComplNone)
 
+oRestrictedMigrate :: OptType
+oRestrictedMigrate =
+  (Option "" ["restricted-migration"]
+   (NoArg (\opts -> Ok opts { optRestrictedMigrate =  True }))
+   "disallow replace-primary moves (aka frf-moves); in evacuation mode, this\
+   \ will ensure that the only migrations are off the drained nodes",
+   OptComplNone)
+
 oExInst :: OptType
 oExInst =
   (Option "" ["exclude-instances"]
diff --git a/src/Ganeti/HTools/Cluster.hs b/src/Ganeti/HTools/Cluster.hs
index 88891a4..e6556ea 100644
--- a/src/Ganeti/HTools/Cluster.hs
+++ b/src/Ganeti/HTools/Cluster.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -94,7 +103,7 @@
 import Ganeti.Compat
 import qualified Ganeti.OpCodes as OpCodes
 import Ganeti.Utils
-import Ganeti.Types (mkNonEmpty)
+import Ganeti.Types (EvacMode(..), mkNonEmpty)
 
 -- * Types
 
@@ -318,70 +327,107 @@
                        }
   in (rini, rfin, runa)
 
+-- | The names and weights of the individual elements in the CV list, together
+-- with their statistical accumulation function and a bit to decide whether it
+-- is a statistics for online nodes.
+detailedCVInfoExt :: [((Double, String), ([Double] -> Statistics, Bool))]
+detailedCVInfoExt = [ ((1,  "free_mem_cv"), (getStdDevStatistics, True))
+                    , ((1,  "free_disk_cv"), (getStdDevStatistics, True))
+                    , ((1,  "n1_cnt"), (getSumStatistics, True))
+                    , ((1,  "reserved_mem_cv"), (getStdDevStatistics, True))
+                    , ((4,  "offline_all_cnt"), (getSumStatistics, False))
+                    , ((16, "offline_pri_cnt"), (getSumStatistics, False))
+                    , ((1,  "vcpu_ratio_cv"), (getStdDevStatistics, True))
+                    , ((1,  "cpu_load_cv"), (getStdDevStatistics, True))
+                    , ((1,  "mem_load_cv"), (getStdDevStatistics, True))
+                    , ((1,  "disk_load_cv"), (getStdDevStatistics, True))
+                    , ((1,  "net_load_cv"), (getStdDevStatistics, True))
+                    , ((2,  "pri_tags_score"), (getSumStatistics, True))
+                    , ((1,  "spindles_cv"), (getStdDevStatistics, True))
+                    ]
+
 -- | The names and weights of the individual elements in the CV list.
 detailedCVInfo :: [(Double, String)]
-detailedCVInfo = [ (1,  "free_mem_cv")
-                 , (1,  "free_disk_cv")
-                 , (1,  "n1_cnt")
-                 , (1,  "reserved_mem_cv")
-                 , (4,  "offline_all_cnt")
-                 , (16, "offline_pri_cnt")
-                 , (1,  "vcpu_ratio_cv")
-                 , (1,  "cpu_load_cv")
-                 , (1,  "mem_load_cv")
-                 , (1,  "disk_load_cv")
-                 , (1,  "net_load_cv")
-                 , (2,  "pri_tags_score")
-                 , (1,  "spindles_cv")
-                 ]
+detailedCVInfo = map fst detailedCVInfoExt
 
 -- | Holds the weights used by 'compCVNodes' for each metric.
 detailedCVWeights :: [Double]
 detailedCVWeights = map fst detailedCVInfo
 
--- | Compute the mem and disk covariance.
-compDetailedCV :: [Node.Node] -> [Double]
-compDetailedCV all_nodes =
+-- | The aggregation functions for the weights
+detailedCVAggregation :: [([Double] -> Statistics, Bool)]
+detailedCVAggregation = map snd detailedCVInfoExt
+
+-- | The bit vector describing which parts of the statistics are
+-- for online nodes.
+detailedCVOnlineStatus :: [Bool]
+detailedCVOnlineStatus = map snd detailedCVAggregation
+
+-- | Compute statistical measures of a single node.
+compDetailedCVNode :: Node.Node -> [Double]
+compDetailedCVNode node =
+  let mem = Node.pMem node
+      dsk = Node.pDsk node
+      n1 = fromIntegral
+           $ if Node.failN1 node
+               then length (Node.sList node) + length (Node.pList node)
+               else 0
+      res = Node.pRem node
+      ipri = fromIntegral . length $ Node.pList node
+      isec = fromIntegral . length $ Node.sList node
+      ioff = ipri + isec
+      cpu = Node.pCpu node
+      DynUtil c1 m1 d1 nn1 = Node.utilLoad node
+      DynUtil c2 m2 d2 nn2 = Node.utilPool node
+      (c_load, m_load, d_load, n_load) = (c1/c2, m1/m2, d1/d2, nn1/nn2)
+      pri_tags = fromIntegral $ Node.conflictingPrimaries node
+      spindles = Node.instSpindles node / Node.hiSpindles node
+  in [ mem, dsk, n1, res, ioff, ipri, cpu
+     , c_load, m_load, d_load, n_load
+     , pri_tags, spindles
+     ]
+
+-- | Compute the statistics of a cluster.
+compClusterStatistics :: [Node.Node] -> [Statistics]
+compClusterStatistics all_nodes =
   let (offline, nodes) = partition Node.offline all_nodes
-      mem_l = map Node.pMem nodes
-      dsk_l = map Node.pDsk nodes
-      -- metric: memory covariance
-      mem_cv = stdDev mem_l
-      -- metric: disk covariance
-      dsk_cv = stdDev dsk_l
-      -- metric: count of instances living on N1 failing nodes
-      n1_score = fromIntegral . sum . map (\n -> length (Node.sList n) +
-                                                 length (Node.pList n)) .
-                 filter Node.failN1 $ nodes :: Double
-      res_l = map Node.pRem nodes
-      -- metric: reserved memory covariance
-      res_cv = stdDev res_l
-      -- offline instances metrics
-      offline_ipri = sum . map (length . Node.pList) $ offline
-      offline_isec = sum . map (length . Node.sList) $ offline
-      -- metric: count of instances on offline nodes
-      off_score = fromIntegral (offline_ipri + offline_isec)::Double
-      -- metric: count of primary instances on offline nodes (this
-      -- helps with evacuation/failover of primary instances on
-      -- 2-node clusters with one node offline)
-      off_pri_score = fromIntegral offline_ipri::Double
-      cpu_l = map Node.pCpu nodes
-      -- metric: covariance of vcpu/pcpu ratio
-      cpu_cv = stdDev cpu_l
-      -- metrics: covariance of cpu, memory, disk and network load
-      (c_load, m_load, d_load, n_load) =
-        unzip4 $ map (\n ->
-                      let DynUtil c1 m1 d1 n1 = Node.utilLoad n
-                          DynUtil c2 m2 d2 n2 = Node.utilPool n
-                      in (c1/c2, m1/m2, d1/d2, n1/n2)) nodes
-      -- metric: conflicting instance count
-      pri_tags_inst = sum $ map Node.conflictingPrimaries nodes
-      pri_tags_score = fromIntegral pri_tags_inst::Double
-      -- metric: spindles %
-      spindles_cv = map (\n -> Node.instSpindles n / Node.hiSpindles n) nodes
-  in [ mem_cv, dsk_cv, n1_score, res_cv, off_score, off_pri_score, cpu_cv
-     , stdDev c_load, stdDev m_load , stdDev d_load, stdDev n_load
-     , pri_tags_score, stdDev spindles_cv ]
+      offline_values = transpose (map compDetailedCVNode offline)
+                       ++ repeat []
+      -- transpose of an empty list is empty and not k times the empty list, as
+      -- would be the transpose of a 0 x k matrix
+      online_values = transpose $ map compDetailedCVNode nodes
+      aggregate (f, True) (onNodes, _) = f onNodes
+      aggregate (f, False) (_, offNodes) = f offNodes
+  in zipWith aggregate detailedCVAggregation
+       $ zip online_values offline_values
+
+-- | Update a cluster statistics by replacing the contribution of one
+-- node by that of another.
+updateClusterStatistics :: [Statistics]
+                           -> (Node.Node, Node.Node) -> [Statistics]
+updateClusterStatistics stats (old, new) =
+  let update = zip (compDetailedCVNode old) (compDetailedCVNode new)
+      online = not $ Node.offline old
+      updateStat forOnline stat upd = if forOnline == online
+                                        then updateStatistics stat upd
+                                        else stat
+  in zipWith3 updateStat detailedCVOnlineStatus stats update
+
+-- | Update a cluster statistics twice.
+updateClusterStatisticsTwice :: [Statistics]
+                                -> (Node.Node, Node.Node)
+                                -> (Node.Node, Node.Node)
+                                -> [Statistics]
+updateClusterStatisticsTwice s a =
+  updateClusterStatistics (updateClusterStatistics s a)
+
+-- | Compute cluster statistics
+compDetailedCV :: [Node.Node] -> [Double]
+compDetailedCV = map getStatisticValue . compClusterStatistics
+
+-- | Compute the cluster score from its statistics
+compCVfromStats :: [Statistics] -> Double
+compCVfromStats = sum . zipWith (*) detailedCVWeights . map getStatisticValue
 
 -- | Compute the /total/ variance.
 compCVNodes :: [Node.Node] -> Double
@@ -510,9 +556,10 @@
     return (new_nl, new_inst, [new_p], new_score)
 
 -- | Tries to allocate an instance on a given pair of nodes.
-allocateOnPair :: Node.List -> Instance.Instance -> Ndx -> Ndx
+allocateOnPair :: [Statistics]
+               -> Node.List -> Instance.Instance -> Ndx -> Ndx
                -> OpResult Node.AllocElement
-allocateOnPair nl inst new_pdx new_sdx =
+allocateOnPair stats nl inst new_pdx new_sdx =
   let tgt_p = Container.find new_pdx nl
       tgt_s = Container.find new_sdx nl
   in do
@@ -522,7 +569,9 @@
     new_s <- Node.addSec tgt_s inst new_pdx
     let new_inst = Instance.setBoth inst new_pdx new_sdx
         new_nl = Container.addTwo new_pdx new_p new_sdx new_s nl
-    return (new_nl, new_inst, [new_p, new_s], compCV new_nl)
+        new_stats = updateClusterStatisticsTwice stats
+                      (tgt_p, new_p) (tgt_s, new_s)
+    return (new_nl, new_inst, [new_p, new_s], compCVfromStats new_stats)
 
 -- | Tries to perform an instance move and returns the best table
 -- between the original one and the new one.
@@ -550,27 +599,39 @@
 possibleMoves :: MirrorType -- ^ The mirroring type of the instance
               -> Bool       -- ^ Whether the secondary node is a valid new node
               -> Bool       -- ^ Whether we can change the primary node
+              -> (Bool, Bool) -- ^ Whether migration is restricted and whether
+                              -- the instance primary is offline
               -> Ndx        -- ^ Target node candidate
               -> [IMove]    -- ^ List of valid result moves
 
-possibleMoves MirrorNone _ _ _ = []
+possibleMoves MirrorNone _ _ _ _ = []
 
-possibleMoves MirrorExternal _ False _ = []
+possibleMoves MirrorExternal _ False _  _ = []
 
-possibleMoves MirrorExternal _ True tdx =
+possibleMoves MirrorExternal _ True _ tdx =
   [ FailoverToAny tdx ]
 
-possibleMoves MirrorInternal _ False tdx =
+possibleMoves MirrorInternal _ False _ tdx =
   [ ReplaceSecondary tdx ]
 
-possibleMoves MirrorInternal True True tdx =
+possibleMoves MirrorInternal _ _ (True, False) tdx =
+  [ ReplaceSecondary tdx
+  ]
+
+possibleMoves MirrorInternal True True (False, _) tdx =
   [ ReplaceSecondary tdx
   , ReplaceAndFailover tdx
   , ReplacePrimary tdx
   , FailoverAndReplace tdx
   ]
 
-possibleMoves MirrorInternal False True tdx =
+possibleMoves MirrorInternal True True (True, True) tdx =
+  [ ReplaceSecondary tdx
+  , ReplaceAndFailover tdx
+  , FailoverAndReplace tdx
+  ]
+
+possibleMoves MirrorInternal False True _ tdx =
   [ ReplaceSecondary tdx
   , ReplaceAndFailover tdx
   ]
@@ -579,10 +640,12 @@
 checkInstanceMove :: [Ndx]             -- ^ Allowed target node indices
                   -> Bool              -- ^ Whether disk moves are allowed
                   -> Bool              -- ^ Whether instance moves are allowed
+                  -> Bool              -- ^ Whether migration is restricted
                   -> Table             -- ^ Original table
                   -> Instance.Instance -- ^ Instance to move
                   -> Table             -- ^ Best new table for this instance
-checkInstanceMove nodes_idx disk_moves inst_moves ini_tbl target =
+checkInstanceMove nodes_idx disk_moves inst_moves rest_mig
+                  ini_tbl@(Table nl _ _ _) target =
   let opdx = Instance.pNode target
       osdx = Instance.sNode target
       bad_nodes = [opdx, osdx]
@@ -593,9 +656,13 @@
                        -- if drbd and allowed to failover
                        then checkSingleStep ini_tbl target ini_tbl Failover
                        else ini_tbl
+      primary_drained = Node.offline
+                        . flip Container.find nl
+                        $ Instance.pNode target
       all_moves =
         if disk_moves
-          then concatMap (possibleMoves mir_type use_secondary inst_moves)
+          then concatMap (possibleMoves mir_type use_secondary inst_moves
+                          (rest_mig, primary_drained))
                nodes
           else []
     in
@@ -606,10 +673,11 @@
 checkMove :: [Ndx]               -- ^ Allowed target node indices
           -> Bool                -- ^ Whether disk moves are allowed
           -> Bool                -- ^ Whether instance moves are allowed
+          -> Bool                -- ^ Whether migration is restricted
           -> Table               -- ^ The current solution
           -> [Instance.Instance] -- ^ List of instances still to move
           -> Table               -- ^ The new solution
-checkMove nodes_idx disk_moves inst_moves ini_tbl victims =
+checkMove nodes_idx disk_moves inst_moves rest_mig ini_tbl victims =
   let Table _ _ _ ini_plc = ini_tbl
       -- we're using rwhnf from the Control.Parallel.Strategies
       -- package; we don't need to use rnf as that would force too
@@ -617,7 +685,7 @@
       -- multi-threaded case the weak head normal form is enough to
       -- spark the evaluation
       tables = parMap rwhnf (checkInstanceMove nodes_idx disk_moves
-                             inst_moves ini_tbl)
+                             inst_moves rest_mig ini_tbl)
                victims
       -- iterate over all instances, computing the best move
       best_tbl = foldl' compareTables ini_tbl tables
@@ -641,10 +709,11 @@
            -> Bool        -- ^ Allow disk moves
            -> Bool        -- ^ Allow instance moves
            -> Bool        -- ^ Only evacuate moves
+           -> Bool        -- ^ Restrict migration
            -> Score       -- ^ Min gain threshold
            -> Score       -- ^ Min gain
            -> Maybe Table -- ^ The resulting table and commands
-tryBalance ini_tbl disk_moves inst_moves evac_mode mg_limit min_gain =
+tryBalance ini_tbl disk_moves inst_moves evac_mode rest_mig mg_limit min_gain =
     let Table ini_nl ini_il ini_cv _ = ini_tbl
         all_inst = Container.elems ini_il
         all_nodes = Container.elems ini_nl
@@ -657,7 +726,8 @@
         reloc_inst = filter (\i -> Instance.movable i &&
                                    Instance.autoBalance i) all_inst'
         node_idx = map Node.idx online_nodes
-        fin_tbl = checkMove node_idx disk_moves inst_moves ini_tbl reloc_inst
+        fin_tbl = checkMove node_idx disk_moves inst_moves rest_mig
+                            ini_tbl reloc_inst
         (Table _ _ fin_cv _) = fin_tbl
     in
       if fin_cv < ini_cv && (ini_cv > mg_limit || ini_cv - fin_cv >= min_gain)
@@ -772,10 +842,11 @@
          -> m AllocSolution   -- ^ Possible solution list
 tryAlloc _  _ _    (Right []) = fail "Not enough online nodes"
 tryAlloc nl _ inst (Right ok_pairs) =
-  let psols = parMap rwhnf (\(p, ss) ->
+  let cstat = compClusterStatistics $ Container.elems nl
+      psols = parMap rwhnf (\(p, ss) ->
                               foldl' (\cstate ->
                                         concatAllocs cstate .
-                                        allocateOnPair nl inst p)
+                                        allocateOnPair cstat nl inst p)
                               emptyAllocSolution ss) ok_pairs
       sols = foldl' sumAllocs emptyAllocSolution psols
   in return $ annotateSolution sols
@@ -867,7 +938,7 @@
       sortedSols = sortMGResults goodSols
   in case sortedSols of
        [] -> Bad $ if null groups'
-                     then "no groups for evacuation: allowed groups was" ++
+                     then "no groups for evacuation: allowed groups was " ++
                           show allowed_gdxs ++ ", all groups: " ++
                           show (map fst groups)
                      else intercalate ", " all_msgs
@@ -1328,14 +1399,20 @@
                                           . flip (tryAlloc nl' il') allocnodes)
                        newinst
           bigSteps = filter isJust . map suffShrink . reverse $ sortedErrs
+          progress (Ok (_, _, _, newil', _)) (Ok (_, _, _, newil, _)) =
+            length newil' > length newil
+          progress _ _ = False
       in if stop then newsol else
-          case bigSteps of
-            Just newinst':_ -> tieredAlloc nl' il' newlimit
-                               newinst' allocnodes ixes' cstats'
-            _ -> case Instance.shrinkByType newinst . last $ sortedErrs of
-                   Bad _ -> newsol
-                   Ok newinst' -> tieredAlloc nl' il' newlimit
-                                  newinst' allocnodes ixes' cstats'
+           let newsol' = case Instance.shrinkByType newinst . last
+                                $ sortedErrs of
+                 Bad _ -> newsol
+                 Ok newinst' -> tieredAlloc nl' il' newlimit
+                                newinst' allocnodes ixes' cstats'
+           in if progress newsol' newsol then newsol' else
+                case bigSteps of
+                  Just newinst':_ -> tieredAlloc nl' il' newlimit
+                                     newinst' allocnodes ixes' cstats'
+                  _ -> newsol
 
 -- * Formatting functions
 
diff --git a/src/Ganeti/HTools/Container.hs b/src/Ganeti/HTools/Container.hs
index ec8a11c..9827abf 100644
--- a/src/Ganeti/HTools/Container.hs
+++ b/src/Ganeti/HTools/Container.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/ExtLoader.hs b/src/Ganeti/HTools/ExtLoader.hs
index a6b19a2..c781907 100644
--- a/src/Ganeti/HTools/ExtLoader.hs
+++ b/src/Ganeti/HTools/ExtLoader.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE BangPatterns #-}
+
 {-| External data loader.
 
 This module holds the external data loading, and thus is the only one
@@ -9,21 +11,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -31,27 +42,43 @@
   ( loadExternalData
   , commonSuffix
   , maybeSaveData
+  , queryAllMonDDCs
+  , pMonDData
   ) where
 
 import Control.Monad
 import Control.Exception
-import Data.Maybe (isJust, fromJust)
+import Data.Maybe (isJust, fromJust, catMaybes)
+import Network.Curl
 import System.FilePath
 import System.IO
 import System.Time (getClockTime)
 import Text.Printf (hPrintf)
 
+import qualified Text.JSON as J
+import qualified Data.Map as Map
+import qualified Data.List as L
+
+import qualified Ganeti.Constants as C
+import qualified Ganeti.DataCollectors.CPUload as CPUload
+import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Backend.Luxi as Luxi
 import qualified Ganeti.HTools.Backend.Rapi as Rapi
 import qualified Ganeti.HTools.Backend.Simu as Simu
 import qualified Ganeti.HTools.Backend.Text as Text
 import qualified Ganeti.HTools.Backend.IAlloc as IAlloc
+import qualified Ganeti.HTools.Node as Node
+import qualified Ganeti.HTools.Instance as Instance
 import Ganeti.HTools.Loader (mergeData, checkData, ClusterData(..)
-                            , commonSuffix)
+                            , commonSuffix, clearDynU)
 
 import Ganeti.BasicTypes
+import Ganeti.Cpu.Types
+import Ganeti.DataCollectors.Types
 import Ganeti.HTools.Types
 import Ganeti.HTools.CLI
+import Ganeti.JSON
+import Ganeti.Logging (logWarning)
 import Ganeti.Utils (sepSplit, tryRead, exitIfBad, exitWhen)
 
 -- | Error beautifier.
@@ -110,13 +137,17 @@
         | otherwise -> return $ Bad "No backend selected! Exiting."
   now <- getClockTime
 
-  let ldresult = input_data >>= mergeData util_data exTags selInsts exInsts now
+  let ignoreDynU = optIgnoreDynu opts
+      eff_u = if ignoreDynU then [] else util_data
+      ldresult = input_data >>= (if ignoreDynU then clearDynU else return)
+                            >>= mergeData eff_u exTags selInsts exInsts now
   cdata <- exitIfBad "failed to load data, aborting" ldresult
-  let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata)
+  cdata' <- if optMonD opts then queryAllMonDDCs cdata opts else return cdata
+  let (fix_msgs, nl) = checkData (cdNodes cdata') (cdInstances cdata')
 
   unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs
 
-  return cdata {cdNodes = nl}
+  return cdata' {cdNodes = nl}
 
 -- | Function to save the cluster data to a file.
 maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to
@@ -131,3 +162,152 @@
   writeFile out_path adata
   hPrintf stderr "The cluster state %s has been written to file '%s'\n"
           msg out_path
+
+-- | Type describing a data collector basic information.
+data DataCollector = DataCollector
+  { dName     :: String           -- ^ Name of the data collector
+  , dCategory :: Maybe DCCategory -- ^ The name of the category
+  }
+
+-- | The actual data types for MonD's Data Collectors.
+data Report = CPUavgloadReport CPUavgload
+
+-- | The list of Data Collectors used by hail and hbal.
+collectors :: Options -> [DataCollector]
+collectors opts =
+  if optIgnoreDynu opts
+    then []
+    else [ DataCollector CPUload.dcName CPUload.dcCategory ]
+
+-- | MonDs Data parsed by a mock file. Representing (node name, list of reports
+-- produced by MonDs Data Collectors).
+type MonDData = (String, [DCReport])
+
+-- | A map storing MonDs data.
+type MapMonDData = Map.Map String [DCReport]
+
+-- | Parse MonD data file contents.
+pMonDData :: String -> Result [MonDData]
+pMonDData input =
+  loadJSArray "Parsing MonD's answer" input >>=
+  mapM (pMonDN . J.fromJSObject)
+
+-- | Parse a node's JSON record.
+pMonDN :: JSRecord -> Result MonDData
+pMonDN a = do
+  node <- tryFromObj "Parsing node's name" a "node"
+  reports <- tryFromObj "Parsing node's reports" a "reports"
+  return (node, reports)
+
+-- | Query all MonDs for all Data Collector.
+queryAllMonDDCs :: ClusterData -> Options -> IO ClusterData
+queryAllMonDDCs cdata opts = do
+  map_mDD <-
+    case optMonDFile opts of
+      Nothing -> return Nothing
+      Just fp -> do
+        monDData_contents <- readFile fp
+        monDData <- exitIfBad "can't parse MonD data"
+                    . pMonDData $ monDData_contents
+        return . Just $ Map.fromList monDData
+  let (ClusterData _ nl il _ _) = cdata
+  (nl', il') <- foldM (queryAllMonDs map_mDD) (nl, il) (collectors opts)
+  return $ cdata {cdNodes = nl', cdInstances = il'}
+
+-- | Query all MonDs for a single Data Collector.
+queryAllMonDs :: Maybe MapMonDData -> (Node.List, Instance.List)
+                 -> DataCollector -> IO (Node.List, Instance.List)
+queryAllMonDs m (nl, il) dc = do
+  elems <- mapM (queryAMonD m dc) (Container.elems nl)
+  let elems' = catMaybes elems
+  if length elems == length elems'
+    then
+      let il' = foldl updateUtilData il elems'
+          nl' = zip (Container.keys nl) elems'
+      in return (Container.fromList nl', il')
+    else do
+      logWarning $ "Didn't receive an answer by all MonDs, " ++ dName dc
+                   ++ "'s data will be ignored."
+      return (nl,il)
+
+-- | Query a specified MonD for a Data Collector.
+fromCurl :: DataCollector -> Node.Node -> IO (Maybe DCReport)
+fromCurl dc node = do
+  (code, !body) <-  curlGetString (prepareUrl dc node) []
+  case code of
+    CurlOK ->
+      case J.decodeStrict body :: J.Result DCReport of
+        J.Ok r -> return $ Just r
+        J.Error _ -> return Nothing
+    _ -> do
+      logWarning $ "Failed to contact node's " ++ Node.name node
+                   ++ " MonD for DC " ++ dName dc
+      return Nothing
+
+-- | Return the data from correct combination of a Data Collector
+-- and a DCReport.
+mkReport :: DataCollector -> Maybe DCReport -> Maybe Report
+mkReport dc dcr =
+  case dcr of
+    Nothing -> Nothing
+    Just dcr' ->
+      case () of
+           _ | CPUload.dcName == dName dc ->
+                 case fromJVal (dcReportData dcr') :: Result CPUavgload of
+                   Ok cav -> Just $ CPUavgloadReport cav
+                   Bad _ -> Nothing
+             | otherwise -> Nothing
+
+-- | Get data report for the specified Data Collector and Node from the map.
+fromFile :: DataCollector -> Node.Node -> MapMonDData -> Maybe DCReport
+fromFile dc node m =
+  let matchDCName dcr = dName dc == dcReportName dcr
+  in maybe Nothing (L.find matchDCName) $ Map.lookup (Node.name node) m
+
+-- | Query a MonD for a single Data Collector.
+queryAMonD :: Maybe MapMonDData -> DataCollector -> Node.Node
+              -> IO (Maybe Node.Node)
+queryAMonD m dc node = do
+  dcReport <-
+    case m of
+      Nothing -> fromCurl dc node
+      Just m' -> return $ fromFile dc node m'
+  case mkReport dc dcReport of
+    Nothing -> return Nothing
+    Just report ->
+      case report of
+        CPUavgloadReport cav ->
+          let ct = cavCpuTotal cav
+              du = Node.utilLoad node
+              du' = du {cpuWeight = ct}
+          in return $ Just node {Node.utilLoad = du'}
+
+-- | Update utilization data.
+updateUtilData :: Instance.List -> Node.Node -> Instance.List
+updateUtilData il node =
+  let ct = cpuWeight (Node.utilLoad node)
+      n_uCpu = Node.uCpu node
+      upd inst =
+        if Node.idx node == Instance.pNode inst
+          then
+            let i_vcpus = Instance.vcpus inst
+                i_util = ct / fromIntegral n_uCpu * fromIntegral i_vcpus
+                i_du = Instance.util inst
+                i_du' = i_du {cpuWeight = i_util}
+            in inst {Instance.util = i_du'}
+          else inst
+  in Container.map upd il
+
+-- | Prepare url to query a single collector.
+prepareUrl :: DataCollector -> Node.Node -> URLString
+prepareUrl dc node =
+  Node.name node ++ ":" ++ show C.defaultMondPort ++ "/"
+  ++ show C.mondLatestApiVersion ++ "/report/" ++
+  getDCCName (dCategory dc) ++ "/" ++ dName dc
+
+-- | Get Category Name.
+getDCCName :: Maybe DCCategory -> String
+getDCCName dcc =
+  case dcc of
+    Nothing -> "default"
+    Just c -> getCategoryName c
diff --git a/src/Ganeti/HTools/Graph.hs b/src/Ganeti/HTools/Graph.hs
index 3bc42d8..22fc0f6 100644
--- a/src/Ganeti/HTools/Graph.hs
+++ b/src/Ganeti/HTools/Graph.hs
@@ -29,21 +29,30 @@
 {-
 
 Copyright (C) 2012, 2013, Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Group.hs b/src/Ganeti/HTools/Group.hs
index 47e9bab..7a285d6 100644
--- a/src/Ganeti/HTools/Group.hs
+++ b/src/Ganeti/HTools/Group.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -31,6 +40,7 @@
   , create
   , setIdx
   , isAllocable
+  , setUnallocable
   ) where
 
 import qualified Ganeti.HTools.Container as Container
@@ -100,3 +110,7 @@
 -- | Checks if a group is allocable.
 isAllocable :: Group -> Bool
 isAllocable = (/= T.AllocUnallocable) . allocPolicy
+
+-- | Makes the group unallocatable
+setUnallocable :: Group -> Group
+setUnallocable t = t { allocPolicy = T.AllocUnallocable }
diff --git a/src/Ganeti/HTools/Instance.hs b/src/Ganeti/HTools/Instance.hs
index 5416425..5302730 100644
--- a/src/Ganeti/HTools/Instance.hs
+++ b/src/Ganeti/HTools/Instance.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Loader.hs b/src/Ganeti/HTools/Loader.hs
index b0a3f4f..c33670a 100644
--- a/src/Ganeti/HTools/Loader.hs
+++ b/src/Ganeti/HTools/Loader.hs
@@ -8,26 +8,36 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.HTools.Loader
   ( mergeData
+  , clearDynU
   , checkData
   , assignIndices
   , setMaster
@@ -61,6 +71,7 @@
 import qualified Ganeti.Constants as C
 import Ganeti.HTools.Types
 import Ganeti.Utils
+import Ganeti.Types (EvacMode)
 
 -- * Constants
 
@@ -290,6 +301,12 @@
          (Ok cdata { cdNodes = nl3, cdInstances = il5 })
          (Bad $ "Unknown instance(s): " ++ show(map lrContent lkp_unknown))
 
+-- | In a cluster description, clear dynamic utilisation information.
+clearDynU :: ClusterData -> Result ClusterData
+clearDynU cdata@(ClusterData _ _ il _ _) =
+  let il2 = Container.map (\ inst -> inst {Instance.util = zeroUtil }) il
+  in Ok cdata { cdInstances = il2 }
+
 -- | Checks the cluster data for consistency.
 checkData :: Node.List -> Instance.List
           -> ([String], Node.List)
diff --git a/src/Ganeti/HTools/Nic.hs b/src/Ganeti/HTools/Nic.hs
index 02f634d..16d20eb 100644
--- a/src/Ganeti/HTools/Nic.hs
+++ b/src/Ganeti/HTools/Nic.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Node.hs b/src/Ganeti/HTools/Node.hs
index b596c66..ebdf52f 100644
--- a/src/Ganeti/HTools/Node.hs
+++ b/src/Ganeti/HTools/Node.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/PeerMap.hs b/src/Ganeti/HTools/PeerMap.hs
index f178578..67e3050 100644
--- a/src/Ganeti/HTools/PeerMap.hs
+++ b/src/Ganeti/HTools/PeerMap.hs
@@ -9,21 +9,30 @@
 {-
 
 Copyright (C) 2009, 2011 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Program/Hail.hs b/src/Ganeti/HTools/Program/Hail.hs
index 50009a3..942d837 100644
--- a/src/Ganeti/HTools/Program/Hail.hs
+++ b/src/Ganeti/HTools/Program/Hail.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -39,7 +48,8 @@
 import Ganeti.HTools.CLI
 import Ganeti.HTools.Backend.IAlloc
 import Ganeti.HTools.Loader (Request(..), ClusterData(..))
-import Ganeti.HTools.ExtLoader (maybeSaveData, loadExternalData)
+import Ganeti.HTools.ExtLoader (maybeSaveData, loadExternalData
+                               , queryAllMonDDCs)
 import Ganeti.Utils
 
 -- | Options list and functions.
@@ -51,6 +61,8 @@
     , oDataFile
     , oNodeSim
     , oVerbose
+    , oIgnoreDyn
+    , oMonD
     ]
 
 -- | The list of arguments supported by the program.
@@ -69,8 +81,11 @@
       cdata <- loadExternalData opts
       let Request rqt _ = r1
       return $ Request rqt cdata
-    else return r1
-
+    else do
+      let Request rqt cdata = r1
+      cdata' <-
+        if optMonD opts then queryAllMonDDCs cdata opts else return cdata
+      return $ Request rqt cdata'
 
 -- | Main function.
 main :: Options -> [String] -> IO ()
diff --git a/src/Ganeti/HTools/Program/Harep.hs b/src/Ganeti/HTools/Program/Harep.hs
index b278af0..883746d 100644
--- a/src/Ganeti/HTools/Program/Harep.hs
+++ b/src/Ganeti/HTools/Program/Harep.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -268,12 +277,12 @@
   when (isJust arData) $ do
     let tag = arTag $ fromJust arData
     putStrLn (">>> Adding the following tag to " ++ iname ++ ":\n" ++ show tag)
-    execJobsWaitOk' [OpTagsSet (TagInstance iname) [tag]]
+    execJobsWaitOk' [OpTagsSet TagKindInstance [tag] (Just iname)]
 
   unless (null rmTags) $ do
     putStr (">>> Removing the following tags from " ++ iname ++ ":\n" ++
             unlines (map show rmTags))
-    execJobsWaitOk' [OpTagsDel (TagInstance iname) rmTags]
+    execJobsWaitOk' [OpTagsDel TagKindInstance rmTags (Just iname)]
 
   return instData { tagsToRemove = [] }
 
@@ -423,6 +432,7 @@
                             , opDelayOnNodes = []
                             , opDelayOnNodeUuids = Nothing
                             , opDelayRepeat = fromJust $ mkNonNegative 0
+                            , opDelayNoLocks = False
                             } : opcodes
               else
                 opcodes
diff --git a/src/Ganeti/HTools/Program/Hbal.hs b/src/Ganeti/HTools/Program/Hbal.hs
index 1f8eaba..f789844 100644
--- a/src/Ganeti/HTools/Program/Hbal.hs
+++ b/src/Ganeti/HTools/Program/Hbal.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -73,6 +82,7 @@
     , oPrintCommands
     , oDataFile
     , oEvacMode
+    , oRestrictedMigrate
     , oRapiMaster
     , luxi
     , oIAllocSrc
@@ -91,6 +101,9 @@
     , oSelInst
     , oInstMoves
     , oDynuFile
+    , oIgnoreDyn 
+    , oMonD
+    , oMonDDataFile
     , oExTags
     , oExInst
     , oSaveCluster
@@ -119,6 +132,7 @@
              -> Int              -- ^ Remaining length
              -> Bool             -- ^ Allow disk moves
              -> Bool             -- ^ Allow instance moves
+             -> Bool             -- ^ Resrict migration
              -> Int              -- ^ Max node name len
              -> Int              -- ^ Max instance name len
              -> [MoveJob]        -- ^ Current command list
@@ -128,13 +142,13 @@
              -> Bool             -- ^ Enable evacuation mode
              -> IO (Cluster.Table, [MoveJob]) -- ^ The resulting table
                                               -- and commands
-iterateDepth printmove ini_tbl max_rounds disk_moves inst_moves nmlen imlen
-             cmd_strs min_score mg_limit min_gain evac_mode =
+iterateDepth printmove ini_tbl max_rounds disk_moves inst_moves rest_mig nmlen
+             imlen cmd_strs min_score mg_limit min_gain evac_mode =
   let Cluster.Table ini_nl ini_il _ _ = ini_tbl
       allowed_next = Cluster.doNextBalance ini_tbl max_rounds min_score
       m_fin_tbl = if allowed_next
                     then Cluster.tryBalance ini_tbl disk_moves inst_moves
-                         evac_mode mg_limit min_gain
+                         evac_mode rest_mig mg_limit min_gain
                     else Nothing
   in case m_fin_tbl of
        Just fin_tbl ->
@@ -151,7 +165,7 @@
                putStrLn sol_line
                hFlush stdout
            iterateDepth printmove fin_tbl max_rounds disk_moves inst_moves
-                        nmlen imlen upd_cmd_strs min_score
+                        rest_mig nmlen imlen upd_cmd_strs min_score
                         mg_limit min_gain evac_mode
        Nothing -> return (ini_tbl, cmd_strs)
 
@@ -386,6 +400,7 @@
   (fin_tbl, cmd_strs) <- iterateDepth True ini_tbl (optMaxLength opts)
                          (optDiskMoves opts)
                          (optInstMoves opts)
+                         (optRestrictedMigrate opts)
                          nmlen imlen [] min_cv
                          (optMinGainLim opts) (optMinGain opts)
                          (optEvacMode opts)
diff --git a/src/Ganeti/HTools/Program/Hcheck.hs b/src/Ganeti/HTools/Program/Hcheck.hs
index 35f32ef..25b9b2a 100644
--- a/src/Ganeti/HTools/Program/Hcheck.hs
+++ b/src/Ganeti/HTools/Program/Hcheck.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU Gene52al Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -261,6 +270,7 @@
                                     (optMaxLength opts)
                                     (optDiskMoves opts)
                                     (optInstMoves opts)
+                                    False
                                     nmlen imlen [] min_cv
                                     (optMinGainLim opts) (optMinGain opts)
                                     (optEvacMode opts)
diff --git a/src/Ganeti/HTools/Program/Hinfo.hs b/src/Ganeti/HTools/Program/Hinfo.hs
index f15977c..05e0d7b 100644
--- a/src/Ganeti/HTools/Program/Hinfo.hs
+++ b/src/Ganeti/HTools/Program/Hinfo.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -61,6 +70,9 @@
     , oVerbose
     , oQuiet
     , oOfflineNode
+    , oIgnoreDyn
+    , oMonD
+    , oMonDDataFile
     ]
 
 -- | The list of arguments supported by the program.
diff --git a/src/Ganeti/HTools/Program/Hroller.hs b/src/Ganeti/HTools/Program/Hroller.hs
index 5c69cdd..74730ed 100644
--- a/src/Ganeti/HTools/Program/Hroller.hs
+++ b/src/Ganeti/HTools/Program/Hroller.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Program/Hscan.hs b/src/Ganeti/HTools/Program/Hscan.hs
index ddc811a..984ef7c 100644
--- a/src/Ganeti/HTools/Program/Hscan.hs
+++ b/src/Ganeti/HTools/Program/Hscan.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Program/Hspace.hs b/src/Ganeti/HTools/Program/Hspace.hs
index be90482..2685b54 100644
--- a/src/Ganeti/HTools/Program/Hspace.hs
+++ b/src/Ganeti/HTools/Program/Hspace.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -32,6 +41,7 @@
 import Control.Monad
 import Data.Char (toUpper, toLower)
 import Data.Function (on)
+import qualified Data.IntMap as IntMap
 import Data.List
 import Data.Maybe (fromMaybe)
 import Data.Ord (comparing)
@@ -41,6 +51,7 @@
 
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Cluster as Cluster
+import qualified Ganeti.HTools.Group as Group
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Instance as Instance
 
@@ -67,6 +78,8 @@
     , oIAllocSrc
     , oVerbose
     , oQuiet
+    , oIndependentGroups
+    , oAcceptExisting
     , oOfflineNode
     , oMachineReadable
     , oMaxCpu
@@ -447,6 +460,8 @@
 
   let verbose = optVerbose opts
       machine_r = optMachineReadable opts
+      independent_grps = optIndependentGroups opts
+      accept_existing = optAcceptExisting opts
 
   orig_cdata@(ClusterData gl fixed_nl il _ ipol) <- loadExternalData opts
   nl <- setNodeStatus opts fixed_nl
@@ -477,15 +492,26 @@
   printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
     (Node.haveExclStorage nl)
 
-  let stop_allocation = case Cluster.computeBadItems nl il of
-                          ([], _) -> Nothing
+  let (bad_nodes, _)  = Cluster.computeBadItems nl il
+      gl' = if accept_existing
+              then gl
+              else foldl (flip $ IntMap.adjust Group.setUnallocable) gl
+                     (map Node.group bad_nodes)
+      grps_remaining = any Group.isAllocable $ IntMap.elems gl'
+      stop_allocation = case () of
+                          _ | accept_existing-> Nothing
+                          _ | independent_grps && grps_remaining -> Nothing
+                          _ | null bad_nodes -> Nothing
                           _ -> Just ([(FailN1, 1)]::FailStats, nl, il, [], [])
       alloclimit = if optMaxLength opts == -1
                    then Nothing
                    else Just (optMaxLength opts)
 
   allocnodes <- exitIfBad "failure during allocation" $
-                Cluster.genAllocNodes gl nl req_nodes True
+                Cluster.genAllocNodes gl' nl req_nodes True
+
+  when (verbose > 3)
+    . hPrintf stderr "Allocatable nodes: %s\n" $ show allocnodes
 
   -- Run the tiered allocation
 
diff --git a/src/Ganeti/HTools/Program/Main.hs b/src/Ganeti/HTools/Program/Main.hs
index 499c6e4..539197b 100644
--- a/src/Ganeti/HTools/Program/Main.hs
+++ b/src/Ganeti/HTools/Program/Main.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/HTools/Types.hs b/src/Ganeti/HTools/Types.hs
index 764c56c..e3249ce 100644
--- a/src/Ganeti/HTools/Types.hs
+++ b/src/Ganeti/HTools/Types.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -69,8 +78,12 @@
   , FailStats
   , OpResult
   , opToResult
-  , EvacMode(..)
   , ISpec(..)
+  , defMinISpec
+  , defStdISpec
+  , maxDisks
+  , maxNics
+  , defMaxISpec
   , MinMaxISpecs(..)
   , IPolicy(..)
   , defIPolicy
@@ -90,7 +103,7 @@
 import qualified Data.Map as M
 import System.Time (ClockTime)
 
-import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import qualified Ganeti.THH as THH
 import Ganeti.BasicTypes
 import Ganeti.Types
@@ -166,42 +179,54 @@
 
 -- | Instance specification type.
 $(THH.buildObject "ISpec" "iSpec"
-  [ THH.renameField "MemorySize" $ THH.simpleField C.ispecMemSize    [t| Int |]
-  , THH.renameField "CpuCount"   $ THH.simpleField C.ispecCpuCount   [t| Int |]
-  , THH.renameField "DiskSize"   $ THH.simpleField C.ispecDiskSize   [t| Int |]
-  , THH.renameField "DiskCount"  $ THH.simpleField C.ispecDiskCount  [t| Int |]
-  , THH.renameField "NicCount"   $ THH.simpleField C.ispecNicCount   [t| Int |]
-  , THH.renameField "SpindleUse" $ THH.simpleField C.ispecSpindleUse [t| Int |]
+  [ THH.renameField "MemorySize" $
+    THH.simpleField ConstantUtils.ispecMemSize    [t| Int |]
+  , THH.renameField "CpuCount"   $
+    THH.simpleField ConstantUtils.ispecCpuCount   [t| Int |]
+  , THH.renameField "DiskSize"   $
+    THH.simpleField ConstantUtils.ispecDiskSize   [t| Int |]
+  , THH.renameField "DiskCount"  $
+    THH.simpleField ConstantUtils.ispecDiskCount  [t| Int |]
+  , THH.renameField "NicCount"   $
+    THH.simpleField ConstantUtils.ispecNicCount   [t| Int |]
+  , THH.renameField "SpindleUse" $
+    THH.simpleField ConstantUtils.ispecSpindleUse [t| Int |]
   ])
 
 -- | The default minimum ispec.
 defMinISpec :: ISpec
-defMinISpec = ISpec { iSpecMemorySize = C.ispecsMinmaxDefaultsMinMemorySize
-                    , iSpecCpuCount   = C.ispecsMinmaxDefaultsMinCpuCount
-                    , iSpecDiskSize   = C.ispecsMinmaxDefaultsMinDiskSize
-                    , iSpecDiskCount  = C.ispecsMinmaxDefaultsMinDiskCount
-                    , iSpecNicCount   = C.ispecsMinmaxDefaultsMinNicCount
-                    , iSpecSpindleUse = C.ispecsMinmaxDefaultsMinSpindleUse
+defMinISpec = ISpec { iSpecMemorySize = 128
+                    , iSpecCpuCount   = 1
+                    , iSpecDiskCount  = 1
+                    , iSpecDiskSize   = 1024
+                    , iSpecNicCount   = 1
+                    , iSpecSpindleUse = 1
                     }
 
 -- | The default standard ispec.
 defStdISpec :: ISpec
-defStdISpec = ISpec { iSpecMemorySize = C.ipolicyDefaultsStdMemorySize
-                    , iSpecCpuCount   = C.ipolicyDefaultsStdCpuCount
-                    , iSpecDiskSize   = C.ipolicyDefaultsStdDiskSize
-                    , iSpecDiskCount  = C.ipolicyDefaultsStdDiskCount
-                    , iSpecNicCount   = C.ipolicyDefaultsStdNicCount
-                    , iSpecSpindleUse = C.ipolicyDefaultsStdSpindleUse
+defStdISpec = ISpec { iSpecMemorySize = 128
+                    , iSpecCpuCount   = 1
+                    , iSpecDiskCount  = 1
+                    , iSpecDiskSize   = 1024
+                    , iSpecNicCount   = 1
+                    , iSpecSpindleUse = 1
                     }
 
+maxDisks :: Int
+maxDisks = 16
+
+maxNics :: Int
+maxNics = 8
+
 -- | The default max ispec.
 defMaxISpec :: ISpec
-defMaxISpec = ISpec { iSpecMemorySize = C.ispecsMinmaxDefaultsMaxMemorySize
-                    , iSpecCpuCount   = C.ispecsMinmaxDefaultsMaxCpuCount
-                    , iSpecDiskSize   = C.ispecsMinmaxDefaultsMaxDiskSize
-                    , iSpecDiskCount  = C.ispecsMinmaxDefaultsMaxDiskCount
-                    , iSpecNicCount   = C.ispecsMinmaxDefaultsMaxNicCount
-                    , iSpecSpindleUse = C.ispecsMinmaxDefaultsMaxSpindleUse
+defMaxISpec = ISpec { iSpecMemorySize = 32768
+                    , iSpecCpuCount   = 8
+                    , iSpecDiskCount  = maxDisks
+                    , iSpecDiskSize   = 1024 * 1024
+                    , iSpecNicCount   = maxNics
+                    , iSpecSpindleUse = 12
                     }
 
 -- | Minimum and maximum instance specs type.
@@ -219,14 +244,15 @@
 -- | Instance policy type.
 $(THH.buildObject "IPolicy" "iPolicy"
   [ THH.renameField "MinMaxISpecs" $
-      THH.simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
-  , THH.renameField "StdSpec" $ THH.simpleField C.ispecsStd [t| ISpec |]
+      THH.simpleField ConstantUtils.ispecsMinmax [t| [MinMaxISpecs] |]
+  , THH.renameField "StdSpec" $
+      THH.simpleField ConstantUtils.ispecsStd [t| ISpec |]
   , THH.renameField "DiskTemplates" $
-      THH.simpleField C.ipolicyDts [t| [DiskTemplate] |]
+      THH.simpleField ConstantUtils.ipolicyDts [t| [DiskTemplate] |]
   , THH.renameField "VcpuRatio" $
-      THH.simpleField C.ipolicyVcpuRatio [t| Double |]
+      THH.simpleField ConstantUtils.ipolicyVcpuRatio [t| Double |]
   , THH.renameField "SpindleRatio" $
-      THH.simpleField C.ipolicySpindleRatio [t| Double |]
+      THH.simpleField ConstantUtils.ipolicySpindleRatio [t| Double |]
   ])
 
 -- | Converts an ISpec type to a RSpec one.
@@ -239,15 +265,16 @@
 
 -- | The default instance policy.
 defIPolicy :: IPolicy
-defIPolicy = IPolicy { iPolicyMinMaxISpecs = defMinMaxISpecs
-                     , iPolicyStdSpec = defStdISpec
-                     -- hardcoding here since Constants.hs exports the
-                     -- string values, not the actual type; and in
-                     -- htools, we are mostly looking at DRBD
-                     , iPolicyDiskTemplates = [minBound..maxBound]
-                     , iPolicyVcpuRatio = C.ipolicyDefaultsVcpuRatio
-                     , iPolicySpindleRatio = C.ipolicyDefaultsSpindleRatio
-                     }
+defIPolicy =
+  IPolicy { iPolicyMinMaxISpecs = defMinMaxISpecs
+          , iPolicyStdSpec = defStdISpec
+          -- hardcoding here since Constants.hs exports the
+          -- string values, not the actual type; and in
+          -- htools, we are mostly looking at DRBD
+          , iPolicyDiskTemplates = [minBound..maxBound]
+          , iPolicyVcpuRatio = ConstantUtils.ipolicyDefaultsVcpuRatio
+          , iPolicySpindleRatio = ConstantUtils.ipolicyDefaultsSpindleRatio
+          }
 
 -- | The dynamic resource specs of a machine (i.e. load or load
 -- capacity, as opposed to size).
@@ -378,31 +405,23 @@
   -- | Updates the index of the element
   setIdx  :: a -> Int -> a
 
--- | The iallocator node-evacuate evac_mode type.
-$(THH.declareSADT "EvacMode"
-       [ ("ChangePrimary",   'C.iallocatorNevacPri)
-       , ("ChangeSecondary", 'C.iallocatorNevacSec)
-       , ("ChangeAll",       'C.iallocatorNevacAll)
-       ])
-$(THH.makeJSONInstance ''EvacMode)
-
 -- | The repair modes for the auto-repair tool.
-$(THH.declareSADT "AutoRepairType"
-       -- Order is important here: from least destructive to most.
-       [ ("ArFixStorage", 'C.autoRepairFixStorage)
-       , ("ArMigrate",    'C.autoRepairMigrate)
-       , ("ArFailover",   'C.autoRepairFailover)
-       , ("ArReinstall",  'C.autoRepairReinstall)
-       ])
+$(THH.declareLADT ''String "AutoRepairType"
+  -- Order is important here: from least destructive to most.
+  [ ("ArFixStorage", "fix-storage")
+  , ("ArMigrate",    "migrate")
+  , ("ArFailover",   "failover")
+  , ("ArReinstall",  "reinstall")
+  ])
 
 -- | The possible auto-repair results.
-$(THH.declareSADT "AutoRepairResult"
-       -- Order is important here: higher results take precedence when an object
-       -- has several result annotations attached.
-       [ ("ArEnoperm", 'C.autoRepairEnoperm)
-       , ("ArSuccess", 'C.autoRepairSuccess)
-       , ("ArFailure", 'C.autoRepairFailure)
-       ])
+$(THH.declareLADT ''String "AutoRepairResult"
+  -- Order is important here: higher results take precedence when an object
+  -- has several result annotations attached.
+  [ ("ArEnoperm", "enoperm")
+  , ("ArSuccess", "success")
+  , ("ArFailure", "failure")
+  ])
 
 -- | The possible auto-repair policy for a given instance.
 data AutoRepairPolicy
diff --git a/src/Ganeti/Hash.hs b/src/Ganeti/Hash.hs
index 1b0b4f6..f0065d8 100644
--- a/src/Ganeti/Hash.hs
+++ b/src/Ganeti/Hash.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Hs2Py/GenConstants.hs b/src/Ganeti/Hs2Py/GenConstants.hs
new file mode 100644
index 0000000..8c71251
--- /dev/null
+++ b/src/Ganeti/Hs2Py/GenConstants.hs
@@ -0,0 +1,62 @@
+{-| Template Haskell code for Haskell to Python constants.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+{-# LANGUAGE TemplateHaskell #-}
+module Ganeti.Hs2Py.GenConstants (genPyConstants) where
+
+import Language.Haskell.TH
+
+import Ganeti.THH
+
+fileFromModule :: Maybe String -> String
+fileFromModule Nothing = ""
+fileFromModule (Just name) = "src/" ++ map dotToSlash name ++ ".hs"
+  where dotToSlash '.' = '/'
+        dotToSlash c = c
+
+comment :: Name -> String
+comment name =
+  "# Generated automatically from Haskell constant '" ++ nameBase name ++
+  "' in file '" ++ fileFromModule (nameModule name) ++ "'"
+
+genList :: Name -> [Name] -> Q [Dec]
+genList name consNames = do
+  let cons = listE $ map (\n -> tupE [mkString n, mkPyValueEx n]) consNames
+  sig <- sigD name [t| [(String, String)] |]
+  fun <- funD name [clause [] (normalB cons) []]
+  return [sig, fun]
+  where mkString n = stringE (comment n ++ "\n" ++ deCamelCase (nameBase n))
+        mkPyValueEx n = [| showValue $(varE n) |]
+
+genPyConstants :: String -> [Name] -> Q [Dec]
+genPyConstants name = genList (mkName name)
diff --git a/src/Ganeti/Hs2Py/GenOpCodes.hs b/src/Ganeti/Hs2Py/GenOpCodes.hs
new file mode 100644
index 0000000..b2daefc
--- /dev/null
+++ b/src/Ganeti/Hs2Py/GenOpCodes.hs
@@ -0,0 +1,90 @@
+{-| GenOpCodes handles Python opcode generation.
+
+GenOpCodes contains the helper functions that generate the Python
+opcodes as strings from the Haskell opcode description.
+
+-}
+
+{-
+
+Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Ganeti.Hs2Py.GenOpCodes (showPyClasses) where
+
+import Data.List (intercalate, zipWith4)
+
+import Ganeti.OpCodes
+import Ganeti.THH
+
+-- | Generates the Python class docstring.
+pyClassDoc :: String -> String
+pyClassDoc doc
+  | length (lines doc) > 1 =
+    "  \"\"\"" ++ doc ++ "\n\n" ++ "  \"\"\"" ++ "\n"
+  | otherwise =
+    "  \"\"\"" ++ doc ++ "\"\"\"" ++ "\n"
+
+-- | Generates an opcode parameter in Python.
+pyClassField :: String -> String -> Maybe PyValueEx -> String -> String
+pyClassField name typ Nothing doc =
+  "(" ++ intercalate ", " [show name, "None", typ, show doc] ++ ")"
+  
+pyClassField name typ (Just (PyValueEx def)) doc =
+  "(" ++ intercalate ", " [show name, showValue def, typ, show doc] ++ ")"
+  
+-- | Comma intercalates and indents opcode parameters in Python.
+intercalateIndent :: [String] -> String
+intercalateIndent xs = intercalate "," (map ("\n    " ++) xs)
+
+-- | Generates an opcode as a Python class.
+showPyClass :: OpCodeDescriptor -> String
+showPyClass (name, typ, doc, fields, types, defs, docs, dsc) =
+  let
+    baseclass
+      | name == "OpInstanceMultiAlloc" = "OpInstanceMultiAllocBase"
+      | otherwise = "OpCode"
+    opDscField
+      | null dsc = ""
+      | otherwise = "  OP_DSC_FIELD = " ++ show dsc ++ "\n"
+    withLU
+      | name == "OpTestDummy" = "\n  WITH_LU = False"
+      | otherwise = ""
+  in
+   "class " ++ name ++ "(" ++ baseclass ++ "):" ++ "\n" ++
+   pyClassDoc doc ++
+   opDscField ++
+   "  OP_PARAMS = [" ++
+   intercalateIndent (zipWith4 pyClassField fields types defs docs) ++
+   "\n    ]" ++ "\n" ++
+   "  OP_RESULT = " ++ typ ++
+   withLU ++ "\n\n"
+
+-- | Generates all opcodes as Python classes.
+showPyClasses :: String
+showPyClasses = concatMap showPyClass pyClasses
diff --git a/src/Ganeti/Hs2Py/ListConstants.hs.in b/src/Ganeti/Hs2Py/ListConstants.hs.in
new file mode 100644
index 0000000..0342528
--- /dev/null
+++ b/src/Ganeti/Hs2Py/ListConstants.hs.in
@@ -0,0 +1,50 @@
+{-| Contains the list of the Haskell to Python constants.
+
+Note that this file is autogenerated by the Makefile with a header
+from @ListConstants.hs.in@.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+{-# LANGUAGE TemplateHaskell #-}
+module Ganeti.Hs2Py.ListConstants where
+
+import Ganeti.Constants
+import Ganeti.Hs2Py.GenConstants
+import Ganeti.PyValueInstances ()
+
+$(genPyConstants "pyConstants"
+  (
+PY_CONSTANT_NAMES[]))
+
+putConstants :: IO ()
+putConstants =
+  sequence_ [ putStrLn (k ++ " = " ++ v) | (k, v) <- pyConstants ]
diff --git a/src/Ganeti/Hs2Py/OpDoc.hs b/src/Ganeti/Hs2Py/OpDoc.hs
new file mode 100644
index 0000000..b4035dc
--- /dev/null
+++ b/src/Ganeti/Hs2Py/OpDoc.hs
@@ -0,0 +1,497 @@
+{-| Implementation of the doc strings for the opcodes.
+
+-}
+
+{-
+
+Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Ganeti.Hs2Py.OpDoc where
+
+
+opClusterPostInit :: String
+opClusterPostInit =
+  "Post cluster initialization.\n\
+\\n\
+\  This opcode does not touch the cluster at all. Its purpose is to run hooks\n\
+\  after the cluster has been initialized."
+
+opClusterDestroy :: String
+opClusterDestroy =
+  "Destroy the cluster.\n\
+\\n\
+\  This opcode has no other parameters. All the state is irreversibly\n\
+\  lost after the execution of this opcode."
+
+opClusterQuery :: String
+opClusterQuery =
+  "Query cluster information."
+
+opClusterVerify :: String
+opClusterVerify =
+  "Submits all jobs necessary to verify the cluster."
+
+opClusterVerifyConfig :: String
+opClusterVerifyConfig =
+  "Verify the cluster config."
+
+opClusterVerifyGroup :: String
+opClusterVerifyGroup =
+  "Run verify on a node group from the cluster.\n\
+\\n\
+\  @type skip_checks: C{list}\n\
+\  @ivar skip_checks: steps to be skipped from the verify process; this\n\
+\                     needs to be a subset of\n\
+\                     L{constants.VERIFY_OPTIONAL_CHECKS}; currently\n\
+\                     only L{constants.VERIFY_NPLUSONE_MEM} can be passed"
+
+opClusterVerifyDisks :: String
+opClusterVerifyDisks =
+  "Verify the cluster disks."
+
+opGroupVerifyDisks :: String
+opGroupVerifyDisks =
+  "Verifies the status of all disks in a node group.\n\
+\\n\
+\  Result: a tuple of three elements:\n\
+\    - dict of node names with issues (values: error msg)\n\
+\    - list of instances with degraded disks (that should be activated)\n\
+\    - dict of instances with missing logical volumes (values: (node, vol)\n\
+\      pairs with details about the missing volumes)\n\
+\\n\
+\  In normal operation, all lists should be empty. A non-empty instance\n\
+\  list (3rd element of the result) is still ok (errors were fixed) but\n\
+\  non-empty node list means some node is down, and probably there are\n\
+\  unfixable drbd errors.\n\
+\\n\
+\  Note that only instances that are drbd-based are taken into\n\
+\  consideration. This might need to be revisited in the future."
+
+opClusterRepairDiskSizes :: String
+opClusterRepairDiskSizes =
+  "Verify the disk sizes of the instances and fixes configuration\n\
+\  mismatches.\n\
+\\n\
+\  Parameters: optional instances list, in case we want to restrict the\n\
+\  checks to only a subset of the instances.\n\
+\\n\
+\  Result: a list of tuples, (instance, disk, parameter, new-size) for\n\
+\  changed configurations.\n\
+\\n\
+\  In normal operation, the list should be empty.\n\
+\\n\
+\  @type instances: list\n\
+\  @ivar instances: the list of instances to check, or empty for all instances"
+
+opClusterConfigQuery :: String
+opClusterConfigQuery =
+  "Query cluster configuration values."
+
+opClusterRename :: String
+opClusterRename =
+  "Rename the cluster.\n\
+\\n\
+\  @type name: C{str}\n\
+\  @ivar name: The new name of the cluster. The name and/or the master IP\n\
+\              address will be changed to match the new name and its IP\n\
+\              address."
+
+opClusterSetParams :: String
+opClusterSetParams =
+  "Change the parameters of the cluster.\n\
+\\n\
+\  @type vg_name: C{str} or C{None}\n\
+\  @ivar vg_name: The new volume group name or None to disable LVM usage."
+
+opClusterRedistConf :: String
+opClusterRedistConf =
+  "Force a full push of the cluster configuration."
+
+opClusterActivateMasterIp :: String
+opClusterActivateMasterIp =
+  "Activate the master IP on the master node."
+
+opClusterDeactivateMasterIp :: String
+opClusterDeactivateMasterIp =
+  "Deactivate the master IP on the master node."
+
+opQuery :: String
+opQuery =
+  "Query for resources/items.\n\
+\\n\
+\  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}\n\
+\  @ivar fields: List of fields to retrieve\n\
+\  @ivar qfilter: Query filter"
+
+opQueryFields :: String
+opQueryFields =
+  "Query for available resource/item fields.\n\
+\\n\
+\  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}\n\
+\  @ivar fields: List of fields to retrieve"
+
+opOobCommand :: String
+opOobCommand =
+  "Interact with OOB."
+
+opRestrictedCommand :: String
+opRestrictedCommand =
+  "Runs a restricted command on node(s)."
+
+opNodeRemove :: String
+opNodeRemove =
+  "Remove a node.\n\
+\\n\
+\  @type node_name: C{str}\n\
+\  @ivar node_name: The name of the node to remove. If the node still has\n\
+\                   instances on it, the operation will fail."
+
+opNodeAdd :: String
+opNodeAdd =
+  "Add a node to the cluster.\n\
+\\n\
+\  @type node_name: C{str}\n\
+\  @ivar node_name: The name of the node to add. This can be a short name,\n\
+\                   but it will be expanded to the FQDN.\n\
+\  @type primary_ip: IP address\n\
+\  @ivar primary_ip: The primary IP of the node. This will be ignored when\n\
+\                    the opcode is submitted, but will be filled during the\n\
+\                    node add (so it will be visible in the job query).\n\
+\  @type secondary_ip: IP address\n\
+\  @ivar secondary_ip: The secondary IP of the node. This needs to be passed\n\
+\                      if the cluster has been initialized in 'dual-network'\n\
+\                      mode, otherwise it must not be given.\n\
+\  @type readd: C{bool}\n\
+\  @ivar readd: Whether to re-add an existing node to the cluster. If\n\
+\               this is not passed, then the operation will abort if the node\n\
+\               name is already in the cluster; use this parameter to\n\
+\               'repair' a node that had its configuration broken, or was\n\
+\               reinstalled without removal from the cluster.\n\
+\  @type group: C{str}\n\
+\  @ivar group: The node group to which this node will belong.\n\
+\  @type vm_capable: C{bool}\n\
+\  @ivar vm_capable: The vm_capable node attribute\n\
+\  @type master_capable: C{bool}\n\
+\  @ivar master_capable: The master_capable node attribute"
+
+opNodeQuery :: String
+opNodeQuery =
+  "Compute the list of nodes."
+
+opNodeQueryvols :: String
+opNodeQueryvols =
+  "Get list of volumes on node."
+
+opNodeQueryStorage :: String
+opNodeQueryStorage =
+  "Get information on storage for node(s)."
+
+opNodeModifyStorage :: String
+opNodeModifyStorage =
+  "Modifies the properies of a storage unit"
+
+opRepairNodeStorage :: String
+opRepairNodeStorage =
+  "Repairs the volume group on a node."
+
+opNodeSetParams :: String
+opNodeSetParams =
+  "Change the parameters of a node."
+
+opNodePowercycle :: String
+opNodePowercycle =
+  "Tries to powercycle a node."
+
+opNodeMigrate :: String
+opNodeMigrate =
+  "Migrate all instances from a node."
+
+opNodeEvacuate :: String
+opNodeEvacuate =
+  "Evacuate instances off a number of nodes."
+
+opInstanceCreate :: String
+opInstanceCreate =
+  "Create an instance.\n\
+\\n\
+\  @ivar instance_name: Instance name\n\
+\  @ivar mode: Instance creation mode (one of\
+\ L{constants.INSTANCE_CREATE_MODES})\n\
+\  @ivar source_handshake: Signed handshake from source (remote import only)\n\
+\  @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)\n\
+\  @ivar source_instance_name: Previous name of instance (remote import only)\n\
+\  @ivar source_shutdown_timeout: Shutdown timeout used for source instance\n\
+\    (remote import only)"
+
+opInstanceMultiAlloc :: String
+opInstanceMultiAlloc =
+  "Allocates multiple instances."
+
+opInstanceReinstall :: String
+opInstanceReinstall =
+  "Reinstall an instance's OS."
+
+opInstanceRemove :: String
+opInstanceRemove =
+  "Remove an instance."
+
+opInstanceRename :: String
+opInstanceRename =
+  "Rename an instance."
+
+opInstanceStartup :: String
+opInstanceStartup =
+  "Startup an instance."
+
+opInstanceShutdown :: String
+opInstanceShutdown =
+  "Shutdown an instance."
+
+opInstanceReboot :: String
+opInstanceReboot =
+  "Reboot an instance."
+
+opInstanceReplaceDisks :: String
+opInstanceReplaceDisks =
+  "Replace the disks of an instance."
+
+opInstanceFailover :: String
+opInstanceFailover =
+  "Failover an instance."
+
+opInstanceMigrate :: String
+opInstanceMigrate =
+  "Migrate an instance.\n\
+\\n\
+\  This migrates (without shutting down an instance) to its secondary\n\
+\  node.\n\
+\\n\
+\  @ivar instance_name: the name of the instance\n\
+\  @ivar mode: the migration mode (live, non-live or None for auto)"
+
+opInstanceMove :: String
+opInstanceMove =
+  "Move an instance.\n\
+\\n\
+\  This move (with shutting down an instance and data copying) to an\n\
+\  arbitrary node.\n\
+\\n\
+\  @ivar instance_name: the name of the instance\n\
+\  @ivar target_node: the destination node"
+
+opInstanceConsole :: String
+opInstanceConsole =
+  "Connect to an instance's console."
+
+opInstanceActivateDisks :: String
+opInstanceActivateDisks =
+  "Activate an instance's disks."
+
+opInstanceDeactivateDisks :: String
+opInstanceDeactivateDisks =
+  "Deactivate an instance's disks."
+
+opInstanceRecreateDisks :: String
+opInstanceRecreateDisks =
+  "Recreate an instance's disks."
+
+opInstanceQuery :: String
+opInstanceQuery =
+  "Compute the list of instances."
+
+opInstanceQueryData :: String
+opInstanceQueryData =
+  "Compute the run-time status of instances."
+
+opInstanceSetParams :: String
+opInstanceSetParams =
+  "Change the parameters of an instance."
+
+opInstanceGrowDisk :: String
+opInstanceGrowDisk =
+  "Grow a disk of an instance."
+
+opInstanceChangeGroup :: String
+opInstanceChangeGroup =
+  "Moves an instance to another node group."
+
+opGroupAdd :: String
+opGroupAdd =
+  "Add a node group to the cluster."
+
+opGroupAssignNodes :: String
+opGroupAssignNodes =
+  "Assign nodes to a node group."
+
+opGroupQuery :: String
+opGroupQuery =
+  "Compute the list of node groups."
+
+opGroupSetParams :: String
+opGroupSetParams =
+  "Change the parameters of a node group."
+
+opGroupRemove :: String
+opGroupRemove =
+  "Remove a node group from the cluster."
+
+opGroupRename :: String
+opGroupRename =
+  "Rename a node group in the cluster."
+
+opGroupEvacuate :: String
+opGroupEvacuate =
+  "Evacuate a node group in the cluster."
+
+opOsDiagnose :: String
+opOsDiagnose =
+  "Compute the list of guest operating systems."
+
+opExtStorageDiagnose :: String
+opExtStorageDiagnose =
+  "Compute the list of external storage providers."
+
+opBackupQuery :: String
+opBackupQuery =
+  "Compute the list of exported images."
+
+opBackupPrepare :: String
+opBackupPrepare =
+  "Prepares an instance export.\n\
+\\n\
+\  @ivar instance_name: Instance name\n\
+\  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})"
+
+opBackupExport :: String
+opBackupExport =
+  "Export an instance.\n\
+\\n\
+\  For local exports, the export destination is the node name. For\n\
+\  remote exports, the export destination is a list of tuples, each\n\
+\  consisting of hostname/IP address, port, magic, HMAC and HMAC\n\
+\  salt. The HMAC is calculated using the cluster domain secret over\n\
+\  the value \"${index}:${hostname}:${port}\". The destination X509 CA\n\
+\  must be a signed certificate.\n\
+\\n\
+\  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})\n\
+\  @ivar target_node: Export destination\n\
+\  @ivar x509_key_name: X509 key to use (remote export only)\n\
+\  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote\n\
+\                             export only)"
+
+opBackupRemove :: String
+opBackupRemove =
+  "Remove an instance's export."
+
+opTagsGet :: String
+opTagsGet =
+  "Returns the tags of the given object."
+
+opTagsSearch :: String
+opTagsSearch =
+  "Searches the tags in the cluster for a given pattern."
+
+opTagsSet :: String
+opTagsSet =
+  "Add a list of tags on a given object."
+
+opTagsDel :: String
+opTagsDel =
+  "Remove a list of tags from a given object."
+
+opTestDelay :: String
+opTestDelay =
+  "Sleeps for a configured amount of time.\n\
+\\n\
+\  This is used just for debugging and testing.\n\
+\\n\
+\  Parameters:\n\
+\    - duration: the time to sleep, in seconds\n\
+\    - on_master: if true, sleep on the master\n\
+\    - on_nodes: list of nodes in which to sleep\n\
+\\n\
+\  If the on_master parameter is true, it will execute a sleep on the\n\
+\  master (before any node sleep).\n\
+\\n\
+\  If the on_nodes list is not empty, it will sleep on those nodes\n\
+\  (after the sleep on the master, if that is enabled).\n\
+\\n\
+\  As an additional feature, the case of duration < 0 will be reported\n\
+\  as an execution error, so this opcode can be used as a failure\n\
+\  generator. The case of duration == 0 will not be treated specially."
+
+opTestAllocator :: String
+opTestAllocator =
+  "Allocator framework testing.\n\
+\\n\
+\  This opcode has two modes:\n\
+\    - gather and return allocator input for a given mode (allocate new\n\
+\      or replace secondary) and a given instance definition (direction\n\
+\      'in')\n\
+\    - run a selected allocator for a given operation (as above) and\n\
+\      return the allocator output (direction 'out')"
+
+opTestJqueue :: String
+opTestJqueue =
+  "Utility opcode to test some aspects of the job queue."
+
+opTestDummy :: String
+opTestDummy =
+  "Utility opcode used by unittests."
+
+opNetworkAdd :: String
+opNetworkAdd =
+  "Add an IP network to the cluster."
+
+opNetworkRemove :: String
+opNetworkRemove =
+  "Remove an existing network from the cluster.\n\
+\     Must not be connected to any nodegroup."
+
+opNetworkSetParams :: String
+opNetworkSetParams =
+  "Modify Network's parameters except for IPv4 subnet"
+
+opNetworkConnect :: String
+opNetworkConnect =
+  "Connect a Network to a specific Nodegroup with the defined netparams\n\
+\     (mode, link). Nics in this Network will inherit those params.\n\
+\     Produce errors if a NIC (that its not already assigned to a network)\n\
+\     has an IP that is contained in the Network this will produce error\
+\ unless\n\
+\     --no-conflicts-check is passed."
+
+opNetworkDisconnect :: String
+opNetworkDisconnect =
+  "Disconnect a Network from a Nodegroup. Produce errors if NICs are\n\
+\     present in the Network unless --no-conficts-check option is passed."
+
+opNetworkQuery :: String
+opNetworkQuery =
+  "Compute the list of networks."
diff --git a/src/Ganeti/Hypervisor/Xen.hs b/src/Ganeti/Hypervisor/Xen.hs
index 3fbffa7..7dbe634 100644
--- a/src/Ganeti/Hypervisor/Xen.hs
+++ b/src/Ganeti/Hypervisor/Xen.hs
@@ -4,21 +4,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Hypervisor.Xen
diff --git a/src/Ganeti/Hypervisor/Xen/Types.hs b/src/Ganeti/Hypervisor/Xen/Types.hs
index b99c999..704eea2 100644
--- a/src/Ganeti/Hypervisor/Xen/Types.hs
+++ b/src/Ganeti/Hypervisor/Xen/Types.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Hypervisor.Xen.Types
diff --git a/src/Ganeti/Hypervisor/Xen/XmParser.hs b/src/Ganeti/Hypervisor/Xen/XmParser.hs
index c24d111..00f1133 100644
--- a/src/Ganeti/Hypervisor/Xen/XmParser.hs
+++ b/src/Ganeti/Hypervisor/Xen/XmParser.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Hypervisor.Xen.XmParser
diff --git a/src/Ganeti/JQueue.hs b/src/Ganeti/JQueue.hs
index 8e01d88..367ef47 100644
--- a/src/Ganeti/JQueue.hs
+++ b/src/Ganeti/JQueue.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2010, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/JSON.hs b/src/Ganeti/JSON.hs
index 9437c75..d1ce5e6 100644
--- a/src/Ganeti/JSON.hs
+++ b/src/Ganeti/JSON.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -138,7 +147,7 @@
 
 -- | Reads the value of a key in a JSON object with a default if
 -- missing. Note that both missing keys and keys with value \'null\'
--- will case the default value to be returned.
+-- will cause the default value to be returned.
 fromObjWithDefault :: (J.JSON a, Monad m) =>
                       JSRecord -> String -> a -> m a
 fromObjWithDefault o k d = liftM (fromMaybe d) $ maybeFromObj o k
diff --git a/src/Ganeti/Jobs.hs b/src/Ganeti/Jobs.hs
index c9ac935..fa73280 100644
--- a/src/Ganeti/Jobs.hs
+++ b/src/Ganeti/Jobs.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Logging.hs b/src/Ganeti/Logging.hs
index f1776df..1fe8e38 100644
--- a/src/Ganeti/Logging.hs
+++ b/src/Ganeti/Logging.hs
@@ -13,21 +13,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -55,13 +64,13 @@
 import System.IO
 
 import Ganeti.THH
-import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 
 -- | Syslog usage type.
-$(declareSADT "SyslogUsage"
-  [ ("SyslogNo",   'C.syslogNo)
-  , ("SyslogYes",  'C.syslogYes)
-  , ("SyslogOnly", 'C.syslogOnly)
+$(declareLADT ''String "SyslogUsage"
+  [ ("SyslogNo",   "no")
+  , ("SyslogYes",  "yes")
+  , ("SyslogOnly", "only")
   ])
 
 -- | Builds the log formatter.
@@ -98,7 +107,7 @@
              -> IO ()
 setupLogging logf program debug stderr_logging console syslog = do
   let level = if debug then DEBUG else INFO
-      destf = if console then Just C.devConsole else logf
+      destf = if console then Just ConstantUtils.devConsole else logf
       fmt = logFormatter program False False
       file_logging = syslog /= SyslogOnly
 
diff --git a/src/Ganeti/Luxi.hs b/src/Ganeti/Luxi.hs
index ebe2f0f..79459e5 100644
--- a/src/Ganeti/Luxi.hs
+++ b/src/Ganeti/Luxi.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -77,8 +86,8 @@
 import Ganeti.JSON
 import Ganeti.OpParams (pTagsObject)
 import Ganeti.OpCodes
-import Ganeti.Runtime
 import qualified Ganeti.Query.Language as Qlang
+import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..))
 import Ganeti.THH
 import Ganeti.Types
 import Ganeti.Utils
@@ -145,10 +154,15 @@
     )
   , (luxiReqQueryClusterInfo, [])
   , (luxiReqQueryTags,
-     [ pTagsObject ])
+     [ pTagsObject 
+     , simpleField "name" [t| String |]
+     ])
   , (luxiReqSubmitJob,
      [ simpleField "job" [t| [MetaOpCode] |] ]
     )
+  , (luxiReqSubmitJobToDrainedQueue,
+     [ simpleField "job" [t| [MetaOpCode] |] ]
+    )
   , (luxiReqSubmitManyJobs,
      [ simpleField "ops" [t| [[MetaOpCode]] |] ]
     )
@@ -368,6 +382,10 @@
               [ops1] <- fromJVal args
               ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
               return $ SubmitJob ops2
+    ReqSubmitJobToDrainedQueue -> do
+              [ops1] <- fromJVal args
+              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
+              return $ SubmitJobToDrainedQueue ops2
     ReqSubmitManyJobs -> do
               [ops1] <- fromJVal args
               ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
@@ -401,8 +419,7 @@
               return $ QueryConfigValues fields
     ReqQueryTags -> do
               (kind, name) <- fromJVal args
-              item <- tagObjectFrom kind name
-              return $ QueryTags item
+              return $ QueryTags kind name
     ReqCancelJob -> do
               [jid] <- fromJVal args
               return $ CancelJob jid
diff --git a/src/Ganeti/Monitoring/Server.hs b/src/Ganeti/Monitoring/Server.hs
index 1959f3d..05ff189 100644
--- a/src/Ganeti/Monitoring/Server.hs
+++ b/src/Ganeti/Monitoring/Server.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -32,22 +41,28 @@
   ) where
 
 import Control.Applicative
+import Control.DeepSeq (force)
+import Control.Exception.Base (evaluate)
 import Control.Monad
 import Control.Monad.IO.Class
 import Data.ByteString.Char8 hiding (map, filter, find)
 import Data.List
+import qualified Data.Map as Map
 import Snap.Core
 import Snap.Http.Server
 import qualified Text.JSON as J
+import Control.Concurrent
 
 import qualified Ganeti.BasicTypes as BT
 import Ganeti.Daemon
+import qualified Ganeti.DataCollectors.CPUload as CPUload
 import qualified Ganeti.DataCollectors.Diskstats as Diskstats
 import qualified Ganeti.DataCollectors.Drbd as Drbd
 import qualified Ganeti.DataCollectors.InstStatus as InstStatus
 import qualified Ganeti.DataCollectors.Lv as Lv
 import Ganeti.DataCollectors.Types
 import qualified Ganeti.Constants as C
+import Ganeti.Runtime
 
 -- * Types and constants definitions
 
@@ -59,7 +74,11 @@
 
 -- | Version of the latest supported http API.
 latestAPIVersion :: Int
-latestAPIVersion = 1
+latestAPIVersion = C.mondLatestApiVersion
+
+-- | A report of a data collector might be stateful or stateless.
+data Report = StatelessR (IO DCReport)
+            | StatefulR (Maybe CollectorData -> IO DCReport)
 
 -- | Type describing a data collector basic information
 data DataCollector = DataCollector
@@ -68,28 +87,35 @@
                                   --   of the collector
   , dKind     :: DCKind           -- ^ Kind (performance or status reporting) of
                                   --   the data collector
-  , dReport   :: IO DCReport      -- ^ Report produced by the collector
+  , dReport   :: Report           -- ^ Report produced by the collector
+  , dUpdate   :: Maybe (Maybe CollectorData -> IO CollectorData)
+                                  -- ^ Update operation for stateful collectors.
   }
 
+
 -- | The list of available builtin data collectors.
 collectors :: [DataCollector]
 collectors =
   [ DataCollector Diskstats.dcName Diskstats.dcCategory Diskstats.dcKind
-      Diskstats.dcReport
-  , DataCollector Drbd.dcName Drbd.dcCategory Drbd.dcKind Drbd.dcReport
+      (StatelessR Diskstats.dcReport) Nothing
+  , DataCollector Drbd.dcName Drbd.dcCategory Drbd.dcKind
+      (StatelessR Drbd.dcReport) Nothing
   , DataCollector InstStatus.dcName InstStatus.dcCategory InstStatus.dcKind
-      InstStatus.dcReport
-  , DataCollector Lv.dcName Lv.dcCategory Lv.dcKind Lv.dcReport
+      (StatelessR InstStatus.dcReport) Nothing
+  , DataCollector Lv.dcName Lv.dcCategory Lv.dcKind
+      (StatelessR Lv.dcReport) Nothing
+  , DataCollector CPUload.dcName CPUload.dcCategory CPUload.dcKind
+      (StatefulR CPUload.dcReport) (Just CPUload.dcUpdate)
   ]
 
 -- * Configuration handling
 
 -- | The default configuration for the HTTP server.
-defaultHttpConf :: Config Snap ()
-defaultHttpConf =
-  setAccessLog (ConfigFileLog C.daemonsExtraLogfilesGanetiMondAccess) .
+defaultHttpConf :: FilePath -> FilePath -> Config Snap ()
+defaultHttpConf accessLog errorLog =
+  setAccessLog (ConfigFileLog accessLog) .
   setCompression False .
-  setErrorLog (ConfigFileLog C.daemonsExtraLogfilesGanetiMondError) $
+  setErrorLog (ConfigFileLog errorLog) $
   setVerbose False
   emptyConfig
 
@@ -101,10 +127,13 @@
 
 -- | Prepare function for monitoring agent.
 prepMain :: PrepFn CheckResult PrepResult
-prepMain opts _ =
+prepMain opts _ = do
+  accessLog <- daemonsExtraLogFile GanetiMond AccessLog
+  errorLog <- daemonsExtraLogFile GanetiMond ErrorLog
   return $
-    setPort (maybe C.defaultMondPort fromIntegral (optPort opts))
-      defaultHttpConf
+    setPort
+      (maybe C.defaultMondPort fromIntegral (optPort opts))
+      (defaultHttpConf accessLog errorLog)
 
 -- * Query answers
 
@@ -113,13 +142,13 @@
 versionQ = writeBS . pack $ J.encode [latestAPIVersion]
 
 -- | Version 1 of the monitoring HTTP API.
-version1Api :: Snap ()
-version1Api =
+version1Api :: MVar CollectorMap -> Snap ()
+version1Api mvar =
   let returnNull = writeBS . pack $ J.encode J.JSNull :: Snap ()
   in ifTop returnNull <|>
      route
        [ ("list", listHandler)
-       , ("report", reportHandler)
+       , ("report", reportHandler mvar)
        ]
 
 -- | Get the JSON representation of a data collector to be used in the collector
@@ -138,20 +167,36 @@
   dir "collectors" . writeBS . pack . J.encode $ map dcListItem collectors
 
 -- | Handler for returning data collector reports.
-reportHandler :: Snap ()
-reportHandler =
+reportHandler :: MVar CollectorMap -> Snap ()
+reportHandler mvar =
   route
-    [ ("all", allReports)
-    , (":category/:collector", oneReport)
+    [ ("all", allReports mvar)
+    , (":category/:collector", oneReport mvar)
     ] <|>
   errorReport
 
 -- | Return the report of all the available collectors.
-allReports :: Snap ()
-allReports = do
-  reports <- mapM (liftIO . dReport) collectors
+allReports :: MVar CollectorMap -> Snap ()
+allReports mvar = do
+  reports <- mapM (liftIO . getReport mvar) collectors
   writeBS . pack . J.encode $ reports
 
+-- | Takes the CollectorMap and a DataCollector and returns the report for this
+-- collector.
+getReport :: MVar CollectorMap -> DataCollector -> IO DCReport
+getReport mvar collector =
+  case dReport collector of
+    StatelessR r -> r
+    StatefulR r -> do
+      colData <- getColData (dName collector) mvar
+      r colData
+
+-- | Returns the data for the corresponding collector.
+getColData :: String -> MVar CollectorMap -> IO (Maybe CollectorData)
+getColData name mvar = do
+  m <- readMVar mvar
+  return $ Map.lookup name m
+
 -- | Returns a category given its name.
 -- If "collector" is given as the name, the collector has no category, and
 -- Nothing will be returned.
@@ -173,9 +218,9 @@
   modifyResponse $ setResponseStatus 404 "Not found"
   writeBS "Resource not found"
 
--- | Return the report of one collector
-oneReport :: Snap ()
-oneReport = do
+-- | Return the report of one collector.
+oneReport :: MVar CollectorMap -> Snap ()
+oneReport mvar = do
   categoryName <- maybe mzero unpack <$> getParam "category"
   collectorName <- maybe mzero unpack <$> getParam "collector"
   category <-
@@ -188,17 +233,46 @@
         filter (\c -> category == dCategory c) collectors of
       Just col -> return col
       Nothing -> fail "Unable to find the requested collector"
-  report <- liftIO $ dReport collector
-  writeBS . pack . J.encode $ report
+  dcr <- liftIO $ getReport mvar collector
+  writeBS . pack . J.encode $ dcr
 
 -- | The function implementing the HTTP API of the monitoring agent.
-monitoringApi :: Snap ()
-monitoringApi =
+monitoringApi :: MVar CollectorMap -> Snap ()
+monitoringApi mvar =
   ifTop versionQ <|>
-  dir "1" version1Api <|>
+  dir "1" (version1Api mvar) <|>
   error404
 
+-- | The function collecting data for each data collector providing a dcUpdate
+-- function.
+collect :: CollectorMap -> DataCollector -> IO CollectorMap
+collect m collector =
+  case dUpdate collector of
+    Nothing -> return m
+    Just update -> do
+      let name = dName collector
+          existing = Map.lookup name m
+      new_data <- update existing
+      _ <- evaluate $ force new_data
+      return $ Map.insert name new_data m
+
+-- | Invokes collect for each data collector.
+collection :: CollectorMap -> IO CollectorMap
+collection m = foldM collect m collectors
+
+-- | The thread responsible for the periodical collection of data for each data
+-- data collector.
+collectord :: MVar CollectorMap -> IO ()
+collectord mvar = do
+  m <- takeMVar mvar
+  m' <- collection m
+  putMVar mvar m'
+  threadDelay $ 10^(6 :: Int) * C.mondTimeInterval
+  collectord mvar
+
 -- | Main function.
 main :: MainFn CheckResult PrepResult
-main _ _ httpConf =
-  httpServe httpConf $ method GET monitoringApi
+main _ _ httpConf = do
+  mvar <- newMVar Map.empty
+  _ <- forkIO $ collectord mvar
+  httpServe httpConf . method GET $ monitoringApi mvar
diff --git a/src/Ganeti/Network.hs b/src/Ganeti/Network.hs
index 5106213..d05d3fa 100644
--- a/src/Ganeti/Network.hs
+++ b/src/Ganeti/Network.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Objects.hs b/src/Ganeti/Objects.hs
index c7e1cf1..3a37e77 100644
--- a/src/Ganeti/Objects.hs
+++ b/src/Ganeti/Objects.hs
@@ -10,28 +10,35 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.Objects
-  ( VType(..)
-  , vTypeFromRaw
-  , HvParams
+  ( HvParams
   , OsParams
   , PartialNicParams(..)
   , FilledNicParams(..)
@@ -39,8 +46,6 @@
   , allNicParamFields
   , PartialNic(..)
   , FileDriver(..)
-  , BlockDriver(..)
-  , DiskMode(..)
   , DiskLogicalId(..)
   , Disk(..)
   , includesLogicalId
@@ -49,8 +54,6 @@
   , FilledBeParams(..)
   , fillBeParams
   , allBeParamFields
-  , AdminState(..)
-  , adminStateFromRaw
   , Instance(..)
   , toDictInstance
   , PartialNDParams(..)
@@ -58,9 +61,6 @@
   , fillNDParams
   , allNDParamFields
   , Node(..)
-  , NodeRole(..)
-  , nodeRoleToRaw
-  , roleDescription
   , AllocPolicy(..)
   , FilledISpecParams(..)
   , PartialISpecParams(..)
@@ -104,7 +104,9 @@
 import Text.JSON (showJSON, readJSON, JSON, JSValue(..), fromJSString)
 import qualified Text.JSON as J
 
+import qualified AutoConf
 import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import Ganeti.JSON
 import Ganeti.Types
 import Ganeti.THH
@@ -119,16 +121,6 @@
   let updated = Map.union custom defaults
   in foldl' (flip Map.delete) updated skip_keys
 
--- | The VTYPES, a mini-type system in Python.
-$(declareSADT "VType"
-  [ ("VTypeString",      'C.vtypeString)
-  , ("VTypeMaybeString", 'C.vtypeMaybeString)
-  , ("VTypeBool",        'C.vtypeBool)
-  , ("VTypeSize",        'C.vtypeSize)
-  , ("VTypeInt",         'C.vtypeInt)
-  ])
-$(makeJSONInstance ''VType)
-
 -- | The hypervisor parameter type. This is currently a simple map,
 -- without type checking on key/value pairs.
 type HvParams = Container JSValue
@@ -155,25 +147,6 @@
 class TagsObject a where
   tagsOf :: a -> Set.Set String
 
--- * Node role object
-
-$(declareSADT "NodeRole"
-  [ ("NROffline",   'C.nrOffline)
-  , ("NRDrained",   'C.nrDrained)
-  , ("NRRegular",   'C.nrRegular)
-  , ("NRCandidate", 'C.nrMcandidate)
-  , ("NRMaster",    'C.nrMaster)
-  ])
-$(makeJSONInstance ''NodeRole)
-
--- | The description of the node role.
-roleDescription :: NodeRole -> String
-roleDescription NROffline   = "offline"
-roleDescription NRDrained   = "drained"
-roleDescription NRRegular   = "regular"
-roleDescription NRCandidate = "master candidate"
-roleDescription NRMaster    = "master"
-
 -- * Network definitions
 
 -- ** Ipv4 types
@@ -281,6 +254,7 @@
 $(buildParam "Nic" "nicp"
   [ simpleField "mode" [t| NICMode |]
   , simpleField "link" [t| String  |]
+  , simpleField "vlan" [t| String |]
   ])
 
 $(buildObject "PartialNic" "nic" $
@@ -296,18 +270,6 @@
 
 -- * Disk definitions
 
-$(declareSADT "DiskMode"
-  [ ("DiskRdOnly", 'C.diskRdonly)
-  , ("DiskRdWr",   'C.diskRdwr)
-  ])
-$(makeJSONInstance ''DiskMode)
-
--- | The persistent block driver type. Currently only one type is allowed.
-$(declareSADT "BlockDriver"
-  [ ("BlockDrvManual", 'C.blockdevDriverManual)
-  ])
-$(makeJSONInstance ''BlockDriver)
-
 -- | Constant for the dev_type key entry in the disk config.
 devType :: String
 devType = "dev_type"
@@ -428,7 +390,6 @@
 -- code currently can't build it.
 data Disk = Disk
   { diskLogicalId  :: DiskLogicalId
---  , diskPhysicalId :: String
   , diskChildren   :: [Disk]
   , diskIvName     :: String
   , diskSize       :: Int
@@ -441,7 +402,6 @@
 $(buildObjectSerialisation "Disk" $
   [ customField 'decodeDLId 'encodeFullDLId ["dev_type"] $
       simpleField "logical_id"    [t| DiskLogicalId   |]
---  , simpleField "physical_id" [t| String   |]
   , defaultField  [| [] |] $ simpleField "children" [t| [Disk] |]
   , defaultField [| "" |] $ simpleField "iv_name" [t| String |]
   , simpleField "size" [t| Int |]
@@ -465,16 +425,8 @@
       any (includesLogicalId vg_name lv_name) $ diskChildren disk
     _ -> False
 
-
 -- * Instance definitions
 
-$(declareSADT "AdminState"
-  [ ("AdminOffline", 'C.adminstOffline)
-  , ("AdminDown",    'C.adminstDown)
-  , ("AdminUp",      'C.adminstUp)
-  ])
-$(makeJSONInstance ''AdminState)
-
 $(buildParam "Be" "bep"
   [ simpleField "minmem"       [t| Int  |]
   , simpleField "maxmem"       [t| Int  |]
@@ -518,12 +470,12 @@
 -- * IPolicy definitions
 
 $(buildParam "ISpec" "ispec"
-  [ simpleField C.ispecMemSize     [t| Int |]
-  , simpleField C.ispecDiskSize    [t| Int |]
-  , simpleField C.ispecDiskCount   [t| Int |]
-  , simpleField C.ispecCpuCount    [t| Int |]
-  , simpleField C.ispecNicCount    [t| Int |]
-  , simpleField C.ispecSpindleUse  [t| Int |]
+  [ simpleField ConstantUtils.ispecMemSize     [t| Int |]
+  , simpleField ConstantUtils.ispecDiskSize    [t| Int |]
+  , simpleField ConstantUtils.ispecDiskCount   [t| Int |]
+  , simpleField ConstantUtils.ispecCpuCount    [t| Int |]
+  , simpleField ConstantUtils.ispecNicCount    [t| Int |]
+  , simpleField ConstantUtils.ispecSpindleUse  [t| Int |]
   ])
 
 $(buildObject "MinMaxISpecs" "mmis"
@@ -534,23 +486,23 @@
 -- | Custom partial ipolicy. This is not built via buildParam since it
 -- has a special 2-level inheritance mode.
 $(buildObject "PartialIPolicy" "ipolicy"
-  [ optionalField . renameField "MinMaxISpecsP"
-                    $ simpleField C.ispecsMinmax   [t| [MinMaxISpecs] |]
-  , optionalField . renameField "StdSpecP"
-                    $ simpleField "std"            [t| PartialISpecParams |]
-  , optionalField . renameField "SpindleRatioP"
-                    $ simpleField "spindle-ratio"  [t| Double |]
-  , optionalField . renameField "VcpuRatioP"
-                    $ simpleField "vcpu-ratio"     [t| Double |]
-  , optionalField . renameField "DiskTemplatesP"
-                    $ simpleField "disk-templates" [t| [DiskTemplate] |]
+  [ optionalField . renameField "MinMaxISpecsP" $
+    simpleField ConstantUtils.ispecsMinmax [t| [MinMaxISpecs] |]
+  , optionalField . renameField "StdSpecP" $
+    simpleField "std" [t| PartialISpecParams |]
+  , optionalField . renameField "SpindleRatioP" $
+    simpleField "spindle-ratio" [t| Double |]
+  , optionalField . renameField "VcpuRatioP" $
+    simpleField "vcpu-ratio" [t| Double |]
+  , optionalField . renameField "DiskTemplatesP" $
+    simpleField "disk-templates" [t| [DiskTemplate] |]
   ])
 
 -- | Custom filled ipolicy. This is not built via buildParam since it
 -- has a special 2-level inheritance mode.
 $(buildObject "FilledIPolicy" "ipolicy"
-  [ renameField "MinMaxISpecs"
-    $ simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
+  [ renameField "MinMaxISpecs" $
+    simpleField ConstantUtils.ispecsMinmax [t| [MinMaxISpecs] |]
   , renameField "StdSpec" $ simpleField "std" [t| FilledISpecParams |]
   , simpleField "spindle-ratio"  [t| Double |]
   , simpleField "vcpu-ratio"     [t| Double |]
@@ -584,6 +536,9 @@
   [ simpleField "oob_program"   [t| String |]
   , simpleField "spindle_count" [t| Int    |]
   , simpleField "exclusive_storage" [t| Bool |]
+  , simpleField "ovs"           [t| Bool |]
+  , simpleField "ovs_name"       [t| String |]
+  , simpleField "ovs_link"       [t| String |]
   ])
 
 $(buildObject "Node" "node" $
@@ -654,8 +609,8 @@
 
 -- | IP family type
 $(declareIADT "IpFamily"
-  [ ("IpFamilyV4", 'C.ip4Family)
-  , ("IpFamilyV6", 'C.ip6Family)
+  [ ("IpFamilyV4", 'AutoConf.pyAfInet4)
+  , ("IpFamilyV6", 'AutoConf.pyAfInet6)
   ])
 $(makeJSONInstance ''IpFamily)
 
diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs
index d7f75ef..5d81b67 100644
--- a/src/Ganeti/OpCodes.hs
+++ b/src/Ganeti/OpCodes.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE ExistentialQuantification, TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 
 {-| Implementation of the opcodes.
 
@@ -7,30 +8,36 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.OpCodes
-  ( OpCode(..)
-  , TagObject(..)
-  , tagObjectFrom
-  , encodeTagObject
-  , decodeTagObject
+  ( pyClasses
+  , OpCode(..)
   , ReplaceDisksMode(..)
   , DiskIndex
   , mkDiskIndex
@@ -47,116 +54,164 @@
   , setOpPriority
   ) where
 
-import Data.Maybe (fromMaybe)
-import Text.JSON (readJSON, JSON, JSValue, makeObj)
+import Text.JSON (readJSON, JSObject, JSON, JSValue(..), makeObj, fromJSObject)
 import qualified Text.JSON
 
 import Ganeti.THH
 
+import qualified Ganeti.Hs2Py.OpDoc as OpDoc
 import Ganeti.OpParams
-import Ganeti.Types (OpSubmitPriority(..), fromNonEmpty)
+import Ganeti.PyValueInstances ()
+import Ganeti.Types
 import Ganeti.Query.Language (queryTypeOpToRaw)
 
+import Data.List (intercalate)
+import Data.Map (Map)
+
+import qualified Ganeti.Constants as C
+
+instance PyValue DiskIndex where
+  showValue = showValue . unDiskIndex
+
+instance PyValue IDiskParams where
+  showValue _ = error "OpCodes.showValue(IDiskParams): unhandled case"
+
+instance PyValue RecreateDisksInfo where
+  showValue RecreateDisksAll = "[]"
+  showValue (RecreateDisksIndices is) = showValue is
+  showValue (RecreateDisksParams is) = showValue is
+
+instance PyValue a => PyValue (SetParamsMods a) where
+  showValue SetParamsEmpty = "[]"
+  showValue _ = error "OpCodes.showValue(SetParamsMods): unhandled case"
+
+instance PyValue a => PyValue (NonNegative a) where
+  showValue = showValue . fromNonNegative
+
+instance PyValue a => PyValue (NonEmpty a) where
+  showValue = showValue . fromNonEmpty
+
+-- FIXME: should use the 'toRaw' function instead of being harcoded or
+-- perhaps use something similar to the NonNegative type instead of
+-- using the declareSADT
+instance PyValue ExportMode where
+  showValue ExportModeLocal = show C.exportModeLocal
+  showValue ExportModeRemote = show C.exportModeLocal
+
+instance PyValue CVErrorCode where
+  showValue = cVErrorCodeToRaw
+
+instance PyValue VerifyOptionalChecks where
+  showValue = verifyOptionalChecksToRaw
+
+instance PyValue INicParams where
+  showValue = error "instance PyValue INicParams: not implemented"
+
+instance PyValue a => PyValue (JSObject a) where
+  showValue obj =
+    "{" ++ intercalate ", " (map showPair (fromJSObject obj)) ++ "}"
+    where showPair (k, v) = show k ++ ":" ++ showValue v
+
+instance PyValue JSValue where
+  showValue (JSObject obj) = showValue obj
+  showValue x = show x
+
+type JobIdListOnly = Map String [(Bool, Either String JobId)]
+
+type InstanceMultiAllocResponse =
+  ([(Bool, Either String JobId)], NonEmptyString)
+
+type QueryFieldDef =
+  (NonEmptyString, NonEmptyString, TagKind, NonEmptyString)
+
+type QueryResponse =
+  ([QueryFieldDef], [[(QueryResultCode, JSValue)]])
+
+type QueryFieldsResponse = [QueryFieldDef]
+
 -- | OpCode representation.
 --
 -- We only implement a subset of Ganeti opcodes: those which are actually used
 -- in the htools codebase.
 $(genOpCode "OpCode"
-  [ ("OpTestDelay",
-     [ pDelayDuration
-     , pDelayOnMaster
-     , pDelayOnNodes
-     , pDelayOnNodeUuids
-     , pDelayRepeat
-     ])
-  , ("OpInstanceReplaceDisks",
-     [ pInstanceName
-     , pInstanceUuid
-     , pEarlyRelease
-     , pIgnoreIpolicy
-     , pReplaceDisksMode
-     , pReplaceDisksList
-     , pRemoteNode
-     , pRemoteNodeUuid
-     , pIallocator
-     ])
-  , ("OpInstanceFailover",
-     [ pInstanceName
-     , pInstanceUuid
-     , pShutdownTimeout
-     , pIgnoreConsistency
-     , pMigrationTargetNode
-     , pMigrationTargetNodeUuid
-     , pIgnoreIpolicy
-     , pIallocator
-     , pMigrationCleanup
-     ])
-  , ("OpInstanceMigrate",
-     [ pInstanceName
-     , pInstanceUuid
-     , pMigrationMode
-     , pMigrationLive
-     , pMigrationTargetNode
-     , pMigrationTargetNodeUuid
-     , pAllowRuntimeChgs
-     , pIgnoreIpolicy
-     , pMigrationCleanup
-     , pIallocator
-     , pAllowFailover
-     ])
-  , ("OpTagsGet",
-     [ pTagsObject
-     , pUseLocking
-     ])
-  , ("OpTagsSearch",
-     [ pTagSearchPattern ])
-  , ("OpTagsSet",
-     [ pTagsObject
-     , pTagsList
-     ])
-  , ("OpTagsDel",
-     [ pTagsObject
-     , pTagsList
-     ])
-  , ("OpClusterPostInit", [])
-  , ("OpClusterDestroy", [])
-  , ("OpClusterQuery", [])
+  [ ("OpClusterPostInit",
+     [t| Bool |],
+     OpDoc.opClusterPostInit,
+     [],
+     [])
+  , ("OpClusterDestroy",
+     [t| NonEmptyString |],
+     OpDoc.opClusterDestroy,
+     [],
+     [])
+  , ("OpClusterQuery",
+     [t| JSObject JSValue |],
+     OpDoc.opClusterQuery,
+     [],
+     [])
   , ("OpClusterVerify",
+     [t| JobIdListOnly |],
+     OpDoc.opClusterVerify,
      [ pDebugSimulateErrors
      , pErrorCodes
      , pSkipChecks
      , pIgnoreErrors
      , pVerbose
      , pOptGroupName
-     ])
+     ],
+     [])
   , ("OpClusterVerifyConfig",
+     [t| Bool |],
+     OpDoc.opClusterVerifyConfig,
      [ pDebugSimulateErrors
      , pErrorCodes
      , pIgnoreErrors
      , pVerbose
-     ])
+     ],
+     [])
   , ("OpClusterVerifyGroup",
+     [t| Bool |],
+     OpDoc.opClusterVerifyGroup,
      [ pGroupName
      , pDebugSimulateErrors
      , pErrorCodes
      , pSkipChecks
      , pIgnoreErrors
      , pVerbose
-     ])
-  , ("OpClusterVerifyDisks", [])
+     ],
+     "group_name")
+  , ("OpClusterVerifyDisks",
+     [t| JobIdListOnly |],
+     OpDoc.opClusterVerifyDisks,
+     [],
+     [])
   , ("OpGroupVerifyDisks",
+     [t| (Map String String, [String], Map String [[String]]) |],
+     OpDoc.opGroupVerifyDisks,
      [ pGroupName
-     ])
+     ],
+     "group_name")
   , ("OpClusterRepairDiskSizes",
+     [t| [(NonEmptyString, NonNegative Int, NonEmptyString, NonNegative Int)]|],
+     OpDoc.opClusterRepairDiskSizes,
      [ pInstances
-     ])
+     ],
+     [])
   , ("OpClusterConfigQuery",
+     [t| [JSValue] |],
+     OpDoc.opClusterConfigQuery,
      [ pOutputFields
-     ])
+     ],
+     [])
   , ("OpClusterRename",
+      [t| NonEmptyString |],
+      OpDoc.opClusterRename,
      [ pName
-     ])
+     ],
+     "name")
   , ("OpClusterSetParams",
+     [t| () |],
+     OpDoc.opClusterSetParams,
      [ pForce
      , pHvState
      , pDiskState
@@ -174,8 +229,8 @@
      , pMaintainNodeHealth
      , pPreallocWipeDisks
      , pNicParams
-     , pNdParams
-     , pIpolicy
+     , withDoc "Cluster-wide node parameter defaults" pNdParams
+     , withDoc "Cluster-wide ipolicy specs" pIpolicy
      , pDrbdHelper
      , pDefaultIAllocator
      , pMasterNetdev
@@ -186,35 +241,75 @@
      , pUseExternalMipScript
      , pEnabledDiskTemplates
      , pModifyEtcHosts
-     , pGlobalFileStorageDir
-     , pGlobalSharedFileStorageDir
-     ])
-  , ("OpClusterRedistConf", [])
-  , ("OpClusterActivateMasterIp", [])
-  , ("OpClusterDeactivateMasterIp", [])
+     , pClusterFileStorageDir
+     , pClusterSharedFileStorageDir
+     ],
+     [])
+  , ("OpClusterRedistConf",
+     [t| () |],
+     OpDoc.opClusterRedistConf,
+     [],
+     [])
+  , ("OpClusterActivateMasterIp",
+     [t| () |],
+     OpDoc.opClusterActivateMasterIp,
+     [],
+     [])
+  , ("OpClusterDeactivateMasterIp",
+     [t| () |],
+     OpDoc.opClusterDeactivateMasterIp,
+     [],
+     [])
   , ("OpQuery",
+     [t| QueryResponse |],
+     OpDoc.opQuery,
      [ pQueryWhat
      , pUseLocking
      , pQueryFields
      , pQueryFilter
-     ])
+     ],
+     "what")
   , ("OpQueryFields",
+     [t| QueryFieldsResponse |],
+     OpDoc.opQueryFields,
      [ pQueryWhat
-     , pQueryFields
-     ])
+     , pQueryFieldsFields
+     ],
+     "what")
   , ("OpOobCommand",
+     [t| [[(QueryResultCode, JSValue)]] |],
+     OpDoc.opOobCommand,
      [ pNodeNames
-     , pNodeUuids
+     , withDoc "List of node UUIDs to run the OOB command against" pNodeUuids
      , pOobCommand
      , pOobTimeout
      , pIgnoreStatus
      , pPowerDelay
-     ])
+     ],
+     [])
+  , ("OpRestrictedCommand",
+     [t| [(Bool, String)] |],
+     OpDoc.opRestrictedCommand,
+     [ pUseLocking
+     , withDoc
+       "Nodes on which the command should be run (at least one)"
+       pRequiredNodes
+     , withDoc
+       "Node UUIDs on which the command should be run (at least one)"
+       pRequiredNodeUuids
+     , pRestrictedCommand
+     ],
+     [])
   , ("OpNodeRemove",
+     [t| () |],
+      OpDoc.opNodeRemove,
      [ pNodeName
      , pNodeUuid
-     ])
+     ],
+     "node_name")
   , ("OpNodeAdd",
+     [t| () |],
+      OpDoc.opNodeAdd,
      [ pNodeName
      , pHvState
      , pDiskState
@@ -225,40 +320,64 @@
      , pMasterCapable
      , pVmCapable
      , pNdParams
-    ])
-  , ("OpNodeQuery", dOldQuery)
+     ],
+     "node_name")
+  , ("OpNodeQuery",
+     [t| [[JSValue]] |],
+     OpDoc.opNodeQuery,
+     [ pOutputFields
+     , withDoc "Empty list to query all nodes, node names otherwise" pNames
+     , pUseLocking
+     ],
+     [])
   , ("OpNodeQueryvols",
+     [t| [JSValue] |],
+     OpDoc.opNodeQueryvols,
      [ pOutputFields
-     , pNodes
-     ])
+     , withDoc "Empty list to query all nodes, node names otherwise" pNodes
+     ],
+     [])
   , ("OpNodeQueryStorage",
+     [t| [[JSValue]] |],
+     OpDoc.opNodeQueryStorage,
      [ pOutputFields
-     , pStorageType
-     , pNodes
+     , pStorageTypeOptional
+     , withDoc
+       "Empty list to query all, list of names to query otherwise"
+       pNodes
      , pStorageName
-     ])
+     ],
+     [])
   , ("OpNodeModifyStorage",
+     [t| () |],
+     OpDoc.opNodeModifyStorage,
      [ pNodeName
      , pNodeUuid
      , pStorageType
      , pStorageName
      , pStorageChanges
-     ])
+     ],
+     "node_name")
   , ("OpRepairNodeStorage",
+      [t| () |],
+      OpDoc.opRepairNodeStorage,
      [ pNodeName
      , pNodeUuid
      , pStorageType
      , pStorageName
      , pIgnoreConsistency
-     ])
+     ],
+     "node_name")
   , ("OpNodeSetParams",
+     [t| [(NonEmptyString, JSValue)] |],
+     OpDoc.opNodeSetParams,
      [ pNodeName
      , pNodeUuid
      , pForce
      , pHvState
      , pDiskState
      , pMasterCandidate
-     , pOffline
+     , withDoc "Whether to mark the node offline" pOffline
      , pDrained
      , pAutoPromote
      , pMasterCapable
@@ -266,13 +385,19 @@
      , pSecondaryIp
      , pNdParams
      , pPowered
-     ])
+     ],
+     "node_name")
   , ("OpNodePowercycle",
+     [t| Maybe NonEmptyString |],
+     OpDoc.opNodePowercycle,
      [ pNodeName
      , pNodeUuid
      , pForce
-     ])
+     ],
+     "node_name")
   , ("OpNodeMigrate",
+     [t| JobIdListOnly |],
+     OpDoc.opNodeMigrate,
      [ pNodeName
      , pNodeUuid
      , pMigrationMode
@@ -282,8 +407,11 @@
      , pAllowRuntimeChgs
      , pIgnoreIpolicy
      , pIallocator
-     ])
+     ],
+     "node_name")
   , ("OpNodeEvacuate",
+     [t| JobIdListOnly |],
+     OpDoc.opNodeEvacuate,
      [ pEarlyRelease
      , pNodeName
      , pNodeUuid
@@ -291,16 +419,20 @@
      , pRemoteNodeUuid
      , pIallocator
      , pEvacMode
-     ])
+     ],
+     "node_name")
   , ("OpInstanceCreate",
+     [t| [NonEmptyString] |],
+     OpDoc.opInstanceCreate,
      [ pInstanceName
      , pForceVariant
      , pWaitForSync
      , pNameCheck
      , pIgnoreIpolicy
+     , pOpportunisticLocking
      , pInstBeParams
      , pInstDisks
-     , pDiskTemplate
+     , pOptDiskTemplate
      , pFileDriver
      , pFileStorageDir
      , pInstHvParams
@@ -326,35 +458,49 @@
      , pSrcNodeUuid
      , pSrcPath
      , pStartInstance
-     , pOpportunisticLocking
      , pInstTags
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceMultiAlloc",
-     [ pIallocator
+     [t| InstanceMultiAllocResponse |],
+     OpDoc.opInstanceMultiAlloc,
+     [ pOpportunisticLocking
+     , pIallocator
      , pMultiAllocInstances
-     , pOpportunisticLocking
-     ])
+     ],
+     [])
   , ("OpInstanceReinstall",
+     [t| () |],
+     OpDoc.opInstanceReinstall,
      [ pInstanceName
      , pInstanceUuid
      , pForceVariant
      , pInstOs
      , pTempOsParams
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceRemove",
+     [t| () |],
+     OpDoc.opInstanceRemove,
      [ pInstanceName
      , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreFailures
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceRename",
+     [t| NonEmptyString |],
+     OpDoc.opInstanceRename,
      [ pInstanceName
      , pInstanceUuid
-     , pNewName
+     , withDoc "New instance name" pNewName
      , pNameCheck
      , pIpCheck
-     ])
+     ],
+     [])
   , ("OpInstanceStartup",
+     [t| () |],
+     OpDoc.opInstanceStartup,
      [ pInstanceName
      , pInstanceUuid
      , pForce
@@ -363,23 +509,76 @@
      , pTempBeParams
      , pNoRemember
      , pStartupPaused
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceShutdown",
+     [t| () |],
+     OpDoc.opInstanceShutdown,
      [ pInstanceName
      , pInstanceUuid
      , pForce
      , pIgnoreOfflineNodes
      , pShutdownTimeout'
      , pNoRemember
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceReboot",
+     [t| () |],
+     OpDoc.opInstanceReboot,
      [ pInstanceName
      , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreSecondaries
      , pRebootType
-     ])
+     ],
+     "instance_name")
+  , ("OpInstanceReplaceDisks",
+     [t| () |],
+     OpDoc.opInstanceReplaceDisks,
+     [ pInstanceName
+     , pInstanceUuid
+     , pEarlyRelease
+     , pIgnoreIpolicy
+     , pReplaceDisksMode
+     , pReplaceDisksList
+     , pRemoteNode
+     , pRemoteNodeUuid
+     , pIallocator
+     ],
+     "instance_name")
+  , ("OpInstanceFailover",
+     [t| () |],
+     OpDoc.opInstanceFailover,
+     [ pInstanceName
+     , pInstanceUuid
+     , pShutdownTimeout
+     , pIgnoreConsistency
+     , pMigrationTargetNode
+     , pMigrationTargetNodeUuid
+     , pIgnoreIpolicy
+     , pMigrationCleanup
+     , pIallocator
+     ],
+     "instance_name")
+  , ("OpInstanceMigrate",
+     [t| () |],
+     OpDoc.opInstanceMigrate,
+     [ pInstanceName
+     , pInstanceUuid
+     , pMigrationMode
+     , pMigrationLive
+     , pMigrationTargetNode
+     , pMigrationTargetNodeUuid
+     , pAllowRuntimeChgs
+     , pIgnoreIpolicy
+     , pMigrationCleanup
+     , pIallocator
+     , pAllowFailover
+     ],
+     "instance_name")
   , ("OpInstanceMove",
+     [t| () |],
+     OpDoc.opInstanceMove,
      [ pInstanceName
      , pInstanceUuid
      , pShutdownTimeout
@@ -387,37 +586,64 @@
      , pMoveTargetNode
      , pMoveTargetNodeUuid
      , pIgnoreConsistency
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceConsole",
+     [t| JSObject JSValue |],
+     OpDoc.opInstanceConsole,
      [ pInstanceName
      , pInstanceUuid
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceActivateDisks",
+     [t| [(NonEmptyString, NonEmptyString, NonEmptyString)] |],
+     OpDoc.opInstanceActivateDisks,
      [ pInstanceName
      , pInstanceUuid
      , pIgnoreDiskSize
      , pWaitForSyncFalse
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceDeactivateDisks",
+     [t| () |],
+     OpDoc.opInstanceDeactivateDisks,
      [ pInstanceName
      , pInstanceUuid
      , pForce
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceRecreateDisks",
+     [t| () |],
+     OpDoc.opInstanceRecreateDisks,
      [ pInstanceName
      , pInstanceUuid
      , pRecreateDisksInfo
-     , pNodes
-     , pNodeUuids
+     , withDoc "New instance nodes, if relocation is desired" pNodes
+     , withDoc "New instance node UUIDs, if relocation is desired" pNodeUuids
      , pIallocator
-     ])
-  , ("OpInstanceQuery", dOldQuery)
+     ],
+     "instance_name")
+  , ("OpInstanceQuery",
+     [t| [[JSValue]] |],
+     OpDoc.opInstanceQuery,
+     [ pOutputFields
+     , pUseLocking
+     , withDoc
+       "Empty list to query all instances, instance names otherwise"
+       pNames
+     ],
+     [])
   , ("OpInstanceQueryData",
+     [t| JSObject (JSObject JSValue) |],
+     OpDoc.opInstanceQueryData,
      [ pUseLocking
      , pInstances
      , pStatic
-     ])
+     ],
+     [])
   , ("OpInstanceSetParams",
+      [t| [(NonEmptyString, JSValue)] |],
+      OpDoc.opInstanceSetParams,
      [ pInstanceName
      , pInstanceUuid
      , pForce
@@ -431,82 +657,136 @@
      , pOptDiskTemplate
      , pPrimaryNode
      , pPrimaryNodeUuid
-     , pRemoteNode
-     , pRemoteNodeUuid
+     , withDoc "Secondary node (used when changing disk template)" pRemoteNode
+     , withDoc
+       "Secondary node UUID (used when changing disk template)"
+       pRemoteNodeUuid
      , pOsNameChange
      , pInstOsParams
      , pWaitForSync
-     , pOffline
+     , withDoc "Whether to mark the instance as offline" pOffline
      , pIpConflictsCheck
-     ])
+     , pHotplug
+     , pHotplugIfPossible
+     ],
+     "instance_name")
   , ("OpInstanceGrowDisk",
+     [t| () |],
+     OpDoc.opInstanceGrowDisk,
      [ pInstanceName
      , pInstanceUuid
      , pWaitForSync
      , pDiskIndex
      , pDiskChgAmount
      , pDiskChgAbsolute
-     ])
+     ],
+     "instance_name")
   , ("OpInstanceChangeGroup",
+     [t| JobIdListOnly |],
+     OpDoc.opInstanceChangeGroup,
      [ pInstanceName
      , pInstanceUuid
      , pEarlyRelease
      , pIallocator
      , pTargetGroups
-     ])
+     ],
+     "instance_name")
   , ("OpGroupAdd",
+     [t| () |],
+     OpDoc.opGroupAdd,
      [ pGroupName
      , pNodeGroupAllocPolicy
      , pGroupNodeParams
      , pDiskParams
      , pHvState
      , pDiskState
-     , pIpolicy
-     ])
+     , withDoc "Group-wide ipolicy specs" pIpolicy
+     ],
+     "group_name")
   , ("OpGroupAssignNodes",
+     [t| () |],
+     OpDoc.opGroupAssignNodes,
      [ pGroupName
      , pForce
-     , pRequiredNodes
-     , pRequiredNodeUuids
-     ])
-  , ("OpGroupQuery", dOldQueryNoLocking)
+     , withDoc "List of nodes to assign" pRequiredNodes
+     , withDoc "List of node UUIDs to assign" pRequiredNodeUuids
+     ],
+     "group_name")
+  , ("OpGroupQuery",
+     [t| [[JSValue]] |],
+     OpDoc.opGroupQuery,
+     [ pOutputFields
+     , withDoc "Empty list to query all groups, group names otherwise" pNames
+     ],
+     [])
   , ("OpGroupSetParams",
+     [t| [(NonEmptyString, JSValue)] |],
+     OpDoc.opGroupSetParams,
      [ pGroupName
      , pNodeGroupAllocPolicy
      , pGroupNodeParams
      , pDiskParams
      , pHvState
      , pDiskState
-     , pIpolicy
-     ])
+     , withDoc "Group-wide ipolicy specs" pIpolicy
+     ],
+     "group_name")
   , ("OpGroupRemove",
-     [ pGroupName ])
-  , ("OpGroupRename",
+     [t| () |],
+     OpDoc.opGroupRemove,
      [ pGroupName
-     , pNewName
-     ])
+     ],
+     "group_name")
+  , ("OpGroupRename",
+     [t| NonEmptyString |],
+     OpDoc.opGroupRename,
+     [ pGroupName
+     , withDoc "New group name" pNewName
+     ],
+     [])
   , ("OpGroupEvacuate",
+     [t| JobIdListOnly |],
+     OpDoc.opGroupEvacuate,
      [ pGroupName
      , pEarlyRelease
      , pIallocator
      , pTargetGroups
-     ])
+     , pSequential
+     , pForceFailover
+     ],
+     "group_name")
   , ("OpOsDiagnose",
+     [t| [[JSValue]] |],
+     OpDoc.opOsDiagnose,
      [ pOutputFields
-     , pNames ])
+     , withDoc "Which operating systems to diagnose" pNames
+     ],
+     [])
   , ("OpExtStorageDiagnose",
+     [t| [[JSValue]] |],
+     OpDoc.opExtStorageDiagnose,
      [ pOutputFields
-     , pNames ])
+     , withDoc "Which ExtStorage Provider to diagnose" pNames
+     ],
+     [])
   , ("OpBackupQuery",
+     [t| JSObject (Either Bool [NonEmptyString]) |],
+     OpDoc.opBackupQuery,
      [ pUseLocking
-     , pNodes
-     ])
+     , withDoc "Empty list to query all nodes, node names otherwise" pNodes
+     ],
+     [])
   , ("OpBackupPrepare",
+     [t| Maybe (JSObject JSValue) |],
+     OpDoc.opBackupPrepare,
      [ pInstanceName
      , pInstanceUuid
      , pExportMode
-     ])
+     ],
+     "instance_name")
   , ("OpBackupExport",
+     [t| (Bool, [Bool]) |],
+     OpDoc.opBackupExport,
      [ pInstanceName
      , pInstanceUuid
      , pShutdownTimeout
@@ -515,15 +795,62 @@
      , pShutdownInstance
      , pRemoveInstance
      , pIgnoreRemoveFailures
-     , pExportMode
+     , defaultField [| ExportModeLocal |] pExportMode
      , pX509KeyName
      , pX509DestCA
-     ])
+     ],
+     "instance_name")
   , ("OpBackupRemove",
+     [t| () |],
+     OpDoc.opBackupRemove,
      [ pInstanceName
      , pInstanceUuid
-     ])
+     ],
+     "instance_name")
+  , ("OpTagsGet",
+     [t| [NonEmptyString] |],
+     OpDoc.opTagsGet,
+     [ pTagsObject
+     , pUseLocking
+     , withDoc "Name of object to retrieve tags from" pTagsName
+     ],
+     "name")
+  , ("OpTagsSearch",
+     [t| [(NonEmptyString, NonEmptyString)] |],
+     OpDoc.opTagsSearch,
+     [ pTagSearchPattern
+     ],
+     "pattern")
+  , ("OpTagsSet",
+     [t| () |],
+     OpDoc.opTagsSet,
+     [ pTagsObject
+     , pTagsList
+     , withDoc "Name of object where tag(s) should be added" pTagsName
+     ],
+     [])
+  , ("OpTagsDel",
+     [t| () |],
+     OpDoc.opTagsDel,
+     [ pTagsObject
+     , pTagsList
+     , withDoc "Name of object where tag(s) should be deleted" pTagsName
+     ],
+     [])
+  , ("OpTestDelay",
+     [t| () |],
+     OpDoc.opTestDelay,
+     [ pDelayDuration
+     , pDelayOnMaster
+     , pDelayOnNodes
+     , pDelayOnNodeUuids
+     , pDelayRepeat
+     , pDelayNoLocks
+     ],
+     "duration")
   , ("OpTestAllocator",
+     [t| String |],
+     OpDoc.opTestAllocator,
      [ pIAllocatorDirection
      , pIAllocatorMode
      , pIAllocatorReqName
@@ -541,20 +868,29 @@
      , pTargetGroups
      , pIAllocatorSpindleUse
      , pIAllocatorCount
-     ])
+     ],
+     "iallocator")
   , ("OpTestJqueue",
+     [t| Bool |],
+     OpDoc.opTestJqueue,
      [ pJQueueNotifyWaitLock
      , pJQueueNotifyExec
      , pJQueueLogMessages
      , pJQueueFail
-     ])
+     ],
+     [])
   , ("OpTestDummy",
+     [t| () |],
+     OpDoc.opTestDummy,
      [ pTestDummyResult
      , pTestDummyMessages
      , pTestDummyFail
      , pTestDummySubmitJobs
-     ])
+     ],
+     [])
   , ("OpNetworkAdd",
+     [t| () |],
+     OpDoc.opNetworkAdd,
      [ pNetworkName
      , pNetworkAddress4
      , pNetworkGateway4
@@ -563,39 +899,54 @@
      , pNetworkMacPrefix
      , pNetworkAddRsvdIps
      , pIpConflictsCheck
-     , pInstTags
-     ])
+     , withDoc "Network tags" pInstTags
+     ],
+     "network_name")
   , ("OpNetworkRemove",
+     [t| () |],
+     OpDoc.opNetworkRemove,
      [ pNetworkName
      , pForce
-     ])
+     ],
+     "network_name")
   , ("OpNetworkSetParams",
+     [t| () |],
+     OpDoc.opNetworkSetParams,
      [ pNetworkName
      , pNetworkGateway4
      , pNetworkAddress6
      , pNetworkGateway6
      , pNetworkMacPrefix
-     , pNetworkAddRsvdIps
+     , withDoc "Which external IP addresses to reserve" pNetworkAddRsvdIps
      , pNetworkRemoveRsvdIps
-     ])
+     ],
+     "network_name")
   , ("OpNetworkConnect",
+     [t| () |],
+     OpDoc.opNetworkConnect,
      [ pGroupName
      , pNetworkName
      , pNetworkMode
      , pNetworkLink
+     , pNetworkVlan
      , pIpConflictsCheck
-     ])
+     ],
+     "network_name")
   , ("OpNetworkDisconnect",
+     [t| () |],
+     OpDoc.opNetworkDisconnect,
      [ pGroupName
      , pNetworkName
-     ])
-  , ("OpNetworkQuery", dOldQuery)
-  , ("OpRestrictedCommand",
-     [ pUseLocking
-     , pRequiredNodes
-     , pRequiredNodeUuids
-     , pRestrictedCommand
-     ])
+     ],
+     "network_name")
+  , ("OpNetworkQuery",
+     [t| [[JSValue]] |],
+     OpDoc.opNetworkQuery,
+     [ pOutputFields
+     , pUseLocking
+     , withDoc "Empty list to query all groups, group names otherwise" pNames
+     ],
+     [])
   ])
 
 -- | Returns the OP_ID for a given opcode value.
@@ -650,8 +1001,7 @@
 opSummaryVal OpBackupPrepare { opInstanceName = s } = Just s
 opSummaryVal OpBackupExport { opInstanceName = s } = Just s
 opSummaryVal OpBackupRemove { opInstanceName = s } = Just s
-opSummaryVal OpTagsGet { opKind = k } =
-  Just . fromMaybe "None" $ tagNameOf k
+opSummaryVal OpTagsGet { opKind = s } = Just (show s)
 opSummaryVal OpTagsSearch { opTagSearchPattern = s } = Just (fromNonEmpty s)
 opSummaryVal OpTestDelay { opDelayDuration = d } = Just (show d)
 opSummaryVal OpTestAllocator { opIallocator = s } =
diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs
index 9b95c4c..5295412 100644
--- a/src/Ganeti/OpParams.hs
+++ b/src/Ganeti/OpParams.hs
@@ -13,32 +13,35 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.OpParams
-  ( TagType(..)
-  , TagObject(..)
-  , tagObjectFrom
-  , tagNameOf
-  , decodeTagObject
-  , encodeTagObject
-  , ReplaceDisksMode(..)
+  ( ReplaceDisksMode(..)
   , DiskIndex
   , mkDiskIndex
   , unDiskIndex
@@ -55,6 +58,7 @@
   , pName
   , pTagsList
   , pTagsObject
+  , pTagsName
   , pOutputFields
   , pShutdownTimeout
   , pShutdownTimeout'
@@ -99,14 +103,16 @@
   , pHvState
   , pDiskState
   , pIgnoreIpolicy
+  , pHotplug
+  , pHotplugIfPossible
   , pAllowRuntimeChgs
   , pInstDisks
   , pDiskTemplate
   , pOptDiskTemplate
   , pFileDriver
   , pFileStorageDir
-  , pGlobalFileStorageDir
-  , pGlobalSharedFileStorageDir
+  , pClusterFileStorageDir
+  , pClusterSharedFileStorageDir
   , pVgName
   , pEnabledHypervisors
   , pHypervisor
@@ -139,6 +145,7 @@
   , pUseExternalMipScript
   , pQueryFields
   , pQueryFilter
+  , pQueryFieldsFields
   , pOobCommand
   , pOobTimeout
   , pIgnoreStatus
@@ -154,6 +161,7 @@
   , pRequiredNodes
   , pRequiredNodeUuids
   , pStorageType
+  , pStorageTypeOptional
   , pStorageChanges
   , pMasterCandidate
   , pOffline
@@ -211,11 +219,13 @@
   , pReplaceDisksMode
   , pReplaceDisksList
   , pAllowFailover
+  , pForceFailover
   , pDelayDuration
   , pDelayOnMaster
   , pDelayOnNodes
   , pDelayOnNodeUuids
   , pDelayRepeat
+  , pDelayNoLocks
   , pIAllocatorDirection
   , pIAllocatorMode
   , pIAllocatorReqName
@@ -246,21 +256,20 @@
   , pNetworkRemoveRsvdIps
   , pNetworkMode
   , pNetworkLink
+  , pNetworkVlan
   , pDryRun
   , pDebugLevel
   , pOpPriority
   , pDependencies
   , pComment
   , pReason
+  , pSequential
   , pEnabledDiskTemplates
-  , dOldQuery
-  , dOldQueryNoLocking
   ) where
 
 import Control.Monad (liftM)
-import qualified Data.Set as Set
-import Text.JSON (readJSON, showJSON, JSON, JSValue(..), fromJSString,
-                  JSObject, toJSObject)
+import Text.JSON (JSON, JSValue(..), JSObject (..), readJSON, showJSON,
+                  fromJSString, toJSObject)
 import qualified Text.JSON
 import Text.JSON.Pretty (pp_value)
 
@@ -273,8 +282,6 @@
 
 -- * Helper functions and types
 
--- * Type aliases
-
 -- | Build a boolean field.
 booleanField :: String -> Field
 booleanField = flip simpleField [t| Bool |]
@@ -299,15 +306,6 @@
 optionalNEStringField :: String -> Field
 optionalNEStringField = optionalField . flip simpleField [t| NonEmptyString |]
 
--- | Unchecked value, should be replaced by a better definition.
-type UncheckedValue = JSValue
-
--- | Unchecked dict, should be replaced by a better definition.
-type UncheckedDict = JSObject JSValue
-
--- | Unchecked list, shoild be replaced by a better definition.
-type UncheckedList = [JSValue]
-
 -- | Function to force a non-negative value, without returning via a
 -- monad. This is needed for, and should be used /only/ in the case of
 -- forcing constants. In case the constant is wrong (< 0), this will
@@ -317,82 +315,8 @@
                   Ok n -> n
                   Bad msg -> error msg
 
--- ** Tags
-
--- | Data type representing what items do the tag operations apply to.
-$(declareSADT "TagType"
-  [ ("TagTypeInstance", 'C.tagInstance)
-  , ("TagTypeNode",     'C.tagNode)
-  , ("TagTypeGroup",    'C.tagNodegroup)
-  , ("TagTypeCluster",  'C.tagCluster)
-  , ("TagTypeNetwork",  'C.tagNetwork)
-  ])
-$(makeJSONInstance ''TagType)
-
--- | Data type holding a tag object (type and object name).
-data TagObject = TagInstance String
-               | TagNode     String
-               | TagGroup    String
-               | TagNetwork  String
-               | TagCluster
-               deriving (Show, Eq)
-
--- | Tag type for a given tag object.
-tagTypeOf :: TagObject -> TagType
-tagTypeOf (TagInstance {}) = TagTypeInstance
-tagTypeOf (TagNode     {}) = TagTypeNode
-tagTypeOf (TagGroup    {}) = TagTypeGroup
-tagTypeOf (TagCluster  {}) = TagTypeCluster
-tagTypeOf (TagNetwork  {}) = TagTypeNetwork
-
--- | Gets the potential tag object name.
-tagNameOf :: TagObject -> Maybe String
-tagNameOf (TagInstance s) = Just s
-tagNameOf (TagNode     s) = Just s
-tagNameOf (TagGroup    s) = Just s
-tagNameOf (TagNetwork  s) = Just s
-tagNameOf  TagCluster     = Nothing
-
--- | Builds a 'TagObject' from a tag type and name.
-tagObjectFrom :: (Monad m) => TagType -> JSValue -> m TagObject
-tagObjectFrom TagTypeInstance (JSString s) =
-  return . TagInstance $ fromJSString s
-tagObjectFrom TagTypeNode     (JSString s) = return . TagNode $ fromJSString s
-tagObjectFrom TagTypeGroup    (JSString s) = return . TagGroup $ fromJSString s
-tagObjectFrom TagTypeNetwork  (JSString s) =
-  return . TagNetwork $ fromJSString s
-tagObjectFrom TagTypeCluster   JSNull      = return TagCluster
-tagObjectFrom t v =
-  fail $ "Invalid tag type/name combination: " ++ show t ++ "/" ++
-         show (pp_value v)
-
--- | Name of the tag \"name\" field.
-tagNameField :: String
-tagNameField = "name"
-
--- | Custom encoder for 'TagObject' as represented in an opcode.
-encodeTagObject :: TagObject -> (JSValue, [(String, JSValue)])
-encodeTagObject t = ( showJSON (tagTypeOf t)
-                    , [(tagNameField, maybe JSNull showJSON (tagNameOf t))] )
-
--- | Custom decoder for 'TagObject' as represented in an opcode.
-decodeTagObject :: (Monad m) => [(String, JSValue)] -> JSValue -> m TagObject
-decodeTagObject obj kind = do
-  ttype <- fromJVal kind
-  tname <- fromObj obj tagNameField
-  tagObjectFrom ttype tname
-
 -- ** Disks
 
--- | Replace disks type.
-$(declareSADT "ReplaceDisksMode"
-  [ ("ReplaceOnPrimary",    'C.replaceDiskPri)
-  , ("ReplaceOnSecondary",  'C.replaceDiskSec)
-  , ("ReplaceNewSecondary", 'C.replaceDiskChg)
-  , ("ReplaceAuto",         'C.replaceDiskAuto)
-  ])
-$(makeJSONInstance ''ReplaceDisksMode)
-
 -- | Disk index type (embedding constraints on the index value via a
 -- smart constructor).
 newtype DiskIndex = DiskIndex { unDiskIndex :: Int }
@@ -420,11 +344,13 @@
 
 -- | NIC modification definition.
 $(buildObject "INicParams" "inic"
-  [ optionalField $ simpleField C.inicMac  [t| NonEmptyString |]
-  , optionalField $ simpleField C.inicIp   [t| String         |]
-  , optionalField $ simpleField C.inicMode [t| NonEmptyString |]
-  , optionalField $ simpleField C.inicLink [t| NonEmptyString |]
-  , optionalField $ simpleField C.inicName [t| NonEmptyString |]
+  [ optionalField $ simpleField C.inicMac    [t| NonEmptyString |]
+  , optionalField $ simpleField C.inicIp     [t| String         |]
+  , optionalField $ simpleField C.inicMode   [t| NonEmptyString |]
+  , optionalField $ simpleField C.inicLink   [t| NonEmptyString |]
+  , optionalField $ simpleField C.inicName   [t| NonEmptyString |]
+  , optionalField $ simpleField C.inicVlan   [t| NonEmptyString |]
+  , optionalField $ simpleField C.inicBridge [t| NonEmptyString |]
   ])
 
 -- | Disk modification definition. FIXME: disksize should be VTYPE_UNIT.
@@ -506,11 +432,11 @@
   readJSON = readSetParams
 
 -- | Custom type for target_node parameter of OpBackupExport, which
--- varies depending on mode. FIXME: this uses an UncheckedList since
+-- varies depending on mode. FIXME: this uses an [JSValue] since
 -- we don't care about individual rows (just like the Python code
 -- tests). But the proper type could be parsed if we wanted.
 data ExportTarget = ExportTargetLocal NonEmptyString
-                  | ExportTargetRemote UncheckedList
+                  | ExportTargetRemote [JSValue]
                     deriving (Eq, Show)
 
 -- | Custom reader for 'ExportTarget'.
@@ -526,396 +452,207 @@
   showJSON (ExportTargetRemote l) = showJSON l
   readJSON = readExportTarget
 
+-- * Common opcode parameters
+
+pDryRun :: Field
+pDryRun =
+  withDoc "Run checks only, don't execute" .
+  optionalField $ booleanField "dry_run"
+
+pDebugLevel :: Field
+pDebugLevel =
+  withDoc "Debug level" .
+  optionalField $ simpleField "debug_level" [t| NonNegative Int |]
+
+pOpPriority :: Field
+pOpPriority =
+  withDoc "Opcode priority. Note: python uses a separate constant,\
+          \ we're using the actual value we know it's the default" .
+  defaultField [| OpPrioNormal |] $
+  simpleField "priority" [t| OpSubmitPriority |]
+
+pDependencies :: Field
+pDependencies =
+  withDoc "Job dependencies" .
+  optionalNullSerField $ simpleField "depends" [t| [JobDependency] |]
+
+pComment :: Field
+pComment =
+  withDoc "Comment field" .
+  optionalNullSerField $ stringField "comment"
+
+pReason :: Field
+pReason =
+  withDoc "Reason trail field" $
+  simpleField C.opcodeReason [t| ReasonTrail |]
+
+pSequential :: Field
+pSequential =
+  withDoc "Sequential job execution" $
+  defaultFalse C.opcodeSequential
+
 -- * Parameters
 
--- | A required instance name (for single-instance LUs).
-pInstanceName :: Field
-pInstanceName = simpleField "instance_name" [t| String |]
-
--- | An instance UUID (for single-instance LUs).
-pInstanceUuid :: Field
-pInstanceUuid = optionalField $ simpleField "instance_uuid" [t| String |]
-
--- | A list of instances.
-pInstances :: Field
-pInstances = defaultField [| [] |] $
-             simpleField "instances" [t| [NonEmptyString] |]
-
--- | A generic name.
-pName :: Field
-pName = simpleField "name" [t| NonEmptyString |]
-
--- | Tags list.
-pTagsList :: Field
-pTagsList = simpleField "tags" [t| [String] |]
-
--- | Tags object.
-pTagsObject :: Field
-pTagsObject =
-  customField 'decodeTagObject 'encodeTagObject [tagNameField] $
-  simpleField "kind" [t| TagObject |]
-
--- | Selected output fields.
-pOutputFields :: Field
-pOutputFields = simpleField "output_fields" [t| [NonEmptyString] |]
-
--- | How long to wait for instance to shut down.
-pShutdownTimeout :: Field
-pShutdownTimeout = defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
-                   simpleField "shutdown_timeout" [t| NonNegative Int |]
-
--- | Another name for the shutdown timeout, because we like to be
--- inconsistent.
-pShutdownTimeout' :: Field
-pShutdownTimeout' =
-  renameField "InstShutdownTimeout" .
-  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
-  simpleField "timeout" [t| NonNegative Int |]
-
--- | Whether to shutdown the instance in backup-export.
-pShutdownInstance :: Field
-pShutdownInstance = defaultTrue "shutdown"
-
--- | Whether to force the operation.
-pForce :: Field
-pForce = defaultFalse "force"
-
--- | Whether to ignore offline nodes.
-pIgnoreOfflineNodes :: Field
-pIgnoreOfflineNodes = defaultFalse "ignore_offline_nodes"
-
--- | A required node name (for single-node LUs).
-pNodeName :: Field
-pNodeName = simpleField "node_name" [t| NonEmptyString |]
-
--- | A node UUID (for single-node LUs).
-pNodeUuid :: Field
-pNodeUuid = optionalField $ simpleField "node_uuid" [t| NonEmptyString |]
-
--- | List of nodes.
-pNodeNames :: Field
-pNodeNames =
-  defaultField [| [] |] $ simpleField "node_names" [t| [NonEmptyString] |]
-
--- | List of node UUIDs.
-pNodeUuids :: Field
-pNodeUuids =
-  optionalField $ simpleField "node_uuids" [t| [NonEmptyString] |]
-
--- | A required node group name (for single-group LUs).
-pGroupName :: Field
-pGroupName = simpleField "group_name" [t| NonEmptyString |]
-
--- | Migration type (live\/non-live).
-pMigrationMode :: Field
-pMigrationMode =
-  renameField "MigrationMode" .
-  optionalField $
-  simpleField "mode" [t| MigrationMode |]
-
--- | Obsolete \'live\' migration mode (boolean).
-pMigrationLive :: Field
-pMigrationLive =
-  renameField "OldLiveMode" . optionalField $ booleanField "live"
-
--- | Migration cleanup parameter.
-pMigrationCleanup :: Field
-pMigrationCleanup = renameField "MigrationCleanup" $ defaultFalse "cleanup"
-
--- | Whether to force an unknown OS variant.
-pForceVariant :: Field
-pForceVariant = defaultFalse "force_variant"
-
--- | Whether to wait for the disk to synchronize.
-pWaitForSync :: Field
-pWaitForSync = defaultTrue "wait_for_sync"
-
--- | Whether to wait for the disk to synchronize (defaults to false).
-pWaitForSyncFalse :: Field
-pWaitForSyncFalse = defaultField [| False |] pWaitForSync
-
--- | Whether to ignore disk consistency
-pIgnoreConsistency :: Field
-pIgnoreConsistency = defaultFalse "ignore_consistency"
-
--- | Storage name.
-pStorageName :: Field
-pStorageName =
-  renameField "StorageName" $ simpleField "name" [t| NonEmptyString |]
-
--- | Whether to use synchronization.
-pUseLocking :: Field
-pUseLocking = defaultFalse "use_locking"
-
--- | Whether to employ opportunistic locking for nodes, meaning nodes already
--- locked by another opcode won't be considered for instance allocation (only
--- when an iallocator is used).
-pOpportunisticLocking :: Field
-pOpportunisticLocking = defaultFalse "opportunistic_locking"
-
--- | Whether to check name.
-pNameCheck :: Field
-pNameCheck = defaultTrue "name_check"
-
--- | Instance allocation policy.
-pNodeGroupAllocPolicy :: Field
-pNodeGroupAllocPolicy = optionalField $
-                        simpleField "alloc_policy" [t| AllocPolicy |]
-
--- | Default node parameters for group.
-pGroupNodeParams :: Field
-pGroupNodeParams = optionalField $ simpleField "ndparams" [t| UncheckedDict |]
-
--- | Resource(s) to query for.
-pQueryWhat :: Field
-pQueryWhat = simpleField "what" [t| Qlang.QueryTypeOp |]
-
--- | Whether to release locks as soon as possible.
-pEarlyRelease :: Field
-pEarlyRelease = defaultFalse "early_release"
-
--- | Whether to ensure instance's IP address is inactive.
-pIpCheck :: Field
-pIpCheck = defaultTrue "ip_check"
-
--- | Check for conflicting IPs.
-pIpConflictsCheck :: Field
-pIpConflictsCheck = defaultTrue "conflicts_check"
-
--- | Do not remember instance state changes.
-pNoRemember :: Field
-pNoRemember = defaultFalse "no_remember"
-
--- | Target node for instance migration/failover.
-pMigrationTargetNode :: Field
-pMigrationTargetNode = optionalNEStringField "target_node"
-
--- | Target node UUID for instance migration/failover.
-pMigrationTargetNodeUuid :: Field
-pMigrationTargetNodeUuid = optionalNEStringField "target_node_uuid"
-
--- | Target node for instance move (required).
-pMoveTargetNode :: Field
-pMoveTargetNode =
-  renameField "MoveTargetNode" $
-  simpleField "target_node" [t| NonEmptyString |]
-
--- | Target node UUID for instance move.
-pMoveTargetNodeUuid :: Field
-pMoveTargetNodeUuid =
-  renameField "MoveTargetNodeUuid" . optionalField $
-  simpleField "target_node_uuid" [t| NonEmptyString |]
-
--- | Pause instance at startup.
-pStartupPaused :: Field
-pStartupPaused = defaultFalse "startup_paused"
-
--- | Verbose mode.
-pVerbose :: Field
-pVerbose = defaultFalse "verbose"
-
--- ** Parameters for cluster verification
-
--- | Whether to simulate errors (useful for debugging).
 pDebugSimulateErrors :: Field
-pDebugSimulateErrors = defaultFalse "debug_simulate_errors"
+pDebugSimulateErrors =
+  withDoc "Whether to simulate errors (useful for debugging)" $
+  defaultFalse "debug_simulate_errors"
 
--- | Error codes.
 pErrorCodes :: Field
-pErrorCodes = defaultFalse "error_codes"
+pErrorCodes = 
+  withDoc "Error codes" $
+  defaultFalse "error_codes"
 
--- | Which checks to skip.
 pSkipChecks :: Field
-pSkipChecks = defaultField [| Set.empty |] $
-              simpleField "skip_checks" [t| Set.Set VerifyOptionalChecks |]
+pSkipChecks = 
+  withDoc "Which checks to skip" .
+  defaultField [| emptyListSet |] $
+  simpleField "skip_checks" [t| ListSet VerifyOptionalChecks |]
 
--- | List of error codes that should be treated as warnings.
 pIgnoreErrors :: Field
-pIgnoreErrors = defaultField [| Set.empty |] $
-                simpleField "ignore_errors" [t| Set.Set CVErrorCode |]
+pIgnoreErrors =
+  withDoc "List of error codes that should be treated as warnings" .
+  defaultField [| emptyListSet |] $
+  simpleField "ignore_errors" [t| ListSet CVErrorCode |]
 
--- | Optional group name.
+pVerbose :: Field
+pVerbose =
+  withDoc "Verbose mode" $
+  defaultFalse "verbose"
+
 pOptGroupName :: Field
-pOptGroupName = renameField "OptGroupName" .
-                optionalField $ simpleField "group_name" [t| NonEmptyString |]
+pOptGroupName =
+  withDoc "Optional group name" .
+  renameField "OptGroupName" .
+  optionalField $ simpleField "group_name" [t| NonEmptyString |]
 
--- | Disk templates' parameter defaults.
-pDiskParams :: Field
-pDiskParams = optionalField $
-              simpleField "diskparams" [t| GenericContainer DiskTemplate
-                                           UncheckedDict |]
+pGroupName :: Field
+pGroupName =
+  withDoc "Group name" $
+  simpleField "group_name" [t| NonEmptyString |]
 
--- * Parameters for node resource model
+-- | Whether to hotplug device.
+pHotplug :: Field
+pHotplug = defaultFalse "hotplug"
 
--- | Set hypervisor states.
+pHotplugIfPossible :: Field
+pHotplugIfPossible = defaultFalse "hotplug_if_possible"
+
+pInstances :: Field
+pInstances =
+  withDoc "List of instances" .
+  defaultField [| [] |] $
+  simpleField "instances" [t| [NonEmptyString] |]
+
+pOutputFields :: Field
+pOutputFields =
+  withDoc "Selected output fields" $
+  simpleField "output_fields" [t| [NonEmptyString] |]
+
+pName :: Field
+pName =
+  withDoc "A generic name" $
+  simpleField "name" [t| NonEmptyString |]
+
+pForce :: Field
+pForce =
+  withDoc "Whether to force the operation" $
+  defaultFalse "force"
+
 pHvState :: Field
-pHvState = optionalField $ simpleField "hv_state" [t| UncheckedDict |]
+pHvState =
+  withDoc "Set hypervisor states" .
+  optionalField $ simpleField "hv_state" [t| JSObject JSValue |]
 
--- | Set disk states.
 pDiskState :: Field
-pDiskState = optionalField $ simpleField "disk_state" [t| UncheckedDict |]
+pDiskState =
+  withDoc "Set disk states" .
+  optionalField $ simpleField "disk_state" [t| JSObject JSValue |]
 
--- | Whether to ignore ipolicy violations.
-pIgnoreIpolicy :: Field
-pIgnoreIpolicy = defaultFalse "ignore_ipolicy"
+-- | Cluster-wide default directory for storing file-backed disks.
+pClusterFileStorageDir :: Field
+pClusterFileStorageDir =
+  renameField "ClusterFileStorageDir" $
+  optionalStringField "file_storage_dir"
 
--- | Allow runtime changes while migrating.
-pAllowRuntimeChgs :: Field
-pAllowRuntimeChgs = defaultTrue "allow_runtime_changes"
-
--- | Utility type for OpClusterSetParams.
-type TestClusterOsListItem = (DdmSimple, NonEmptyString)
-
--- | Utility type of OsList.
-type TestClusterOsList = [TestClusterOsListItem]
-
--- Utility type for NIC definitions.
---type TestNicDef = INicParams
-
--- | List of instance disks.
-pInstDisks :: Field
-pInstDisks = renameField "instDisks" $ simpleField "disks" [t| [IDiskParams] |]
-
--- | Instance disk template.
-pDiskTemplate :: Field
-pDiskTemplate = simpleField "disk_template" [t| DiskTemplate |]
-
--- | Instance disk template.
-pOptDiskTemplate :: Field
-pOptDiskTemplate =
-  optionalField .
-  renameField "OptDiskTemplate" $
-  simpleField "disk_template" [t| DiskTemplate |]
-
--- | File driver.
-pFileDriver :: Field
-pFileDriver = optionalField $ simpleField "file_driver" [t| FileDriver |]
-
--- | Directory for storing file-backed disks.
-pFileStorageDir :: Field
-pFileStorageDir = optionalNEStringField "file_storage_dir"
-
--- | Global directory for storing file-backed disks.
-pGlobalFileStorageDir :: Field
-pGlobalFileStorageDir = optionalNEStringField "file_storage_dir"
-
--- | Global directory for storing shared-file-backed disks.
-pGlobalSharedFileStorageDir :: Field
-pGlobalSharedFileStorageDir = optionalNEStringField "shared_file_storage_dir"
+-- | Cluster-wide default directory for storing shared-file-backed disks.
+pClusterSharedFileStorageDir :: Field
+pClusterSharedFileStorageDir =
+  renameField "ClusterSharedFileStorageDir" $
+  optionalStringField "shared_file_storage_dir"
 
 -- | Volume group name.
 pVgName :: Field
-pVgName = optionalStringField "vg_name"
+pVgName =
+  withDoc "Volume group name" $
+  optionalStringField "vg_name"
 
--- | List of enabled hypervisors.
 pEnabledHypervisors :: Field
 pEnabledHypervisors =
+  withDoc "List of enabled hypervisors" .
   optionalField $
-  simpleField "enabled_hypervisors" [t| NonEmpty Hypervisor |]
+  simpleField "enabled_hypervisors" [t| [Hypervisor] |]
 
--- | List of enabled disk templates.
-pEnabledDiskTemplates :: Field
-pEnabledDiskTemplates =
-  optionalField $
-  simpleField "enabled_disk_templates" [t| NonEmpty DiskTemplate |]
-
--- | Selected hypervisor for an instance.
-pHypervisor :: Field
-pHypervisor =
-  optionalField $
-  simpleField "hypervisor" [t| Hypervisor |]
-
--- | Cluster-wide hypervisor parameters, hypervisor-dependent.
 pClusterHvParams :: Field
 pClusterHvParams =
+  withDoc "Cluster-wide hypervisor parameters, hypervisor-dependent" .
   renameField "ClusterHvParams" .
   optionalField $
-  simpleField "hvparams" [t| Container UncheckedDict |]
+  simpleField "hvparams" [t| GenericContainer String (JSObject JSValue) |]
 
--- | Instance hypervisor parameters.
-pInstHvParams :: Field
-pInstHvParams =
-  renameField "InstHvParams" .
-  defaultField [| toJSObject [] |] $
-  simpleField "hvparams" [t| UncheckedDict |]
-
--- | Cluster-wide beparams.
 pClusterBeParams :: Field
 pClusterBeParams =
+  withDoc "Cluster-wide backend parameter defaults" .
   renameField "ClusterBeParams" .
-  optionalField $ simpleField "beparams" [t| UncheckedDict |]
+  optionalField $ simpleField "beparams" [t| JSObject JSValue |]
 
--- | Instance beparams.
-pInstBeParams :: Field
-pInstBeParams =
-  renameField "InstBeParams" .
-  defaultField [| toJSObject [] |] $
-  simpleField "beparams" [t| UncheckedDict |]
-
--- | Reset instance parameters to default if equal.
-pResetDefaults :: Field
-pResetDefaults = defaultFalse "identify_defaults"
-
--- | Cluster-wide per-OS hypervisor parameter defaults.
 pOsHvp :: Field
-pOsHvp = optionalField $ simpleField "os_hvp" [t| Container UncheckedDict |]
+pOsHvp =
+  withDoc "Cluster-wide per-OS hypervisor parameter defaults" .
+  optionalField $
+  simpleField "os_hvp" [t| GenericContainer String (JSObject JSValue) |]
 
--- | Cluster-wide OS parameter defaults.
 pClusterOsParams :: Field
 pClusterOsParams =
+  withDoc "Cluster-wide OS parameter defaults" .
   renameField "ClusterOsParams" .
-  optionalField $ simpleField "osparams" [t| Container UncheckedDict |]
+  optionalField $
+  simpleField "osparams" [t| GenericContainer String (JSObject JSValue) |]
 
--- | Instance OS parameters.
-pInstOsParams :: Field
-pInstOsParams =
-  renameField "InstOsParams" . defaultField [| toJSObject [] |] $
-  simpleField "osparams" [t| UncheckedDict |]
+pDiskParams :: Field
+pDiskParams =
+  withDoc "Disk templates' parameter defaults" .
+  optionalField $
+  simpleField "diskparams"
+              [t| GenericContainer DiskTemplate (JSObject JSValue) |]
 
--- | Temporary OS parameters (currently only in reinstall, might be
--- added to install as well).
-pTempOsParams :: Field
-pTempOsParams =
-  renameField "TempOsParams" .
-  optionalField $ simpleField "osparams" [t| UncheckedDict |]
-
--- | Temporary hypervisor parameters, hypervisor-dependent.
-pTempHvParams :: Field
-pTempHvParams =
-  renameField "TempHvParams" .
-  defaultField [| toJSObject [] |] $
-  simpleField "hvparams" [t| UncheckedDict |]
-
--- | Temporary backend parameters.
-pTempBeParams :: Field
-pTempBeParams =
-  renameField "TempBeParams" .
-  defaultField [| toJSObject [] |] $
-  simpleField "beparams" [t| UncheckedDict |]
-
--- | Candidate pool size.
 pCandidatePoolSize :: Field
 pCandidatePoolSize =
+  withDoc "Master candidate pool size" .
   optionalField $ simpleField "candidate_pool_size" [t| Positive Int |]
 
--- | Set UID pool, must be list of lists describing UID ranges (two
--- items, start and end inclusive.
 pUidPool :: Field
-pUidPool = optionalField $ simpleField "uid_pool" [t| [[(Int, Int)]] |]
+pUidPool =
+  withDoc "Set UID pool, must be list of lists describing UID ranges\
+          \ (two items, start and end inclusive)" .
+  optionalField $ simpleField "uid_pool" [t| [(Int, Int)] |]
 
--- | Extend UID pool, must be list of lists describing UID ranges (two
--- items, start and end inclusive.
 pAddUids :: Field
-pAddUids = optionalField $ simpleField "add_uids" [t| [[(Int, Int)]] |]
+pAddUids =
+  withDoc "Extend UID pool, must be list of lists describing UID\
+          \ ranges (two items, start and end inclusive)" .
+  optionalField $ simpleField "add_uids" [t| [(Int, Int)] |]
 
--- | Shrink UID pool, must be list of lists describing UID ranges (two
--- items, start and end inclusive) to be removed.
 pRemoveUids :: Field
-pRemoveUids = optionalField $ simpleField "remove_uids" [t| [[(Int, Int)]] |]
+pRemoveUids =
+  withDoc "Shrink UID pool, must be list of lists describing UID\
+          \ ranges (two items, start and end inclusive) to be removed" .
+  optionalField $ simpleField "remove_uids" [t| [(Int, Int)] |]
 
--- | Whether to automatically maintain node health.
 pMaintainNodeHealth :: Field
-pMaintainNodeHealth = optionalField $ booleanField "maintain_node_health"
+pMaintainNodeHealth =
+  withDoc "Whether to automatically maintain node health" .
+  optionalField $ booleanField "maintain_node_health"
 
 -- | Whether to modify and keep in sync the @/etc/hosts@ files of nodes.
 pModifyEtcHosts :: Field
@@ -923,635 +660,988 @@
 
 -- | Whether to wipe disks before allocating them to instances.
 pPreallocWipeDisks :: Field
-pPreallocWipeDisks = optionalField $ booleanField "prealloc_wipe_disks"
+pPreallocWipeDisks =
+  withDoc "Whether to wipe disks before allocating them to instances" .
+  optionalField $ booleanField "prealloc_wipe_disks"
 
--- | Cluster-wide NIC parameter defaults.
 pNicParams :: Field
-pNicParams = optionalField $ simpleField "nicparams" [t| INicParams |]
+pNicParams =
+  withDoc "Cluster-wide NIC parameter defaults" .
+  optionalField $ simpleField "nicparams" [t| INicParams |]
 
--- | Instance NIC definitions.
-pInstNics :: Field
-pInstNics = simpleField "nics" [t| [INicParams] |]
-
--- | Cluster-wide node parameter defaults.
-pNdParams :: Field
-pNdParams = optionalField $ simpleField "ndparams" [t| UncheckedDict |]
-
--- | Cluster-wide ipolicy specs.
 pIpolicy :: Field
-pIpolicy = optionalField $ simpleField "ipolicy" [t| UncheckedDict |]
+pIpolicy =
+  withDoc "Ipolicy specs" .
+  optionalField $ simpleField "ipolicy" [t| JSObject JSValue |]
 
--- | DRBD helper program.
 pDrbdHelper :: Field
-pDrbdHelper = optionalStringField "drbd_helper"
+pDrbdHelper =
+  withDoc "DRBD helper program" $
+  optionalStringField "drbd_helper"
 
--- | Default iallocator for cluster.
 pDefaultIAllocator :: Field
-pDefaultIAllocator = optionalStringField "default_iallocator"
+pDefaultIAllocator =
+  withDoc "Default iallocator for cluster" $
+  optionalStringField "default_iallocator"
 
--- | Master network device.
 pMasterNetdev :: Field
-pMasterNetdev = optionalStringField "master_netdev"
+pMasterNetdev =
+  withDoc "Master network device" $
+  optionalStringField "master_netdev"
 
--- | Netmask of the master IP.
 pMasterNetmask :: Field
 pMasterNetmask =
+  withDoc "Netmask of the master IP" .
   optionalField $ simpleField "master_netmask" [t| NonNegative Int |]
 
--- | List of reserved LVs.
 pReservedLvs :: Field
 pReservedLvs =
+  withDoc "List of reserved LVs" .
   optionalField $ simpleField "reserved_lvs" [t| [NonEmptyString] |]
 
--- | Modify list of hidden operating systems: each modification must
--- have two items, the operation and the OS name; the operation can be
--- add or remove.
 pHiddenOs :: Field
-pHiddenOs = optionalField $ simpleField "hidden_os" [t| TestClusterOsList |]
+pHiddenOs =
+  withDoc "Modify list of hidden operating systems: each modification\
+          \ must have two items, the operation and the OS name; the operation\
+          \ can be add or remove" .
+  optionalField $ simpleField "hidden_os" [t| [(DdmSimple, NonEmptyString)] |]
 
--- | Modify list of blacklisted operating systems: each modification
--- must have two items, the operation and the OS name; the operation
--- can be add or remove.
 pBlacklistedOs :: Field
 pBlacklistedOs =
-  optionalField $ simpleField "blacklisted_os" [t| TestClusterOsList |]
+  withDoc "Modify list of blacklisted operating systems: each\
+          \ modification must have two items, the operation and the OS name;\
+          \ the operation can be add or remove" .
+  optionalField $
+  simpleField "blacklisted_os" [t| [(DdmSimple, NonEmptyString)] |]
 
--- | Whether to use an external master IP address setup script.
 pUseExternalMipScript :: Field
-pUseExternalMipScript = optionalField $ booleanField "use_external_mip_script"
+pUseExternalMipScript =
+  withDoc "Whether to use an external master IP address setup script" .
+  optionalField $ booleanField "use_external_mip_script"
 
--- | Requested fields.
+pEnabledDiskTemplates :: Field
+pEnabledDiskTemplates =
+  withDoc "List of enabled disk templates" .
+  optionalField $
+  simpleField "enabled_disk_templates" [t| [DiskTemplate] |]
+
+pQueryWhat :: Field
+pQueryWhat =
+  withDoc "Resource(s) to query for" $
+  simpleField "what" [t| Qlang.QueryTypeOp |]
+
+pUseLocking :: Field
+pUseLocking =
+  withDoc "Whether to use synchronization" $
+  defaultFalse "use_locking"
+
 pQueryFields :: Field
-pQueryFields = simpleField "fields" [t| [NonEmptyString] |]
+pQueryFields =
+  withDoc "Requested fields" $
+  simpleField "fields" [t| [NonEmptyString] |]
 
--- | Query filter.
 pQueryFilter :: Field
-pQueryFilter = simpleField "qfilter" [t| Qlang.Filter String |]
+pQueryFilter =
+  withDoc "Query filter" .
+  optionalField $ simpleField "qfilter" [t| [JSValue] |]
 
--- | OOB command to run.
+pQueryFieldsFields :: Field
+pQueryFieldsFields =
+  withDoc "Requested fields; if not given, all are returned" .
+  renameField "QueryFieldsFields" $
+  optionalField pQueryFields
+
+pNodeNames :: Field
+pNodeNames =
+  withDoc "List of node names to run the OOB command against" .
+  defaultField [| [] |] $ simpleField "node_names" [t| [NonEmptyString] |]
+
+pNodeUuids :: Field
+pNodeUuids =
+  withDoc "List of node UUIDs" .
+  optionalField $ simpleField "node_uuids" [t| [NonEmptyString] |]
+
 pOobCommand :: Field
-pOobCommand = simpleField "command" [t| OobCommand |]
+pOobCommand =
+  withDoc "OOB command to run" $
+  simpleField "command" [t| OobCommand |]
 
--- | Timeout before the OOB helper will be terminated.
 pOobTimeout :: Field
 pOobTimeout =
-  defaultField [| C.oobTimeout |] $ simpleField "timeout" [t| Int |]
+  withDoc "Timeout before the OOB helper will be terminated" .
+  defaultField [| C.oobTimeout |] $
+  simpleField "timeout" [t| Int |]
 
--- | Ignores the node offline status for power off.
 pIgnoreStatus :: Field
-pIgnoreStatus = defaultFalse "ignore_status"
+pIgnoreStatus =
+  withDoc "Ignores the node offline status for power off" $
+  defaultFalse "ignore_status"
 
--- | Time in seconds to wait between powering on nodes.
 pPowerDelay :: Field
 pPowerDelay =
   -- FIXME: we can't use the proper type "NonNegative Double", since
   -- the default constant is a plain Double, not a non-negative one.
+  -- And trying to fix the constant introduces a cyclic import.
+  withDoc "Time in seconds to wait between powering on nodes" .
   defaultField [| C.oobPowerDelay |] $
   simpleField "power_delay" [t| Double |]
 
--- | Primary IP address.
-pPrimaryIp :: Field
-pPrimaryIp = optionalStringField "primary_ip"
-
--- | Secondary IP address.
-pSecondaryIp :: Field
-pSecondaryIp = optionalNEStringField "secondary_ip"
-
--- | Whether node is re-added to cluster.
-pReadd :: Field
-pReadd = defaultFalse "readd"
-
--- | Initial node group.
-pNodeGroup :: Field
-pNodeGroup = optionalNEStringField "group"
-
--- | Whether node can become master or master candidate.
-pMasterCapable :: Field
-pMasterCapable = optionalField $ booleanField "master_capable"
-
--- | Whether node can host instances.
-pVmCapable :: Field
-pVmCapable = optionalField $ booleanField "vm_capable"
-
--- | List of names.
-pNames :: Field
-pNames = defaultField [| [] |] $ simpleField "names" [t| [NonEmptyString] |]
-
--- | List of node names.
-pNodes :: Field
-pNodes = defaultField [| [] |] $ simpleField "nodes" [t| [NonEmptyString] |]
-
--- | Required list of node names.
 pRequiredNodes :: Field
 pRequiredNodes =
+  withDoc "Required list of node names" .
   renameField "ReqNodes " $ simpleField "nodes" [t| [NonEmptyString] |]
 
--- | Required list of node names.
 pRequiredNodeUuids :: Field
 pRequiredNodeUuids =
+  withDoc "Required list of node UUIDs" .
   renameField "ReqNodeUuids " . optionalField $
-    simpleField "node_uuids" [t| [NonEmptyString] |]
+  simpleField "node_uuids" [t| [NonEmptyString] |]
 
--- | Storage type.
+pRestrictedCommand :: Field
+pRestrictedCommand =
+  withDoc "Restricted command name" .
+  renameField "RestrictedCommand" $
+  simpleField "command" [t| NonEmptyString |]
+
+pNodeName :: Field
+pNodeName =
+  withDoc "A required node name (for single-node LUs)" $
+  simpleField "node_name" [t| NonEmptyString |]
+
+pNodeUuid :: Field
+pNodeUuid =
+  withDoc "A node UUID (for single-node LUs)" .
+  optionalField $ simpleField "node_uuid" [t| NonEmptyString |]
+
+pPrimaryIp :: Field
+pPrimaryIp =
+  withDoc "Primary IP address" .
+  optionalField $
+  simpleField "primary_ip" [t| NonEmptyString |]
+
+pSecondaryIp :: Field
+pSecondaryIp =
+  withDoc "Secondary IP address" $
+  optionalNEStringField "secondary_ip"
+
+pReadd :: Field
+pReadd =
+  withDoc "Whether node is re-added to cluster" $
+  defaultFalse "readd"
+
+pNodeGroup :: Field
+pNodeGroup =
+  withDoc "Initial node group" $
+  optionalNEStringField "group"
+
+pMasterCapable :: Field
+pMasterCapable =
+  withDoc "Whether node can become master or master candidate" .
+  optionalField $ booleanField "master_capable"
+
+pVmCapable :: Field
+pVmCapable =
+  withDoc "Whether node can host instances" .
+  optionalField $ booleanField "vm_capable"
+
+pNdParams :: Field
+pNdParams =
+  withDoc "Node parameters" .
+  renameField "genericNdParams" .
+  optionalField $ simpleField "ndparams" [t| JSObject JSValue |]
+  
+pNames :: Field
+pNames =
+  withDoc "List of names" .
+  defaultField [| [] |] $ simpleField "names" [t| [NonEmptyString] |]
+
+pNodes :: Field
+pNodes =
+  withDoc "List of nodes" .
+  defaultField [| [] |] $ simpleField "nodes" [t| [NonEmptyString] |]
+
 pStorageType :: Field
-pStorageType = simpleField "storage_type" [t| StorageType |]
+pStorageType =
+  withDoc "Storage type" $ simpleField "storage_type" [t| StorageType |]
 
--- | Storage changes (unchecked).
+pStorageTypeOptional :: Field
+pStorageTypeOptional =
+  withDoc "Storage type" .
+  renameField "StorageTypeOptional" .
+  optionalField $ simpleField "storage_type" [t| StorageType |]
+
+pStorageName :: Field
+pStorageName =
+  withDoc "Storage name" .
+  renameField "StorageName" .
+  optionalField $ simpleField "name" [t| NonEmptyString |]
+
 pStorageChanges :: Field
-pStorageChanges = simpleField "changes" [t| UncheckedDict |]
+pStorageChanges =
+  withDoc "Requested storage changes" $
+  simpleField "changes" [t| JSObject JSValue |]
 
--- | Whether the node should become a master candidate.
+pIgnoreConsistency :: Field
+pIgnoreConsistency =
+  withDoc "Whether to ignore disk consistency" $
+  defaultFalse "ignore_consistency"
+
 pMasterCandidate :: Field
-pMasterCandidate = optionalField $ booleanField "master_candidate"
+pMasterCandidate =
+  withDoc "Whether the node should become a master candidate" .
+  optionalField $ booleanField "master_candidate"
 
--- | Whether the node should be marked as offline.
 pOffline :: Field
-pOffline = optionalField $ booleanField "offline"
+pOffline =
+  withDoc "Whether to mark the node or instance offline" .
+  optionalField $ booleanField "offline"
 
--- | Whether the node should be marked as drained.
 pDrained ::Field
-pDrained = optionalField $ booleanField "drained"
+pDrained =
+  withDoc "Whether to mark the node as drained" .
+  optionalField $ booleanField "drained"
 
--- | Whether node(s) should be promoted to master candidate if necessary.
 pAutoPromote :: Field
-pAutoPromote = defaultFalse "auto_promote"
+pAutoPromote =
+  withDoc "Whether node(s) should be promoted to master candidate if\
+          \ necessary" $
+  defaultFalse "auto_promote"
 
--- | Whether the node should be marked as powered
 pPowered :: Field
-pPowered = optionalField $ booleanField "powered"
+pPowered =
+  withDoc "Whether the node should be marked as powered" .
+  optionalField $ booleanField "powered"
 
--- | Iallocator for deciding the target node for shared-storage
--- instances during migrate and failover.
+pMigrationMode :: Field
+pMigrationMode =
+  withDoc "Migration type (live/non-live)" .
+  renameField "MigrationMode" .
+  optionalField $
+  simpleField "mode" [t| MigrationMode |]
+
+pMigrationLive :: Field
+pMigrationLive =
+  withDoc "Obsolete \'live\' migration mode (do not use)" .
+  renameField "OldLiveMode" . optionalField $ booleanField "live"
+
+pMigrationTargetNode :: Field
+pMigrationTargetNode =
+  withDoc "Target node for instance migration/failover" $
+  optionalNEStringField "target_node"
+
+pMigrationTargetNodeUuid :: Field
+pMigrationTargetNodeUuid =
+  withDoc "Target node UUID for instance migration/failover" $
+  optionalNEStringField "target_node_uuid"
+
+pAllowRuntimeChgs :: Field
+pAllowRuntimeChgs =
+  withDoc "Whether to allow runtime changes while migrating" $
+  defaultTrue "allow_runtime_changes"
+
+pIgnoreIpolicy :: Field
+pIgnoreIpolicy =
+  withDoc "Whether to ignore ipolicy violations" $
+  defaultFalse "ignore_ipolicy"
+  
 pIallocator :: Field
-pIallocator = optionalNEStringField "iallocator"
+pIallocator =
+  withDoc "Iallocator for deciding the target node for shared-storage\
+          \ instances" $
+  optionalNEStringField "iallocator"
 
--- | New secondary node.
+pEarlyRelease :: Field
+pEarlyRelease =
+  withDoc "Whether to release locks as soon as possible" $
+  defaultFalse "early_release"
+
 pRemoteNode :: Field
-pRemoteNode = optionalNEStringField "remote_node"
+pRemoteNode =
+  withDoc "New secondary node" $
+  optionalNEStringField "remote_node"
 
--- | New secondary node UUID.
 pRemoteNodeUuid :: Field
-pRemoteNodeUuid = optionalNEStringField "remote_node_uuid"
+pRemoteNodeUuid =
+  withDoc "New secondary node UUID" $
+  optionalNEStringField "remote_node_uuid"
 
--- | Node evacuation mode.
 pEvacMode :: Field
-pEvacMode = renameField "EvacMode" $ simpleField "mode" [t| NodeEvacMode |]
+pEvacMode =
+  withDoc "Node evacuation mode" .
+  renameField "EvacMode" $ simpleField "mode" [t| EvacMode |]
 
--- | Instance creation mode.
+pInstanceName :: Field
+pInstanceName =
+  withDoc "A required instance name (for single-instance LUs)" $
+  simpleField "instance_name" [t| String |]
+
+pForceVariant :: Field
+pForceVariant =
+  withDoc "Whether to force an unknown OS variant" $
+  defaultFalse "force_variant"
+
+pWaitForSync :: Field
+pWaitForSync =
+  withDoc "Whether to wait for the disk to synchronize" $
+  defaultTrue "wait_for_sync"
+
+pNameCheck :: Field
+pNameCheck =
+  withDoc "Whether to check name" $
+  defaultTrue "name_check"
+
+pInstBeParams :: Field
+pInstBeParams =
+  withDoc "Backend parameters for instance" .
+  renameField "InstBeParams" .
+  defaultField [| toJSObject [] |] $
+  simpleField "beparams" [t| JSObject JSValue |]
+
+pInstDisks :: Field
+pInstDisks =
+  withDoc "List of instance disks" .
+  renameField "instDisks" $ simpleField "disks" [t| [IDiskParams] |]
+
+pDiskTemplate :: Field
+pDiskTemplate =
+  withDoc "Disk template" $
+  simpleField "disk_template" [t| DiskTemplate |]
+
+pFileDriver :: Field
+pFileDriver =
+  withDoc "Driver for file-backed disks" .
+  optionalField $ simpleField "file_driver" [t| FileDriver |]
+
+pFileStorageDir :: Field
+pFileStorageDir =
+  withDoc "Directory for storing file-backed disks" $
+  optionalNEStringField "file_storage_dir"
+
+pInstHvParams :: Field
+pInstHvParams =
+  withDoc "Hypervisor parameters for instance, hypervisor-dependent" .
+  renameField "InstHvParams" .
+  defaultField [| toJSObject [] |] $
+  simpleField "hvparams" [t| JSObject JSValue |]
+
+pHypervisor :: Field
+pHypervisor =
+  withDoc "Selected hypervisor for an instance" .
+  optionalField $
+  simpleField "hypervisor" [t| Hypervisor |]
+
+pResetDefaults :: Field
+pResetDefaults =
+  withDoc "Reset instance parameters to default if equal" $
+  defaultFalse "identify_defaults"
+
+pIpCheck :: Field
+pIpCheck =
+  withDoc "Whether to ensure instance's IP address is inactive" $
+  defaultTrue "ip_check"
+
+pIpConflictsCheck :: Field
+pIpConflictsCheck =
+  withDoc "Whether to check for conflicting IP addresses" $
+  defaultTrue "conflicts_check"
+
 pInstCreateMode :: Field
 pInstCreateMode =
+  withDoc "Instance creation mode" .
   renameField "InstCreateMode" $ simpleField "mode" [t| InstCreateMode |]
 
--- | Do not install the OS (will disable automatic start).
+pInstNics :: Field
+pInstNics =
+  withDoc "List of NIC (network interface) definitions" $
+  simpleField "nics" [t| [INicParams] |]
+
 pNoInstall :: Field
-pNoInstall = optionalField $ booleanField "no_install"
+pNoInstall =
+  withDoc "Do not install the OS (will disable automatic start)" .
+  optionalField $ booleanField "no_install"
 
--- | OS type for instance installation.
 pInstOs :: Field
-pInstOs = optionalNEStringField "os_type"
+pInstOs =
+  withDoc "OS type for instance installation" $
+  optionalNEStringField "os_type"
 
--- | Primary node for an instance.
+pInstOsParams :: Field
+pInstOsParams =
+  withDoc "OS parameters for instance" .
+  renameField "InstOsParams" .
+  defaultField [| toJSObject [] |] $
+  simpleField "osparams" [t| JSObject JSValue |]
+
 pPrimaryNode :: Field
-pPrimaryNode = optionalNEStringField "pnode"
+pPrimaryNode =
+  withDoc "Primary node for an instance" $
+  optionalNEStringField "pnode"
 
--- | Primary node UUID for an instance.
 pPrimaryNodeUuid :: Field
-pPrimaryNodeUuid = optionalNEStringField "pnode_uuid"
+pPrimaryNodeUuid =
+  withDoc "Primary node UUID for an instance" $
+  optionalNEStringField "pnode_uuid"
 
--- | Secondary node for an instance.
 pSecondaryNode :: Field
-pSecondaryNode = optionalNEStringField "snode"
+pSecondaryNode =
+  withDoc "Secondary node for an instance" $
+  optionalNEStringField "snode"
 
--- | Secondary node UUID for an instance.
 pSecondaryNodeUuid :: Field
-pSecondaryNodeUuid = optionalNEStringField "snode_uuid"
+pSecondaryNodeUuid =
+  withDoc "Secondary node UUID for an instance" $
+  optionalNEStringField "snode_uuid"
 
--- | Signed handshake from source (remote import only).
 pSourceHandshake :: Field
 pSourceHandshake =
-  optionalField $ simpleField "source_handshake" [t| UncheckedList |]
+  withDoc "Signed handshake from source (remote import only)" .
+  optionalField $ simpleField "source_handshake" [t| [JSValue] |]
 
--- | Source instance name (remote import only).
 pSourceInstance :: Field
-pSourceInstance = optionalNEStringField "source_instance_name"
+pSourceInstance =
+  withDoc "Source instance name (remote import only)" $
+  optionalNEStringField "source_instance_name"
 
--- | How long source instance was given to shut down (remote import only).
 -- FIXME: non-negative int, whereas the constant is a plain int.
 pSourceShutdownTimeout :: Field
 pSourceShutdownTimeout =
+  withDoc "How long source instance was given to shut down (remote import\
+          \ only)" .
   defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
   simpleField "source_shutdown_timeout" [t| NonNegative Int |]
 
--- | Source X509 CA in PEM format (remote import only).
 pSourceX509Ca :: Field
-pSourceX509Ca = optionalNEStringField "source_x509_ca"
+pSourceX509Ca =
+  withDoc "Source X509 CA in PEM format (remote import only)" $
+  optionalNEStringField "source_x509_ca"
 
--- | Source node for import.
 pSrcNode :: Field
-pSrcNode = optionalNEStringField "src_node"
+pSrcNode =
+  withDoc "Source node for import" $
+  optionalNEStringField "src_node"
 
--- | Source node for import.
 pSrcNodeUuid :: Field
-pSrcNodeUuid = optionalNEStringField "src_node_uuid"
+pSrcNodeUuid =
+  withDoc "Source node UUID for import" $
+  optionalNEStringField "src_node_uuid"
 
--- | Source directory for import.
 pSrcPath :: Field
-pSrcPath = optionalNEStringField "src_path"
+pSrcPath =
+  withDoc "Source directory for import" $
+  optionalNEStringField "src_path"
 
--- | Whether to start instance after creation.
 pStartInstance :: Field
-pStartInstance = defaultTrue "start"
+pStartInstance =
+  withDoc "Whether to start instance after creation" $
+  defaultTrue "start"
 
--- | Instance tags. FIXME: unify/simplify with pTags, once that
--- migrates to NonEmpty String.
+-- FIXME: unify/simplify with pTags, once that migrates to NonEmpty String"
 pInstTags :: Field
 pInstTags =
+  withDoc "Instance tags" .
   renameField "InstTags" .
   defaultField [| [] |] $
   simpleField "tags" [t| [NonEmptyString] |]
 
--- | Unchecked list of OpInstanceCreate, used in OpInstanceMultiAlloc.
 pMultiAllocInstances :: Field
 pMultiAllocInstances =
+  withDoc "List of instance create opcodes describing the instances to\
+          \ allocate" .
   renameField "InstMultiAlloc" .
   defaultField [| [] |] $
-  simpleField "instances"[t| UncheckedList |]
+  simpleField "instances"[t| [JSValue] |]
 
--- | Ignore failures parameter.
+pOpportunisticLocking :: Field
+pOpportunisticLocking =
+  withDoc "Whether to employ opportunistic locking for nodes, meaning\
+          \ nodes already locked by another opcode won't be considered for\
+          \ instance allocation (only when an iallocator is used)" $
+  defaultFalse "opportunistic_locking"
+
+pInstanceUuid :: Field
+pInstanceUuid =
+  withDoc "An instance UUID (for single-instance LUs)" .
+  optionalField $ simpleField "instance_uuid" [t| NonEmptyString |]
+
+pTempOsParams :: Field
+pTempOsParams =
+  withDoc "Temporary OS parameters (currently only in reinstall, might be\
+          \ added to install as well)" .
+  renameField "TempOsParams" .
+  optionalField $ simpleField "osparams" [t| JSObject JSValue |]
+
+pShutdownTimeout :: Field
+pShutdownTimeout =
+  withDoc "How long to wait for instance to shut down" .
+  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
+  simpleField "shutdown_timeout" [t| NonNegative Int |]
+
+-- | Another name for the shutdown timeout, because we like to be
+-- inconsistent.
+pShutdownTimeout' :: Field
+pShutdownTimeout' =
+  withDoc "How long to wait for instance to shut down" .
+  renameField "InstShutdownTimeout" .
+  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
+  simpleField "timeout" [t| NonNegative Int |]
+
 pIgnoreFailures :: Field
-pIgnoreFailures = defaultFalse "ignore_failures"
+pIgnoreFailures =
+  withDoc "Whether to ignore failures during removal" $
+  defaultFalse "ignore_failures"
 
--- | New instance or cluster name.
 pNewName :: Field
-pNewName = simpleField "new_name" [t| NonEmptyString |]
+pNewName =
+  withDoc "New group or instance name" $
+  simpleField "new_name" [t| NonEmptyString |]
+  
+pIgnoreOfflineNodes :: Field
+pIgnoreOfflineNodes =
+  withDoc "Whether to ignore offline nodes" $
+  defaultFalse "ignore_offline_nodes"
 
--- | Whether to start the instance even if secondary disks are failing.
+pTempHvParams :: Field
+pTempHvParams =
+  withDoc "Temporary hypervisor parameters, hypervisor-dependent" .
+  renameField "TempHvParams" .
+  defaultField [| toJSObject [] |] $
+  simpleField "hvparams" [t| JSObject JSValue |]
+
+pTempBeParams :: Field
+pTempBeParams =
+  withDoc "Temporary backend parameters" .
+  renameField "TempBeParams" .
+  defaultField [| toJSObject [] |] $
+  simpleField "beparams" [t| JSObject JSValue |]
+
+pNoRemember :: Field
+pNoRemember =
+  withDoc "Do not remember instance state changes" $
+  defaultFalse "no_remember"
+
+pStartupPaused :: Field
+pStartupPaused =
+  withDoc "Pause instance at startup" $
+  defaultFalse "startup_paused"
+
 pIgnoreSecondaries :: Field
-pIgnoreSecondaries = defaultFalse "ignore_secondaries"
+pIgnoreSecondaries =
+  withDoc "Whether to start the instance even if secondary disks are failing" $
+  defaultFalse "ignore_secondaries"
 
--- | How to reboot the instance.
 pRebootType :: Field
-pRebootType = simpleField "reboot_type" [t| RebootType |]
+pRebootType =
+  withDoc "How to reboot the instance" $
+  simpleField "reboot_type" [t| RebootType |]
 
--- | Whether to ignore recorded disk size.
+pReplaceDisksMode :: Field
+pReplaceDisksMode =
+  withDoc "Replacement mode" .
+  renameField "ReplaceDisksMode" $ simpleField "mode" [t| ReplaceDisksMode |]
+
+pReplaceDisksList :: Field
+pReplaceDisksList =
+  withDoc "List of disk indices" .
+  renameField "ReplaceDisksList" .
+  defaultField [| [] |] $
+  simpleField "disks" [t| [DiskIndex] |]
+
+pMigrationCleanup :: Field
+pMigrationCleanup =
+  withDoc "Whether a previously failed migration should be cleaned up" .
+  renameField "MigrationCleanup" $ defaultFalse "cleanup"
+
+pAllowFailover :: Field
+pAllowFailover =
+  withDoc "Whether we can fallback to failover if migration is not possible" $
+  defaultFalse "allow_failover"
+
+pForceFailover :: Field
+pForceFailover =
+  withDoc "Disallow migration moves and always use failovers" $
+  defaultFalse "force_failover"
+
+pMoveTargetNode :: Field
+pMoveTargetNode =
+  withDoc "Target node for instance move" .
+  renameField "MoveTargetNode" $
+  simpleField "target_node" [t| NonEmptyString |]
+
+pMoveTargetNodeUuid :: Field
+pMoveTargetNodeUuid =
+  withDoc "Target node UUID for instance move" .
+  renameField "MoveTargetNodeUuid" . optionalField $
+  simpleField "target_node_uuid" [t| NonEmptyString |]
+
 pIgnoreDiskSize :: Field
-pIgnoreDiskSize = defaultFalse "ignore_size"
-
--- | Disk list for recreate disks.
+pIgnoreDiskSize =
+  withDoc "Whether to ignore recorded disk size" $
+  defaultFalse "ignore_size"
+  
+pWaitForSyncFalse :: Field
+pWaitForSyncFalse =
+  withDoc "Whether to wait for the disk to synchronize (defaults to false)" $
+  defaultField [| False |] pWaitForSync
+  
 pRecreateDisksInfo :: Field
 pRecreateDisksInfo =
+  withDoc "Disk list for recreate disks" .
   renameField "RecreateDisksInfo" .
   defaultField [| RecreateDisksAll |] $
   simpleField "disks" [t| RecreateDisksInfo |]
 
--- | Whether to only return configuration data without querying nodes.
 pStatic :: Field
-pStatic = defaultFalse "static"
+pStatic =
+  withDoc "Whether to only return configuration data without querying nodes" $
+  defaultFalse "static"
 
--- | InstanceSetParams NIC changes.
 pInstParamsNicChanges :: Field
 pInstParamsNicChanges =
+  withDoc "List of NIC changes" .
   renameField "InstNicChanges" .
   defaultField [| SetParamsEmpty |] $
   simpleField "nics" [t| SetParamsMods INicParams |]
 
--- | InstanceSetParams Disk changes.
 pInstParamsDiskChanges :: Field
 pInstParamsDiskChanges =
+  withDoc "List of disk changes" .
   renameField "InstDiskChanges" .
   defaultField [| SetParamsEmpty |] $
   simpleField "disks" [t| SetParamsMods IDiskParams |]
 
--- | New runtime memory.
 pRuntimeMem :: Field
-pRuntimeMem = optionalField $ simpleField "runtime_mem" [t| Positive Int |]
+pRuntimeMem =
+  withDoc "New runtime memory" .
+  optionalField $ simpleField "runtime_mem" [t| Positive Int |]
 
--- | Change the instance's OS without reinstalling the instance
+pOptDiskTemplate :: Field
+pOptDiskTemplate =
+  withDoc "Instance disk template" .
+  optionalField .
+  renameField "OptDiskTemplate" $
+  simpleField "disk_template" [t| DiskTemplate |]
+
 pOsNameChange :: Field
-pOsNameChange = optionalNEStringField "os_name"
+pOsNameChange =
+  withDoc "Change the instance's OS without reinstalling the instance" $
+  optionalNEStringField "os_name"
 
--- | Disk index for e.g. grow disk.
 pDiskIndex :: Field
-pDiskIndex = renameField "DiskIndex " $ simpleField "disk" [t| DiskIndex |]
+pDiskIndex =
+  withDoc "Disk index for e.g. grow disk" .
+  renameField "DiskIndex " $ simpleField "disk" [t| DiskIndex |]
 
--- | Disk amount to add or grow to.
 pDiskChgAmount :: Field
 pDiskChgAmount =
+  withDoc "Disk amount to add or grow to" .
   renameField "DiskChgAmount" $ simpleField "amount" [t| NonNegative Int |]
 
--- | Whether the amount parameter is an absolute target or a relative one.
 pDiskChgAbsolute :: Field
-pDiskChgAbsolute = renameField "DiskChkAbsolute" $ defaultFalse "absolute"
+pDiskChgAbsolute =
+  withDoc
+    "Whether the amount parameter is an absolute target or a relative one" .
+  renameField "DiskChkAbsolute" $ defaultFalse "absolute"
 
--- | Destination group names or UUIDs (defaults to \"all but current group\".
 pTargetGroups :: Field
 pTargetGroups =
+  withDoc
+    "Destination group names or UUIDs (defaults to \"all but current group\")" .
   optionalField $ simpleField "target_groups" [t| [NonEmptyString] |]
 
--- | Export mode field.
+pNodeGroupAllocPolicy :: Field
+pNodeGroupAllocPolicy =
+  withDoc "Instance allocation policy" .
+  optionalField $
+  simpleField "alloc_policy" [t| AllocPolicy |]
+
+pGroupNodeParams :: Field
+pGroupNodeParams =
+  withDoc "Default node parameters for group" .
+  optionalField $ simpleField "ndparams" [t| JSObject JSValue |]
+
 pExportMode :: Field
 pExportMode =
+  withDoc "Export mode" .
   renameField "ExportMode" $ simpleField "mode" [t| ExportMode |]
 
--- | Export target_node field, depends on mode.
+-- FIXME: Rename target_node as it changes meaning for different
+-- export modes (e.g. "destination")
 pExportTargetNode :: Field
 pExportTargetNode =
+  withDoc "Target node (depends on export mode)" .
   renameField "ExportTarget" $
   simpleField "target_node" [t| ExportTarget |]
 
--- | Export target node UUID field.
 pExportTargetNodeUuid :: Field
 pExportTargetNodeUuid =
+  withDoc "Target node UUID (if local export)" .
   renameField "ExportTargetNodeUuid" . optionalField $
   simpleField "target_node_uuid" [t| NonEmptyString |]
 
--- | Whether to remove instance after export.
+pShutdownInstance :: Field
+pShutdownInstance =
+  withDoc "Whether to shutdown the instance before export" $
+  defaultTrue "shutdown"
+
 pRemoveInstance :: Field
-pRemoveInstance = defaultFalse "remove_instance"
+pRemoveInstance =
+  withDoc "Whether to remove instance after export" $
+  defaultFalse "remove_instance"
 
--- | Whether to ignore failures while removing instances.
 pIgnoreRemoveFailures :: Field
-pIgnoreRemoveFailures = defaultFalse "ignore_remove_failures"
+pIgnoreRemoveFailures =
+  withDoc "Whether to ignore failures while removing instances" $
+  defaultFalse "ignore_remove_failures"
 
--- | Name of X509 key (remote export only).
 pX509KeyName :: Field
-pX509KeyName = optionalField $ simpleField "x509_key_name" [t| UncheckedList |]
+pX509KeyName =
+  withDoc "Name of X509 key (remote export only)" .
+  optionalField $ simpleField "x509_key_name" [t| [JSValue] |]
 
--- | Destination X509 CA (remote export only).
 pX509DestCA :: Field
-pX509DestCA = optionalNEStringField "destination_x509_ca"
+pX509DestCA =
+  withDoc "Destination X509 CA (remote export only)" $
+  optionalNEStringField "destination_x509_ca"
 
--- | Search pattern (regular expression). FIXME: this should be
--- compiled at load time?
+pTagsObject :: Field
+pTagsObject =
+  withDoc "Tag kind" $
+  simpleField "kind" [t| TagKind |]
+
+pTagsName :: Field
+pTagsName =
+  withDoc "Name of object" .
+  renameField "TagsGetName" .
+  optionalField $ simpleField "name" [t| String |]
+
+pTagsList :: Field
+pTagsList =
+  withDoc "List of tag names" $
+  simpleField "tags" [t| [String] |]
+
+-- FIXME: this should be compiled at load time?
 pTagSearchPattern :: Field
 pTagSearchPattern =
-  renameField "TagSearchPattern" $ simpleField "pattern" [t| NonEmptyString |]
+  withDoc "Search pattern (regular expression)" .
+  renameField "TagSearchPattern" $
+  simpleField "pattern" [t| NonEmptyString |]
 
--- | Restricted command name.
-pRestrictedCommand :: Field
-pRestrictedCommand =
-  renameField "RestrictedCommand" $
-  simpleField "command" [t| NonEmptyString |]
-
--- | Replace disks mode.
-pReplaceDisksMode :: Field
-pReplaceDisksMode =
-  renameField "ReplaceDisksMode" $ simpleField "mode" [t| ReplaceDisksMode |]
-
--- | List of disk indices.
-pReplaceDisksList :: Field
-pReplaceDisksList =
-  renameField "ReplaceDisksList" $ simpleField "disks" [t| [DiskIndex] |]
-
--- | Whether do allow failover in migrations.
-pAllowFailover :: Field
-pAllowFailover = defaultFalse "allow_failover"
-
--- * Test opcode parameters
-
--- | Duration parameter for 'OpTestDelay'.
 pDelayDuration :: Field
 pDelayDuration =
-  renameField "DelayDuration" $ simpleField "duration" [t| Double |]
+  withDoc "Duration parameter for 'OpTestDelay'" .
+  renameField "DelayDuration" $
+  simpleField "duration" [t| Double |]
 
--- | on_master field for 'OpTestDelay'.
 pDelayOnMaster :: Field
-pDelayOnMaster = renameField "DelayOnMaster" $ defaultTrue "on_master"
+pDelayOnMaster =
+  withDoc "on_master field for 'OpTestDelay'" .
+  renameField "DelayOnMaster" $
+  defaultTrue "on_master"
 
--- | on_nodes field for 'OpTestDelay'.
 pDelayOnNodes :: Field
 pDelayOnNodes =
+  withDoc "on_nodes field for 'OpTestDelay'" .
   renameField "DelayOnNodes" .
   defaultField [| [] |] $
   simpleField "on_nodes" [t| [NonEmptyString] |]
 
--- | on_node_uuids field for 'OpTestDelay'.
 pDelayOnNodeUuids :: Field
 pDelayOnNodeUuids =
+  withDoc "on_node_uuids field for 'OpTestDelay'" .
   renameField "DelayOnNodeUuids" . optionalField $
   simpleField "on_node_uuids" [t| [NonEmptyString] |]
 
--- | Repeat parameter for OpTestDelay.
 pDelayRepeat :: Field
 pDelayRepeat =
+  withDoc "Repeat parameter for OpTestDelay" .
   renameField "DelayRepeat" .
   defaultField [| forceNonNeg (0::Int) |] $
   simpleField "repeat" [t| NonNegative Int |]
 
--- | IAllocator test direction.
+pDelayNoLocks :: Field
+pDelayNoLocks =
+  withDoc "Don't take locks during the delay" .
+  renameField "DelayNoLocks" $
+  defaultTrue "no_locks"
+
 pIAllocatorDirection :: Field
 pIAllocatorDirection =
+  withDoc "IAllocator test direction" .
   renameField "IAllocatorDirection" $
   simpleField "direction" [t| IAllocatorTestDir |]
 
--- | IAllocator test mode.
 pIAllocatorMode :: Field
 pIAllocatorMode =
+  withDoc "IAllocator test mode" .
   renameField "IAllocatorMode" $
   simpleField "mode" [t| IAllocatorMode |]
 
--- | IAllocator target name (new instance, node to evac, etc.).
 pIAllocatorReqName :: Field
 pIAllocatorReqName =
+  withDoc "IAllocator target name (new instance, node to evac, etc.)" .
   renameField "IAllocatorReqName" $ simpleField "name" [t| NonEmptyString |]
 
--- | Custom OpTestIAllocator nics.
 pIAllocatorNics :: Field
 pIAllocatorNics =
-  renameField "IAllocatorNics" $ simpleField "nics" [t| [UncheckedDict] |]
+  withDoc "Custom OpTestIAllocator nics" .
+  renameField "IAllocatorNics" .
+  optionalField $ simpleField "nics" [t| [INicParams] |]
 
--- | Custom OpTestAllocator disks.
 pIAllocatorDisks :: Field
 pIAllocatorDisks =
-  renameField "IAllocatorDisks" $ simpleField "disks" [t| UncheckedList |]
+  withDoc "Custom OpTestAllocator disks" .
+  renameField "IAllocatorDisks" .
+  optionalField $ simpleField "disks" [t| [JSValue] |]
 
--- | IAllocator memory field.
 pIAllocatorMemory :: Field
 pIAllocatorMemory =
+  withDoc "IAllocator memory field" .
   renameField "IAllocatorMem" .
   optionalField $
   simpleField "memory" [t| NonNegative Int |]
 
--- | IAllocator vcpus field.
 pIAllocatorVCpus :: Field
 pIAllocatorVCpus =
+  withDoc "IAllocator vcpus field" .
   renameField "IAllocatorVCpus" .
   optionalField $
   simpleField "vcpus" [t| NonNegative Int |]
 
--- | IAllocator os field.
 pIAllocatorOs :: Field
-pIAllocatorOs = renameField "IAllocatorOs" $ optionalNEStringField "os"
+pIAllocatorOs =
+  withDoc "IAllocator os field" .
+  renameField "IAllocatorOs" $ optionalNEStringField "os"
 
--- | IAllocator instances field.
 pIAllocatorInstances :: Field
 pIAllocatorInstances =
+  withDoc "IAllocator instances field" .
   renameField "IAllocatorInstances " .
   optionalField $
   simpleField "instances" [t| [NonEmptyString] |]
 
--- | IAllocator evac mode.
 pIAllocatorEvacMode :: Field
 pIAllocatorEvacMode =
+  withDoc "IAllocator evac mode" .
   renameField "IAllocatorEvacMode" .
   optionalField $
-  simpleField "evac_mode" [t| NodeEvacMode |]
+  simpleField "evac_mode" [t| EvacMode |]
 
--- | IAllocator spindle use.
 pIAllocatorSpindleUse :: Field
 pIAllocatorSpindleUse =
+  withDoc "IAllocator spindle use" .
   renameField "IAllocatorSpindleUse" .
   defaultField [| forceNonNeg (1::Int) |] $
   simpleField "spindle_use" [t| NonNegative Int |]
 
--- | IAllocator count field.
 pIAllocatorCount :: Field
 pIAllocatorCount =
+  withDoc "IAllocator count field" .
   renameField "IAllocatorCount" .
   defaultField [| forceNonNeg (1::Int) |] $
   simpleField "count" [t| NonNegative Int |]
 
--- | 'OpTestJqueue' notify_waitlock.
 pJQueueNotifyWaitLock :: Field
-pJQueueNotifyWaitLock = defaultFalse "notify_waitlock"
+pJQueueNotifyWaitLock =
+  withDoc "'OpTestJqueue' notify_waitlock" $
+  defaultFalse "notify_waitlock"
 
--- | 'OpTestJQueue' notify_exec.
 pJQueueNotifyExec :: Field
-pJQueueNotifyExec = defaultFalse "notify_exec"
+pJQueueNotifyExec =
+  withDoc "'OpTestJQueue' notify_exec" $
+  defaultFalse "notify_exec"
 
--- | 'OpTestJQueue' log_messages.
 pJQueueLogMessages :: Field
 pJQueueLogMessages =
+  withDoc "'OpTestJQueue' log_messages" .
   defaultField [| [] |] $ simpleField "log_messages" [t| [String] |]
 
--- | 'OpTestJQueue' fail attribute.
 pJQueueFail :: Field
 pJQueueFail =
+  withDoc "'OpTestJQueue' fail attribute" .
   renameField "JQueueFail" $ defaultFalse "fail"
 
--- | 'OpTestDummy' result field.
 pTestDummyResult :: Field
 pTestDummyResult =
-  renameField "TestDummyResult" $ simpleField "result" [t| UncheckedValue |]
+  withDoc "'OpTestDummy' result field" .
+  renameField "TestDummyResult" $ simpleField "result" [t| JSValue |]
 
--- | 'OpTestDummy' messages field.
 pTestDummyMessages :: Field
 pTestDummyMessages =
+  withDoc "'OpTestDummy' messages field" .
   renameField "TestDummyMessages" $
-  simpleField "messages" [t| UncheckedValue |]
+  simpleField "messages" [t| JSValue |]
 
--- | 'OpTestDummy' fail field.
 pTestDummyFail :: Field
 pTestDummyFail =
-  renameField "TestDummyFail" $ simpleField "fail" [t| UncheckedValue |]
+  withDoc "'OpTestDummy' fail field" .
+  renameField "TestDummyFail" $ simpleField "fail" [t| JSValue |]
 
--- | 'OpTestDummy' submit_jobs field.
 pTestDummySubmitJobs :: Field
 pTestDummySubmitJobs =
+  withDoc "'OpTestDummy' submit_jobs field" .
   renameField "TestDummySubmitJobs" $
-  simpleField "submit_jobs" [t| UncheckedValue |]
+  simpleField "submit_jobs" [t| JSValue |]
 
--- * Network parameters
-
--- | Network name.
 pNetworkName :: Field
-pNetworkName = simpleField "network_name" [t| NonEmptyString |]
+pNetworkName =
+  withDoc "Network name" $
+  simpleField "network_name" [t| NonEmptyString |]
 
--- | Network address (IPv4 subnet). FIXME: no real type for this.
 pNetworkAddress4 :: Field
 pNetworkAddress4 =
+  withDoc "Network address (IPv4 subnet)" .
   renameField "NetworkAddress4" $
-  simpleField "network" [t| NonEmptyString |]
+  simpleField "network" [t| IPv4Network |]
 
--- | Network gateway (IPv4 address). FIXME: no real type for this.
 pNetworkGateway4 :: Field
 pNetworkGateway4 =
-  renameField "NetworkGateway4" $
-  optionalNEStringField "gateway"
+  withDoc "Network gateway (IPv4 address)" .
+  renameField "NetworkGateway4" .
+  optionalField $ simpleField "gateway" [t| IPv4Address |]
 
--- | Network address (IPv6 subnet). FIXME: no real type for this.
 pNetworkAddress6 :: Field
 pNetworkAddress6 =
-  renameField "NetworkAddress6" $
-  optionalNEStringField "network6"
+  withDoc "Network address (IPv6 subnet)" .
+  renameField "NetworkAddress6" .
+  optionalField $ simpleField "network6" [t| IPv6Network |]
 
--- | Network gateway (IPv6 address). FIXME: no real type for this.
 pNetworkGateway6 :: Field
 pNetworkGateway6 =
-  renameField "NetworkGateway6" $
-  optionalNEStringField "gateway6"
+  withDoc "Network gateway (IPv6 address)" .
+  renameField "NetworkGateway6" .
+  optionalField $ simpleField "gateway6" [t| IPv6Address |]
 
--- | Network specific mac prefix (that overrides the cluster one).
 pNetworkMacPrefix :: Field
 pNetworkMacPrefix =
+  withDoc "Network specific mac prefix (that overrides the cluster one)" .
   renameField "NetMacPrefix" $
   optionalNEStringField "mac_prefix"
 
--- | Network add reserved IPs.
 pNetworkAddRsvdIps :: Field
 pNetworkAddRsvdIps =
+  withDoc "Which IP addresses to reserve" .
   renameField "NetworkAddRsvdIps" .
   optionalField $
-  simpleField "add_reserved_ips" [t| [NonEmptyString] |]
+  simpleField "add_reserved_ips" [t| [IPv4Address] |]
 
--- | Network remove reserved IPs.
 pNetworkRemoveRsvdIps :: Field
 pNetworkRemoveRsvdIps =
+  withDoc "Which external IP addresses to release" .
   renameField "NetworkRemoveRsvdIps" .
   optionalField $
-  simpleField "remove_reserved_ips" [t| [NonEmptyString] |]
+  simpleField "remove_reserved_ips" [t| [IPv4Address] |]
 
--- | Network mode when connecting to a group.
 pNetworkMode :: Field
-pNetworkMode = simpleField "network_mode" [t| NICMode |]
+pNetworkMode =
+  withDoc "Network mode when connecting to a group" $
+  simpleField "network_mode" [t| NICMode |]
 
--- | Network link when connecting to a group.
 pNetworkLink :: Field
-pNetworkLink = simpleField "network_link" [t| NonEmptyString |]
+pNetworkLink =
+  withDoc "Network link when connecting to a group" $
+  simpleField "network_link" [t| NonEmptyString |]
 
--- * Common opcode parameters
-
--- | Run checks only, don't execute.
-pDryRun :: Field
-pDryRun = optionalField $ booleanField "dry_run"
-
--- | Debug level.
-pDebugLevel :: Field
-pDebugLevel = optionalField $ simpleField "debug_level" [t| NonNegative Int |]
-
--- | Opcode priority. Note: python uses a separate constant, we're
--- using the actual value we know it's the default.
-pOpPriority :: Field
-pOpPriority =
-  defaultField [| OpPrioNormal |] $
-  simpleField "priority" [t| OpSubmitPriority |]
-
--- | Job dependencies.
-pDependencies :: Field
-pDependencies =
-  optionalNullSerField $ simpleField "depends" [t| [JobDependency] |]
-
--- | Comment field.
-pComment :: Field
-pComment = optionalNullSerField $ stringField "comment"
-
--- | Reason trail field.
-pReason :: Field
-pReason = simpleField C.opcodeReason [t| ReasonTrail |]
-
--- * Entire opcode parameter list
-
--- | Old-style query opcode, with locking.
-dOldQuery :: [Field]
-dOldQuery =
-  [ pOutputFields
-  , pNames
-  , pUseLocking
-  ]
-
--- | Old-style query opcode, without locking.
-dOldQueryNoLocking :: [Field]
-dOldQueryNoLocking =
-  [ pOutputFields
-  , pNames
-  ]
+pNetworkVlan :: Field
+pNetworkVlan =
+  withDoc "Network vlan when connecting to a group" .
+  defaultField [| "" |] $ stringField "network_vlan"
diff --git a/src/Ganeti/Parsers.hs b/src/Ganeti/Parsers.hs
new file mode 100644
index 0000000..10b0e41
--- /dev/null
+++ b/src/Ganeti/Parsers.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-| Utility functions for several parsers
+
+This module holds the definition for some utility functions for two
+parsers.  The parser for the @/proc/stat@ file and the parser for the
+@/proc/diskstats@ file.
+
+-}
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+module Ganeti.Parsers where
+
+import Control.Applicative ((*>))
+import qualified Data.Attoparsec.Text as A
+import Data.Attoparsec.Text (Parser)
+import Data.Text (unpack)
+
+-- * Utility functions
+
+-- | Our own space-skipping function, because A.skipSpace also skips
+-- newline characters. It skips ZERO or more spaces, so it does not
+-- fail if there are no spaces.
+skipSpaces :: Parser ()
+skipSpaces = A.skipWhile A.isHorizontalSpace
+
+-- | A parser recognizing a number preceeded by spaces.
+numberP :: Parser Int
+numberP = skipSpaces *> A.decimal
+
+-- | A parser recognizing a word preceded by spaces, and closed by a space.
+stringP :: Parser String
+stringP = skipSpaces *> fmap unpack (A.takeWhile $ not . A.isHorizontalSpace)
diff --git a/src/Ganeti/Path.hs b/src/Ganeti/Path.hs
index 0592cda..3a12338 100644
--- a/src/Ganeti/Path.hs
+++ b/src/Ganeti/Path.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -43,7 +52,7 @@
 import System.FilePath
 import System.Posix.Env (getEnvDefault)
 
-import qualified Ganeti.Constants as C
+import AutoConf
 
 -- | Simple helper to concat two paths.
 pjoin :: IO String -> String -> IO String
@@ -64,7 +73,7 @@
 
 -- | Directory for data.
 dataDir :: IO FilePath
-dataDir = addNodePrefix $ C.autoconfLocalstatedir </> "lib" </> "ganeti"
+dataDir = addNodePrefix $ AutoConf.localstatedir </> "lib" </> "ganeti"
 
 -- | Helper for building on top of dataDir (internal).
 dataDirP :: FilePath -> IO FilePath
@@ -72,11 +81,11 @@
 
 -- | Directory for runtime files.
 runDir :: IO FilePath
-runDir = addNodePrefix $ C.autoconfLocalstatedir </> "run" </> "ganeti"
+runDir = addNodePrefix $ AutoConf.localstatedir </> "run" </> "ganeti"
 
 -- | Directory for log files.
 logDir :: IO FilePath
-logDir = addNodePrefix $ C.autoconfLocalstatedir </> "log" </> "ganeti"
+logDir = addNodePrefix $ AutoConf.localstatedir </> "log" </> "ganeti"
 
 -- | Directory for Unix sockets.
 socketDir :: IO FilePath
diff --git a/src/Ganeti/PyValueInstances.hs b/src/Ganeti/PyValueInstances.hs
new file mode 100644
index 0000000..db24fad
--- /dev/null
+++ b/src/Ganeti/PyValueInstances.hs
@@ -0,0 +1,90 @@
+{-| PyValueInstances contains instances for the 'PyValue' typeclass.
+
+The typeclass 'PyValue' converts Haskell values to Python values.
+This module contains instances of this typeclass for several generic
+types.  These instances are used in the Haskell to Python generation
+of opcodes and constants, for example.
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+{-# LANGUAGE FlexibleInstances, OverlappingInstances,
+             TypeSynonymInstances, IncoherentInstances #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Ganeti.PyValueInstances where
+
+import Data.List (intercalate)
+import Data.Map (Map)
+import qualified Data.Map as Map
+import qualified Data.Set as Set (toList)
+
+import Ganeti.BasicTypes
+import Ganeti.THH
+
+instance PyValue Bool where
+  showValue = show
+
+instance PyValue Int where
+  showValue = show
+
+instance PyValue Integer where
+  showValue = show
+
+instance PyValue Double where
+  showValue = show
+
+instance PyValue Char where
+  showValue = show
+
+instance (PyValue a, PyValue b) => PyValue (a, b) where
+  showValue (x, y) = "(" ++ showValue x ++ "," ++ showValue y ++ ")"
+
+instance (PyValue a, PyValue b, PyValue c) => PyValue (a, b, c) where
+  showValue (x, y, z) =
+    "(" ++
+    showValue x ++ "," ++
+    showValue y ++ "," ++
+    showValue z ++
+    ")"
+
+instance PyValue String where
+  showValue = show
+
+instance PyValue a => PyValue [a] where
+  showValue xs = "[" ++ intercalate "," (map showValue xs) ++ "]"
+
+instance (PyValue k, PyValue a) => PyValue (Map k a) where
+  showValue mp =
+    "{" ++ intercalate ", " (map showPair (Map.assocs mp)) ++ "}"
+    where showPair (k, x) = showValue k ++ ":" ++ showValue x
+
+instance PyValue a => PyValue (ListSet a) where
+  showValue = showValue . Set.toList . unListSet
diff --git a/src/Ganeti/Query/Cluster.hs b/src/Ganeti/Query/Cluster.hs
index 0c6d985..fe3ea9f 100644
--- a/src/Ganeti/Query/Cluster.hs
+++ b/src/Ganeti/Query/Cluster.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Common.hs b/src/Ganeti/Query/Common.hs
index 086ecad..968df92 100644
--- a/src/Ganeti/Query/Common.hs
+++ b/src/Ganeti/Query/Common.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -37,7 +46,6 @@
   , serialFields
   , tagsFields
   , dictFieldGetter
-  , buildQFTLookup
   , buildNdParamField
   ) where
 
@@ -51,6 +59,7 @@
 import Ganeti.Rpc
 import Ganeti.Query.Language
 import Ganeti.Query.Types
+import Ganeti.Types
 
 -- * Generic functions
 
@@ -146,20 +155,13 @@
 dictFieldGetter :: (DictObject a) => String -> Maybe a -> ResultEntry
 dictFieldGetter k = maybe rsNoData (rsMaybeNoData . lookup k . toDict)
 
--- | Build an optimised lookup map from a Python _PARAMETER_TYPES
--- association list.
-buildQFTLookup :: [(String, String)] -> Map.Map String FieldType
-buildQFTLookup =
-  Map.fromList .
-  map (\(k, v) -> (k, maybe QFTOther vTypeToQFT (vTypeFromRaw v)))
-
 -- | Ndparams optimised lookup map.
 ndParamTypes :: Map.Map String FieldType
-ndParamTypes = buildQFTLookup C.ndsParameterTypes
+ndParamTypes = Map.map vTypeToQFT C.ndsParameterTypes
 
 -- | Ndparams title map.
 ndParamTitles :: Map.Map String FieldTitle
-ndParamTitles = Map.fromList C.ndsParameterTitles
+ndParamTitles = C.ndsParameterTitles
 
 -- | Ndparam getter builder: given a field, it returns a FieldConfig
 -- getter, that is a function that takes the config and the object and
diff --git a/src/Ganeti/Query/Export.hs b/src/Ganeti/Query/Export.hs
index d45b930..2ecfaa2 100644
--- a/src/Ganeti/Query/Export.hs
+++ b/src/Ganeti/Query/Export.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Filter.hs b/src/Ganeti/Query/Filter.hs
index ced50a9..93eb611 100644
--- a/src/Ganeti/Query/Filter.hs
+++ b/src/Ganeti/Query/Filter.hs
@@ -26,21 +26,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Group.hs b/src/Ganeti/Query/Group.hs
index 8bb182d..d589007 100644
--- a/src/Ganeti/Query/Group.hs
+++ b/src/Ganeti/Query/Group.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Job.hs b/src/Ganeti/Query/Job.hs
index 78ccbe8..bce8fb6 100644
--- a/src/Ganeti/Query/Job.hs
+++ b/src/Ganeti/Query/Job.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Language.hs b/src/Ganeti/Query/Language.hs
index 7cc52db..550ddad 100644
--- a/src/Ganeti/Query/Language.hs
+++ b/src/Ganeti/Query/Language.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Network.hs b/src/Ganeti/Query/Network.hs
index b7487f4..eebe777 100644
--- a/src/Ganeti/Query/Network.hs
+++ b/src/Ganeti/Query/Network.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -106,7 +115,8 @@
 
 -- | Given a network's UUID, this function lists all connections from
 -- the network to nodegroups including the respective mode and links.
-getGroupConnections :: ConfigData -> String -> [(String, String, String)]
+getGroupConnections ::
+  ConfigData -> String -> [(String, String, String, String)]
 getGroupConnections cfg network_uuid =
   mapMaybe (getGroupConnection network_uuid)
   ((Map.elems . fromContainer . configNodegroups) cfg)
@@ -115,13 +125,14 @@
 -- a tuple of the group's name, the mode and the link by which the
 -- network is connected to the group. Returns 'Nothing' if the network
 -- is not connected to the group.
-getGroupConnection :: String -> NodeGroup -> Maybe (String, String, String)
+getGroupConnection ::
+  String -> NodeGroup -> Maybe (String, String, String, String)
 getGroupConnection network_uuid group =
   let networks = fromContainer . groupNetworks $ group
   in case Map.lookup network_uuid networks of
     Nothing -> Nothing
     Just net ->
-      Just (groupName group, getNicMode net, getNicLink net)
+      Just (groupName group, getNicMode net, getNicLink net, getNicVlan net)
 
 -- | Retrieves the network's mode and formats it human-readable,
 -- also in case it is not available.
@@ -129,22 +140,26 @@
 getNicMode nic_params =
   maybe "-" nICModeToRaw $ nicpModeP nic_params
 
--- | Retrieves the network's link and formats it human-readable, also in
+-- | Retrieves the network's vlan and formats it human-readable, also in
 -- case it it not available.
 getNicLink :: PartialNicParams -> String
 getNicLink nic_params = fromMaybe "-" (nicpLinkP nic_params)
 
+-- | Retrieves the network's link and formats it human-readable, also in
+-- case it it not available.
+getNicVlan :: PartialNicParams -> String
+getNicVlan nic_params = fromMaybe "-" (nicpVlanP nic_params)
+
 -- | Retrieves the network's instances' names.
 getInstances :: ConfigData -> String -> [String]
 getInstances cfg network_uuid =
-  map instName (filter (instIsConnected cfg network_uuid)
+  map instName (filter (instIsConnected network_uuid)
     ((Map.elems . fromContainer . configInstances) cfg))
 
 -- | Helper function that checks if an instance is linked to the given network.
-instIsConnected :: ConfigData -> String -> Instance -> Bool
-instIsConnected cfg network_uuid inst =
-  network_uuid `elem` mapMaybe (getNetworkUuid cfg)
-    (mapMaybe nicNetwork (instNics inst))
+instIsConnected :: String -> Instance -> Bool
+instIsConnected network_uuid inst =
+  network_uuid `elem` mapMaybe nicNetwork (instNics inst)
 
 -- | Helper function to look up a network's UUID by its name
 getNetworkUuid :: ConfigData -> String -> Maybe String
diff --git a/src/Ganeti/Query/Node.hs b/src/Ganeti/Query/Node.hs
index 8f154c6..4d60886 100644
--- a/src/Ganeti/Query/Node.hs
+++ b/src/Ganeti/Query/Node.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Query/Query.hs b/src/Ganeti/Query/Query.hs
index f3c7ce5..68a1216 100644
--- a/src/Ganeti/Query/Query.hs
+++ b/src/Ganeti/Query/Query.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -56,6 +65,7 @@
 import Control.DeepSeq
 import Control.Monad (filterM, foldM)
 import Control.Monad.Trans (lift)
+import qualified Data.Foldable as Foldable
 import Data.List (intercalate)
 import Data.Maybe (fromMaybe)
 import qualified Data.Map as Map
@@ -260,7 +270,9 @@
   cfilter <- resultT $ compileFilter Query.Job.fieldsMap qfilter
   let selected = getSelectedFields Query.Job.fieldsMap fields
       (fdefs, fgetters, _) = unzip3 selected
-      live' = live && needsLiveData fgetters
+      (_, filtergetters, _) = unzip3 . getSelectedFields Query.Job.fieldsMap
+                                $ Foldable.toList qfilter
+      live' = live && needsLiveData (fgetters ++ filtergetters)
       disabled_data = Bad "live data disabled"
   -- runs first pass of the filter, without a runtime context; this
   -- will limit the jobs that we'll load from disk
diff --git a/src/Ganeti/Query/Server.hs b/src/Ganeti/Query/Server.hs
index 33a052b..5f449ce 100644
--- a/src/Ganeti/Query/Server.hs
+++ b/src/Ganeti/Query/Server.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -36,6 +45,7 @@
 import Control.Exception
 import Control.Monad (forever)
 import Data.Bits (bitSize)
+import qualified Data.Set as Set (toList)
 import Data.IORef
 import qualified Network.Socket as S
 import qualified Text.JSON as J
@@ -43,6 +53,7 @@
 import System.Info (arch)
 
 import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils (unFrozenSet)
 import Ganeti.Errors
 import qualified Ganeti.Path as Path
 import Ganeti.Daemon
@@ -52,11 +63,12 @@
 import Ganeti.BasicTypes
 import Ganeti.Logging
 import Ganeti.Luxi
-import Ganeti.OpCodes (TagObject(..))
 import qualified Ganeti.Query.Language as Qlang
 import qualified Ganeti.Query.Cluster as QCluster
 import Ganeti.Query.Query
 import Ganeti.Query.Filter (makeSimpleFilter)
+import Ganeti.Types
+import qualified Ganeti.Version as Version
 
 -- | Helper for classic queries.
 handleClassicQuery :: ConfigData      -- ^ Cluster config
@@ -97,9 +109,11 @@
       obj = [ ("software_version", showJSON C.releaseVersion)
             , ("protocol_version", showJSON C.protocolVersion)
             , ("config_version", showJSON C.configVersion)
-            , ("os_api_version", showJSON $ maximum C.osApiVersions)
+            , ("os_api_version", showJSON . maximum .
+                                 Set.toList . ConstantUtils.unFrozenSet $
+                                 C.osApiVersions)
             , ("export_version", showJSON C.exportVersion)
-            , ("vcs_version", showJSON C.vcsVersion)
+            , ("vcs_version", showJSON Version.version)
             , ("architecture", showJSON arch_tuple)
             , ("name", showJSON $ clusterClusterName cluster)
             , ("master", showJSON (case master of
@@ -151,14 +165,14 @@
     Ok _ -> return . Ok . J.makeObj $ obj
     Bad ex -> return $ Bad ex
 
-handleCall cfg (QueryTags kind) =
+handleCall cfg (QueryTags kind name) = do
   let tags = case kind of
-               TagCluster       -> Ok . clusterTags $ configCluster cfg
-               TagGroup    name -> groupTags <$> Config.getGroup    cfg name
-               TagNode     name -> nodeTags  <$> Config.getNode     cfg name
-               TagInstance name -> instTags  <$> Config.getInstance cfg name
-               TagNetwork  name -> networkTags  <$> Config.getNetwork cfg name
-  in return (J.showJSON <$> tags)
+               TagKindCluster  -> Ok . clusterTags $ configCluster cfg
+               TagKindGroup    -> groupTags   <$> Config.getGroup    cfg name
+               TagKindNode     -> nodeTags    <$> Config.getNode     cfg name
+               TagKindInstance -> instTags    <$> Config.getInstance cfg name
+               TagKindNetwork  -> networkTags <$> Config.getNetwork  cfg name
+  return (J.showJSON <$> tags)
 
 handleCall cfg (Query qkind qfields qfilter) = do
   result <- query cfg True (Qlang.Query qkind qfields qfilter)
diff --git a/src/Ganeti/Query/Types.hs b/src/Ganeti/Query/Types.hs
index 4c74dba..df06ffc 100644
--- a/src/Ganeti/Query/Types.hs
+++ b/src/Ganeti/Query/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/Rpc.hs b/src/Ganeti/Rpc.hs
index 7aefd4a..2166f91 100644
--- a/src/Ganeti/Rpc.hs
+++ b/src/Ganeti/Rpc.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -60,7 +69,6 @@
   , RpcCallVersion(..)
   , RpcResultVersion(..)
 
-  , StorageField(..)
   , RpcCallStorageList(..)
   , RpcResultStorageList(..)
 
@@ -69,8 +77,6 @@
 
   , RpcCallExportList(..)
   , RpcResultExportList(..)
-
-  , rpcTimeoutFromRaw -- FIXME: Not used anywhere
   ) where
 
 import Control.Arrow (second)
@@ -124,16 +130,6 @@
 
 type ERpcError = Either RpcError
 
--- | Basic timeouts for RPC calls.
-$(declareIADT "RpcTimeout"
-  [ ( "Urgent",    'C.rpcTmoUrgent )
-  , ( "Fast",      'C.rpcTmoFast )
-  , ( "Normal",    'C.rpcTmoNormal )
-  , ( "Slow",      'C.rpcTmoSlow )
-  , ( "FourHours", 'C.rpcTmo4hrs )
-  , ( "OneDay",    'C.rpcTmo1day )
-  ])
-
 -- | A generic class for RPC calls.
 class (J.JSON a) => RpcCall a where
   -- | Give the (Python) name of the procedure.
@@ -169,7 +165,7 @@
       node_address = if isIpV6 node_ip
                      then "[" ++ node_ip ++ "]"
                      else node_ip
-      port = snd C.daemonsPortsGanetiNoded
+      port = C.defaultNodedPort
       path_prefix = "https://" ++ node_address ++ ":" ++ show port
   in path_prefix ++ "/" ++ rpcCallName call
 
@@ -421,18 +417,6 @@
 
 -- ** StorageList
 
--- | StorageList
-
--- FIXME: This may be moved to Objects
-$(declareSADT "StorageField"
-  [ ( "SFUsed",        'C.sfUsed)
-  , ( "SFName",        'C.sfName)
-  , ( "SFAllocatable", 'C.sfAllocatable)
-  , ( "SFFree",        'C.sfFree)
-  , ( "SFSize",        'C.sfSize)
-  ])
-$(makeJSONInstance ''StorageField)
-
 $(buildObject "RpcCallStorageList" "rpcCallStorageList"
   [ simpleField "su_name" [t| StorageType |]
   , simpleField "su_args" [t| [String] |]
diff --git a/src/Ganeti/Runtime.hs b/src/Ganeti/Runtime.hs
index bf24e94..4388a28 100644
--- a/src/Ganeti/Runtime.hs
+++ b/src/Ganeti/Runtime.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -30,9 +39,13 @@
   , RuntimeEnts
   , daemonName
   , daemonOnlyOnMaster
+  , daemonLogBase
   , daemonUser
   , daemonGroup
+  , ExtraLogReason(..)
   , daemonLogFile
+  , daemonsExtraLogbase
+  , daemonsExtraLogFile
   , daemonPidFile
   , getEnts
   , verifyDaemonUser
@@ -49,10 +62,12 @@
 import System.Posix.User
 import Text.Printf
 
-import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import qualified Ganeti.Path as Path
 import Ganeti.BasicTypes
 
+import AutoConf
+
 data GanetiDaemon = GanetiMasterd
                   | GanetiNoded
                   | GanetiRapi
@@ -73,12 +88,12 @@
 
 -- | Returns the daemon name for a given daemon.
 daemonName :: GanetiDaemon -> String
-daemonName GanetiMasterd = C.masterd
-daemonName GanetiNoded   = C.noded
-daemonName GanetiRapi    = C.rapi
-daemonName GanetiConfd   = C.confd
-daemonName GanetiLuxid   = C.luxid
-daemonName GanetiMond    = C.mond
+daemonName GanetiMasterd = "ganeti-masterd"
+daemonName GanetiNoded   = "ganeti-noded"
+daemonName GanetiRapi    = "ganeti-rapi"
+daemonName GanetiConfd   = "ganeti-confd"
+daemonName GanetiLuxid   = "ganeti-luxid"
+daemonName GanetiMond    = "ganeti-mond"
 
 -- | Returns whether the daemon only runs on the master node.
 daemonOnlyOnMaster :: GanetiDaemon -> Bool
@@ -91,32 +106,41 @@
 
 -- | Returns the log file base for a daemon.
 daemonLogBase :: GanetiDaemon -> String
-daemonLogBase GanetiMasterd = C.daemonsLogbaseGanetiMasterd
-daemonLogBase GanetiNoded   = C.daemonsLogbaseGanetiNoded
-daemonLogBase GanetiRapi    = C.daemonsLogbaseGanetiRapi
-daemonLogBase GanetiConfd   = C.daemonsLogbaseGanetiConfd
-daemonLogBase GanetiLuxid   = C.daemonsLogbaseGanetiLuxid
-daemonLogBase GanetiMond    = C.daemonsLogbaseGanetiMond
+daemonLogBase GanetiMasterd = "master-daemon"
+daemonLogBase GanetiNoded   = "node-daemon"
+daemonLogBase GanetiRapi    = "rapi-daemon"
+daemonLogBase GanetiConfd   = "conf-daemon"
+daemonLogBase GanetiLuxid   = "luxi-daemon"
+daemonLogBase GanetiMond    = "monitoring-daemon"
 
 -- | Returns the configured user name for a daemon.
 daemonUser :: GanetiDaemon -> String
-daemonUser GanetiMasterd = C.masterdUser
-daemonUser GanetiNoded   = C.nodedUser
-daemonUser GanetiRapi    = C.rapiUser
-daemonUser GanetiConfd   = C.confdUser
-daemonUser GanetiLuxid   = C.luxidUser
-daemonUser GanetiMond    = C.mondUser
+daemonUser GanetiMasterd = AutoConf.masterdUser
+daemonUser GanetiNoded   = AutoConf.nodedUser
+daemonUser GanetiRapi    = AutoConf.rapiUser
+daemonUser GanetiConfd   = AutoConf.confdUser
+daemonUser GanetiLuxid   = AutoConf.luxidUser
+daemonUser GanetiMond    = AutoConf.mondUser
 
 -- | Returns the configured group for a daemon.
 daemonGroup :: GanetiGroup -> String
-daemonGroup (DaemonGroup GanetiMasterd) = C.masterdGroup
-daemonGroup (DaemonGroup GanetiNoded)   = C.nodedGroup
-daemonGroup (DaemonGroup GanetiRapi)    = C.rapiGroup
-daemonGroup (DaemonGroup GanetiConfd)   = C.confdGroup
-daemonGroup (DaemonGroup GanetiLuxid)   = C.luxidGroup
-daemonGroup (DaemonGroup GanetiMond)    = C.mondGroup
-daemonGroup (ExtraGroup  DaemonsGroup)  = C.daemonsGroup
-daemonGroup (ExtraGroup  AdminGroup)    = C.adminGroup
+daemonGroup (DaemonGroup GanetiMasterd) = AutoConf.masterdGroup
+daemonGroup (DaemonGroup GanetiNoded)   = AutoConf.nodedGroup
+daemonGroup (DaemonGroup GanetiRapi)    = AutoConf.rapiGroup
+daemonGroup (DaemonGroup GanetiConfd)   = AutoConf.confdGroup
+daemonGroup (DaemonGroup GanetiLuxid)   = AutoConf.luxidGroup
+daemonGroup (DaemonGroup GanetiMond)    = AutoConf.mondGroup
+daemonGroup (ExtraGroup  DaemonsGroup)  = AutoConf.daemonsGroup
+daemonGroup (ExtraGroup  AdminGroup)    = AutoConf.adminGroup
+
+data ExtraLogReason = AccessLog | ErrorLog
+
+-- | Some daemons might require more than one logfile.  Specifically,
+-- right now only the Haskell http library "snap", used by the
+-- monitoring daemon, requires multiple log files.
+daemonsExtraLogbase :: GanetiDaemon -> ExtraLogReason -> String
+daemonsExtraLogbase daemon AccessLog = daemonLogBase daemon ++ "-access"
+daemonsExtraLogbase daemon ErrorLog = daemonLogBase daemon ++ "-error"
 
 -- | Returns the log file for a daemon.
 daemonLogFile :: GanetiDaemon -> IO FilePath
@@ -124,6 +148,12 @@
   logDir <- Path.logDir
   return $ logDir </> daemonLogBase daemon <.> "log"
 
+-- | Returns the extra log files for a daemon.
+daemonsExtraLogFile :: GanetiDaemon -> ExtraLogReason -> IO FilePath
+daemonsExtraLogFile daemon logreason = do
+  logDir <- Path.logDir
+  return $ logDir </> daemonsExtraLogbase daemon logreason <.> "log"
+
 -- | Returns the pid file name for a daemon.
 daemonPidFile :: GanetiDaemon -> IO FilePath
 daemonPidFile daemon = do
@@ -182,4 +212,4 @@
                    \expected %d\n" name
               (fromIntegral actual::Int)
               (fromIntegral expected::Int) :: IO ()
-    exitWith $ ExitFailure C.exitFailure
+    exitWith $ ExitFailure ConstantUtils.exitFailure
diff --git a/src/Ganeti/Ssconf.hs b/src/Ganeti/Ssconf.hs
index dd3a52c..80ccc6c 100644
--- a/src/Ganeti/Ssconf.hs
+++ b/src/Ganeti/Ssconf.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -36,8 +45,6 @@
   , sSFilePrefix
   ) where
 
-import Ganeti.THH
-
 import Control.Exception
 import Control.Monad (liftM)
 import Data.Maybe (fromMaybe)
@@ -45,9 +52,11 @@
 import System.FilePath ((</>))
 import System.IO.Error (isDoesNotExistError)
 
+import qualified AutoConf
+import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
 import qualified Ganeti.Path as Path
-import Ganeti.BasicTypes
+import Ganeti.THH
 import Ganeti.Utils
 
 -- | Maximum ssconf file size we support.
@@ -119,14 +128,16 @@
 
 -- | Parses a string containing an IP family
 parseIPFamily :: Int -> Result Socket.Family
-parseIPFamily fam | fam == C.ip4Family = Ok Socket.AF_INET
-                  | fam == C.ip6Family = Ok Socket.AF_INET6
+parseIPFamily fam | fam == AutoConf.pyAfInet4 = Ok Socket.AF_INET
+                  | fam == AutoConf.pyAfInet6 = Ok Socket.AF_INET6
                   | otherwise = Bad $ "Unknown af_family value: " ++ show fam
 
 -- | Read the primary IP family.
 getPrimaryIPFamily :: Maybe FilePath -> IO (Result Socket.Family)
 getPrimaryIPFamily optpath = do
-  result <- readSSConfFile optpath (Just (show C.ip4Family)) SSPrimaryIpFamily
+  result <- readSSConfFile optpath
+                           (Just (show AutoConf.pyAfInet4))
+                           SSPrimaryIpFamily
   return (liftM rStripSpace result >>=
           tryRead "Parsing af_family" >>= parseIPFamily)
 
diff --git a/src/Ganeti/Storage/Diskstats/Parser.hs b/src/Ganeti/Storage/Diskstats/Parser.hs
index 1199f32..64d3885 100644
--- a/src/Ganeti/Storage/Diskstats/Parser.hs
+++ b/src/Ganeti/Storage/Diskstats/Parser.hs
@@ -8,57 +8,65 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Diskstats.Parser (diskstatsParser) where
 
-import Control.Applicative ((<*>), (*>), (<*), (<$>))
+import Control.Applicative ((<*>), (<*), (<$>))
 import qualified Data.Attoparsec.Text as A
 import qualified Data.Attoparsec.Combinator as AC
 import Data.Attoparsec.Text (Parser)
-import Data.Text (unpack)
 
+import Ganeti.Parsers
 import Ganeti.Storage.Diskstats.Types
 
--- * Utility functions
-
--- | Our own space-skipping function, because A.skipSpace also skips
--- newline characters. It skips ZERO or more spaces, so it does not
--- fail if there are no spaces.
-skipSpaces :: Parser ()
-skipSpaces = A.skipWhile A.isHorizontalSpace
-
--- | A parser recognizing a number preceeded by spaces.
-numberP :: Parser Int
-numberP = skipSpaces *> A.decimal
-
--- | A parser recognizing a word preceded by spaces, and closed by a space.
-stringP :: Parser String
-stringP = skipSpaces *> fmap unpack (A.takeWhile $ not . A.isHorizontalSpace)
-
 -- * Parser implementation
 
 -- | The parser for one line of the diskstatus file.
 oneDiskstatsParser :: Parser Diskstats
 oneDiskstatsParser =
-  Diskstats <$> numberP <*> numberP <*> stringP <*> numberP <*> numberP
-    <*> numberP <*> numberP <*> numberP <*> numberP <*> numberP <*> numberP
-    <*> numberP <*> numberP <*> numberP <* A.endOfLine
+  let majorP = numberP
+      minorP = numberP
+      nameP = stringP
+      readsNumP = numberP
+      mergedReadsP = numberP
+      secReadP = numberP
+      timeReadP = numberP
+      writesP = numberP
+      mergedWritesP = numberP
+      secWrittenP = numberP
+      timeWriteP = numberP
+      iosP = numberP
+      timeIOP = numberP
+      wIOmillisP = numberP
+  in
+    Diskstats <$> majorP <*> minorP <*> nameP <*> readsNumP <*> mergedReadsP
+      <*> secReadP <*> timeReadP <*> writesP <*> mergedWritesP <*> secWrittenP
+      <*> timeWriteP <*> iosP <*> timeIOP <*> wIOmillisP <* A.endOfLine
 
 -- | The parser for a whole diskstatus file.
 diskstatsParser :: Parser [Diskstats]
diff --git a/src/Ganeti/Storage/Diskstats/Types.hs b/src/Ganeti/Storage/Diskstats/Types.hs
index 97bc12e..5f9b09a 100644
--- a/src/Ganeti/Storage/Diskstats/Types.hs
+++ b/src/Ganeti/Storage/Diskstats/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Diskstats.Types
diff --git a/src/Ganeti/Storage/Drbd/Parser.hs b/src/Ganeti/Storage/Drbd/Parser.hs
index e2ae40b..c9c8dce 100644
--- a/src/Ganeti/Storage/Drbd/Parser.hs
+++ b/src/Ganeti/Storage/Drbd/Parser.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Drbd.Parser (drbdStatusParser, commaIntParser) where
diff --git a/src/Ganeti/Storage/Drbd/Types.hs b/src/Ganeti/Storage/Drbd/Types.hs
index ec9befe..a60c510 100644
--- a/src/Ganeti/Storage/Drbd/Types.hs
+++ b/src/Ganeti/Storage/Drbd/Types.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Drbd.Types
diff --git a/src/Ganeti/Storage/Lvm/LVParser.hs b/src/Ganeti/Storage/Lvm/LVParser.hs
index 54d1bdf..7ea08cb 100644
--- a/src/Ganeti/Storage/Lvm/LVParser.hs
+++ b/src/Ganeti/Storage/Lvm/LVParser.hs
@@ -9,21 +9,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Lvm.LVParser (lvParser, lvCommand, lvParams) where
diff --git a/src/Ganeti/Storage/Lvm/Types.hs b/src/Ganeti/Storage/Lvm/Types.hs
index e4c096f..53999e5 100644
--- a/src/Ganeti/Storage/Lvm/Types.hs
+++ b/src/Ganeti/Storage/Lvm/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 module Ganeti.Storage.Lvm.Types
diff --git a/src/Ganeti/Storage/Utils.hs b/src/Ganeti/Storage/Utils.hs
index d86b461..a44bdd9 100644
--- a/src/Ganeti/Storage/Utils.hs
+++ b/src/Ganeti/Storage/Utils.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/Ganeti/THH.hs b/src/Ganeti/THH.hs
index aa14642..cff27a2 100644
--- a/src/Ganeti/THH.hs
+++ b/src/Ganeti/THH.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE ExistentialQuantification, ParallelListComp, TemplateHaskell #-}
 
 {-| TemplateHaskell helper for Ganeti Haskell code.
 
@@ -11,36 +11,52 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Ganeti.THH ( declareSADT
+                  , declareLADT
+                  , declareILADT
                   , declareIADT
                   , makeJSONInstance
+                  , deCamelCase
                   , genOpID
                   , genAllConstr
                   , genAllOpIDs
+                  , PyValue(..)
+                  , PyValueEx(..)
+                  , OpCodeDescriptor
                   , genOpCode
                   , genStrOfOp
                   , genStrOfKey
                   , genLuxiOp
-                  , Field
+                  , Field (..)
                   , simpleField
+                  , withDoc
                   , defaultField
                   , optionalField
                   , optionalNullSerField
@@ -62,7 +78,6 @@
 import Control.Monad (liftM)
 import Data.Char
 import Data.List
-import Data.Maybe (fromMaybe)
 import qualified Data.Set as Set
 import Language.Haskell.TH
 
@@ -71,6 +86,9 @@
 
 import Ganeti.JSON
 
+import Data.Maybe
+import Data.Functor ((<$>))
+
 -- * Exported types
 
 -- | Class of objects that can be converted to 'JSObject'
@@ -94,6 +112,7 @@
                    , fieldDefault     :: Maybe (Q Exp)
                    , fieldConstr      :: Maybe String
                    , fieldIsOptional  :: OptionalType
+                   , fieldDoc         :: String
                    }
 
 -- | Generates a simple field.
@@ -107,8 +126,13 @@
         , fieldDefault     = Nothing
         , fieldConstr      = Nothing
         , fieldIsOptional  = NotOptional
+        , fieldDoc         = ""
         }
 
+withDoc :: String -> Field -> Field
+withDoc doc field =
+  field { fieldDoc = doc }
+
 -- | Sets the renamed constructor field.
 renameField :: String -> Field -> Field
 renameField constrName field = field { fieldConstr = Just constrName }
@@ -222,8 +246,11 @@
 -- | A definition for ADTs with simple fields.
 type SimpleObject = [SimpleConstructor]
 
--- | A type alias for a constructor of a regular object.
-type Constructor = (String, [Field])
+-- | A type alias for an opcode constructor of a regular object.
+type OpCodeConstructor = (String, Q Type, String, [Field], String)
+
+-- | A type alias for a Luxi constructor of a regular object.
+type LuxiConstructor = (String, [Field])
 
 -- * Helper functions
 
@@ -344,7 +371,7 @@
 --               | s == \"value2\" = Cons2
 --               | otherwise = fail /.../
 -- @
-genFromRaw :: Name -> Name -> Name -> [(String, Name)] -> Q [Dec]
+genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
 genFromRaw traw fname tname constructors = do
   -- signature of form (Monad m) => String -> m $name
   sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
@@ -353,7 +380,7 @@
       varpe = varE varp
   clauses <- mapM (\(c, v) -> do
                      -- the clause match condition
-                     g <- normalG [| $varpe == $(varE v) |]
+                     g <- normalG [| $varpe == $(reprE v) |]
                      -- the clause result
                      r <- [| return $(conE (mkName c)) |]
                      return (g, r)) constructors
@@ -383,21 +410,38 @@
 --
 -- Note that this is basically just a custom show\/read instance,
 -- nothing else.
-declareADT :: Name -> String -> [(String, Name)] -> Q [Dec]
-declareADT traw sname cons = do
+declareADT
+  :: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
+declareADT fn traw sname cons = do
   let name = mkName sname
       ddecl = strADTDecl name (map fst cons)
       -- process cons in the format expected by genToRaw
-      cons' = map (\(a, b) -> (a, Right b)) cons
+      cons' = map (\(a, b) -> (a, fn b)) cons
   toraw <- genToRaw traw (toRawName sname) name cons'
-  fromraw <- genFromRaw traw (fromRawName sname) name cons
+  fromraw <- genFromRaw traw (fromRawName sname) name cons'
   return $ ddecl:toraw ++ fromraw
 
+declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
+declareLADT = declareADT Left
+
+declareILADT :: String -> [(String, Int)] -> Q [Dec]
+declareILADT sname cons = do
+  consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
+  consFns <- concat <$> sequence
+             [ do sig <- sigD n [t| Int |]
+                  let expr = litE (IntegerL (toInteger i))
+                  fn <- funD n [clause [] (normalB expr) []]
+                  return [sig, fn]
+             | n <- consNames
+             | (_, i) <- cons ]
+  let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
+  (consFns ++) <$> declareADT Right ''Int sname cons'
+
 declareIADT :: String -> [(String, Name)] -> Q [Dec]
-declareIADT = declareADT ''Int
+declareIADT = declareADT Right ''Int
 
 declareSADT :: String -> [(String, Name)] -> Q [Dec]
-declareSADT = declareADT ''String
+declareSADT = declareADT Right ''String
 
 -- | Creates the showJSON member of a JSON instance declaration.
 --
@@ -520,42 +564,207 @@
 -- | OpCode parameter (field) type.
 type OpParam = (String, Q Type, Q Exp)
 
+-- * Python code generation
+
+-- | Converts Haskell values into Python values
+--
+-- This is necessary for the default values of opcode parameters and
+-- return values.  For example, if a default value or return type is a
+-- Data.Map, then it must be shown as a Python dictioanry.
+class PyValue a where
+  showValue :: a -> String
+
+-- | Encapsulates Python default values
+data PyValueEx = forall a. PyValue a => PyValueEx a
+
+instance PyValue PyValueEx where
+  showValue (PyValueEx x) = showValue x
+
+-- | Transfers opcode data between the opcode description (through
+-- @genOpCode@) and the Python code generation functions.
+type OpCodeDescriptor =
+  (String, String, String, [String],
+   [String], [Maybe PyValueEx], [String], String)
+
+-- | Strips out the module name
+--
+-- @
+-- pyBaseName "Data.Map" = "Map"
+-- @
+pyBaseName :: String -> String
+pyBaseName str =
+  case span (/= '.') str of
+    (x, []) -> x
+    (_, _:x) -> pyBaseName x
+
+-- | Converts a Haskell type name into a Python type name.
+--
+-- @
+-- pyTypename "Bool" = "ht.TBool"
+-- @
+pyTypeName :: Show a => a -> String
+pyTypeName name =
+  "ht.T" ++ (case pyBaseName (show name) of
+                "()" -> "None"
+                "Map" -> "DictOf"
+                "Set" -> "SetOf"
+                "ListSet" -> "SetOf"
+                "Either" -> "Or"
+                "GenericContainer" -> "DictOf"
+                "JSValue" -> "Any"
+                "JSObject" -> "Object"
+                str -> str)
+
+-- | Converts a Haskell type into a Python type.
+--
+-- @
+-- pyType [Int] = "ht.TListOf(ht.TInt)"
+-- @
+pyType :: Type -> Q String
+pyType (AppT typ1 typ2) =
+  do t <- pyCall typ1 typ2
+     return $ t ++ ")"
+
+pyType (ConT name) = return (pyTypeName name)
+pyType ListT = return "ht.TListOf"
+pyType (TupleT 0) = return "ht.TNone"
+pyType (TupleT _) = return "ht.TTupleOf"
+pyType typ = error $ "unhandled case for type " ++ show typ
+        
+-- | Converts a Haskell type application into a Python type.
+--
+-- @
+-- Maybe Int = "ht.TMaybe(ht.TInt)"
+-- @
+pyCall :: Type -> Type -> Q String
+pyCall (AppT typ1 typ2) arg =
+  do t <- pyCall typ1 typ2
+     targ <- pyType arg
+     return $ t ++ ", " ++ targ
+
+pyCall typ1 typ2 =
+  do t1 <- pyType typ1
+     t2 <- pyType typ2
+     return $ t1 ++ "(" ++ t2
+
+-- | @pyType opt typ@ converts Haskell type @typ@ into a Python type,
+-- where @opt@ determines if the converted type is optional (i.e.,
+-- Maybe).
+--
+-- @
+-- pyType False [Int] = "ht.TListOf(ht.TInt)" (mandatory)
+-- pyType True [Int] = "ht.TMaybe(ht.TListOf(ht.TInt))" (optional)
+-- @
+pyOptionalType :: Bool -> Type -> Q String
+pyOptionalType opt typ
+  | opt = do t <- pyType typ
+             return $ "ht.TMaybe(" ++ t ++ ")"
+  | otherwise = pyType typ
+
+-- | Optionally encapsulates default values in @PyValueEx@.
+--
+-- @maybeApp exp typ@ returns a quoted expression that encapsulates
+-- the default value @exp@ of an opcode parameter cast to @typ@ in a
+-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
+-- expression with @Nothing@.
+maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
+maybeApp Nothing _ =
+  [| Nothing |]
+
+maybeApp (Just expr) typ =
+  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
+
+
+-- | Generates a Python type according to whether the field is
+-- optional
+genPyType :: OptionalType -> Q Type -> Q ExpQ
+genPyType opt typ =
+  do t <- typ
+     stringE <$> pyOptionalType (opt /= NotOptional) t
+
+-- | Generates Python types from opcode parameters.
+genPyTypes :: [Field] -> Q ExpQ
+genPyTypes fs =
+  listE <$> mapM (\f -> genPyType (fieldIsOptional f) (fieldType f)) fs
+
+-- | Generates Python default values from opcode parameters.
+genPyDefaults :: [Field] -> ExpQ
+genPyDefaults fs =
+  listE $ map (\f -> maybeApp (fieldDefault f) (fieldType f)) fs
+
+-- | Generates a Haskell function call to "showPyClass" with the
+-- necessary information on how to build the Python class string.
+pyClass :: OpCodeConstructor -> ExpQ
+pyClass (consName, consType, consDoc, consFields, consDscField) =
+  do let pyClassVar = varNameE "showPyClass"
+         consName' = stringE consName
+     consType' <- genPyType NotOptional consType
+     let consDoc' = stringE consDoc
+         consFieldNames = listE $ map (stringE . fieldName) consFields
+         consFieldDocs = listE $ map (stringE . fieldDoc) consFields
+     consFieldTypes <- genPyTypes consFields
+     let consFieldDefaults = genPyDefaults consFields
+     [| ($consName',
+         $consType',
+         $consDoc',
+         $consFieldNames,
+         $consFieldTypes,
+         $consFieldDefaults,
+         $consFieldDocs,
+         consDscField) |]
+
+-- | Generates a function called "pyClasses" that holds the list of
+-- all the opcode descriptors necessary for generating the Python
+-- opcodes.
+pyClasses :: [OpCodeConstructor] -> Q [Dec]
+pyClasses cons =
+  do let name = mkName "pyClasses"
+         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
+     fn <- FunD name <$> (:[]) <$> declClause cons
+     return [sig, fn]
+  where declClause c =
+          clause [] (normalB (ListE <$> mapM pyClass c)) []
+
+-- | Converts from an opcode constructor to a Luxi constructor.
+opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
+opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
+
 -- | Generates the OpCode data type.
 --
 -- This takes an opcode logical definition, and builds both the
 -- datatype and the JSON serialisation out of it. We can't use a
 -- generic serialisation since we need to be compatible with Ganeti's
 -- own, so we have a few quirks to work around.
-genOpCode :: String        -- ^ Type name to use
-          -> [Constructor] -- ^ Constructor name and parameters
+genOpCode :: String              -- ^ Type name to use
+          -> [OpCodeConstructor] -- ^ Constructor name and parameters
           -> Q [Dec]
 genOpCode name cons = do
   let tname = mkName name
-  decl_d <- mapM (\(cname, fields) -> do
+  decl_d <- mapM (\(cname, _, _, fields, _) -> do
                     -- we only need the type of the field, without Q
                     fields' <- mapM (fieldTypeInfo "op") fields
                     return $ RecC (mkName cname) fields')
             cons
   let declD = DataD [] tname [] decl_d [''Show, ''Eq]
-
   let (allfsig, allffn) = genAllOpFields "allOpFields" cons
   save_decs <- genSaveOpCode tname "saveOpCode" "toDictOpCode"
-               cons (uncurry saveConstructor) True
+               (map opcodeConsToLuxiCons cons) saveConstructor True
   (loadsig, loadfn) <- genLoadOpCode cons
-  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs
+  pyDecls <- pyClasses cons
+  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs ++ pyDecls
 
 -- | Generates the function pattern returning the list of fields for a
 -- given constructor.
-genOpConsFields :: Constructor -> Clause
-genOpConsFields (cname, fields) =
+genOpConsFields :: OpCodeConstructor -> Clause
+genOpConsFields (cname, _, _, fields, _) =
   let op_id = deCamelCase cname
       fvals = map (LitE . StringL) . sort . nub $
               concatMap (\f -> fieldName f:fieldExtraKeys f) fields
   in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
 
 -- | Generates a list of all fields of an opcode constructor.
-genAllOpFields  :: String        -- ^ Function name
-                -> [Constructor] -- ^ Object definition
+genAllOpFields  :: String              -- ^ Function name
+                -> [OpCodeConstructor] -- ^ Object definition
                 -> (Dec, Dec)
 genAllOpFields sname opdefs =
   let cclauses = map genOpConsFields opdefs
@@ -569,11 +778,9 @@
 -- This matches the opcode with variables named the same as the
 -- constructor fields (just so that the spliced in code looks nicer),
 -- and passes those name plus the parameter definition to 'saveObjectField'.
-saveConstructor :: String    -- ^ The constructor name
-                -> [Field]   -- ^ The parameter definitions for this
-                             -- constructor
-                -> Q Clause  -- ^ Resulting clause
-saveConstructor sname fields = do
+saveConstructor :: LuxiConstructor -- ^ The constructor
+                -> Q Clause        -- ^ Resulting clause
+saveConstructor (sname, fields) = do
   let cname = mkName sname
   fnames <- mapM (newName . fieldVariable) fields
   let pat = conP cname (map varP fnames)
@@ -590,14 +797,14 @@
 --
 -- This builds a per-constructor match clause that contains the
 -- respective constructor-serialisation code.
-genSaveOpCode :: Name                      -- ^ Object ype
-              -> String                    -- ^ To 'JSValue' function name
-              -> String                    -- ^ To 'JSObject' function name
-              -> [Constructor]             -- ^ Object definition
-              -> (Constructor -> Q Clause) -- ^ Constructor save fn
-              -> Bool                      -- ^ Whether to generate
-                                           -- obj or just a
-                                           -- list\/tuple of values
+genSaveOpCode :: Name                          -- ^ Object ype
+              -> String                        -- ^ To 'JSValue' function name
+              -> String                        -- ^ To 'JSObject' function name
+              -> [LuxiConstructor]             -- ^ Object definition
+              -> (LuxiConstructor -> Q Clause) -- ^ Constructor save fn
+              -> Bool                          -- ^ Whether to generate
+                                               -- obj or just a
+                                               -- list\/tuple of values
               -> Q [Dec]
 genSaveOpCode tname jvalstr tdstr opdefs fn gen_object = do
   tdclauses <- mapM fn opdefs
@@ -615,8 +822,8 @@
          , ValD (VarP jvalname) (NormalB jvalclause) []]
 
 -- | Generates load code for a single constructor of the opcode data type.
-loadConstructor :: String -> [Field] -> Q Exp
-loadConstructor sname fields = do
+loadConstructor :: OpCodeConstructor -> Q Exp
+loadConstructor (sname, _, _, fields, _) = do
   let name = mkName sname
   fbinds <- mapM loadObjectField fields
   let (fnames, fstmts) = unzip fbinds
@@ -625,7 +832,7 @@
   return $ DoE fstmts'
 
 -- | Generates the loadOpCode function.
-genLoadOpCode :: [Constructor] -> Q (Dec, Dec)
+genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec)
 genLoadOpCode opdefs = do
   let fname = mkName "loadOpCode"
       arg1 = mkName "v"
@@ -635,10 +842,10 @@
                                  (JSON.readJSON $(varE arg1)) |]
   st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |]
   -- the match results (per-constructor blocks)
-  mexps <- mapM (uncurry loadConstructor) opdefs
+  mexps <- mapM loadConstructor opdefs
   fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
-  let mpats = map (\(me, c) ->
-                       let mp = LitP . StringL . deCamelCase . fst $ c
+  let mpats = map (\(me, (consName, _, _, _, _)) ->
+                       let mp = LitP . StringL . deCamelCase $ consName
                        in Match mp (NormalB me) []
                   ) $ zip mexps opdefs
       defmatch = Match WildP (NormalB fails) []
@@ -670,7 +877,7 @@
 --
 -- * type
 --
-genLuxiOp :: String -> [Constructor] -> Q [Dec]
+genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
 genLuxiOp name cons = do
   let tname = mkName name
   decl_d <- mapM (\(cname, fields) -> do
@@ -688,7 +895,7 @@
   return $ declD:save_decs ++ req_defs
 
 -- | Generates the \"save\" clause for entire LuxiOp constructor.
-saveLuxiConstructor :: Constructor -> Q Clause
+saveLuxiConstructor :: LuxiConstructor -> Q Clause
 saveLuxiConstructor (sname, fields) = do
   let cname = mkName sname
   fnames <- mapM (newName . fieldVariable) fields
diff --git a/src/Ganeti/Types.hs b/src/Ganeti/Types.hs
index 70de831..7c8d4c3 100644
--- a/src/Ganeti/Types.hs
+++ b/src/Ganeti/Types.hs
@@ -12,21 +12,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -40,6 +49,9 @@
   , DiskTemplate(..)
   , diskTemplateToRaw
   , diskTemplateFromRaw
+  , TagKind(..)
+  , tagKindToRaw
+  , tagKindFromRaw
   , NonNegative
   , fromNonNegative
   , mkNonNegative
@@ -53,23 +65,44 @@
   , fromNonEmpty
   , mkNonEmpty
   , NonEmptyString
+  , QueryResultCode
+  , IPv4Address
+  , mkIPv4Address
+  , IPv4Network
+  , mkIPv4Network
+  , IPv6Address
+  , mkIPv6Address
+  , IPv6Network
+  , mkIPv6Network
   , MigrationMode(..)
+  , migrationModeToRaw
   , VerifyOptionalChecks(..)
+  , verifyOptionalChecksToRaw
   , DdmSimple(..)
   , DdmFull(..)
+  , ddmFullToRaw
   , CVErrorCode(..)
   , cVErrorCodeToRaw
   , Hypervisor(..)
   , hypervisorToRaw
   , OobCommand(..)
+  , oobCommandToRaw
+  , OobStatus(..)
+  , oobStatusToRaw
   , StorageType(..)
   , storageTypeToRaw
-  , NodeEvacMode(..)
+  , EvacMode(..)
+  , evacModeToRaw
   , FileDriver(..)
+  , fileDriverToRaw
   , InstCreateMode(..)
+  , instCreateModeToRaw
   , RebootType(..)
+  , rebootTypeToRaw
   , ExportMode(..)
+  , exportModeToRaw
   , IAllocatorTestDir(..)
+  , iAllocatorTestDirToRaw
   , IAllocatorMode(..)
   , iAllocatorModeToRaw
   , NICMode(..)
@@ -94,6 +127,7 @@
   , opStatusToRaw
   , opStatusFromRaw
   , ELogType(..)
+  , eLogTypeToRaw
   , ReasonElem
   , ReasonTrail
   , StorageUnit(..)
@@ -101,6 +135,36 @@
   , StorageKey
   , addParamsToStorageUnit
   , diskTemplateToStorageType
+  , VType(..)
+  , vTypeFromRaw
+  , vTypeToRaw
+  , NodeRole(..)
+  , nodeRoleToRaw
+  , roleDescription
+  , DiskMode(..)
+  , diskModeToRaw
+  , BlockDriver(..)
+  , blockDriverToRaw
+  , AdminState(..)
+  , adminStateFromRaw
+  , adminStateToRaw
+  , StorageField(..)
+  , storageFieldToRaw
+  , DiskAccessMode(..)
+  , diskAccessModeToRaw
+  , LocalDiskStatus(..)
+  , localDiskStatusFromRaw
+  , localDiskStatusToRaw
+  , localDiskStatusName
+  , ReplaceDisksMode(..)
+  , replaceDisksModeToRaw
+  , RpcTimeout(..)
+  , rpcTimeoutFromRaw -- FIXME: no used anywhere
+  , rpcTimeoutToRaw
+  , HotplugTarget(..)
+  , hotplugTargetToRaw
+  , HotplugAction(..)
+  , hotplugActionToRaw
   ) where
 
 import Control.Monad (liftM)
@@ -108,9 +172,9 @@
 import Text.JSON (JSON, readJSON, showJSON)
 import Data.Ratio (numerator, denominator)
 
-import qualified Ganeti.Constants as C
-import qualified Ganeti.THH as THH
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import Ganeti.JSON
+import qualified Ganeti.THH as THH
 import Ganeti.Utils
 
 -- * Generic types
@@ -166,6 +230,10 @@
 mkNonEmpty [] = fail "Received empty value for non-empty list"
 mkNonEmpty xs = return (NonEmpty xs)
 
+instance (Eq a, Ord a) => Ord (NonEmpty a) where
+  NonEmpty { fromNonEmpty = x1 } `compare` NonEmpty { fromNonEmpty = x2 } =
+    x1 `compare` x2
+
 instance (JSON.JSON a) => JSON.JSON (NonEmpty a) where
   showJSON = JSON.showJSON . fromNonEmpty
   readJSON v = JSON.readJSON v >>= mkNonEmpty
@@ -173,150 +241,231 @@
 -- | A simple type alias for non-empty strings.
 type NonEmptyString = NonEmpty Char
 
+type QueryResultCode = Int
+
+newtype IPv4Address = IPv4Address { fromIPv4Address :: String }
+  deriving (Show, Eq)
+
+-- FIXME: this should check that 'address' is a valid ip
+mkIPv4Address :: Monad m => String -> m IPv4Address
+mkIPv4Address address =
+  return IPv4Address { fromIPv4Address = address }
+
+instance JSON.JSON IPv4Address where
+  showJSON = JSON.showJSON . fromIPv4Address
+  readJSON v = JSON.readJSON v >>= mkIPv4Address
+
+newtype IPv4Network = IPv4Network { fromIPv4Network :: String }
+  deriving (Show, Eq)
+
+-- FIXME: this should check that 'address' is a valid ip
+mkIPv4Network :: Monad m => String -> m IPv4Network
+mkIPv4Network address =
+  return IPv4Network { fromIPv4Network = address }
+
+instance JSON.JSON IPv4Network where
+  showJSON = JSON.showJSON . fromIPv4Network
+  readJSON v = JSON.readJSON v >>= mkIPv4Network
+
+newtype IPv6Address = IPv6Address { fromIPv6Address :: String }
+  deriving (Show, Eq)
+
+-- FIXME: this should check that 'address' is a valid ip
+mkIPv6Address :: Monad m => String -> m IPv6Address
+mkIPv6Address address =
+  return IPv6Address { fromIPv6Address = address }
+
+instance JSON.JSON IPv6Address where
+  showJSON = JSON.showJSON . fromIPv6Address
+  readJSON v = JSON.readJSON v >>= mkIPv6Address
+
+newtype IPv6Network = IPv6Network { fromIPv6Network :: String }
+  deriving (Show, Eq)
+
+-- FIXME: this should check that 'address' is a valid ip
+mkIPv6Network :: Monad m => String -> m IPv6Network
+mkIPv6Network address =
+  return IPv6Network { fromIPv6Network = address }
+
+instance JSON.JSON IPv6Network where
+  showJSON = JSON.showJSON . fromIPv6Network
+  readJSON v = JSON.readJSON v >>= mkIPv6Network
+
 -- * Ganeti types
 
 -- | Instance disk template type.
-$(THH.declareSADT "DiskTemplate"
-       [ ("DTDiskless",   'C.dtDiskless)
-       , ("DTFile",       'C.dtFile)
-       , ("DTSharedFile", 'C.dtSharedFile)
-       , ("DTPlain",      'C.dtPlain)
-       , ("DTBlock",      'C.dtBlock)
-       , ("DTDrbd8",      'C.dtDrbd8)
-       , ("DTRbd",        'C.dtRbd)
-       , ("DTExt",        'C.dtExt)
+$(THH.declareLADT ''String "DiskTemplate"
+       [ ("DTDiskless",   "diskless")
+       , ("DTFile",       "file")
+       , ("DTSharedFile", "sharedfile")
+       , ("DTPlain",      "plain")
+       , ("DTBlock",      "blockdev")
+       , ("DTDrbd8",      "drbd")
+       , ("DTRbd",        "rbd")
+       , ("DTExt",        "ext")
        ])
 $(THH.makeJSONInstance ''DiskTemplate)
 
+instance THH.PyValue DiskTemplate where
+  showValue = show . diskTemplateToRaw
+
 instance HasStringRepr DiskTemplate where
   fromStringRepr = diskTemplateFromRaw
   toStringRepr = diskTemplateToRaw
 
+-- | Data type representing what items the tag operations apply to.
+$(THH.declareLADT ''String "TagKind"
+  [ ("TagKindInstance", "instance")
+  , ("TagKindNode",     "node")
+  , ("TagKindGroup",    "nodegroup")
+  , ("TagKindCluster",  "cluster")
+  , ("TagKindNetwork",  "network")
+  ])
+$(THH.makeJSONInstance ''TagKind)
+
 -- | The Group allocation policy type.
 --
 -- Note that the order of constructors is important as the automatic
 -- Ord instance will order them in the order they are defined, so when
 -- changing this data type be careful about the interaction with the
 -- desired sorting order.
-$(THH.declareSADT "AllocPolicy"
-       [ ("AllocPreferred",   'C.allocPolicyPreferred)
-       , ("AllocLastResort",  'C.allocPolicyLastResort)
-       , ("AllocUnallocable", 'C.allocPolicyUnallocable)
+$(THH.declareLADT ''String "AllocPolicy"
+       [ ("AllocPreferred",   "preferred")
+       , ("AllocLastResort",  "last_resort")
+       , ("AllocUnallocable", "unallocable")
        ])
 $(THH.makeJSONInstance ''AllocPolicy)
 
 -- | The Instance real state type. FIXME: this could be improved to
 -- just wrap a /NormalState AdminStatus | ErrorState ErrorCondition/.
-$(THH.declareSADT "InstanceStatus"
-       [ ("StatusDown",    'C.inststAdmindown)
-       , ("StatusOffline", 'C.inststAdminoffline)
-       , ("ErrorDown",     'C.inststErrordown)
-       , ("ErrorUp",       'C.inststErrorup)
-       , ("NodeDown",      'C.inststNodedown)
-       , ("NodeOffline",   'C.inststNodeoffline)
-       , ("Running",       'C.inststRunning)
-       , ("WrongNode",     'C.inststWrongnode)
+$(THH.declareLADT ''String "InstanceStatus"
+       [ ("StatusDown",    "ADMIN_down")
+       , ("StatusOffline", "ADMIN_offline")
+       , ("ErrorDown",     "ERROR_down")
+       , ("ErrorUp",       "ERROR_up")
+       , ("NodeDown",      "ERROR_nodedown")
+       , ("NodeOffline",   "ERROR_nodeoffline")
+       , ("Running",       "running")
+       , ("WrongNode",     "ERROR_wrongnode")
        ])
 $(THH.makeJSONInstance ''InstanceStatus)
 
 -- | Migration mode.
-$(THH.declareSADT "MigrationMode"
-     [ ("MigrationLive",    'C.htMigrationLive)
-     , ("MigrationNonLive", 'C.htMigrationNonlive)
+$(THH.declareLADT ''String "MigrationMode"
+     [ ("MigrationLive",    "live")
+     , ("MigrationNonLive", "non-live")
      ])
 $(THH.makeJSONInstance ''MigrationMode)
 
 -- | Verify optional checks.
-$(THH.declareSADT "VerifyOptionalChecks"
-     [ ("VerifyNPlusOneMem", 'C.verifyNplusoneMem)
+$(THH.declareLADT ''String "VerifyOptionalChecks"
+     [ ("VerifyNPlusOneMem", "nplusone_mem")
      ])
 $(THH.makeJSONInstance ''VerifyOptionalChecks)
 
 -- | Cluster verify error codes.
-$(THH.declareSADT "CVErrorCode"
-  [ ("CvECLUSTERCFG",                  'C.cvEclustercfgCode)
-  , ("CvECLUSTERCERT",                 'C.cvEclustercertCode)
-  , ("CvECLUSTERFILECHECK",            'C.cvEclusterfilecheckCode)
-  , ("CvECLUSTERDANGLINGNODES",        'C.cvEclusterdanglingnodesCode)
-  , ("CvECLUSTERDANGLINGINST",         'C.cvEclusterdanglinginstCode)
-  , ("CvEINSTANCEBADNODE",             'C.cvEinstancebadnodeCode)
-  , ("CvEINSTANCEDOWN",                'C.cvEinstancedownCode)
-  , ("CvEINSTANCELAYOUT",              'C.cvEinstancelayoutCode)
-  , ("CvEINSTANCEMISSINGDISK",         'C.cvEinstancemissingdiskCode)
-  , ("CvEINSTANCEFAULTYDISK",          'C.cvEinstancefaultydiskCode)
-  , ("CvEINSTANCEWRONGNODE",           'C.cvEinstancewrongnodeCode)
-  , ("CvEINSTANCESPLITGROUPS",         'C.cvEinstancesplitgroupsCode)
-  , ("CvEINSTANCEPOLICY",              'C.cvEinstancepolicyCode)
-  , ("CvENODEDRBD",                    'C.cvEnodedrbdCode)
-  , ("CvENODEDRBDHELPER",              'C.cvEnodedrbdhelperCode)
-  , ("CvENODEFILECHECK",               'C.cvEnodefilecheckCode)
-  , ("CvENODEHOOKS",                   'C.cvEnodehooksCode)
-  , ("CvENODEHV",                      'C.cvEnodehvCode)
-  , ("CvENODELVM",                     'C.cvEnodelvmCode)
-  , ("CvENODEN1",                      'C.cvEnoden1Code)
-  , ("CvENODENET",                     'C.cvEnodenetCode)
-  , ("CvENODEOS",                      'C.cvEnodeosCode)
-  , ("CvENODEORPHANINSTANCE",          'C.cvEnodeorphaninstanceCode)
-  , ("CvENODEORPHANLV",                'C.cvEnodeorphanlvCode)
-  , ("CvENODERPC",                     'C.cvEnoderpcCode)
-  , ("CvENODESSH",                     'C.cvEnodesshCode)
-  , ("CvENODEVERSION",                 'C.cvEnodeversionCode)
-  , ("CvENODESETUP",                   'C.cvEnodesetupCode)
-  , ("CvENODETIME",                    'C.cvEnodetimeCode)
-  , ("CvENODEOOBPATH",                 'C.cvEnodeoobpathCode)
-  , ("CvENODEUSERSCRIPTS",             'C.cvEnodeuserscriptsCode)
-  , ("CvENODEFILESTORAGEPATHS",        'C.cvEnodefilestoragepathsCode)
-  , ("CvENODEFILESTORAGEPATHUNUSABLE", 'C.cvEnodefilestoragepathunusableCode)
+$(THH.declareLADT ''String "CVErrorCode"
+  [ ("CvECLUSTERCFG",                  "ECLUSTERCFG")
+  , ("CvECLUSTERCERT",                 "ECLUSTERCERT")
+  , ("CvECLUSTERFILECHECK",            "ECLUSTERFILECHECK")
+  , ("CvECLUSTERDANGLINGNODES",        "ECLUSTERDANGLINGNODES")
+  , ("CvECLUSTERDANGLINGINST",         "ECLUSTERDANGLINGINST")
+  , ("CvEINSTANCEBADNODE",             "EINSTANCEBADNODE")
+  , ("CvEINSTANCEDOWN",                "EINSTANCEDOWN")
+  , ("CvEINSTANCELAYOUT",              "EINSTANCELAYOUT")
+  , ("CvEINSTANCEMISSINGDISK",         "EINSTANCEMISSINGDISK")
+  , ("CvEINSTANCEFAULTYDISK",          "EINSTANCEFAULTYDISK")
+  , ("CvEINSTANCEWRONGNODE",           "EINSTANCEWRONGNODE")
+  , ("CvEINSTANCESPLITGROUPS",         "EINSTANCESPLITGROUPS")
+  , ("CvEINSTANCEPOLICY",              "EINSTANCEPOLICY")
+  , ("CvEINSTANCEUNSUITABLENODE",      "EINSTANCEUNSUITABLENODE")
+  , ("CvEINSTANCEMISSINGCFGPARAMETER", "EINSTANCEMISSINGCFGPARAMETER")
+  , ("CvENODEDRBD",                    "ENODEDRBD")
+  , ("CvENODEDRBDVERSION",             "ENODEDRBDVERSION")
+  , ("CvENODEDRBDHELPER",              "ENODEDRBDHELPER")
+  , ("CvENODEFILECHECK",               "ENODEFILECHECK")
+  , ("CvENODEHOOKS",                   "ENODEHOOKS")
+  , ("CvENODEHV",                      "ENODEHV")
+  , ("CvENODELVM",                     "ENODELVM")
+  , ("CvENODEN1",                      "ENODEN1")
+  , ("CvENODENET",                     "ENODENET")
+  , ("CvENODEOS",                      "ENODEOS")
+  , ("CvENODEORPHANINSTANCE",          "ENODEORPHANINSTANCE")
+  , ("CvENODEORPHANLV",                "ENODEORPHANLV")
+  , ("CvENODERPC",                     "ENODERPC")
+  , ("CvENODESSH",                     "ENODESSH")
+  , ("CvENODEVERSION",                 "ENODEVERSION")
+  , ("CvENODESETUP",                   "ENODESETUP")
+  , ("CvENODETIME",                    "ENODETIME")
+  , ("CvENODEOOBPATH",                 "ENODEOOBPATH")
+  , ("CvENODEUSERSCRIPTS",             "ENODEUSERSCRIPTS")
+  , ("CvENODEFILESTORAGEPATHS",        "ENODEFILESTORAGEPATHS")
+  , ("CvENODEFILESTORAGEPATHUNUSABLE", "ENODEFILESTORAGEPATHUNUSABLE")
   , ("CvENODESHAREDFILESTORAGEPATHUNUSABLE",
-     'C.cvEnodesharedfilestoragepathunusableCode)
+     "ENODESHAREDFILESTORAGEPATHUNUSABLE")
+  , ("CvEGROUPDIFFERENTPVSIZE",        "EGROUPDIFFERENTPVSIZE")
   ])
 $(THH.makeJSONInstance ''CVErrorCode)
 
 -- | Dynamic device modification, just add\/remove version.
-$(THH.declareSADT "DdmSimple"
-     [ ("DdmSimpleAdd",    'C.ddmAdd)
-     , ("DdmSimpleRemove", 'C.ddmRemove)
+$(THH.declareLADT ''String "DdmSimple"
+     [ ("DdmSimpleAdd",    "add")
+     , ("DdmSimpleRemove", "remove")
      ])
 $(THH.makeJSONInstance ''DdmSimple)
 
 -- | Dynamic device modification, all operations version.
-$(THH.declareSADT "DdmFull"
-     [ ("DdmFullAdd",    'C.ddmAdd)
-     , ("DdmFullRemove", 'C.ddmRemove)
-     , ("DdmFullModify", 'C.ddmModify)
+--
+-- TODO: DDM_SWAP, DDM_MOVE?
+$(THH.declareLADT ''String "DdmFull"
+     [ ("DdmFullAdd",    "add")
+     , ("DdmFullRemove", "remove")
+     , ("DdmFullModify", "modify")
      ])
 $(THH.makeJSONInstance ''DdmFull)
 
 -- | Hypervisor type definitions.
-$(THH.declareSADT "Hypervisor"
-  [ ( "Kvm",    'C.htKvm )
-  , ( "XenPvm", 'C.htXenPvm )
-  , ( "Chroot", 'C.htChroot )
-  , ( "XenHvm", 'C.htXenHvm )
-  , ( "Lxc",    'C.htLxc )
-  , ( "Fake",   'C.htFake )
+$(THH.declareLADT ''String "Hypervisor"
+  [ ("Kvm",    "kvm")
+  , ("XenPvm", "xen-pvm")
+  , ("Chroot", "chroot")
+  , ("XenHvm", "xen-hvm")
+  , ("Lxc",    "lxc")
+  , ("Fake",   "fake")
   ])
 $(THH.makeJSONInstance ''Hypervisor)
 
+instance THH.PyValue Hypervisor where
+  showValue = show . hypervisorToRaw
+
 -- | Oob command type.
-$(THH.declareSADT "OobCommand"
-  [ ("OobHealth",      'C.oobHealth)
-  , ("OobPowerCycle",  'C.oobPowerCycle)
-  , ("OobPowerOff",    'C.oobPowerOff)
-  , ("OobPowerOn",     'C.oobPowerOn)
-  , ("OobPowerStatus", 'C.oobPowerStatus)
+$(THH.declareLADT ''String "OobCommand"
+  [ ("OobHealth",      "health")
+  , ("OobPowerCycle",  "power-cycle")
+  , ("OobPowerOff",    "power-off")
+  , ("OobPowerOn",     "power-on")
+  , ("OobPowerStatus", "power-status")
   ])
 $(THH.makeJSONInstance ''OobCommand)
 
+-- | Oob command status
+$(THH.declareLADT ''String "OobStatus"
+  [ ("OobStatusCritical", "CRITICAL")
+  , ("OobStatusOk",       "OK")
+  , ("OobStatusUnknown",  "UNKNOWN")
+  , ("OobStatusWarning",  "WARNING")
+  ])
+$(THH.makeJSONInstance ''OobStatus)
+
 -- | Storage type.
-$(THH.declareSADT "StorageType"
-  [ ("StorageFile", 'C.stFile)
-  , ("StorageLvmPv", 'C.stLvmPv)
-  , ("StorageLvmVg", 'C.stLvmVg)
-  , ("StorageDiskless", 'C.stDiskless)
-  , ("StorageBlock", 'C.stBlock)
-  , ("StorageRados", 'C.stRados)
-  , ("StorageExt", 'C.stExt)
+$(THH.declareLADT ''String "StorageType"
+  [ ("StorageFile", "file")
+  , ("StorageLvmPv", "lvm-pv")
+  , ("StorageLvmVg", "lvm-vg")
+  , ("StorageDiskless", "diskless")
+  , ("StorageBlock", "blockdev")
+  , ("StorageRados", "rados")
+  , ("StorageExt", "ext")
   ])
 $(THH.makeJSONInstance ''StorageType)
 
@@ -370,7 +519,7 @@
 showSULvm :: StorageType -> StorageKey -> SPExclusiveStorage -> String
 showSULvm st sk es = show (storageTypeToRaw st, sk, [es])
 
--- | Mapping fo disk templates to storage type
+-- | Mapping from disk templates to storage types
 -- FIXME: This is semantically the same as the constant
 -- C.diskTemplatesStorageType, remove this when python constants
 -- are generated from haskell constants
@@ -395,87 +544,94 @@
 addParamsToStorageUnit _ (SURaw StorageRados key) = SURados key
 
 -- | Node evac modes.
-$(THH.declareSADT "NodeEvacMode"
-  [ ("NEvacPrimary",   'C.iallocatorNevacPri)
-  , ("NEvacSecondary", 'C.iallocatorNevacSec)
-  , ("NEvacAll",       'C.iallocatorNevacAll)
+--
+-- This is part of the 'IAllocator' interface and it is used, for
+-- example, in 'Ganeti.HTools.Loader.RqType'.  However, it must reside
+-- in this module, and not in 'Ganeti.HTools.Types', because it is
+-- also used by 'Ganeti.Constants'.
+$(THH.declareLADT ''String "EvacMode"
+  [ ("ChangePrimary",   "primary-only")
+  , ("ChangeSecondary", "secondary-only")
+  , ("ChangeAll",       "all")
   ])
-$(THH.makeJSONInstance ''NodeEvacMode)
+$(THH.makeJSONInstance ''EvacMode)
 
 -- | The file driver type.
-$(THH.declareSADT "FileDriver"
-  [ ("FileLoop",   'C.fdLoop)
-  , ("FileBlktap", 'C.fdBlktap)
+$(THH.declareLADT ''String "FileDriver"
+  [ ("FileLoop",   "loop")
+  , ("FileBlktap", "blktap")
+  , ("FileBlktap2", "blktap2")
   ])
 $(THH.makeJSONInstance ''FileDriver)
 
 -- | The instance create mode.
-$(THH.declareSADT "InstCreateMode"
-  [ ("InstCreate",       'C.instanceCreate)
-  , ("InstImport",       'C.instanceImport)
-  , ("InstRemoteImport", 'C.instanceRemoteImport)
+$(THH.declareLADT ''String "InstCreateMode"
+  [ ("InstCreate",       "create")
+  , ("InstImport",       "import")
+  , ("InstRemoteImport", "remote-import")
   ])
 $(THH.makeJSONInstance ''InstCreateMode)
 
 -- | Reboot type.
-$(THH.declareSADT "RebootType"
-  [ ("RebootSoft", 'C.instanceRebootSoft)
-  , ("RebootHard", 'C.instanceRebootHard)
-  , ("RebootFull", 'C.instanceRebootFull)
+$(THH.declareLADT ''String "RebootType"
+  [ ("RebootSoft", "soft")
+  , ("RebootHard", "hard")
+  , ("RebootFull", "full")
   ])
 $(THH.makeJSONInstance ''RebootType)
 
 -- | Export modes.
-$(THH.declareSADT "ExportMode"
-  [ ("ExportModeLocal",  'C.exportModeLocal)
-  , ("ExportModeRemove", 'C.exportModeRemote)
+$(THH.declareLADT ''String "ExportMode"
+  [ ("ExportModeLocal",  "local")
+  , ("ExportModeRemote", "remote")
   ])
 $(THH.makeJSONInstance ''ExportMode)
 
 -- | IAllocator run types (OpTestIAllocator).
-$(THH.declareSADT "IAllocatorTestDir"
-  [ ("IAllocatorDirIn",  'C.iallocatorDirIn)
-  , ("IAllocatorDirOut", 'C.iallocatorDirOut)
+$(THH.declareLADT ''String "IAllocatorTestDir"
+  [ ("IAllocatorDirIn",  "in")
+  , ("IAllocatorDirOut", "out")
   ])
 $(THH.makeJSONInstance ''IAllocatorTestDir)
 
 -- | IAllocator mode. FIXME: use this in "HTools.Backend.IAlloc".
-$(THH.declareSADT "IAllocatorMode"
-  [ ("IAllocatorAlloc",       'C.iallocatorModeAlloc)
-  , ("IAllocatorMultiAlloc",  'C.iallocatorModeMultiAlloc)
-  , ("IAllocatorReloc",       'C.iallocatorModeReloc)
-  , ("IAllocatorNodeEvac",    'C.iallocatorModeNodeEvac)
-  , ("IAllocatorChangeGroup", 'C.iallocatorModeChgGroup)
+$(THH.declareLADT ''String "IAllocatorMode"
+  [ ("IAllocatorAlloc",       "allocate")
+  , ("IAllocatorMultiAlloc",  "multi-allocate")
+  , ("IAllocatorReloc",       "relocate")
+  , ("IAllocatorNodeEvac",    "node-evacuate")
+  , ("IAllocatorChangeGroup", "change-group")
   ])
 $(THH.makeJSONInstance ''IAllocatorMode)
 
 -- | Network mode.
-$(THH.declareSADT "NICMode"
-  [ ("NMBridged", 'C.nicModeBridged)
-  , ("NMRouted",  'C.nicModeRouted)
-  , ("NMOvs",     'C.nicModeOvs)
+$(THH.declareLADT ''String "NICMode"
+  [ ("NMBridged", "bridged")
+  , ("NMRouted",  "routed")
+  , ("NMOvs",     "openvswitch")
+  , ("NMPool",    "pool")
   ])
 $(THH.makeJSONInstance ''NICMode)
 
 -- | The JobStatus data type. Note that this is ordered especially
 -- such that greater\/lesser comparison on values of this type makes
 -- sense.
-$(THH.declareSADT "JobStatus"
-       [ ("JOB_STATUS_QUEUED",    'C.jobStatusQueued)
-       , ("JOB_STATUS_WAITING",   'C.jobStatusWaiting)
-       , ("JOB_STATUS_CANCELING", 'C.jobStatusCanceling)
-       , ("JOB_STATUS_RUNNING",   'C.jobStatusRunning)
-       , ("JOB_STATUS_CANCELED",  'C.jobStatusCanceled)
-       , ("JOB_STATUS_SUCCESS",   'C.jobStatusSuccess)
-       , ("JOB_STATUS_ERROR",     'C.jobStatusError)
-       ])
+$(THH.declareLADT ''String "JobStatus"
+  [ ("JOB_STATUS_QUEUED",    "queued")
+  , ("JOB_STATUS_WAITING",   "waiting")
+  , ("JOB_STATUS_CANCELING", "canceling")
+  , ("JOB_STATUS_RUNNING",   "running")
+  , ("JOB_STATUS_CANCELED",  "canceled")
+  , ("JOB_STATUS_SUCCESS",   "success")
+  , ("JOB_STATUS_ERROR",     "error")
+  ])
 $(THH.makeJSONInstance ''JobStatus)
 
 -- | Finalized job status.
-$(THH.declareSADT "FinalizedJobStatus"
-  [ ("JobStatusCanceled",   'C.jobStatusCanceled)
-  , ("JobStatusSuccessful", 'C.jobStatusSuccess)
-  , ("JobStatusFailed",     'C.jobStatusError)
+$(THH.declareLADT ''String "FinalizedJobStatus"
+  [ ("JobStatusCanceled",   "canceled")
+  , ("JobStatusSuccessful", "success")
+  , ("JobStatusFailed",     "error")
   ])
 $(THH.makeJSONInstance ''FinalizedJobStatus)
 
@@ -533,9 +689,9 @@
 
 -- | Valid opcode priorities for submit.
 $(THH.declareIADT "OpSubmitPriority"
-  [ ("OpPrioLow",    'C.opPrioLow)
-  , ("OpPrioNormal", 'C.opPrioNormal)
-  , ("OpPrioHigh",   'C.opPrioHigh)
+  [ ("OpPrioLow",    'ConstantUtils.priorityLow)
+  , ("OpPrioNormal", 'ConstantUtils.priorityNormal)
+  , ("OpPrioHigh",   'ConstantUtils.priorityHigh)
   ])
 $(THH.makeJSONInstance ''OpSubmitPriority)
 
@@ -553,22 +709,22 @@
 fmtSubmitPriority OpPrioHigh   = "high"
 
 -- | Our ADT for the OpCode status at runtime (while in a job).
-$(THH.declareSADT "OpStatus"
-  [ ("OP_STATUS_QUEUED",    'C.opStatusQueued)
-  , ("OP_STATUS_WAITING",   'C.opStatusWaiting)
-  , ("OP_STATUS_CANCELING", 'C.opStatusCanceling)
-  , ("OP_STATUS_RUNNING",   'C.opStatusRunning)
-  , ("OP_STATUS_CANCELED",  'C.opStatusCanceled)
-  , ("OP_STATUS_SUCCESS",   'C.opStatusSuccess)
-  , ("OP_STATUS_ERROR",     'C.opStatusError)
+$(THH.declareLADT ''String "OpStatus"
+  [ ("OP_STATUS_QUEUED",    "queued")
+  , ("OP_STATUS_WAITING",   "waiting")
+  , ("OP_STATUS_CANCELING", "canceling")
+  , ("OP_STATUS_RUNNING",   "running")
+  , ("OP_STATUS_CANCELED",  "canceled")
+  , ("OP_STATUS_SUCCESS",   "success")
+  , ("OP_STATUS_ERROR",     "error")
   ])
 $(THH.makeJSONInstance ''OpStatus)
 
 -- | Type for the job message type.
-$(THH.declareSADT "ELogType"
-  [ ("ELogMessage",      'C.elogMessage)
-  , ("ELogRemoteImport", 'C.elogRemoteImport)
-  , ("ELogJqueueTest",   'C.elogJqueueTest)
+$(THH.declareLADT ''String "ELogType"
+  [ ("ELogMessage",      "message")
+  , ("ELogRemoteImport", "remote-import")
+  , ("ELogJqueueTest",   "jqueue-test")
   ])
 $(THH.makeJSONInstance ''ELogType)
 
@@ -577,3 +733,131 @@
 
 -- | Type representing a reason trail.
 type ReasonTrail = [ReasonElem]
+
+-- | The VTYPES, a mini-type system in Python.
+$(THH.declareLADT ''String "VType"
+  [ ("VTypeString",      "string")
+  , ("VTypeMaybeString", "maybe-string")
+  , ("VTypeBool",        "bool")
+  , ("VTypeSize",        "size")
+  , ("VTypeInt",         "int")
+  ])
+$(THH.makeJSONInstance ''VType)
+
+instance THH.PyValue VType where
+  showValue = THH.showValue . vTypeToRaw
+
+-- * Node role type
+
+$(THH.declareLADT ''String "NodeRole"
+  [ ("NROffline",   "O")
+  , ("NRDrained",   "D")
+  , ("NRRegular",   "R")
+  , ("NRCandidate", "C")
+  , ("NRMaster",    "M")
+  ])
+$(THH.makeJSONInstance ''NodeRole)
+
+-- | The description of the node role.
+roleDescription :: NodeRole -> String
+roleDescription NROffline   = "offline"
+roleDescription NRDrained   = "drained"
+roleDescription NRRegular   = "regular"
+roleDescription NRCandidate = "master candidate"
+roleDescription NRMaster    = "master"
+
+-- * Disk types
+
+$(THH.declareLADT ''String "DiskMode"
+  [ ("DiskRdOnly", "ro")
+  , ("DiskRdWr",   "rw")
+  ])
+$(THH.makeJSONInstance ''DiskMode)
+
+-- | The persistent block driver type. Currently only one type is allowed.
+$(THH.declareLADT ''String "BlockDriver"
+  [ ("BlockDrvManual", "manual")
+  ])
+$(THH.makeJSONInstance ''BlockDriver)
+
+-- * Instance types
+
+$(THH.declareLADT ''String "AdminState"
+  [ ("AdminOffline", "offline")
+  , ("AdminDown",    "down")
+  , ("AdminUp",      "up")
+  ])
+$(THH.makeJSONInstance ''AdminState)
+
+-- * Storage field type
+
+$(THH.declareLADT ''String "StorageField"
+  [ ( "SFUsed",        "used")
+  , ( "SFName",        "name")
+  , ( "SFAllocatable", "allocatable")
+  , ( "SFFree",        "free")
+  , ( "SFSize",        "size")
+  ])
+$(THH.makeJSONInstance ''StorageField)
+
+-- * Disk access protocol
+
+$(THH.declareLADT ''String "DiskAccessMode"
+  [ ( "DiskUserspace",   "userspace")
+  , ( "DiskKernelspace", "kernelspace")
+  ])
+$(THH.makeJSONInstance ''DiskAccessMode)
+
+-- | Local disk status
+--
+-- Python code depends on:
+--   DiskStatusOk < DiskStatusUnknown < DiskStatusFaulty
+$(THH.declareILADT "LocalDiskStatus"
+  [ ("DiskStatusFaulty",  3)
+  , ("DiskStatusOk",      1)
+  , ("DiskStatusUnknown", 2)
+  ])
+
+localDiskStatusName :: LocalDiskStatus -> String
+localDiskStatusName DiskStatusFaulty = "faulty"
+localDiskStatusName DiskStatusOk = "ok"
+localDiskStatusName DiskStatusUnknown = "unknown"
+
+-- | Replace disks type.
+$(THH.declareLADT ''String "ReplaceDisksMode"
+  [ -- Replace disks on primary
+    ("ReplaceOnPrimary",    "replace_on_primary")
+    -- Replace disks on secondary
+  , ("ReplaceOnSecondary",  "replace_on_secondary")
+    -- Change secondary node
+  , ("ReplaceNewSecondary", "replace_new_secondary")
+  , ("ReplaceAuto",         "replace_auto")
+  ])
+$(THH.makeJSONInstance ''ReplaceDisksMode)
+
+-- | Basic timeouts for RPC calls.
+$(THH.declareILADT "RpcTimeout"
+  [ ("Urgent",    60)       -- 1 minute
+  , ("Fast",      5 * 60)   -- 5 minutes
+  , ("Normal",    15 * 60)  -- 15 minutes
+  , ("Slow",      3600)     -- 1 hour
+  , ("FourHours", 4 * 3600) -- 4 hours
+  , ("OneDay",    86400)    -- 1 day
+  ])
+
+-- | Hotplug action.
+
+$(THH.declareLADT ''String "HotplugAction"
+  [ ("HAAdd", "hotadd")
+  , ("HARemove",  "hotremove")
+  , ("HAMod",     "hotmod")
+  ])
+$(THH.makeJSONInstance ''HotplugAction)
+
+-- | Hotplug Device Target.
+
+$(THH.declareLADT ''String "HotplugTarget"
+  [ ("HTDisk", "hotdisk")
+  , ("HTNic",  "hotnic")
+  ])
+$(THH.makeJSONInstance ''HotplugTarget)
diff --git a/src/Ganeti/Utils.hs b/src/Ganeti/Utils.hs
index 0029f36..fa8ccd7 100644
--- a/src/Ganeti/Utils.hs
+++ b/src/Ganeti/Utils.hs
@@ -1,23 +1,34 @@
+{-# LANGUAGE BangPatterns #-}
+
 {-| Utility functions. -}
 
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -26,6 +37,11 @@
   , debugFn
   , debugXy
   , sepSplit
+  , Statistics
+  , getSumStatistics
+  , getStdDevStatistics
+  , getStatisticValue
+  , updateStatistics
   , stdDev
   , if'
   , select
@@ -71,7 +87,7 @@
 import Network.Socket
 
 import Ganeti.BasicTypes
-import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import Ganeti.Logging
 import Ganeti.Runtime
 import System.IO
@@ -144,6 +160,48 @@
       av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
   in sqrt (av / ll) -- stddev
 
+-- | Abstract type of statistical accumulations. They behave as if the given
+-- statistics were computed on the list of values, but they allow a potentially
+-- more efficient update of a given value.
+data Statistics = SumStatistics Double
+                | StdDevStatistics Double Double Double deriving Show
+                  -- count, sum, and not the sum of squares---instead the
+                  -- computed variance for better precission.
+
+-- | Get a statistics that sums up the values.
+getSumStatistics :: [Double] -> Statistics
+getSumStatistics = SumStatistics . sum
+
+-- | Get a statistics for the standard deviation.
+getStdDevStatistics :: [Double] -> Statistics
+getStdDevStatistics xs =
+  let (nt, st) = foldl' (\(n, s) x ->
+                            let !n' = n + 1
+                                !s' = s + x
+                            in (n', s'))
+                 (0, 0) xs
+      mean = st / nt
+      nvar = foldl' (\v x -> let d = x - mean in v + d * d) 0 xs
+  in StdDevStatistics nt st (nvar / nt)
+
+-- | Obtain the value of a statistics.
+getStatisticValue :: Statistics -> Double
+getStatisticValue (SumStatistics s) = s
+getStatisticValue (StdDevStatistics _ _ var) = sqrt var
+
+-- | In a given statistics replace on value by another. This
+-- will only give meaningful results, if the original value
+-- was actually part of the statistics.
+updateStatistics :: Statistics -> (Double, Double) -> Statistics
+updateStatistics (SumStatistics s) (x, y) = SumStatistics $ s +  (y - x)
+updateStatistics (StdDevStatistics n s var) (x, y) =
+  let !ds = y - x
+      !dss = y * y - x * x
+      !dnnvar = (n * dss - 2 * s * ds) - ds * ds
+      !s' = s + ds
+      !var' = max 0 $ var + dnnvar / (n * n)
+  in StdDevStatistics n s' var'
+
 -- *  Logical functions
 
 -- Avoid syntactic sugar and enhance readability. These functions are proposed
@@ -317,7 +375,7 @@
 -- This is a Linux-specific method as it uses the /proc filesystem.
 newUUID :: IO String
 newUUID = do
-  contents <- readFile C.randomUuidFile
+  contents <- readFile ConstantUtils.randomUuidFile
   return $! rStripSpace $ take 128 contents
 
 -- | Returns the current time as an 'Integer' representing the number
diff --git a/src/ganeti-mond.hs b/src/ganeti-mond.hs
index a7d5541..57c2e9b 100644
--- a/src/ganeti-mond.hs
+++ b/src/ganeti-mond.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/hconfd.hs b/src/hconfd.hs
index ff10cee..88e3344 100644
--- a/src/hconfd.hs
+++ b/src/hconfd.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/hluxid.hs b/src/hluxid.hs
index b4e9e54..f0b7af4 100644
--- a/src/hluxid.hs
+++ b/src/hluxid.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/hs2py.hs b/src/hs2py.hs
new file mode 100644
index 0000000..b1e57a6
--- /dev/null
+++ b/src/hs2py.hs
@@ -0,0 +1,50 @@
+{-| Haskell to Python opcode generation program.
+
+-}
+
+{-
+
+Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+import Ganeti.Hs2Py.GenOpCodes
+import Ganeti.Hs2Py.ListConstants
+
+import System.Environment (getArgs)
+import System.Exit (exitFailure)
+import System.IO (hPutStrLn, stderr)
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    ["--opcodes"] -> putStrLn showPyClasses
+    ["--constants"] -> putConstants
+    _ -> do
+      hPutStrLn stderr "Usage: hs2py --opcodes | --constants"
+      exitFailure
diff --git a/src/htools.hs b/src/htools.hs
index 7770b2c..d6d30fe 100644
--- a/src/htools.hs
+++ b/src/htools.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/mon-collector.hs b/src/mon-collector.hs
index d434e32..e0e991b 100644
--- a/src/mon-collector.hs
+++ b/src/mon-collector.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/src/rpc-test.hs b/src/rpc-test.hs
index 40a2073..3b10fc1 100644
--- a/src/rpc-test.hs
+++ b/src/rpc-test.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/data/htools/hbal-dyn.data b/test/data/htools/hbal-dyn.data
new file mode 100644
index 0000000..052cb0f
--- /dev/null
+++ b/test/data/htools/hbal-dyn.data
@@ -0,0 +1,14 @@
+group-01|fake-uuid-01|preferred||
+
+node-01-000|91552|0|91040|3100|3100|32|M|fake-uuid-01|1
+node-01-001|91552|0|91040|3100|3100|32|N|fake-uuid-01|1
+
+inst-00|128|0|1|running|Y|node-01-000||ext||1
+inst-01|128|0|1|running|Y|node-01-000||ext||1
+inst-02|128|0|1|running|Y|node-01-000||ext||1
+inst-03|128|0|1|running|Y|node-01-000||ext||1
+inst-10|256|0|2|running|Y|node-01-001||ext||1
+inst-11|256|0|2|running|Y|node-01-001||ext||1
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/hbal-evac.data b/test/data/htools/hbal-evac.data
new file mode 100644
index 0000000..a1f9d02
--- /dev/null
+++ b/test/data/htools/hbal-evac.data
@@ -0,0 +1,16 @@
+group-01|fake-uuid-01|preferred||
+
+node-E|4000|0|2000|6000|3000|32|Y|fake-uuid-01|4
+node-1|4000|0|3000|6000|4000|32|N|fake-uuid-01|4
+node-2|4000|0|3000|6000|1000|32|N|fake-uuid-01|4
+node-3|4000|0|2000|6000|2000|32|N|fake-uuid-01|4
+
+inst-p1|1000|1000|1|running|Y|node-E|node-1|drbd||1
+inst-p2|1000|1000|1|running|Y|node-E|node-3|drbd||1
+inst-s1|1000|1000|1|running|Y|node-2|node-E|drbd||1
+inst-12|1000|1000|1|running|Y|node-1|node-2|drbd||1
+inst-32a|1000|1000|1|running|Y|node-3|node-2|drbd||1
+inst-32b|1000|1000|1|running|Y|node-3|node-2|drbd||1
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/hspace-groups-one.data b/test/data/htools/hspace-groups-one.data
new file mode 100644
index 0000000..8970b98
--- /dev/null
+++ b/test/data/htools/hspace-groups-one.data
@@ -0,0 +1,14 @@
+group-01|fake-uuid-01|preferred||
+
+node-01-001|2049|0|1|3073|1|16|N|fake-uuid-01|1||N|0|1
+node-01-002|2049|0|1025|3073|1|16|N|fake-uuid-01|1||N|0|1
+node-02-001|2049|0|2049|2049|2049|16|N|fake-uuid-01|1||N|0|1
+node-02-002|2049|0|2049|2049|2049|16|N|fake-uuid-01|1||N|0|1
+
+old-0|1024|1024|1|running|Y|node-01-001|node-01-002|drbd||1|1
+old-1|1024|1024|1|running|Y|node-01-002|node-01-001|drbd||1|1
+old-2|1024|1024|1|running|Y|node-01-001|node-01-002|drbd||1|1
+
+
+|1024,1,1024,1,1,1|1024,1,1024,1,1,1;2048,8,2048,16,8,12|drbd|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/hspace-groups-two.data b/test/data/htools/hspace-groups-two.data
new file mode 100644
index 0000000..97bf62b
--- /dev/null
+++ b/test/data/htools/hspace-groups-two.data
@@ -0,0 +1,16 @@
+group-01|fake-uuid-01|preferred||
+group-02|fake-uuid-02|preferred||
+
+node-01-001|2049|0|1|3073|1|16|N|fake-uuid-01|1||N|0|1
+node-01-002|2049|0|1025|3073|1|16|N|fake-uuid-01|1||N|0|1
+node-02-001|2049|0|2049|2049|2049|16|N|fake-uuid-02|1||N|0|1
+node-02-002|2049|0|2049|2049|2049|16|N|fake-uuid-02|1||N|0|1
+
+old-0|1024|1024|1|running|Y|node-01-001|node-01-002|drbd||1|1
+old-1|1024|1024|1|running|Y|node-01-002|node-01-001|drbd||1|1
+old-2|1024|1024|1|running|Y|node-01-001|node-01-002|drbd||1|1
+
+
+|1024,1,1024,1,1,1|1024,1,1024,1,1,1;2048,8,2048,16,8,12|drbd|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-02|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/instance-prim-sec.txt b/test/data/instance-prim-sec.txt
index a947dfb..5fa6841 100644
--- a/test/data/instance-prim-sec.txt
+++ b/test/data/instance-prim-sec.txt
@@ -12,10 +12,6 @@
              "df9ff3f6-a833-48ff-8bd5-bff2eaeab759.disk0_data"
            ],
            "params": {},
-           "physical_id": [
-             "xenvg",
-             "df9ff3f6-a833-48ff-8bd5-bff2eaeab759.disk0_data"
-           ],
            "size": 1024,
            "uuid": "eaff6322-1bfb-4d59-b306-4535730917cc"
          },
@@ -26,10 +22,6 @@
              "df9ff3f6-a833-48ff-8bd5-bff2eaeab759.disk0_meta"
            ],
            "params": {},
-           "physical_id": [
-             "xenvg",
-             "df9ff3f6-a833-48ff-8bd5-bff2eaeab759.disk0_meta"
-           ],
            "size": 128,
            "uuid": "bf512e95-2a49-4cb3-8d1f-30a503f6bf1b"
          }
@@ -46,14 +38,6 @@
        ],
        "mode": "rw",
        "params": {},
-       "physical_id": [
-         "172.16.241.3",
-         11000,
-         "172.16.241.2",
-         11000,
-         0,
-         "9bdb15fb7ab6bb4610a313d654ed4d0d2433713e"
-       ],
        "size": 1024,
        "uuid": "5d61e205-bf89-4ba8-a319-589b7bb7419e"
      }
diff --git a/test/data/kvm_runtime.json b/test/data/kvm_runtime.json
new file mode 100644
index 0000000..d9fb8c1
--- /dev/null
+++ b/test/data/kvm_runtime.json
@@ -0,0 +1,139 @@
+[
+  [
+    "/usr/bin/kvm",
+    "-name",
+    "xen-test-inst2",
+    "-m",
+    1024,
+    "-smp",
+    "1",
+    "-pidfile",
+    "/var/run/ganeti/kvm-hypervisor/pid/xen-test-inst2",
+    "-balloon",
+    "virtio",
+    "-daemonize",
+    "-machine",
+    "pc-1.1",
+    "-monitor",
+    "unix:/var/run/ganeti/kvm-hypervisor/ctrl/xen-test-inst2.monitor,server,nowait",
+    "-serial",
+    "unix:/var/run/ganeti/kvm-hypervisor/ctrl/xen-test-inst2.serial,server,nowait",
+    "-usb",
+    "-usbdevice",
+    "tablet",
+    "-vnc",
+    ":5100"
+  ],
+  [
+    {
+      "mac": "aa:00:00:bf:2f:16",
+      "nicparams": {
+        "link": "br0",
+        "mode": "bridged"
+      },
+      "pci": 6,
+      "uuid": "003fc157-66a8-4e6d-8b7e-ec4f69751396"
+    }
+  ],
+  {
+    "acpi": true,
+    "boot_order": "disk",
+    "cdrom2_image_path": "",
+    "cdrom_disk_type": "",
+    "cdrom_image_path": "",
+    "cpu_cores": 0,
+    "cpu_mask": "all",
+    "cpu_sockets": 0,
+    "cpu_threads": 0,
+    "cpu_type": "",
+    "disk_cache": "default",
+    "disk_type": "paravirtual",
+    "floppy_image_path": "",
+    "initrd_path": "",
+    "kernel_args": "ro",
+    "kernel_path": "",
+    "keymap": "",
+    "kvm_extra": "",
+    "kvm_flag": "",
+    "kvm_path": "/usr/bin/kvm",
+    "machine_version": "",
+    "mem_path": "",
+    "migration_bandwidth": 32,
+    "migration_downtime": 30,
+    "migration_mode": "live",
+    "migration_port": 8102,
+    "nic_type": "paravirtual",
+    "reboot_behavior": "reboot",
+    "root_path": "/dev/vda1",
+    "security_domain": "",
+    "security_model": "none",
+    "serial_console": true,
+    "serial_speed": 38400,
+    "soundhw": "",
+    "spice_bind": "",
+    "spice_image_compression": "",
+    "spice_ip_version": 0,
+    "spice_jpeg_wan_compression": "",
+    "spice_password_file": "",
+    "spice_playback_compression": true,
+    "spice_streaming_video": "",
+    "spice_tls_ciphers": "HIGH:-DES:-3DES:-EXPORT:-ADH",
+    "spice_use_tls": false,
+    "spice_use_vdagent": true,
+    "spice_zlib_glz_wan_compression": "",
+    "usb_devices": "",
+    "usb_mouse": "",
+    "use_chroot": false,
+    "use_localtime": false,
+    "vga": "",
+    "vhost_net": false,
+    "vnc_bind_address": "0.0.0.0",
+    "vnc_password_file": "",
+    "vnc_tls": false,
+    "vnc_x509_path": "",
+    "vnc_x509_verify": false,
+    "vnet_hdr": true
+  },
+  [
+    [
+      {
+        "dev_type": "lvm",
+        "iv_name": "disk/0",
+        "logical_id": [
+          "autovg",
+          "b9d4ee8e-c81b-42eb-9899-60481886c7ac.disk0"
+        ],
+        "mode": "rw",
+        "name": "disk0",
+        "params": {
+          "stripes": 1
+        },
+        "pci": 4,
+        "size": 1024,
+        "uuid": "7c079136-2573-4112-82d0-0d3d2aa90d8f"
+      },
+      "/var/run/ganeti/instance-disks/xen-test-inst2:0",
+      "rbd://123451214123/"
+    ],
+    [
+      {
+        "dev_type": "lvm",
+        "iv_name": "disk/1",
+        "logical_id": [
+          "autovg",
+          "602c0a3b-d09b-4ebe-9774-5f12ef654a1f.disk1"
+        ],
+        "mode": "rw",
+        "name": "disk1",
+        "params": {
+          "stripes": 1
+        },
+        "pci": 5,
+        "size": 512,
+        "uuid": "9f5c5bd4-6f60-480b-acdc-9bb1a4b7df79"
+      },
+      "/var/run/ganeti/instance-disks/xen-test-inst2:1",
+      null
+    ]
+  ]
+]
diff --git a/test/data/mond-data.txt b/test/data/mond-data.txt
new file mode 100644
index 0000000..0f5447c
--- /dev/null
+++ b/test/data/mond-data.txt
@@ -0,0 +1 @@
+[{"node":"node1.example.com","reports":[{"name":"cpu-avg-load","version":"B","format_version":1,"timestamp":1379507272000000000,"category":null,"kind":0,"data":{"cpu_number":4,"cpus":[4.108859597350646e-2,4.456554528165781e-2,6.203619909502262e-2,5.595448881893895e-2],"cpu_total":0.203643517607712}}]},{"node":"node2.example.com","reports":[{"name":"cpu-avg-load","version":"B","format_version":1,"timestamp":1379507280000000000,"category":null,"kind":0,"data":{"cpu_number":2,"cpus":[4.155409618511363e-3,3.4586452012150787e-3],"cpu_total":7.614031289927129e-3}}]}]
diff --git a/test/hs/Test/AutoConf.hs b/test/hs/Test/AutoConf.hs
new file mode 100644
index 0000000..b43aa20
--- /dev/null
+++ b/test/hs/Test/AutoConf.hs
@@ -0,0 +1,379 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-| Unittests for 'AutoConf'
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Test.AutoConf where
+
+import qualified Data.Char as Char (isAlpha)
+import Test.HUnit as HUnit
+
+import qualified AutoConf
+import qualified Test.Ganeti.TestHelper as TestHelper
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
+
+-- | 'isFilePath x' tests whether @x@ is a valid filepath
+--
+-- A valid filepath must be absolute and must not contain commas.
+isFilePath :: String -> Bool
+isFilePath ('/':str) = ',' `notElem` str
+isFilePath _ = False
+
+-- | 'isGntScript x' tests whether @x@ is a valid Ganeti script
+--
+-- A valid Ganeti script is prefixed by "gnt-" and the rest of the
+-- 'String' contains only alphabetic 'Char's.
+isGntScript :: String -> Bool
+isGntScript str =
+  case span (/= '-') str of
+    (x, '-':y) -> x == "gnt" && all Char.isAlpha y
+    _ -> False
+
+-- | 'isGroup x' tests whether @x@ is a valid group name
+--
+-- A valid group name name is an alphabetic 'String' possibly
+-- containing '-'.
+isGroup :: String -> Bool
+isGroup = all (\c -> Char.isAlpha c || c == '-')
+
+-- | 'isProgram x' tests whether @x@ is a valid program name
+--
+-- A valid program name is an alphabetic 'String'.
+isProgram :: String -> Bool
+isProgram = all Char.isAlpha
+
+-- | 'isUser x' tests whether @x@ is a valid username
+--
+-- See 'isGroup'.
+isUser :: String -> Bool
+isUser = isGroup
+
+case_versionSuffix :: Assertion
+case_versionSuffix =
+  HUnit.assertBool
+    "'versionSuffix' is invalid"
+    (case AutoConf.versionSuffix of
+        "" -> True
+        '~':x -> not (null x)
+        _ -> False)
+
+case_localstatedir :: Assertion
+case_localstatedir =
+  HUnit.assertBool
+    "'localstatedir' is invalid"
+    (isFilePath AutoConf.localstatedir)
+
+case_sysconfdir :: Assertion
+case_sysconfdir =
+  HUnit.assertBool
+    "'sysconfdir' is invalid"
+    (isFilePath AutoConf.sysconfdir)
+
+case_sshConfigDir :: Assertion
+case_sshConfigDir =
+  HUnit.assertBool
+    "'sshConfigDir' is invalid"
+    (isFilePath AutoConf.sshConfigDir)
+
+case_sshLoginUser :: Assertion
+case_sshLoginUser =
+  HUnit.assertBool
+    "'sshLoginUser' is invalid"
+    (isUser AutoConf.sshLoginUser)
+
+case_sshConsoleUser :: Assertion
+case_sshConsoleUser =
+  HUnit.assertBool
+    "'sshConsoleUser' is invalid"
+    (isUser AutoConf.sshConsoleUser)
+
+case_exportDir :: Assertion
+case_exportDir =
+  HUnit.assertBool
+    "'exportDir' is invalid"
+    (isFilePath AutoConf.exportDir)
+
+case_osSearchPath :: Assertion
+case_osSearchPath =
+  HUnit.assertBool
+    "'osSearchPath' is invalid"
+    (all isFilePath AutoConf.osSearchPath)
+
+case_esSearchPath :: Assertion
+case_esSearchPath =
+  HUnit.assertBool
+    "'esSearchPath' is invalid"
+    (all isFilePath AutoConf.esSearchPath)
+
+case_xenBootloader :: Assertion
+case_xenBootloader =
+  HUnit.assertBool
+    "'xenBootloader' is invalid"
+    (null AutoConf.xenBootloader || isFilePath AutoConf.xenBootloader)
+
+case_xenConfigDir :: Assertion
+case_xenConfigDir =
+  HUnit.assertBool
+    "'xenConfigDir' is invalid"
+    (isFilePath AutoConf.xenConfigDir)
+
+case_xenKernel :: Assertion
+case_xenKernel =
+  HUnit.assertBool
+    "'xenKernel' is invalid"
+    (isFilePath AutoConf.xenKernel)
+
+case_xenInitrd :: Assertion
+case_xenInitrd =
+  HUnit.assertBool
+    "'xenInitrd' is invalid"
+    (isFilePath AutoConf.xenInitrd)
+
+case_kvmKernel :: Assertion
+case_kvmKernel =
+  HUnit.assertBool
+    "'kvmKernel' is invalid"
+    (isFilePath AutoConf.kvmKernel)
+
+case_iallocatorSearchPath :: Assertion
+case_iallocatorSearchPath =
+  HUnit.assertBool
+    "'iallocatorSearchPath' is invalid"
+    (all isFilePath AutoConf.iallocatorSearchPath)
+
+case_kvmPath :: Assertion
+case_kvmPath =
+  HUnit.assertBool
+    "'kvmPath' is invalid"
+    (isFilePath AutoConf.kvmPath)
+
+case_ipPath :: Assertion
+case_ipPath =
+  HUnit.assertBool
+    "'ipPath' is invalid"
+    (isFilePath AutoConf.ipPath)
+
+case_socatPath :: Assertion
+case_socatPath =
+  HUnit.assertBool
+    "'socatPath' is invalid"
+    (isFilePath AutoConf.socatPath)
+
+case_toolsdir :: Assertion
+case_toolsdir =
+  HUnit.assertBool
+    "'toolsdir' is invalid"
+    (isFilePath AutoConf.toolsdir)
+
+case_gntScripts :: Assertion
+case_gntScripts =
+  HUnit.assertBool
+    "'gntScripts' is invalid"
+    (all isGntScript AutoConf.gntScripts)
+
+case_htoolsProgs :: Assertion
+case_htoolsProgs =
+  HUnit.assertBool
+    "'htoolsProgs' is invalid"
+    (all isProgram AutoConf.htoolsProgs)
+
+case_pkglibdir :: Assertion
+case_pkglibdir =
+  HUnit.assertBool
+    "'pkglibdir' is invalid"
+    (isFilePath AutoConf.pkglibdir)
+
+case_sharedir :: Assertion
+case_sharedir =
+  HUnit.assertBool
+    "'sharedir' is invalid"
+    (isFilePath AutoConf.sharedir)
+
+case_versionedsharedir :: Assertion
+case_versionedsharedir =
+  HUnit.assertBool
+    "'versionedsharedir' is invalid"
+    (isFilePath AutoConf.versionedsharedir)
+
+case_drbdBarriers :: Assertion
+case_drbdBarriers =
+  HUnit.assertBool
+    "'drbdBarriers' is invalid"
+    (AutoConf.drbdBarriers `elem` ["n", "bf"])
+
+case_syslogUsage :: Assertion
+case_syslogUsage =
+  HUnit.assertBool
+    "'syslogUsage' is invalid"
+    (AutoConf.syslogUsage `elem` ["no", "yes", "only"])
+
+case_daemonsGroup :: Assertion
+case_daemonsGroup =
+  HUnit.assertBool
+    "'daemonsGroup' is invalid"
+    (isGroup AutoConf.daemonsGroup)
+
+case_adminGroup :: Assertion
+case_adminGroup =
+  HUnit.assertBool
+    "'adminGroup' is invalid"
+    (isGroup AutoConf.adminGroup)
+
+case_masterdUser :: Assertion
+case_masterdUser =
+  HUnit.assertBool
+    "'masterdUser' is invalid"
+    (isUser AutoConf.masterdUser)
+
+case_masterdGroup :: Assertion
+case_masterdGroup =
+  HUnit.assertBool
+    "'masterdGroup' is invalid"
+    (isGroup AutoConf.masterdGroup)
+
+case_rapiUser :: Assertion
+case_rapiUser =
+  HUnit.assertBool
+    "'rapiUser' is invalid"
+    (isUser AutoConf.rapiUser)
+
+case_rapiGroup :: Assertion
+case_rapiGroup =
+  HUnit.assertBool
+    "'rapiGroup' is invalid"
+    (isGroup AutoConf.rapiGroup)
+
+case_confdUser :: Assertion
+case_confdUser =
+  HUnit.assertBool
+    "'confdUser' is invalid"
+    (isUser AutoConf.confdUser)
+
+case_confdGroup :: Assertion
+case_confdGroup =
+  HUnit.assertBool
+    "'confdGroup' is invalid"
+    (isGroup AutoConf.confdGroup)
+
+case_luxidUser :: Assertion
+case_luxidUser =
+  HUnit.assertBool
+    "'luxidUser' is invalid"
+    (isUser AutoConf.luxidUser)
+
+case_luxidGroup :: Assertion
+case_luxidGroup =
+  HUnit.assertBool
+    "'luxidGroup' is invalid"
+    (isGroup AutoConf.luxidGroup)
+
+case_nodedUser :: Assertion
+case_nodedUser =
+  HUnit.assertBool
+    "'nodedUser' is invalid"
+    (isUser AutoConf.nodedUser)
+
+case_nodedGroup :: Assertion
+case_nodedGroup =
+  HUnit.assertBool
+    "'nodedGroup' is invalid"
+    (isGroup AutoConf.nodedGroup)
+
+case_mondUser :: Assertion
+case_mondUser =
+  HUnit.assertBool
+    "'mondUser' is invalid"
+    (isUser AutoConf.mondUser)
+
+case_mondGroup :: Assertion
+case_mondGroup =
+  HUnit.assertBool
+    "'mondGroup' is invalid"
+    (isUser AutoConf.mondGroup)
+
+case_diskSeparator :: Assertion
+case_diskSeparator =
+  HUnit.assertBool
+    "'diskSeparator' is invalid"
+    (not (null AutoConf.diskSeparator))
+
+case_qemuimgPath :: Assertion
+case_qemuimgPath =
+  HUnit.assertBool
+    "'qemuimgPath' is invalid"
+    (isFilePath AutoConf.qemuimgPath)
+
+TestHelper.testSuite "AutoConf"
+  [ 'case_versionSuffix
+  , 'case_localstatedir
+  , 'case_sysconfdir
+  , 'case_sshConfigDir
+  , 'case_sshLoginUser
+  , 'case_sshConsoleUser
+  , 'case_exportDir
+  , 'case_osSearchPath
+  , 'case_esSearchPath
+  , 'case_xenBootloader
+  , 'case_xenConfigDir
+  , 'case_xenKernel
+  , 'case_xenInitrd
+  , 'case_kvmKernel
+  , 'case_iallocatorSearchPath
+  , 'case_kvmPath
+  , 'case_ipPath
+  , 'case_socatPath
+  , 'case_toolsdir
+  , 'case_gntScripts
+  , 'case_htoolsProgs
+  , 'case_pkglibdir
+  , 'case_sharedir
+  , 'case_versionedsharedir
+  , 'case_drbdBarriers
+  , 'case_syslogUsage
+  , 'case_daemonsGroup
+  , 'case_adminGroup
+  , 'case_masterdUser
+  , 'case_masterdGroup
+  , 'case_rapiUser
+  , 'case_rapiGroup
+  , 'case_confdUser
+  , 'case_confdGroup
+  , 'case_luxidUser
+  , 'case_luxidGroup
+  , 'case_nodedUser
+  , 'case_nodedGroup
+  , 'case_mondUser
+  , 'case_mondGroup
+  , 'case_diskSeparator
+  , 'case_qemuimgPath ]
diff --git a/test/hs/Test/Ganeti/Attoparsec.hs b/test/hs/Test/Ganeti/Attoparsec.hs
index aec066b..c8cec88 100644
--- a/test/hs/Test/Ganeti/Attoparsec.hs
+++ b/test/hs/Test/Ganeti/Attoparsec.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/BasicTypes.hs b/test/hs/Test/Ganeti/BasicTypes.hs
index d365ab0..60ca398 100644
--- a/test/hs/Test/Ganeti/BasicTypes.hs
+++ b/test/hs/Test/Ganeti/BasicTypes.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Common.hs b/test/hs/Test/Ganeti/Common.hs
index b9a505f..8e12aa3 100644
--- a/test/hs/Test/Ganeti/Common.hs
+++ b/test/hs/Test/Ganeti/Common.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Confd/Types.hs b/test/hs/Test/Ganeti/Confd/Types.hs
index 0c5b40c..3bc7167 100644
--- a/test/hs/Test/Ganeti/Confd/Types.hs
+++ b/test/hs/Test/Ganeti/Confd/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Confd/Utils.hs b/test/hs/Test/Ganeti/Confd/Utils.hs
index 9d27deb..df31197 100644
--- a/test/hs/Test/Ganeti/Confd/Utils.hs
+++ b/test/hs/Test/Ganeti/Confd/Utils.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Constants.hs b/test/hs/Test/Ganeti/Constants.hs
new file mode 100644
index 0000000..dbc2d8d
--- /dev/null
+++ b/test/hs/Test/Ganeti/Constants.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-| Unittests for constants
+
+-}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Test.Ganeti.Constants (testConstants) where
+
+import Test.HUnit (Assertion)
+import qualified Test.HUnit as HUnit
+
+import qualified Ganeti.Constants as Constants
+import qualified Ganeti.ConstantUtils as ConstantUtils
+import qualified Test.Ganeti.TestHelper as TestHelper
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
+
+case_buildVersion :: Assertion
+case_buildVersion = do
+  HUnit.assertBool "Config major lower-bound violation"
+                   (Constants.configMajor >= 0)
+  HUnit.assertBool "Config major upper-bound violation"
+                   (Constants.configMajor <= 99)
+  HUnit.assertBool "Config minor lower-bound violation"
+                   (Constants.configMinor >= 0)
+  HUnit.assertBool "Config minor upper-bound violation"
+                   (Constants.configMinor <= 99)
+  HUnit.assertBool "Config revision lower-bound violation"
+                   (Constants.configRevision >= 0)
+  HUnit.assertBool "Config revision upper-bound violation"
+                   (Constants.configRevision <= 9999)
+  HUnit.assertBool "Config version lower-bound violation"
+                   (Constants.configVersion >= 0)
+  HUnit.assertBool "Config version upper-bound violation"
+                   (Constants.configVersion <= 99999999)
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion 0 0 0) 0
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion 10 10 1010) 10101010
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion 12 34 5678) 12345678
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion 99 99 9999) 99999999
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion
+                     Constants.configMajor
+                     Constants.configMinor
+                     Constants.configRevision) Constants.configVersion
+  HUnit.assertEqual "Build version"
+                    (ConstantUtils.buildVersion
+                     Constants.configMajor
+                     Constants.configMinor
+                     Constants.configRevision) Constants.protocolVersion
+
+TestHelper.testSuite "Constants"
+  [ 'case_buildVersion
+  ]
diff --git a/test/hs/Test/Ganeti/Daemon.hs b/test/hs/Test/Ganeti/Daemon.hs
index e883e39..007120b 100644
--- a/test/hs/Test/Ganeti/Daemon.hs
+++ b/test/hs/Test/Ganeti/Daemon.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Errors.hs b/test/hs/Test/Ganeti/Errors.hs
index 3bf7cac..70f8e38 100644
--- a/test/hs/Test/Ganeti/Errors.hs
+++ b/test/hs/Test/Ganeti/Errors.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Backend/Simu.hs b/test/hs/Test/Ganeti/HTools/Backend/Simu.hs
index 0ad16df..800d260 100644
--- a/test/hs/Test/Ganeti/HTools/Backend/Simu.hs
+++ b/test/hs/Test/Ganeti/HTools/Backend/Simu.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Backend/Text.hs b/test/hs/Test/Ganeti/HTools/Backend/Text.hs
index 1e54a21..6eb6f5f 100644
--- a/test/hs/Test/Ganeti/HTools/Backend/Text.hs
+++ b/test/hs/Test/Ganeti/HTools/Backend/Text.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/CLI.hs b/test/hs/Test/Ganeti/HTools/CLI.hs
index 15881ae..eab6a82 100644
--- a/test/hs/Test/Ganeti/HTools/CLI.hs
+++ b/test/hs/Test/Ganeti/HTools/CLI.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Cluster.hs b/test/hs/Test/Ganeti/HTools/Cluster.hs
index fe63b79..d584049 100644
--- a/test/hs/Test/Ganeti/HTools/Cluster.hs
+++ b/test/hs/Test/Ganeti/HTools/Cluster.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -30,6 +39,7 @@
 
 import Test.QuickCheck hiding (Result)
 
+import Control.Monad (liftM)
 import qualified Data.IntMap as IntMap
 import Data.Maybe
 
@@ -48,6 +58,7 @@
 import qualified Ganeti.HTools.Instance as Instance
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Types as Types
+import qualified Ganeti.Types as Types (EvacMode(..))
 
 {-# ANN module "HLint: ignore Use camelCase" #-}
 
@@ -67,7 +78,9 @@
                       && Node.availCpu node > size * Types.unitCpu
 
 canBalance :: Cluster.Table -> Bool -> Bool -> Bool -> Bool
-canBalance tbl dm im evac = isJust $ Cluster.tryBalance tbl dm im evac 0 0
+canBalance tbl@(Cluster.Table _ _ ini_cv _)  dm im evac =
+  maybe False (\(Cluster.Table _ _ fin_cv _) -> ini_cv - fin_cv > 1e-12)
+  $ Cluster.tryBalance tbl dm im evac False 0 0
 
 -- | Assigns a new fresh instance to a cluster; this is not
 -- allocation, so no resource checks are done.
@@ -146,8 +159,8 @@
                  tbl = Cluster.Table xnl il' cv []
              in printTestCase "Cluster can be balanced after allocation"
                   (not (canBalance tbl True True False)) .&&.
-                printTestCase "Solution score differs from actual node list:"
-                  (Cluster.compCV xnl ==? cv)
+                printTestCase "Solution score differs from actual node list"
+                  (abs (Cluster.compCV xnl - cv) < 1e-12)
 
 -- | Checks that on a 2-5 node cluster, we can allocate a random
 -- instance spec via tiered allocation (whatever the original instance
@@ -156,7 +169,8 @@
 prop_CanTieredAlloc :: Property
 prop_CanTieredAlloc =
   forAll (choose (2, 5)) $ \count ->
-  forAll (genOnlineNode `suchThat` isNodeBig 4) $ \node ->
+  forAll (liftM (Node.setPolicy Types.defIPolicy)
+            (genOnlineNode `suchThat` isNodeBig 5)) $ \node ->
   forAll (genInstanceMaybeBiggerThanNode node) $ \inst ->
   let nl = makeSmallCluster node count
       il = Container.empty
diff --git a/test/hs/Test/Ganeti/HTools/Container.hs b/test/hs/Test/Ganeti/HTools/Container.hs
index ccd94f6..725580e 100644
--- a/test/hs/Test/Ganeti/HTools/Container.hs
+++ b/test/hs/Test/Ganeti/HTools/Container.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/ExtLoader.hs b/test/hs/Test/Ganeti/HTools/ExtLoader.hs
new file mode 100644
index 0000000..40f92d0
--- /dev/null
+++ b/test/hs/Test/Ganeti/HTools/ExtLoader.hs
@@ -0,0 +1,121 @@
+{-| Unittests for the MonD data parse function -}
+
+{-
+
+Copyright (C) 2013 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-}
+
+module Test.Ganeti.HTools.ExtLoader where
+
+import Data.Ratio
+
+import qualified Test.HUnit as HUnit
+import qualified Text.JSON as J
+
+import qualified Ganeti.BasicTypes as BT
+import qualified Ganeti.DataCollectors.CPUload as CPUload
+
+import Ganeti.Cpu.Types (CPUavgload(..))
+import Ganeti.DataCollectors.Types (DCReport(..))
+import Ganeti.HTools.ExtLoader
+import Ganeti.JSON
+import Test.Ganeti.TestCommon
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
+
+-- | Test a MonD data file.
+case_parseMonDData :: HUnit.Assertion
+case_parseMonDData = do
+  let mond_data_file = "mond-data.txt"
+      n1 = "node1.example.com"
+      n2 = "node2.example.com"
+      t1 = 1379507272000000000
+      t2 = 1379507280000000000
+      cpu_number1 = 4
+      cpu_number2 = 2
+      cpus1 = [ 0.04108859597350646,0.04456554528165781
+               , 0.06203619909502262,0.05595448881893895]
+      cpus2 = [0.004155409618511363,0.0034586452012150787]
+      cpu_total1 = 0.203643517607712
+      cpu_total2 = 0.007614031289927129
+      dcr1 = DCReport CPUload.dcName CPUload.dcVersion CPUload.dcFormatVersion
+               t1 CPUload.dcCategory CPUload.dcKind
+               (J.showJSON (CPUavgload cpu_number1 cpus1 cpu_total1))
+      dcr2 = DCReport CPUload.dcName CPUload.dcVersion CPUload.dcFormatVersion
+               t2 CPUload.dcCategory CPUload.dcKind
+               (J.showJSON (CPUavgload cpu_number2 cpus2 cpu_total2))
+      expected_list = [(n1,[dcr1]),(n2,[dcr2])]
+  ans <- readTestData mond_data_file
+  case pMonDData ans of
+    BT.Ok l -> HUnit.assertBool ("Parsing " ++ mond_data_file ++ " failed")
+                 (isAlEqual expected_list l)
+    BT.Bad s -> HUnit.assertFailure $ "Parsing failed: " ++ s
+
+-- | Check for quality two list of tuples.
+isAlEqual :: [(String, [DCReport])] -> [(String, [DCReport])] -> Bool
+isAlEqual a b = and (zipWith tupleIsAlEqual a b)
+
+-- | Check a tuple for quality.
+tupleIsAlEqual :: (String, [DCReport]) -> (String, [DCReport]) -> Bool
+tupleIsAlEqual (na, a) (nb, b) =
+  na == nb
+  && and (zipWith dcReportIsAlmostEqual a b)
+
+-- | Check if two DCReports are equal. Only reports from CPUload Data
+-- Collectors are supported.
+dcReportIsAlmostEqual :: DCReport -> DCReport -> Bool
+dcReportIsAlmostEqual a b =
+  dcReportName a == dcReportName b
+  && dcReportVersion a == dcReportVersion b
+  && dcReportFormatVersion a == dcReportFormatVersion b
+  && dcReportTimestamp a == dcReportTimestamp b
+  && dcReportCategory a == dcReportCategory b
+  && dcReportKind a == dcReportKind b
+  && case () of
+       _ | CPUload.dcName == dcReportName a ->
+             cpuavgloadDataIsAlmostEq (dcReportData a) (dcReportData b)
+         | otherwise -> False
+
+-- | Converts two JSValue objects and compares them.
+cpuavgloadDataIsAlmostEq :: J.JSValue -> J.JSValue -> Bool
+cpuavgloadDataIsAlmostEq a b =
+  case fromJVal a :: BT.Result CPUavgload of
+    BT.Bad _ -> False
+    BT.Ok cavA ->
+      case fromJVal b :: BT.Result CPUavgload of
+           BT.Bad _ -> False
+           BT.Ok cavB -> compareCPUavgload cavA cavB
+
+-- | Compares two CPuavgload objects.
+compareCPUavgload :: CPUavgload -> CPUavgload -> Bool
+compareCPUavgload a b =
+  let relError x y = relativeError x y <= 1e-9
+  in cavCpuNumber a == cavCpuNumber b
+     && relError (cavCpuTotal a) (cavCpuTotal b)
+     && length (cavCpus a) == length (cavCpus b)
+     && and (zipWith relError (cavCpus a) (cavCpus b))
diff --git a/test/hs/Test/Ganeti/HTools/Graph.hs b/test/hs/Test/Ganeti/HTools/Graph.hs
index 31a619a..ad44d71 100644
--- a/test/hs/Test/Ganeti/HTools/Graph.hs
+++ b/test/hs/Test/Ganeti/HTools/Graph.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Instance.hs b/test/hs/Test/Ganeti/HTools/Instance.hs
index 60c6ba1..2791ad7 100644
--- a/test/hs/Test/Ganeti/HTools/Instance.hs
+++ b/test/hs/Test/Ganeti/HTools/Instance.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -39,6 +48,7 @@
 import Control.Monad (liftM)
 import Test.QuickCheck hiding (Result)
 
+import Test.Ganeti.TestHTools (nullISpec)
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 import Test.Ganeti.HTools.Types ()
@@ -52,25 +62,32 @@
 
 -- * Arbitrary instances
 
--- | Generates a random instance with maximum disk/mem/cpu values.
-genInstanceSmallerThan :: Int -> Int -> Int -> Maybe Int ->
-                          Gen Instance.Instance
-genInstanceSmallerThan lim_mem lim_dsk lim_cpu lim_spin = do
+-- | Generates a random instance with maximum and minimum disk/mem/cpu values.
+genInstanceWithin :: Int -> Int -> Int -> Int
+                  -> Int -> Int -> Int -> Maybe Int
+                  -> Gen Instance.Instance
+genInstanceWithin min_mem min_dsk min_cpu min_spin
+                  max_mem max_dsk max_cpu max_spin = do
   name <- genFQDN
-  mem <- choose (0, lim_mem)
-  dsk <- choose (0, lim_dsk)
+  mem <- choose (min_mem, max_mem)
+  dsk <- choose (min_dsk, max_dsk)
   run_st <- arbitrary
   pn <- arbitrary
   sn <- arbitrary
-  vcpus <- choose (0, lim_cpu)
+  vcpus <- choose (min_cpu, max_cpu)
   dt <- arbitrary
-  spindles <- case lim_spin of
-    Nothing -> genMaybe $ choose (0, maxSpindles)
-    Just ls -> liftM Just $ choose (0, ls)
+  spindles <- case max_spin of
+    Nothing -> genMaybe $ choose (min_spin, maxSpindles)
+    Just ls -> liftM Just $ choose (min_spin, ls)
   let disk = Instance.Disk dsk spindles
   return $ Instance.create
     name mem dsk [disk] vcpus run_st [] True pn sn dt 1 []
 
+-- | Generate an instance with maximum disk/mem/cpu values.
+genInstanceSmallerThan :: Int -> Int -> Int -> Maybe Int
+                       -> Gen Instance.Instance
+genInstanceSmallerThan = genInstanceWithin 0 0 0 0
+
 -- | Generates an instance smaller than a node.
 genInstanceSmallerThanNode :: Node.Node -> Gen Instance.Instance
 genInstanceSmallerThanNode node =
@@ -82,12 +99,20 @@
                           else Nothing)
 
 -- | Generates an instance possibly bigger than a node.
+-- In any case, that instance will be bigger than the node's ipolicy's lower
+-- bound.
 genInstanceMaybeBiggerThanNode :: Node.Node -> Gen Instance.Instance
 genInstanceMaybeBiggerThanNode node =
-  genInstanceSmallerThan (Node.availMem  node + Types.unitMem * 2)
-                         (Node.availDisk node + Types.unitDsk * 3)
-                         (Node.availCpu  node + Types.unitCpu * 4)
-                         (if Node.exclStorage node
+  let minISpec = runListHead nullISpec Types.minMaxISpecsMinSpec
+                 . Types.iPolicyMinMaxISpecs $ Node.iPolicy node
+  in genInstanceWithin (Types.iSpecMemorySize minISpec)
+                       (Types.iSpecDiskSize minISpec)
+                       (Types.iSpecCpuCount minISpec)
+                       (Types.iSpecSpindleUse minISpec)
+                       (Node.availMem  node + Types.unitMem * 2)
+                       (Node.availDisk node + Types.unitDsk * 3)
+                       (Node.availCpu  node + Types.unitCpu * 4)
+                       (if Node.exclStorage node
                           then Just $ Node.fSpindles node +
                                Types.unitSpindle * 5
                           else Nothing)
diff --git a/test/hs/Test/Ganeti/HTools/Loader.hs b/test/hs/Test/Ganeti/HTools/Loader.hs
index 673a443..e680bfd 100644
--- a/test/hs/Test/Ganeti/HTools/Loader.hs
+++ b/test/hs/Test/Ganeti/HTools/Loader.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Node.hs b/test/hs/Test/Ganeti/HTools/Node.hs
index 79d26b1..fc63ac5 100644
--- a/test/hs/Test/Ganeti/HTools/Node.hs
+++ b/test/hs/Test/Ganeti/HTools/Node.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/PeerMap.hs b/test/hs/Test/Ganeti/HTools/PeerMap.hs
index 810c1fd..a8b1458 100644
--- a/test/hs/Test/Ganeti/HTools/PeerMap.hs
+++ b/test/hs/Test/Ganeti/HTools/PeerMap.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/HTools/Types.hs b/test/hs/Test/Ganeti/HTools/Types.hs
index 1379878..a3e5276 100644
--- a/test/hs/Test/Ganeti/HTools/Types.hs
+++ b/test/hs/Test/Ganeti/HTools/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -31,7 +40,6 @@
   , Types.AllocPolicy(..)
   , Types.DiskTemplate(..)
   , Types.FailMode(..)
-  , Types.EvacMode(..)
   , Types.ISpec(..)
   , Types.IPolicy(..)
   , nullIPolicy
@@ -41,7 +49,6 @@
 import Test.HUnit
 
 import Control.Applicative
-import Data.List (sort)
 import Control.Monad (replicateM)
 
 import Test.Ganeti.TestHelper
@@ -51,6 +58,7 @@
 
 import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
+import Ganeti.ConstantUtils
 import qualified Ganeti.HTools.Types as Types
 
 {-# ANN module "HLint: ignore Use camelCase" #-}
@@ -61,8 +69,6 @@
 
 $(genArbitrary ''Types.FailMode)
 
-$(genArbitrary ''Types.EvacMode)
-
 instance Arbitrary a => Arbitrary (Types.OpResult a) where
   arbitrary = arbitrary >>= \c ->
               if c
@@ -155,9 +161,6 @@
 prop_IPolicy_serialisation :: Types.IPolicy -> Property
 prop_IPolicy_serialisation = testSerialisation
 
-prop_EvacMode_serialisation :: Types.EvacMode -> Property
-prop_EvacMode_serialisation = testSerialisation
-
 prop_opToResult :: Types.OpResult Int -> Property
 prop_opToResult op =
   case op of
@@ -185,22 +188,21 @@
                  , Types.ArFailover
                  , Types.ArReinstall
                  ]
-      all_hs_raw = map Types.autoRepairTypeToRaw [minBound..maxBound]
+      all_hs_raw = mkSet $ map Types.autoRepairTypeToRaw [minBound..maxBound]
   assertEqual "Haskell order" expected [minBound..maxBound]
   assertEqual "consistent with Python" C.autoRepairAllTypes all_hs_raw
 
 -- | Test 'AutoRepairResult' type is equivalent with Python codebase.
 case_AutoRepairResult_pyequiv :: Assertion
 case_AutoRepairResult_pyequiv = do
-  let all_py_results = sort C.autoRepairAllResults
-      all_hs_results = sort $
+  let all_py_results = C.autoRepairAllResults
+      all_hs_results = mkSet $
                        map Types.autoRepairResultToRaw [minBound..maxBound]
   assertEqual "for AutoRepairResult equivalence" all_py_results all_hs_results
 
 testSuite "HTools/Types"
             [ 'prop_ISpec_serialisation
             , 'prop_IPolicy_serialisation
-            , 'prop_EvacMode_serialisation
             , 'prop_opToResult
             , 'prop_eitherToResult
             , 'case_AutoRepairType_sort
diff --git a/test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs b/test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs
index 2124528..6eb20cc 100644
--- a/test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs
+++ b/test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -120,22 +129,6 @@
     Left msg -> assertFailure $ "Parsing failed: " ++ msg
     Right obtained -> assertEqual fileName expectedContent obtained
 
--- | Computes the relative error of two 'Double' numbers.
---
--- This is the \"relative error\" algorithm in
--- http:\/\/randomascii.wordpress.com\/2012\/02\/25\/
--- comparing-floating-point-numbers-2012-edition (URL split due to too
--- long line).
-relativeError :: Double -> Double -> Double
-relativeError d1 d2 =
-  let delta = abs $ d1 - d2
-      a1 = abs d1
-      a2 = abs d2
-      greatest = max a1 a2
-  in if delta == 0
-       then 0
-       else delta / greatest
-
 -- | Determines whether two LispConfig are equal, with the exception of Double
 -- values, that just need to be \"almost equal\".
 --
diff --git a/test/hs/Test/Ganeti/JQueue.hs b/test/hs/Test/Ganeti/JQueue.hs
index e237336..8c30927 100644
--- a/test/hs/Test/Ganeti/JQueue.hs
+++ b/test/hs/Test/Ganeti/JQueue.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -161,7 +170,7 @@
                Text.JSON.Ok jobs' -> return jobs'
                Error msg ->
                  assertFailure ("Unable to decode jobs: " ++ msg)
-                 -- this already raised an expection, but we need it
+                 -- this already raised an exception, but we need it
                  -- for proper types
                  >> fail "Unable to decode jobs"
   assertEqual "Mismatch in number of returned jobs"
diff --git a/test/hs/Test/Ganeti/JSON.hs b/test/hs/Test/Ganeti/JSON.hs
index 006e195..0356f99 100644
--- a/test/hs/Test/Ganeti/JSON.hs
+++ b/test/hs/Test/Ganeti/JSON.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Jobs.hs b/test/hs/Test/Ganeti/Jobs.hs
index 6391780..118b466 100644
--- a/test/hs/Test/Ganeti/Jobs.hs
+++ b/test/hs/Test/Ganeti/Jobs.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Luxi.hs b/test/hs/Test/Ganeti/Luxi.hs
index 6eb8c12..c9a76fe 100644
--- a/test/hs/Test/Ganeti/Luxi.hs
+++ b/test/hs/Test/Ganeti/Luxi.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -73,8 +82,12 @@
                               listOf genFQDN <*> arbitrary
       Luxi.ReqQueryConfigValues -> Luxi.QueryConfigValues <$> genFields
       Luxi.ReqQueryClusterInfo -> pure Luxi.QueryClusterInfo
-      Luxi.ReqQueryTags -> Luxi.QueryTags <$> arbitrary
+      Luxi.ReqQueryTags -> do
+        kind <- arbitrary
+        Luxi.QueryTags kind <$> genLuxiTagName kind
       Luxi.ReqSubmitJob -> Luxi.SubmitJob <$> resize maxOpCodes arbitrary
+      Luxi.ReqSubmitJobToDrainedQueue -> Luxi.SubmitJobToDrainedQueue <$>
+                                         resize maxOpCodes arbitrary
       Luxi.ReqSubmitManyJobs -> Luxi.SubmitManyJobs <$>
                                 resize maxOpCodes arbitrary
       Luxi.ReqWaitForJobChange -> Luxi.WaitForJobChange <$> arbitrary <*>
diff --git a/test/hs/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs
index 05f2f44..39d9234 100644
--- a/test/hs/Test/Ganeti/Objects.hs
+++ b/test/hs/Test/Ganeti/Objects.hs
@@ -9,21 +9,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -166,7 +175,7 @@
   uuid <- arbitrary
   -- generate some more networks than the given ones
   num_more_nets <- choose (0,3)
-  more_nets <- vectorOf num_more_nets genName
+  more_nets <- vectorOf num_more_nets genUUID
   let genNic net = PartialNic mac ip nicparams net name uuid
       partial_nics = map (genNic . Just)
                          (List.nub (nets ++ more_nets))
diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
index 4b08b24..f61e88f 100644
--- a/test/hs/Test/Ganeti/OpCodes.hs
+++ b/test/hs/Test/Ganeti/OpCodes.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -45,7 +54,7 @@
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 import Test.Ganeti.Types ()
-import Test.Ganeti.Query.Language
+import Test.Ganeti.Query.Language ()
 
 import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
@@ -58,13 +67,23 @@
 
 -- * Arbitrary instances
 
-instance Arbitrary OpCodes.TagObject where
-  arbitrary = oneof [ OpCodes.TagInstance <$> genFQDN
-                    , OpCodes.TagNode     <$> genFQDN
-                    , OpCodes.TagGroup    <$> genFQDN
-                    , OpCodes.TagNetwork  <$> genFQDN
-                    , pure OpCodes.TagCluster
-                    ]
+instance (Ord k, Arbitrary k, Arbitrary a) => Arbitrary (Map.Map k a) where
+  arbitrary = Map.fromList <$> arbitrary
+
+arbitraryOpTagsGet :: Gen OpCodes.OpCode
+arbitraryOpTagsGet = do
+  kind <- arbitrary
+  OpCodes.OpTagsSet kind <$> arbitrary <*> genOpCodesTagName kind
+
+arbitraryOpTagsSet :: Gen OpCodes.OpCode
+arbitraryOpTagsSet = do
+  kind <- arbitrary
+  OpCodes.OpTagsSet kind <$> genTags <*> genOpCodesTagName kind
+
+arbitraryOpTagsDel :: Gen OpCodes.OpCode
+arbitraryOpTagsDel = do
+  kind <- arbitrary
+  OpCodes.OpTagsDel kind <$> genTags <*> genOpCodesTagName kind
 
 $(genArbitrary ''OpCodes.ReplaceDisksMode)
 
@@ -75,7 +94,9 @@
 
 instance Arbitrary INicParams where
   arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
-              genMaybe genNameNE <*> genMaybe genNameNE <*> genMaybe genNameNE
+              genMaybe genNameNE <*> genMaybe genNameNE <*>
+              genMaybe genNameNE <*> genMaybe genNameNE <*>
+              genMaybe genNameNE
 
 instance Arbitrary IDiskParams where
   arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
@@ -110,7 +131,7 @@
     case op_id of
       "OP_TEST_DELAY" ->
         OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
-          genNodeNamesNE <*> return Nothing <*> arbitrary
+          genNodeNamesNE <*> return Nothing <*> arbitrary <*> arbitrary
       "OP_INSTANCE_REPLACE_DISKS" ->
         OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
@@ -118,33 +139,33 @@
       "OP_INSTANCE_FAILOVER" ->
         OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
         arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
-        return Nothing <*> arbitrary <*> genMaybe genNameNE <*> arbitrary
+        return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
       "OP_INSTANCE_MIGRATE" ->
         OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
           return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
           genMaybe genNameNE <*> arbitrary
       "OP_TAGS_GET" ->
-        OpCodes.OpTagsGet <$> arbitrary <*> arbitrary
+        arbitraryOpTagsGet
       "OP_TAGS_SEARCH" ->
         OpCodes.OpTagsSearch <$> genNameNE
       "OP_TAGS_SET" ->
-        OpCodes.OpTagsSet <$> arbitrary <*> genTags
+        arbitraryOpTagsSet
       "OP_TAGS_DEL" ->
-        OpCodes.OpTagsSet <$> arbitrary <*> genTags
+        arbitraryOpTagsDel
       "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
       "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
       "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
       "OP_CLUSTER_VERIFY" ->
         OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
-          genSet Nothing <*> genSet Nothing <*> arbitrary <*>
+          genListSet Nothing <*> genListSet Nothing <*> arbitrary <*>
           genMaybe genNameNE
       "OP_CLUSTER_VERIFY_CONFIG" ->
         OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
-          genSet Nothing <*> arbitrary
+          genListSet Nothing <*> arbitrary
       "OP_CLUSTER_VERIFY_GROUP" ->
         OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
-          arbitrary <*> genSet Nothing <*> genSet Nothing <*> arbitrary
+          arbitrary <*> genListSet Nothing <*> genListSet Nothing <*> arbitrary
       "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
       "OP_GROUP_VERIFY_DISKS" ->
         OpCodes.OpGroupVerifyDisks <$> genNameNE
@@ -156,7 +177,7 @@
         OpCodes.OpClusterRename <$> genNameNE
       "OP_CLUSTER_SET_PARAMS" ->
         OpCodes.OpClusterSetParams <$> arbitrary <*> emptyMUD <*> emptyMUD <*>
-          arbitrary <*> genMaybe (listOf1 arbitrary >>= mkNonEmpty) <*>
+          arbitrary <*> genMaybe arbitrary <*>
           genMaybe genEmptyContainer <*> emptyMUD <*>
           genMaybe genEmptyContainer <*> genMaybe genEmptyContainer <*>
           genMaybe genEmptyContainer <*> genMaybe arbitrary <*>
@@ -165,15 +186,16 @@
           emptyMUD <*> emptyMUD <*> arbitrary <*>
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
-          genMaybe (genName >>= mkNonEmpty) <*>
-          genMaybe (genName >>= mkNonEmpty)
+          genMaybe genName <*>
+          genMaybe genName
       "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
       "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
         pure OpCodes.OpClusterActivateMasterIp
       "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
         pure OpCodes.OpClusterDeactivateMasterIp
       "OP_QUERY" ->
-        OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*> genFilter
+        OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*>
+        pure Nothing
       "OP_QUERY_FIELDS" ->
         OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
       "OP_OOB_COMMAND" ->
@@ -184,7 +206,7 @@
         OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
       "OP_NODE_ADD" ->
         OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
-          genMaybe genName <*> genMaybe genNameNE <*> arbitrary <*>
+          genMaybe genNameNE <*> genMaybe genNameNE <*> arbitrary <*>
           genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
       "OP_NODE_QUERY" ->
         OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
@@ -192,13 +214,13 @@
         OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
       "OP_NODE_QUERY_STORAGE" ->
         OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
-          genNodeNamesNE <*> genNameNE
+          genNodeNamesNE <*> genMaybe genNameNE
       "OP_NODE_MODIFY_STORAGE" ->
         OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
-          arbitrary <*> genNameNE <*> pure emptyJSObject
+          arbitrary <*> genMaybe genNameNE <*> pure emptyJSObject
       "OP_REPAIR_NODE_STORAGE" ->
         OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
-          arbitrary <*> genNameNE <*> arbitrary
+          arbitrary <*> genMaybe genNameNE <*> arbitrary
       "OP_NODE_SET_PARAMS" ->
         OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
           arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
@@ -217,21 +239,19 @@
           genMaybe genNameNE <*> arbitrary
       "OP_INSTANCE_CREATE" ->
         OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
-          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
-          pure emptyJSObject <*> arbitrary <*> genMaybe genNameNE <*>
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
-          genMaybe genNameNE <*>
-          genMaybe genNodeNameNE <*> return Nothing <*>
-          genMaybe genNodeNameNE <*> return Nothing <*>
-          genMaybe (pure []) <*> genMaybe genNodeNameNE <*>
-          arbitrary <*> genMaybe genNodeNameNE <*> return Nothing <*>
-          genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
-          arbitrary <*> arbitrary <*> (genTags >>= mapM mkNonEmpty)
+          pure emptyJSObject <*> arbitrary <*> arbitrary <*> arbitrary <*>
+          genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
+          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> arbitrary <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
+          genMaybe genNameNE <*> genMaybe genNodeNameNE <*> return Nothing <*>
+          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe (pure []) <*>
+          genMaybe genNodeNameNE <*> arbitrary <*> genMaybe genNodeNameNE <*>
+          return Nothing <*> genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
+          arbitrary <*> (genTags >>= mapM mkNonEmpty)
       "OP_INSTANCE_MULTI_ALLOC" ->
-        OpCodes.OpInstanceMultiAlloc <$> genMaybe genNameNE <*> pure [] <*>
-          arbitrary
+        OpCodes.OpInstanceMultiAlloc <$> arbitrary <*> genMaybe genNameNE <*>
+        pure []
       "OP_INSTANCE_REINSTALL" ->
         OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
           arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
@@ -268,7 +288,7 @@
           arbitrary <*> genNodeNamesNE <*> return Nothing <*>
           genMaybe genNameNE
       "OP_INSTANCE_QUERY" ->
-        OpCodes.OpInstanceQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
+        OpCodes.OpInstanceQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
       "OP_INSTANCE_QUERY_DATA" ->
         OpCodes.OpInstanceQueryData <$> arbitrary <*>
           genNodeNamesNE <*> arbitrary
@@ -279,7 +299,7 @@
           pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
           return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
           genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
-          arbitrary <*> arbitrary
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
       "OP_INSTANCE_GROW_DISK" ->
         OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
@@ -306,7 +326,7 @@
         OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
       "OP_GROUP_EVACUATE" ->
         OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
-          genMaybe genNameNE <*> genMaybe genNamesNE
+          genMaybe genNameNE <*> genMaybe genNamesNE <*> arbitrary <*> arbitrary
       "OP_OS_DIAGNOSE" ->
         OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
       "OP_EXT_STORAGE_DIAGNOSE" ->
@@ -324,7 +344,7 @@
         OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
       "OP_TEST_ALLOCATOR" ->
         OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
-          genNameNE <*> pure [] <*> pure [] <*>
+          genNameNE <*> genMaybe (pure []) <*> genMaybe (pure []) <*>
           arbitrary <*> genMaybe genNameNE <*>
           (genTags >>= mapM mkNonEmpty) <*>
           arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
@@ -337,24 +357,24 @@
         OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
           pure J.JSNull <*> pure J.JSNull
       "OP_NETWORK_ADD" ->
-        OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*>
-          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
-          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
+        OpCodes.OpNetworkAdd <$> genNameNE <*> genIPv4Network <*>
+          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
+          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
           arbitrary <*> (genTags >>= mapM mkNonEmpty)
       "OP_NETWORK_REMOVE" ->
         OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
       "OP_NETWORK_SET_PARAMS" ->
         OpCodes.OpNetworkSetParams <$> genNameNE <*>
-          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
-          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
-          genMaybe (listOf genIp4Addr)
+          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
+          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
+          genMaybe (listOf genIPv4Address)
       "OP_NETWORK_CONNECT" ->
         OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
-          arbitrary <*> genNameNE <*> arbitrary
+          arbitrary <*> genNameNE <*> arbitrary <*> arbitrary
       "OP_NETWORK_DISCONNECT" ->
         OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
       "OP_NETWORK_QUERY" ->
-        OpCodes.OpNetworkQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
+        OpCodes.OpNetworkQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
       "OP_RESTRICTED_COMMAND" ->
         OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
           return Nothing <*> genNameNE
@@ -442,8 +462,21 @@
 -- | Check that Python and Haskell defined the same opcode list.
 case_AllDefined :: HUnit.Assertion
 case_AllDefined = do
-  let py_ops = sort C.opcodesOpIds
-      hs_ops = sort OpCodes.allOpIDs
+  py_stdout <-
+     runPython "from ganeti import opcodes\n\
+               \from ganeti import serializer\n\
+               \import sys\n\
+               \print serializer.Dump([opid for opid in opcodes.OP_MAPPING])\n"
+               ""
+     >>= checkPythonResult
+  py_ops <- case J.decode py_stdout::J.Result [String] of
+               J.Ok ops -> return ops
+               J.Error msg ->
+                 HUnit.assertFailure ("Unable to decode opcode names: " ++ msg)
+                 -- this already raised an expection, but we need it
+                 -- for proper types
+                 >> fail "Unable to decode opcode names"
+  let hs_ops = sort OpCodes.allOpIDs
       extra_py = py_ops \\ hs_ops
       extra_hs = hs_ops \\ py_ops
   HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
@@ -482,8 +515,8 @@
         ) opcodes
   py_stdout <-
      runPython "from ganeti import opcodes\n\
-               \import sys\n\
                \from ganeti import serializer\n\
+               \import sys\n\
                \op_data = serializer.Load(sys.stdin.read())\n\
                \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
                \for op in decoded:\n\
@@ -545,19 +578,6 @@
   let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
   in OpCodes.opComment common ==? Just comment
 
--- | Tests wrong tag object building (cluster takes only jsnull, the
--- other take a string, so we test the opposites).
-case_TagObject_fail :: Assertion
-case_TagObject_fail =
-  mapM_ (\(t, j) -> assertEqual (show t ++ "/" ++ J.encode j) Nothing $
-                    tagObjectFrom t j)
-    [ (TagTypeCluster,  J.showJSON "abc")
-    , (TagTypeInstance, J.JSNull)
-    , (TagTypeNode,     J.JSNull)
-    , (TagTypeGroup,    J.JSNull)
-    , (TagTypeNetwork,  J.JSNull)
-    ]
-
 -- | Tests wrong (negative) disk index.
 prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
 prop_mkDiskIndex_fail (Positive i) =
@@ -597,7 +617,6 @@
             , 'case_py_compat_types
             , 'case_py_compat_fields
             , 'prop_setOpComment
-            , 'case_TagObject_fail
             , 'prop_mkDiskIndex_fail
             , 'case_readRecreateDisks_fail
             , 'case_readDdmOldChanges_fail
diff --git a/test/hs/Test/Ganeti/Query/Filter.hs b/test/hs/Test/Ganeti/Query/Filter.hs
index 56d39ec..72adfac 100644
--- a/test/hs/Test/Ganeti/Query/Filter.hs
+++ b/test/hs/Test/Ganeti/Query/Filter.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Query/Language.hs b/test/hs/Test/Ganeti/Query/Language.hs
index 7934edb..98cf5f8 100644
--- a/test/hs/Test/Ganeti/Query/Language.hs
+++ b/test/hs/Test/Ganeti/Query/Language.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Query/Network.hs b/test/hs/Test/Ganeti/Query/Network.hs
index 64efa28..01cbb26 100644
--- a/test/hs/Test/Ganeti/Query/Network.hs
+++ b/test/hs/Test/Ganeti/Query/Network.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -33,7 +42,6 @@
 import Ganeti.JSON
 import Ganeti.Objects
 import Ganeti.Query.Network
-import Ganeti.Types
 
 import Test.Ganeti.Objects
 import Test.Ganeti.TestCommon
@@ -67,9 +75,8 @@
 prop_instIsConnected cfg =
   let nets = (fromContainer . configNetworks) cfg
       net_keys = Map.keys nets
-      net_names = map (fromNonEmpty . networkName) (Map.elems nets)
-  in  forAll (genInstWithNets net_names) $ \inst ->
-      True ==? all (\nk -> instIsConnected cfg nk inst) net_keys
+  in  forAll (genInstWithNets net_keys) $ \inst ->
+      True ==? all (`instIsConnected` inst) net_keys
 
 -- | Tests whether instances that are not connected to a network are
 -- correctly classified as such.
@@ -77,10 +84,9 @@
 prop_instIsConnected_notFound cfg network_uuid =
   let nets = (fromContainer . configNetworks) cfg
       net_keys = Map.keys nets
-      net_names = map (fromNonEmpty . networkName) (Map.elems nets)
   in  notElem network_uuid net_keys ==>
-      forAll (genInstWithNets net_names) $ \inst ->
-        not (instIsConnected cfg network_uuid inst)
+      forAll (genInstWithNets net_keys) $ \inst ->
+        not (instIsConnected network_uuid inst)
 
 testSuite "Query_Network"
   [ 'prop_getGroupConnection
diff --git a/test/hs/Test/Ganeti/Query/Query.hs b/test/hs/Test/Ganeti/Query/Query.hs
index 43efbe2..db3f7bf 100644
--- a/test/hs/Test/Ganeti/Query/Query.hs
+++ b/test/hs/Test/Ganeti/Query/Query.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Rpc.hs b/test/hs/Test/Ganeti/Rpc.hs
index 36afe9f..5b2671a 100644
--- a/test/hs/Test/Ganeti/Rpc.hs
+++ b/test/hs/Test/Ganeti/Rpc.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Runtime.hs b/test/hs/Test/Ganeti/Runtime.hs
index 9c3679b..cc07cda 100644
--- a/test/hs/Test/Ganeti/Runtime.hs
+++ b/test/hs/Test/Ganeti/Runtime.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Ssconf.hs b/test/hs/Test/Ganeti/Ssconf.hs
index 3546018..e7f9738 100644
--- a/test/hs/Test/Ganeti/Ssconf.hs
+++ b/test/hs/Test/Ganeti/Ssconf.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs b/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs
index 38430ec..8193ae9 100644
--- a/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs
+++ b/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Storage/Drbd/Parser.hs b/test/hs/Test/Ganeti/Storage/Drbd/Parser.hs
index 4ecaa2f..b241d16 100644
--- a/test/hs/Test/Ganeti/Storage/Drbd/Parser.hs
+++ b/test/hs/Test/Ganeti/Storage/Drbd/Parser.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Storage/Drbd/Types.hs b/test/hs/Test/Ganeti/Storage/Drbd/Types.hs
index 42e3c50..546d438 100644
--- a/test/hs/Test/Ganeti/Storage/Drbd/Types.hs
+++ b/test/hs/Test/Ganeti/Storage/Drbd/Types.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Storage/Lvm/LVParser.hs b/test/hs/Test/Ganeti/Storage/Lvm/LVParser.hs
index 7c3d7bd..9a00799 100644
--- a/test/hs/Test/Ganeti/Storage/Lvm/LVParser.hs
+++ b/test/hs/Test/Ganeti/Storage/Lvm/LVParser.hs
@@ -6,21 +6,30 @@
 {-
 
 Copyright (C) 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/THH.hs b/test/hs/Test/Ganeti/THH.hs
index 5d0c9d7..f918dce 100644
--- a/test/hs/Test/Ganeti/THH.hs
+++ b/test/hs/Test/Ganeti/THH.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2012 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/TestCommon.hs b/test/hs/Test/Ganeti/TestCommon.hs
index 4246ffe..9e0fcc7 100644
--- a/test/hs/Test/Ganeti/TestCommon.hs
+++ b/test/hs/Test/Ganeti/TestCommon.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -50,12 +59,13 @@
   , SmallRatio(..)
   , genSetHelper
   , genSet
-  , genIp4AddrStr
-  , genIp4Addr
-  , genIp4NetWithNetmask
-  , genIp4Net
+  , genListSet
+  , genIPv4Address
+  , genIPv4Network
   , genIp6Addr
   , genIp6Net
+  , genOpCodesTagName
+  , genLuxiTagName
   , netmask2NumHosts
   , testSerialisation
   , resultProp
@@ -64,6 +74,7 @@
   , testParser
   , genPropParser
   , genNonNegative
+  , relativeError
   ) where
 
 import Control.Applicative
@@ -279,34 +290,36 @@
            newelem <- elements candidates `suchThat` (`Set.notMember` set)
            return (Set.insert newelem set)) Set.empty [1..size']
 
--- | Generates a set of arbitrary elements.
+-- | Generates a 'Set' of arbitrary elements.
 genSet :: (Ord a, Bounded a, Enum a) => Maybe Int -> Gen (Set.Set a)
 genSet = genSetHelper [minBound..maxBound]
 
--- | Generate an arbitrary IPv4 address in textual form (non empty).
-genIp4Addr :: Gen NonEmptyString
-genIp4Addr = genIp4AddrStr >>= mkNonEmpty
+-- | Generates a 'Set' of arbitrary elements wrapped in a 'ListSet'
+genListSet :: (Ord a, Bounded a, Enum a) => Maybe Int
+              -> Gen (BasicTypes.ListSet a)
+genListSet is = BasicTypes.ListSet <$> genSet is
 
 -- | Generate an arbitrary IPv4 address in textual form.
-genIp4AddrStr :: Gen String
-genIp4AddrStr = do
+genIPv4 :: Gen String
+genIPv4 = do
   a <- choose (1::Int, 255)
   b <- choose (0::Int, 255)
   c <- choose (0::Int, 255)
   d <- choose (0::Int, 255)
-  return $ intercalate "." (map show [a, b, c, d])
+  return . intercalate "." $ map show [a, b, c, d]
 
--- | Generates an arbitrary IPv4 address with a given netmask in textual form.
-genIp4NetWithNetmask :: Int -> Gen NonEmptyString
-genIp4NetWithNetmask netmask = do
-  ip <- genIp4AddrStr
-  mkNonEmpty $ ip ++ "/" ++ show netmask
+genIPv4Address :: Gen IPv4Address
+genIPv4Address = mkIPv4Address =<< genIPv4
 
 -- | Generate an arbitrary IPv4 network in textual form.
-genIp4Net :: Gen NonEmptyString
-genIp4Net = do
+genIPv4AddrRange :: Gen String
+genIPv4AddrRange = do
+  ip <- genIPv4
   netmask <- choose (8::Int, 30)
-  genIp4NetWithNetmask netmask
+  return $ ip ++ "/" ++ show netmask
+
+genIPv4Network :: Gen IPv4Network
+genIPv4Network = mkIPv4Network =<< genIPv4AddrRange
 
 -- | Helper function to compute the number of hosts in a network
 -- given the netmask. (For IPv4 only.)
@@ -329,6 +342,18 @@
   ip <- genIp6Addr
   return $ ip ++ "/" ++ show netmask
 
+-- | Generates a valid, arbitrary tag name with respect to the given
+-- 'TagKind' for opcodes.
+genOpCodesTagName :: TagKind -> Gen (Maybe String)
+genOpCodesTagName TagKindCluster = return Nothing
+genOpCodesTagName _ = Just <$> genFQDN
+
+-- | Generates a valid, arbitrary tag name with respect to the given
+-- 'TagKind' for Luxi.
+genLuxiTagName :: TagKind -> Gen String
+genLuxiTagName TagKindCluster = return ""
+genLuxiTagName _ = genFQDN
+
 -- * Helper functions
 
 -- | Checks for serialisation idempotence.
@@ -389,3 +414,19 @@
 genNonNegative :: Gen Int
 genNonNegative =
   fmap fromIntegral (arbitrary::Gen (Test.QuickCheck.NonNegative Int))
+
+-- | Computes the relative error of two 'Double' numbers.
+--
+-- This is the \"relative error\" algorithm in
+-- http:\/\/randomascii.wordpress.com\/2012\/02\/25\/
+-- comparing-floating-point-numbers-2012-edition (URL split due to too
+-- long line).
+relativeError :: Double -> Double -> Double
+relativeError d1 d2 =
+  let delta = abs $ d1 - d2
+      a1 = abs d1
+      a2 = abs d2
+      greatest = max a1 a2
+  in if delta == 0
+       then 0
+       else delta / greatest
diff --git a/test/hs/Test/Ganeti/TestHTools.hs b/test/hs/Test/Ganeti/TestHTools.hs
index 2788aef..2817f2e 100644
--- a/test/hs/Test/Ganeti/TestHTools.hs
+++ b/test/hs/Test/Ganeti/TestHTools.hs
@@ -7,26 +7,36 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
 module Test.Ganeti.TestHTools
   ( nullIPolicy
+  , nullISpec
   , defGroup
   , defGroupList
   , defGroupAssoc
@@ -49,17 +59,21 @@
 
 -- * Helpers
 
+-- | An ISpec with 0 resources.
+nullISpec :: Types.ISpec
+nullISpec = Types.ISpec { Types.iSpecMemorySize = 0
+                        , Types.iSpecCpuCount   = 0
+                        , Types.iSpecDiskSize   = 0
+                        , Types.iSpecDiskCount  = 0
+                        , Types.iSpecNicCount   = 0
+                        , Types.iSpecSpindleUse = 0
+                        }
+
 -- | Null iPolicy, and by null we mean very liberal.
 nullIPolicy :: Types.IPolicy
 nullIPolicy = Types.IPolicy
   { Types.iPolicyMinMaxISpecs = [Types.MinMaxISpecs
-    { Types.minMaxISpecsMinSpec = Types.ISpec { Types.iSpecMemorySize = 0
-                                              , Types.iSpecCpuCount   = 0
-                                              , Types.iSpecDiskSize   = 0
-                                              , Types.iSpecDiskCount  = 0
-                                              , Types.iSpecNicCount   = 0
-                                              , Types.iSpecSpindleUse = 0
-                                              }
+    { Types.minMaxISpecsMinSpec = nullISpec
     , Types.minMaxISpecsMaxSpec = Types.ISpec
       { Types.iSpecMemorySize = maxBound
       , Types.iSpecCpuCount   = maxBound
diff --git a/test/hs/Test/Ganeti/TestHelper.hs b/test/hs/Test/Ganeti/TestHelper.hs
index e600026..399ad58 100644
--- a/test/hs/Test/Ganeti/TestHelper.hs
+++ b/test/hs/Test/Ganeti/TestHelper.hs
@@ -7,21 +7,30 @@
 {-
 
 Copyright (C) 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
diff --git a/test/hs/Test/Ganeti/Types.hs b/test/hs/Test/Ganeti/Types.hs
index c5ece4b..8785eb5 100644
--- a/test/hs/Test/Ganeti/Types.hs
+++ b/test/hs/Test/Ganeti/Types.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -37,7 +46,6 @@
   , JobId(..)
   ) where
 
-import Data.List (sort)
 import Test.QuickCheck as QuickCheck hiding (Result)
 import Test.HUnit
 import qualified Text.JSON as J
@@ -47,6 +55,7 @@
 
 import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
+import qualified Ganeti.ConstantUtils as ConstantUtils
 import Ganeti.Types as Types
 import Ganeti.JSON
 
@@ -102,6 +111,8 @@
 
 $(genArbitrary ''Hypervisor)
 
+$(genArbitrary ''TagKind)
+
 $(genArbitrary ''OobCommand)
 
 -- | Valid storage types.
@@ -113,7 +124,7 @@
 instance Arbitrary StorageType where
   arbitrary = elements allStorageTypes
 
-$(genArbitrary ''NodeEvacMode)
+$(genArbitrary ''EvacMode)
 
 $(genArbitrary ''FileDriver)
 
@@ -249,8 +260,9 @@
 -- | Tests equivalence with Python, based on Constants.hs code.
 case_CVErrorCode_pyequiv :: Assertion
 case_CVErrorCode_pyequiv = do
-  let all_py_codes = sort C.cvAllEcodesStrings
-      all_hs_codes = sort $ map Types.cVErrorCodeToRaw [minBound..maxBound]
+  let all_py_codes = C.cvAllEcodesStrings
+      all_hs_codes = ConstantUtils.mkSet $
+                     map Types.cVErrorCodeToRaw [minBound..maxBound]
   assertEqual "for CVErrorCode equivalence" all_py_codes all_hs_codes
 
 -- | Test 'Hypervisor' serialisation.
@@ -266,7 +278,7 @@
 prop_StorageType_serialisation = testSerialisation
 
 -- | Test 'NodeEvacMode' serialisation.
-prop_NodeEvacMode_serialisation :: NodeEvacMode -> Property
+prop_NodeEvacMode_serialisation :: EvacMode -> Property
 prop_NodeEvacMode_serialisation = testSerialisation
 
 -- | Test 'FileDriver' serialisation.
@@ -296,8 +308,9 @@
 -- | Tests equivalence with Python, based on Constants.hs code.
 case_IAllocatorMode_pyequiv :: Assertion
 case_IAllocatorMode_pyequiv = do
-  let all_py_codes = sort C.validIallocatorModes
-      all_hs_codes = sort $ map Types.iAllocatorModeToRaw [minBound..maxBound]
+  let all_py_codes = C.validIallocatorModes
+      all_hs_codes = ConstantUtils.mkSet $
+                     map Types.iAllocatorModeToRaw [minBound..maxBound]
   assertEqual "for IAllocatorMode equivalence" all_py_codes all_hs_codes
 
 -- | Test 'NICMode' serialisation.
@@ -327,8 +340,9 @@
 -- | Tests equivalence with Python, based on Constants.hs code.
 case_NICMode_pyequiv :: Assertion
 case_NICMode_pyequiv = do
-  let all_py_codes = sort C.nicValidModes
-      all_hs_codes = sort $ map Types.nICModeToRaw [minBound..maxBound]
+  let all_py_codes = C.nicValidModes
+      all_hs_codes = ConstantUtils.mkSet $
+                     map Types.nICModeToRaw [minBound..maxBound]
   assertEqual "for NICMode equivalence" all_py_codes all_hs_codes
 
 -- | Test 'FinalizedJobStatus' serialisation.
@@ -338,9 +352,9 @@
 -- | Tests equivalence with Python, based on Constants.hs code.
 case_FinalizedJobStatus_pyequiv :: Assertion
 case_FinalizedJobStatus_pyequiv = do
-  let all_py_codes = sort C.jobsFinalized
-      all_hs_codes = sort $ map Types.finalizedJobStatusToRaw
-                            [minBound..maxBound]
+  let all_py_codes = C.jobsFinalized
+      all_hs_codes = ConstantUtils.mkSet $
+                     map Types.finalizedJobStatusToRaw [minBound..maxBound]
   assertEqual "for FinalizedJobStatus equivalence" all_py_codes all_hs_codes
 
 -- | Tests JobId serialisation (both from string and ints).
diff --git a/test/hs/Test/Ganeti/Utils.hs b/test/hs/Test/Ganeti/Utils.hs
index 8436cd3..844c215 100644
--- a/test/hs/Test/Ganeti/Utils.hs
+++ b/test/hs/Test/Ganeti/Utils.hs
@@ -8,21 +8,30 @@
 {-
 
 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -318,6 +327,23 @@
         (splitleft, splitright, trail) = splitEithers es
         emptylist = []::[Int]
 
+-- | Test the update function for standard deviations against the naive
+-- implementation.
+prop_stddev_update :: Property
+prop_stddev_update =
+  forAll (choose (0, 6) >>= flip vectorOf (choose (0, 1))) $ \xs ->
+  forAll (choose (0, 1)) $ \a ->
+  forAll (choose (0, 1)) $ \b ->
+  forAll (choose (1, 6) >>= flip vectorOf (choose (0, 1))) $ \ys ->
+  let original = xs ++ [a] ++ ys
+      modified = xs ++ [b] ++ ys
+      with_update = getStatisticValue
+                    $ updateStatistics (getStdDevStatistics original) (a,b)
+      direct = stdDev modified
+  in printTestCase ("Value computed by update " ++ show with_update
+                    ++ " differs too much from correct value " ++ show direct)
+                   (abs (with_update - direct) < 1e-10)
+
 -- | Test list for the Utils module.
 testSuite "Utils"
             [ 'prop_commaJoinSplit
@@ -344,4 +370,5 @@
             , 'prop_chompPrefix_empty_string
             , 'prop_chompPrefix_nothing
             , 'prop_splitRecombineEithers
+            , 'prop_stddev_update
             ]
diff --git a/test/hs/cli-tests-defs.sh b/test/hs/cli-tests-defs.sh
index b33a756..3bdd5ab 100644
--- a/test/hs/cli-tests-defs.sh
+++ b/test/hs/cli-tests-defs.sh
@@ -1,21 +1,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is an shell testing configuration fragment.
 
diff --git a/test/hs/htest.hs b/test/hs/htest.hs
index 9c417bb..bc0b798 100644
--- a/test/hs/htest.hs
+++ b/test/hs/htest.hs
@@ -5,21 +5,30 @@
 {-
 
 Copyright (C) 2009, 2011, 2012, 2013 Google Inc.
+All rights reserved.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
 
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 -}
 
@@ -30,10 +39,12 @@
 import System.Environment (getArgs)
 import System.Log.Logger
 
+import Test.AutoConf
 import Test.Ganeti.TestImports ()
 import Test.Ganeti.Attoparsec
 import Test.Ganeti.BasicTypes
 import Test.Ganeti.Common
+import Test.Ganeti.Constants
 import Test.Ganeti.Confd.Utils
 import Test.Ganeti.Confd.Types
 import Test.Ganeti.Daemon
@@ -87,9 +98,11 @@
 -- | All our defined tests.
 allTests :: [Test]
 allTests =
-  [ testBasicTypes
+  [ testAutoConf
+  , testBasicTypes
   , testAttoparsec
   , testCommon
+  , testConstants
   , testConfd_Types
   , testConfd_Utils
   , testDaemon
diff --git a/test/hs/live-test.sh b/test/hs/live-test.sh
index 78b680f..f97151c 100755
--- a/test/hs/live-test.sh
+++ b/test/hs/live-test.sh
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is a live-testing script for most/all of the htools
 # programs. It needs either to run on a live cluster or access to a
diff --git a/test/hs/offline-test.sh b/test/hs/offline-test.sh
index b3b5427..be137ac 100755
--- a/test/hs/offline-test.sh
+++ b/test/hs/offline-test.sh
@@ -1,21 +1,30 @@
 #!/bin/bash
 
 # Copyright (C) 2012 Google Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # This is an offline testing script for most/all of the htools
 # programs, checking basic command line functionality.
@@ -105,4 +114,4 @@
 echo Running shelltest...
 
 shelltest $SHELLTESTARGS \
-  ${TOP_SRCDIR:-.}/test/hs/shelltests/htools-$TESTS.test
\ No newline at end of file
+  ${TOP_SRCDIR:-.}/test/hs/shelltests/htools-$TESTS.test
diff --git a/test/hs/shelltests/htools-balancing.test b/test/hs/shelltests/htools-balancing.test
index 98b87b4..7208c48 100644
--- a/test/hs/shelltests/htools-balancing.test
+++ b/test/hs/shelltests/htools-balancing.test
@@ -127,3 +127,13 @@
 ./test/hs/hbal -t$TESTDATA_DIR/n1-failure.data -G group-02
 >>>/Group size 0 nodes, 0 instances/
 >>>= 0
+
+# By default, hbal should assume equal, non-zero utilisation
+./test/hs/hbal -t$TESTDATA_DIR/hbal-dyn.data
+>>>/Solution length=1/
+>>>=0
+
+# ...but the --ignore-dynu option should be honored
+./test/hs/hbal -t$TESTDATA_DIR/hbal-dyn.data --ignore-dynu
+>>>/Cluster is already well balanced/
+>>>=0
diff --git a/test/hs/shelltests/htools-hail.test b/test/hs/shelltests/htools-hail.test
index 7bb6478..18ea37c 100644
--- a/test/hs/shelltests/htools-hail.test
+++ b/test/hs/shelltests/htools-hail.test
@@ -108,6 +108,11 @@
 >>> /"success":false,.*FailSpindles: 1"/
 >>>= 0
 
+# check that hail correctly parses admin state
+./test/hs/hail -v -v $TESTDATA_DIR/hail-alloc-drbd.json
+>>>2 /runSt = StatusDown/
+>>>=0
+
 # check that hail can use the simu backend
 ./test/hs/hail --simu p,8,8T,16g,16 $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,/
diff --git a/test/hs/shelltests/htools-hbal-evac.test b/test/hs/shelltests/htools-hbal-evac.test
new file mode 100644
index 0000000..fe06802
--- /dev/null
+++ b/test/hs/shelltests/htools-hbal-evac.test
@@ -0,0 +1,17 @@
+./test/hs/hbal -t $TESTDATA_DIR/hbal-evac.data
+>>>/inst-32. node-3:node-2 => node-2:node-1.*
+(.|
+)*Solution length=4/
+>>>= 0
+
+./test/hs/hbal --evac-mode -t $TESTDATA_DIR/hbal-evac.data
+>>>/a=f r:node-1 f
+(.|
+)*Solution length=3/
+>>>= 0
+
+./test/hs/hbal --evac-mode --restricted-migration -t $TESTDATA_DIR/hbal-evac.data
+>>>/a=f r:node-1
+(.|
+)*Solution length=3/
+>>>= 0
diff --git a/test/hs/shelltests/htools-hspace.test b/test/hs/shelltests/htools-hspace.test
index 59846da..48b5452 100644
--- a/test/hs/shelltests/htools-hspace.test
+++ b/test/hs/shelltests/htools-hspace.test
@@ -56,3 +56,29 @@
 # VCPU-dominated allocation
 ./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-vcpu.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '32768,65536,4,12=4 32768,65536,2,12=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 10"
 >>>=0
+
+# Presence of overfull group
+./test/hs/hspace -t $TESTDATA_DIR/hspace-groups-one.data
+>>>/0 instances allocated/
+>>>=0
+
+./test/hs/hspace --independent-groups -t $TESTDATA_DIR/hspace-groups-one.data
+>>>/0 instances allocated/
+>>>=0
+
+./test/hs/hspace --accept-existing -t $TESTDATA_DIR/hspace-groups-one.data
+>>>/2 instances allocated/
+>>>=0
+
+./test/hs/hspace -t $TESTDATA_DIR/hspace-groups-two.data
+>>>/0 instances allocated/
+>>>=0
+
+./test/hs/hspace --independent-groups -t $TESTDATA_DIR/hspace-groups-two.data
+>>>/2 instances allocated/
+>>>=0
+
+./test/hs/hspace --accept-existing -t $TESTDATA_DIR/hspace-groups-two.data
+>>>/2 instances allocated/
+>>>=0
+
diff --git a/test/py/__init__.py b/test/py/__init__.py
new file mode 100644
index 0000000..afeb9b4
--- /dev/null
+++ b/test/py/__init__.py
@@ -0,0 +1,30 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""This module contains all python test code"""
diff --git a/test/py/bash_completion.bash b/test/py/bash_completion.bash
index 012ec27..0d2d3e3 100755
--- a/test/py/bash_completion.bash
+++ b/test/py/bash_completion.bash
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u -o pipefail
 
diff --git a/test/py/cfgupgrade_unittest.py b/test/py/cfgupgrade_unittest.py
index ca1121c..4d58055 100755
--- a/test/py/cfgupgrade_unittest.py
+++ b/test/py/cfgupgrade_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing tools/cfgupgrade"""
@@ -33,6 +42,8 @@
 from ganeti import serializer
 from ganeti import netutils
 
+from ganeti.utils import version
+
 import testutils
 
 
@@ -40,9 +51,11 @@
   return {
     "version": constants.CONFIG_VERSION,
     "cluster": {
-      "master_node": "node1-uuid"
+      "master_node": "node1-uuid",
+      "ipolicy": None
     },
     "instances": {},
+    "networks": {},
     "nodegroups": {},
     "nodes": {
       "node1-uuid": {
@@ -196,7 +209,7 @@
     self.assertFalse(os.path.exists(os.path.dirname(self.rapi_users_path)))
 
     utils.WriteFile(self.rapi_users_path_pre24, data="some user\n")
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), False)
 
     self.assertTrue(os.path.isdir(os.path.dirname(self.rapi_users_path)))
     self.assert_(os.path.islink(self.rapi_users_path_pre24))
@@ -212,7 +225,7 @@
 
     os.mkdir(os.path.dirname(self.rapi_users_path))
     utils.WriteFile(self.rapi_users_path, data="other user\n")
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), False)
 
     self.assert_(os.path.islink(self.rapi_users_path_pre24))
     self.assert_(os.path.isfile(self.rapi_users_path))
@@ -229,7 +242,7 @@
     os.symlink(self.rapi_users_path, self.rapi_users_path_pre24)
     utils.WriteFile(self.rapi_users_path, data="hello world\n")
 
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 2, 0), False)
 
     self.assert_(os.path.isfile(self.rapi_users_path) and
                  not os.path.islink(self.rapi_users_path))
@@ -248,7 +261,7 @@
     utils.WriteFile(self.rapi_users_path_pre24, data="hello world\n")
 
     self.assertRaises(Exception, self._TestSimpleUpgrade,
-                      constants.BuildVersion(2, 2, 0), False)
+                      version.BuildVersion(2, 2, 0), False)
 
     for path in [self.rapi_users_path, self.rapi_users_path_pre24]:
       self.assert_(os.path.isfile(path) and not os.path.islink(path))
@@ -261,7 +274,7 @@
     self.assertFalse(os.path.exists(self.rapi_users_path_pre24))
 
     utils.WriteFile(self.rapi_users_path_pre24, data="some user\n")
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), True)
 
     self.assertFalse(os.path.isdir(os.path.dirname(self.rapi_users_path)))
     self.assertTrue(os.path.isfile(self.rapi_users_path_pre24) and
@@ -274,7 +287,7 @@
 
     os.mkdir(os.path.dirname(self.rapi_users_path))
     utils.WriteFile(self.rapi_users_path, data="other user\n")
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), True)
 
     self.assertTrue(os.path.isfile(self.rapi_users_path) and
                     not os.path.islink(self.rapi_users_path))
@@ -289,7 +302,7 @@
     os.symlink(self.rapi_users_path, self.rapi_users_path_pre24)
     utils.WriteFile(self.rapi_users_path, data="hello world\n")
 
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 2, 0), True)
 
     self.assertTrue(os.path.islink(self.rapi_users_path_pre24))
     self.assertTrue(os.path.isfile(self.rapi_users_path) and
@@ -302,7 +315,7 @@
   def testFileStoragePathsDryRun(self):
     self.assertFalse(os.path.exists(self.file_storage_paths))
 
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 6, 0), True,
+    self._TestSimpleUpgrade(version.BuildVersion(2, 6, 0), True,
                             file_storage_dir=self.tmpdir,
                             shared_file_storage_dir="/tmp")
 
@@ -311,7 +324,7 @@
   def testFileStoragePathsBoth(self):
     self.assertFalse(os.path.exists(self.file_storage_paths))
 
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 6, 0), False,
+    self._TestSimpleUpgrade(version.BuildVersion(2, 6, 0), False,
                             file_storage_dir=self.tmpdir,
                             shared_file_storage_dir="/tmp")
 
@@ -327,7 +340,7 @@
   def testFileStoragePathsSharedOnly(self):
     self.assertFalse(os.path.exists(self.file_storage_paths))
 
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 5, 0), False,
+    self._TestSimpleUpgrade(version.BuildVersion(2, 5, 0), False,
                             file_storage_dir=None,
                             shared_file_storage_dir=self.tmpdir)
 
@@ -338,28 +351,28 @@
     self.assertFalse(lines)
 
   def testUpgradeFrom_2_0(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 0, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 0, 0), False)
 
   def testUpgradeFrom_2_1(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 1, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 1, 0), False)
 
   def testUpgradeFrom_2_2(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 2, 0), False)
 
   def testUpgradeFrom_2_3(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), False)
 
   def testUpgradeFrom_2_4(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 4, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 4, 0), False)
 
   def testUpgradeFrom_2_5(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 5, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 5, 0), False)
 
   def testUpgradeFrom_2_6(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 6, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 6, 0), False)
 
   def testUpgradeFrom_2_7(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 7, 0), False)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 7, 0), False)
 
   def testUpgradeFullConfigFrom_2_7(self):
     self._TestUpgradeFromFile("cluster_config_2.7.json", False)
@@ -384,27 +397,28 @@
   def testDowngradeFullConfig(self):
     """Test for upgrade + downgrade combination."""
     # This test can work only with the previous version of a configuration!
-    oldconfname = "cluster_config_2.8.json"
+    oldconfname = "cluster_config_2.9.json"
     self._TestUpgradeFromFile(oldconfname, False)
     _RunUpgrade(self.tmpdir, False, True, downgrade=True)
     oldconf = self._LoadTestDataConfig(oldconfname)
     newconf = self._LoadConfig()
+
+    # downgrade from 2.10 to 2.9 does not add physical_id to disks, which is ok
+    # TODO (2.11): Remove this code, it's not required to downgrade from 2.11
+    #              to 2.10
+    def RemovePhysicalId(disk):
+      if "children" in disk:
+        for d in disk["children"]:
+          RemovePhysicalId(d)
+      if "physical_id" in disk:
+        del disk["physical_id"]
+
+    for inst in oldconf["instances"].values():
+      for disk in inst["disks"]:
+        RemovePhysicalId(disk)
+
     self.assertEqual(oldconf, newconf)
 
-  def testDowngradeFrom_2_9(self):
-    cfg29_name = "cluster_config_2.9.json"
-    cfg29 = self._LoadTestDataConfig(cfg29_name)
-    self._CreateValidConfigDir()
-    utils.WriteFile(self.config_path, data=serializer.DumpJson(cfg29))
-    _RunUpgrade(self.tmpdir, False, True, downgrade=True)
-    cfg28 = self._LoadConfig()
-
-    hvparams = cfg28["cluster"]["hvparams"]
-    for xen_variant in [constants.HT_XEN_PVM, constants.HT_XEN_HVM]:
-      xen_params = hvparams[xen_variant]
-      self.assertTrue(constants.HV_XEN_CMD not in xen_params)
-      self.assertTrue(constants.HV_VIF_SCRIPT not in xen_params)
-
   def testDowngradeFullConfigBackwardFrom_2_7(self):
     """Test for upgrade + downgrade + upgrade combination."""
     self._TestUpgradeFromFile("cluster_config_2.7.json", False)
@@ -427,25 +441,25 @@
     self._RunDowngradeTwice()
 
   def testUpgradeDryRunFrom_2_0(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 0, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 0, 0), True)
 
   def testUpgradeDryRunFrom_2_1(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 1, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 1, 0), True)
 
   def testUpgradeDryRunFrom_2_2(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 2, 0), True)
 
   def testUpgradeDryRunFrom_2_3(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 3, 0), True)
 
   def testUpgradeDryRunFrom_2_4(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 4, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 4, 0), True)
 
   def testUpgradeDryRunFrom_2_5(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 5, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 5, 0), True)
 
   def testUpgradeDryRunFrom_2_6(self):
-    self._TestSimpleUpgrade(constants.BuildVersion(2, 6, 0), True)
+    self._TestSimpleUpgrade(version.BuildVersion(2, 6, 0), True)
 
   def testUpgradeCurrentDryRun(self):
     self._TestSimpleUpgrade(constants.CONFIG_VERSION, True)
diff --git a/test/py/check-cert-expired_unittest.bash b/test/py/check-cert-expired_unittest.bash
index fbdba38..609a220 100755
--- a/test/py/check-cert-expired_unittest.bash
+++ b/test/py/check-cert-expired_unittest.bash
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 set -o pipefail
diff --git a/test/py/cmdlib/__init__.py b/test/py/cmdlib/__init__.py
new file mode 100644
index 0000000..04310d2
--- /dev/null
+++ b/test/py/cmdlib/__init__.py
@@ -0,0 +1,30 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""This module contains all cmdlib unit tests"""
diff --git a/test/py/cmdlib/backup_unittest.py b/test/py/cmdlib/backup_unittest.py
new file mode 100644
index 0000000..456e831
--- /dev/null
+++ b/test/py/cmdlib/backup_unittest.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUBackup*"""
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import opcodes
+from ganeti import query
+
+from testsupport import *
+
+import testutils
+
+
+class TestLUBackupQuery(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUBackupQuery, self).setUp()
+
+    self.fields = query._BuildExportFields().keys()
+
+  def testFailingExportList(self):
+    self.rpc.call_export_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddFailedNode(self.master) \
+        .Build()
+    op = opcodes.OpBackupQuery(nodes=[self.master.name])
+    ret = self.ExecOpCode(op)
+    self.assertEqual({self.master.name: False}, ret)
+
+  def testQueryOneNode(self):
+    self.rpc.call_export_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master,
+                           ["mock_export1", "mock_export2"]) \
+        .Build()
+    op = opcodes.OpBackupQuery(nodes=[self.master.name])
+    ret = self.ExecOpCode(op)
+    self.assertEqual({self.master.name: ["mock_export1", "mock_export2"]}, ret)
+
+  def testQueryAllNodes(self):
+    node = self.cfg.AddNewNode()
+    self.rpc.call_export_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, ["mock_export1"]) \
+        .AddSuccessfulNode(node, ["mock_export2"]) \
+        .Build()
+    op = opcodes.OpBackupQuery()
+    ret = self.ExecOpCode(op)
+    self.assertEqual({
+                       self.master.name: ["mock_export1"],
+                       node.name: ["mock_export2"]
+                     }, ret)
+
+
+class TestLUBackupPrepare(CmdlibTestCase):
+  @patchUtils("instance_utils")
+  def testPrepareLocalExport(self, utils):
+    utils.ReadOneLineFile.return_value = "cluster_secret"
+    inst = self.cfg.AddNewInstance()
+    op = opcodes.OpBackupPrepare(instance_name=inst.name,
+                                 mode=constants.EXPORT_MODE_LOCAL)
+    self.ExecOpCode(op)
+
+  @patchUtils("instance_utils")
+  def testPrepareRemoteExport(self, utils):
+    utils.ReadOneLineFile.return_value = "cluster_secret"
+    inst = self.cfg.AddNewInstance()
+    self.rpc.call_x509_cert_create.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(inst.primary_node,
+                                    ("key_name",
+                                     testutils.ReadTestData("cert1.pem")))
+    op = opcodes.OpBackupPrepare(instance_name=inst.name,
+                                 mode=constants.EXPORT_MODE_REMOTE)
+    self.ExecOpCode(op)
+
+
+class TestLUBackupExportBase(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUBackupExportBase, self).setUp()
+
+    self.rpc.call_instance_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, ("/dev/mock_path",
+                                                  "/dev/mock_link_name",
+                                                  None))
+
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, None)
+
+    self.rpc.call_blockdev_snapshot.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, ("mock_vg", "mock_id"))
+
+    self.rpc.call_blockdev_remove.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, None)
+
+    self.rpc.call_export_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, "export_daemon")
+
+    def ImpExpStatus(node_uuid, name):
+      return self.RpcResultsBuilder() \
+               .CreateSuccessfulNodeResult(node_uuid,
+                                           [objects.ImportExportStatus(
+                                             exit_status=0
+                                           )])
+    self.rpc.call_impexp_status.side_effect = ImpExpStatus
+
+    def ImpExpCleanup(node_uuid, name):
+      return self.RpcResultsBuilder() \
+               .CreateSuccessfulNodeResult(node_uuid)
+    self.rpc.call_impexp_cleanup.side_effect = ImpExpCleanup
+
+    self.rpc.call_finalize_export.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, None)
+
+  def testRemoveRunningInstanceWithoutShutdown(self):
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    op = opcodes.OpBackupExport(instance_name=inst.name,
+                                target_node=self.master.name,
+                                shutdown=False,
+                                remove_instance=True)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can not remove instance without shutting it down before")
+
+  def testUnsupportedDiskTemplate(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
+    op = opcodes.OpBackupExport(instance_name=inst.name,
+                                target_node=self.master.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Export not supported for instances with file-based disks")
+
+
+class TestLUBackupExportLocalExport(TestLUBackupExportBase):
+  def setUp(self):
+    super(TestLUBackupExportLocalExport, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+    self.target_node = self.cfg.AddNewNode()
+    self.op = opcodes.OpBackupExport(mode=constants.EXPORT_MODE_LOCAL,
+                                     instance_name=self.inst.name,
+                                     target_node=self.target_node.name)
+
+    self.rpc.call_import_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.target_node, "import_daemon")
+
+  def testExportWithShutdown(self):
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    op = self.CopyOpCode(self.op, instance_name=inst.name, shutdown=True)
+    self.ExecOpCode(op)
+
+  def testExportDeactivatedDisks(self):
+    self.ExecOpCode(self.op)
+
+  def testExportRemoveInstance(self):
+    op = self.CopyOpCode(self.op, remove_instance=True)
+    self.ExecOpCode(op)
+
+
+class TestLUBackupExportRemoteExport(TestLUBackupExportBase):
+  def setUp(self):
+    super(TestLUBackupExportRemoteExport, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+    self.op = opcodes.OpBackupExport(mode=constants.EXPORT_MODE_REMOTE,
+                                     instance_name=self.inst.name,
+                                     target_node=[],
+                                     x509_key_name=["mock_key_name"],
+                                     destination_x509_ca="mock_dest_ca")
+
+  def testRemoteExportWithoutX509KeyName(self):
+    op = self.CopyOpCode(self.op, x509_key_name=self.REMOVE)
+    self.ExecOpCodeExpectOpPrereqError(op,
+                                       "Missing X509 key name for encryption")
+
+  def testRemoteExportWithoutX509DestCa(self):
+    op = self.CopyOpCode(self.op, destination_x509_ca=self.REMOVE)
+    self.ExecOpCodeExpectOpPrereqError(op,
+                                       "Missing destination X509 CA")
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/cluster_unittest.py b/test/py/cmdlib/cluster_unittest.py
new file mode 100644
index 0000000..8c1ccfa
--- /dev/null
+++ b/test/py/cmdlib/cluster_unittest.py
@@ -0,0 +1,2150 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUCluster*
+
+"""
+
+import OpenSSL
+
+import unittest
+import operator
+import os
+import tempfile
+import shutil
+
+from ganeti import constants
+from ganeti import errors
+from ganeti import netutils
+from ganeti import objects
+from ganeti import opcodes
+from ganeti import utils
+from ganeti import pathutils
+from ganeti import query
+from ganeti.cmdlib import cluster
+from ganeti.hypervisor import hv_xen
+
+from testsupport import *
+
+import testutils
+
+
+class TestCertVerification(testutils.GanetiTestCase):
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testVerifyCertificate(self):
+    cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
+
+    nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
+
+    (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
+    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
+
+    # Try to load non-certificate file
+    invalid_cert = testutils.TestDataFilename("bdev-net.txt")
+    (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
+    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
+
+
+class TestClusterVerifySsh(unittest.TestCase):
+  def testMultipleGroups(self):
+    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
+    mygroupnodes = [
+      objects.Node(name="node20", group="my", offline=False),
+      objects.Node(name="node21", group="my", offline=False),
+      objects.Node(name="node22", group="my", offline=False),
+      objects.Node(name="node23", group="my", offline=False),
+      objects.Node(name="node24", group="my", offline=False),
+      objects.Node(name="node25", group="my", offline=False),
+      objects.Node(name="node26", group="my", offline=True),
+      ]
+    nodes = [
+      objects.Node(name="node1", group="g1", offline=True),
+      objects.Node(name="node2", group="g1", offline=False),
+      objects.Node(name="node3", group="g1", offline=False),
+      objects.Node(name="node4", group="g1", offline=True),
+      objects.Node(name="node5", group="g1", offline=False),
+      objects.Node(name="node10", group="xyz", offline=False),
+      objects.Node(name="node11", group="xyz", offline=False),
+      objects.Node(name="node40", group="alloff", offline=True),
+      objects.Node(name="node41", group="alloff", offline=True),
+      objects.Node(name="node50", group="aaa", offline=False),
+      ] + mygroupnodes
+    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
+
+    (online, perhost) = fn(mygroupnodes, "my", nodes)
+    self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
+    self.assertEqual(set(perhost.keys()), set(online))
+
+    self.assertEqual(perhost, {
+      "node20": ["node10", "node2", "node50"],
+      "node21": ["node11", "node3", "node50"],
+      "node22": ["node10", "node5", "node50"],
+      "node23": ["node11", "node2", "node50"],
+      "node24": ["node10", "node3", "node50"],
+      "node25": ["node11", "node5", "node50"],
+      })
+
+  def testSingleGroup(self):
+    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
+    nodes = [
+      objects.Node(name="node1", group="default", offline=True),
+      objects.Node(name="node2", group="default", offline=False),
+      objects.Node(name="node3", group="default", offline=False),
+      objects.Node(name="node4", group="default", offline=True),
+      ]
+    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
+
+    (online, perhost) = fn(nodes, "default", nodes)
+    self.assertEqual(online, ["node2", "node3"])
+    self.assertEqual(set(perhost.keys()), set(online))
+
+    self.assertEqual(perhost, {
+      "node2": [],
+      "node3": [],
+      })
+
+
+class TestLUClusterActivateMasterIp(CmdlibTestCase):
+  def testSuccess(self):
+    op = opcodes.OpClusterActivateMasterIp()
+
+    self.rpc.call_node_activate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+
+    self.ExecOpCode(op)
+
+    self.rpc.call_node_activate_master_ip.assert_called_once_with(
+      self.master_uuid, self.cfg.GetMasterNetworkParameters(), False)
+
+  def testFailure(self):
+    op = opcodes.OpClusterActivateMasterIp()
+
+    self.rpc.call_node_activate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master) \
+
+    self.ExecOpCodeExpectOpExecError(op)
+
+
+class TestLUClusterDeactivateMasterIp(CmdlibTestCase):
+  def testSuccess(self):
+    op = opcodes.OpClusterDeactivateMasterIp()
+
+    self.rpc.call_node_deactivate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+
+    self.ExecOpCode(op)
+
+    self.rpc.call_node_deactivate_master_ip.assert_called_once_with(
+      self.master_uuid, self.cfg.GetMasterNetworkParameters(), False)
+
+  def testFailure(self):
+    op = opcodes.OpClusterDeactivateMasterIp()
+
+    self.rpc.call_node_deactivate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master) \
+
+    self.ExecOpCodeExpectOpExecError(op)
+
+
+class TestLUClusterConfigQuery(CmdlibTestCase):
+  def testInvalidField(self):
+    op = opcodes.OpClusterConfigQuery(output_fields=["pinky_bunny"])
+
+    self.ExecOpCodeExpectOpPrereqError(op, "pinky_bunny")
+
+  def testAllFields(self):
+    op = opcodes.OpClusterConfigQuery(output_fields=query.CLUSTER_FIELDS.keys())
+
+    self.rpc.call_get_watcher_pause.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, -1)
+
+    ret = self.ExecOpCode(op)
+
+    self.assertEqual(1, self.rpc.call_get_watcher_pause.call_count)
+    self.assertEqual(len(ret), len(query.CLUSTER_FIELDS))
+
+  def testEmpytFields(self):
+    op = opcodes.OpClusterConfigQuery(output_fields=[])
+
+    self.ExecOpCode(op)
+
+    self.assertFalse(self.rpc.call_get_watcher_pause.called)
+
+
+class TestLUClusterDestroy(CmdlibTestCase):
+  def testExistingNodes(self):
+    op = opcodes.OpClusterDestroy()
+
+    self.cfg.AddNewNode()
+    self.cfg.AddNewNode()
+
+    self.ExecOpCodeExpectOpPrereqError(op, "still 2 node\(s\)")
+
+  def testExistingInstances(self):
+    op = opcodes.OpClusterDestroy()
+
+    self.cfg.AddNewInstance()
+    self.cfg.AddNewInstance()
+
+    self.ExecOpCodeExpectOpPrereqError(op, "still 2 instance\(s\)")
+
+  def testEmptyCluster(self):
+    op = opcodes.OpClusterDestroy()
+
+    self.ExecOpCode(op)
+
+    self.assertSingleHooksCall([self.master.name],
+                               "cluster-destroy",
+                               constants.HOOKS_PHASE_POST)
+
+
+class TestLUClusterPostInit(CmdlibTestCase):
+  def testExecuion(self):
+    op = opcodes.OpClusterPostInit()
+
+    self.ExecOpCode(op)
+
+    self.assertSingleHooksCall([self.master.uuid],
+                               "cluster-init",
+                               constants.HOOKS_PHASE_POST)
+
+
+class TestLUClusterQuery(CmdlibTestCase):
+  def testSimpleInvocation(self):
+    op = opcodes.OpClusterQuery()
+
+    self.ExecOpCode(op)
+
+  def testIPv6Cluster(self):
+    op = opcodes.OpClusterQuery()
+
+    self.cluster.primary_ip_family = netutils.IP6Address.family
+
+    self.ExecOpCode(op)
+
+
+class TestLUClusterRedistConf(CmdlibTestCase):
+  def testSimpleInvocation(self):
+    op = opcodes.OpClusterRedistConf()
+
+    self.ExecOpCode(op)
+
+
+class TestLUClusterRename(CmdlibTestCase):
+  NEW_NAME = "new-name.example.com"
+  NEW_IP = "203.0.113.100"
+
+  def testNoChanges(self):
+    op = opcodes.OpClusterRename(name=self.cfg.GetClusterName())
+
+    self.ExecOpCodeExpectOpPrereqError(op, "name nor the IP address")
+
+  def testReachableIp(self):
+    op = opcodes.OpClusterRename(name=self.NEW_NAME)
+
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.NEW_NAME, self.NEW_IP)
+    self.netutils_mod.TcpPing.return_value = True
+
+    self.ExecOpCodeExpectOpPrereqError(op, "is reachable on the network")
+
+  def testValidRename(self):
+    op = opcodes.OpClusterRename(name=self.NEW_NAME)
+
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.NEW_NAME, self.NEW_IP)
+
+    self.ExecOpCode(op)
+
+    self.assertEqual(1, self.ssh_mod.WriteKnownHostsFile.call_count)
+    self.rpc.call_node_deactivate_master_ip.assert_called_once_with(
+      self.master_uuid, self.cfg.GetMasterNetworkParameters(), False)
+    self.rpc.call_node_activate_master_ip.assert_called_once_with(
+      self.master_uuid, self.cfg.GetMasterNetworkParameters(), False)
+
+  def testRenameOfflineMaster(self):
+    op = opcodes.OpClusterRename(name=self.NEW_NAME)
+
+    self.master.offline = True
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.NEW_NAME, self.NEW_IP)
+
+    self.ExecOpCode(op)
+
+
+class TestLUClusterRepairDiskSizes(CmdlibTestCase):
+  def testNoInstances(self):
+    op = opcodes.OpClusterRepairDiskSizes()
+
+    self.ExecOpCode(op)
+
+  def _SetUpInstanceSingleDisk(self, dev_type=constants.DT_PLAIN):
+    pnode = self.master
+    snode = self.cfg.AddNewNode()
+
+    disk = self.cfg.CreateDisk(dev_type=dev_type,
+                               primary_node=pnode,
+                               secondary_node=snode)
+    inst = self.cfg.AddNewInstance(disks=[disk])
+
+    return (inst, disk)
+
+  def testSingleInstanceOnFailingNode(self):
+    (inst, _) = self._SetUpInstanceSingleDisk()
+    op = opcodes.OpClusterRepairDiskSizes(instances=[inst.name])
+
+    self.rpc.call_blockdev_getdimensions.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("Failure in blockdev_getdimensions")
+
+  def _ExecOpClusterRepairDiskSizes(self, node_data):
+    # not specifying instances repairs all
+    op = opcodes.OpClusterRepairDiskSizes()
+
+    self.rpc.call_blockdev_getdimensions.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, node_data)
+
+    return self.ExecOpCode(op)
+
+  def testInvalidResultData(self):
+    for data in [[], [None], ["invalid"], [("still", "invalid")]]:
+      self.ResetMocks()
+
+      self._SetUpInstanceSingleDisk()
+      self._ExecOpClusterRepairDiskSizes(data)
+
+      self.mcpu.assertLogContainsRegex("ignoring")
+
+  def testCorrectSize(self):
+    self._SetUpInstanceSingleDisk()
+    changed = self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, None)])
+    self.mcpu.assertLogIsEmpty()
+    self.assertEqual(0, len(changed))
+
+  def testWrongSize(self):
+    self._SetUpInstanceSingleDisk()
+    changed = self._ExecOpClusterRepairDiskSizes([(512 * 1024 * 1024, None)])
+    self.assertEqual(1, len(changed))
+
+  def testCorrectDRBD(self):
+    self._SetUpInstanceSingleDisk(dev_type=constants.DT_DRBD8)
+    changed = self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, None)])
+    self.mcpu.assertLogIsEmpty()
+    self.assertEqual(0, len(changed))
+
+  def testWrongDRBDChild(self):
+    (_, disk) = self._SetUpInstanceSingleDisk(dev_type=constants.DT_DRBD8)
+    disk.children[0].size = 512
+    changed = self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, None)])
+    self.assertEqual(1, len(changed))
+
+  def testExclusiveStorageInvalidResultData(self):
+    self._SetUpInstanceSingleDisk()
+    self.master.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, None)])
+
+    self.mcpu.assertLogContainsRegex(
+      "did not return valid spindles information")
+
+  def testExclusiveStorageCorrectSpindles(self):
+    (_, disk) = self._SetUpInstanceSingleDisk()
+    disk.spindles = 1
+    self.master.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    changed = self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, 1)])
+    self.assertEqual(0, len(changed))
+
+  def testExclusiveStorageWrongSpindles(self):
+    self._SetUpInstanceSingleDisk()
+    self.master.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    changed = self._ExecOpClusterRepairDiskSizes([(1024 * 1024 * 1024, 1)])
+    self.assertEqual(1, len(changed))
+
+
+class TestLUClusterSetParams(CmdlibTestCase):
+  UID_POOL = [(10, 1000)]
+
+  def testUidPool(self):
+    op = opcodes.OpClusterSetParams(uid_pool=self.UID_POOL)
+    self.ExecOpCode(op)
+    self.assertEqual(self.UID_POOL, self.cluster.uid_pool)
+
+  def testAddUids(self):
+    old_pool = [(1, 9)]
+    self.cluster.uid_pool = list(old_pool)
+    op = opcodes.OpClusterSetParams(add_uids=self.UID_POOL)
+    self.ExecOpCode(op)
+    self.assertEqual(set(self.UID_POOL + old_pool),
+                     set(self.cluster.uid_pool))
+
+  def testRemoveUids(self):
+    additional_pool = [(1, 9)]
+    self.cluster.uid_pool = self.UID_POOL + additional_pool
+    op = opcodes.OpClusterSetParams(remove_uids=self.UID_POOL)
+    self.ExecOpCode(op)
+    self.assertEqual(additional_pool, self.cluster.uid_pool)
+
+  def testMasterNetmask(self):
+    op = opcodes.OpClusterSetParams(master_netmask=26)
+    self.ExecOpCode(op)
+    self.assertEqual(26, self.cluster.master_netmask)
+
+  def testInvalidDiskparams(self):
+    for diskparams in [{constants.DT_DISKLESS: {constants.LV_STRIPES: 0}},
+                       {constants.DT_DRBD8: {constants.RBD_POOL: "pool"}},
+                       {constants.DT_DRBD8: {constants.RBD_ACCESS: "bunny"}}]:
+      self.ResetMocks()
+      op = opcodes.OpClusterSetParams(diskparams=diskparams)
+      self.ExecOpCodeExpectOpPrereqError(op, "verify diskparams")
+
+  def testValidDiskparams(self):
+    diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool",
+                                     constants.RBD_ACCESS: "kernelspace"}}
+    op = opcodes.OpClusterSetParams(diskparams=diskparams)
+    self.ExecOpCode(op)
+    self.assertEqual(diskparams[constants.DT_RBD],
+                     self.cluster.diskparams[constants.DT_RBD])
+
+  def testMinimalDiskparams(self):
+    diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool"}}
+    self.cluster.diskparams = {}
+    op = opcodes.OpClusterSetParams(diskparams=diskparams)
+    self.ExecOpCode(op)
+    self.assertEqual(diskparams, self.cluster.diskparams)
+
+  def testValidDiskparamsAccess(self):
+    for value in constants.DISK_VALID_ACCESS_MODES:
+      self.ResetMocks()
+      op = opcodes.OpClusterSetParams(diskparams={
+        constants.DT_RBD: {constants.RBD_ACCESS: value}
+      })
+      self.ExecOpCode(op)
+      got = self.cluster.diskparams[constants.DT_RBD][constants.RBD_ACCESS]
+      self.assertEqual(value, got)
+
+  def testInvalidDiskparamsAccess(self):
+    for value in ["default", "pinky_bunny"]:
+      self.ResetMocks()
+      op = opcodes.OpClusterSetParams(diskparams={
+        constants.DT_RBD: {constants.RBD_ACCESS: value}
+      })
+      self.ExecOpCodeExpectOpPrereqError(op, "Invalid value of 'rbd:access'")
+
+  def testUnsetDrbdHelperWithDrbdDisks(self):
+    self.cfg.AddNewInstance(disks=[
+      self.cfg.CreateDisk(dev_type=constants.DT_DRBD8, create_nodes=True)])
+    op = opcodes.OpClusterSetParams(drbd_helper="")
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot disable drbd helper")
+
+  def testFileStorageDir(self):
+    op = opcodes.OpClusterSetParams(file_storage_dir="/random/path")
+    self.ExecOpCode(op)
+
+  def testSetFileStorageDirToCurrentValue(self):
+    op = opcodes.OpClusterSetParams(
+           file_storage_dir=self.cluster.file_storage_dir)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("file storage dir already set to value")
+
+  def testUnsetFileStorageDirFileStorageEnabled(self):
+    self.cfg.SetEnabledDiskTemplates([constants.DT_FILE])
+    op = opcodes.OpClusterSetParams(file_storage_dir='')
+    self.ExecOpCodeExpectOpPrereqError(op, "Unsetting the 'file' storage")
+
+  def testUnsetFileStorageDirFileStorageDisabled(self):
+    self.cfg.SetEnabledDiskTemplates([constants.DT_PLAIN])
+    op = opcodes.OpClusterSetParams(file_storage_dir='')
+    self.ExecOpCode(op)
+
+  def testSetFileStorageDirFileStorageDisabled(self):
+    self.cfg.SetEnabledDiskTemplates([constants.DT_PLAIN])
+    op = opcodes.OpClusterSetParams(file_storage_dir='/some/path/')
+    self.ExecOpCode(op)
+    self.mcpu.assertLogContainsRegex("although file storage is not enabled")
+
+  def testValidDrbdHelper(self):
+    node1 = self.cfg.AddNewNode()
+    node1.offline = True
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, "/bin/true") \
+        .AddOfflineNode(node1) \
+        .Build()
+    op = opcodes.OpClusterSetParams(drbd_helper="/bin/true")
+    self.ExecOpCode(op)
+    self.mcpu.assertLogContainsRegex("Not checking drbd helper on offline node")
+
+  def testDrbdHelperFailingNode(self):
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddFailedNode(self.master) \
+        .Build()
+    op = opcodes.OpClusterSetParams(drbd_helper="/bin/true")
+    self.ExecOpCodeExpectOpPrereqError(op, "Error checking drbd helper")
+
+  def testInvalidDrbdHelper(self):
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, "/bin/false") \
+        .Build()
+    op = opcodes.OpClusterSetParams(drbd_helper="/bin/true")
+    self.ExecOpCodeExpectOpPrereqError(op, "drbd helper is /bin/false")
+
+  def testDrbdHelperWithoutDrbdDiskTemplate(self):
+    drbd_helper = "/bin/random_helper"
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, drbd_helper) \
+        .Build()
+    op = opcodes.OpClusterSetParams(drbd_helper=drbd_helper)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("but did not enable")
+
+  def testResetDrbdHelperDrbdDisabled(self):
+    drbd_helper = ""
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    op = opcodes.OpClusterSetParams(drbd_helper=drbd_helper)
+    self.ExecOpCode(op)
+
+    self.assertEqual(None, self.cluster.drbd_usermode_helper)
+
+  def testResetDrbdHelperDrbdEnabled(self):
+    drbd_helper = ""
+    self.cluster.enabled_disk_templates = [constants.DT_DRBD8]
+    op = opcodes.OpClusterSetParams(drbd_helper=drbd_helper)
+    self.ExecOpCodeExpectOpPrereqError(
+        op, "Cannot disable drbd helper while DRBD is enabled.")
+
+  def testEnableDrbdNoHelper(self):
+    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
+    self.cluster.drbd_usermode_helper = None
+    enabled_disk_templates = [constants.DT_DRBD8]
+    op = opcodes.OpClusterSetParams(
+        enabled_disk_templates=enabled_disk_templates)
+    self.ExecOpCodeExpectOpPrereqError(
+        op, "Cannot enable DRBD without a DRBD usermode helper set")
+
+  def testEnableDrbdHelperSet(self):
+    drbd_helper = "/bin/random_helper"
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, drbd_helper) \
+        .Build()
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    self.cluster.drbd_usermode_helper = drbd_helper
+    enabled_disk_templates = [constants.DT_DRBD8]
+    op = opcodes.OpClusterSetParams(
+        enabled_disk_templates=enabled_disk_templates,
+        ipolicy={constants.IPOLICY_DTS: enabled_disk_templates})
+    self.ExecOpCode(op)
+
+    self.assertEqual(drbd_helper, self.cluster.drbd_usermode_helper)
+
+  def testDrbdHelperAlreadySet(self):
+    drbd_helper = "/bin/true"
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, "/bin/true") \
+        .Build()
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    op = opcodes.OpClusterSetParams(drbd_helper=drbd_helper)
+    self.ExecOpCode(op)
+
+    self.assertEqual(drbd_helper, self.cluster.drbd_usermode_helper)
+    self.mcpu.assertLogContainsRegex("DRBD helper already in desired state")
+
+  def testSetDrbdHelper(self):
+    drbd_helper = "/bin/true"
+    self.rpc.call_drbd_helper.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, "/bin/true") \
+        .Build()
+    self.cluster.drbd_usermode_helper = "/bin/false"
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DRBD8])
+    op = opcodes.OpClusterSetParams(drbd_helper=drbd_helper)
+    self.ExecOpCode(op)
+
+    self.assertEqual(drbd_helper, self.cluster.drbd_usermode_helper)
+
+  def testBeparams(self):
+    beparams = {constants.BE_VCPUS: 32}
+    op = opcodes.OpClusterSetParams(beparams=beparams)
+    self.ExecOpCode(op)
+    self.assertEqual(32, self.cluster
+                           .beparams[constants.PP_DEFAULT][constants.BE_VCPUS])
+
+  def testNdparams(self):
+    ndparams = {constants.ND_EXCLUSIVE_STORAGE: True}
+    op = opcodes.OpClusterSetParams(ndparams=ndparams)
+    self.ExecOpCode(op)
+    self.assertEqual(True, self.cluster
+                             .ndparams[constants.ND_EXCLUSIVE_STORAGE])
+
+  def testNdparamsResetOobProgram(self):
+    ndparams = {constants.ND_OOB_PROGRAM: ""}
+    op = opcodes.OpClusterSetParams(ndparams=ndparams)
+    self.ExecOpCode(op)
+    self.assertEqual(constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM],
+                     self.cluster.ndparams[constants.ND_OOB_PROGRAM])
+
+  def testHvState(self):
+    hv_state = {constants.HT_FAKE: {constants.HVST_CPU_TOTAL: 8}}
+    op = opcodes.OpClusterSetParams(hv_state=hv_state)
+    self.ExecOpCode(op)
+    self.assertEqual(8, self.cluster.hv_state_static
+                          [constants.HT_FAKE][constants.HVST_CPU_TOTAL])
+
+  def testDiskState(self):
+    disk_state = {
+      constants.DT_PLAIN: {
+        "mock_vg": {constants.DS_DISK_TOTAL: 10}
+      }
+    }
+    op = opcodes.OpClusterSetParams(disk_state=disk_state)
+    self.ExecOpCode(op)
+    self.assertEqual(10, self.cluster
+                           .disk_state_static[constants.DT_PLAIN]["mock_vg"]
+                             [constants.DS_DISK_TOTAL])
+
+  def testDefaultIPolicy(self):
+    ipolicy = constants.IPOLICY_DEFAULTS
+    op = opcodes.OpClusterSetParams(ipolicy=ipolicy)
+    self.ExecOpCode(op)
+
+  def testIPolicyNewViolation(self):
+    import ganeti.constants as C
+    ipolicy = C.IPOLICY_DEFAULTS
+    ipolicy[C.ISPECS_MINMAX][0][C.ISPECS_MIN][C.ISPEC_MEM_SIZE] = 128
+    ipolicy[C.ISPECS_MINMAX][0][C.ISPECS_MAX][C.ISPEC_MEM_SIZE] = 128
+
+    self.cfg.AddNewInstance(beparams={C.BE_MINMEM: 512, C.BE_MAXMEM: 512})
+    op = opcodes.OpClusterSetParams(ipolicy=ipolicy)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("instances violate them")
+
+  def testNicparamsNoInstance(self):
+    nicparams = {
+      constants.NIC_LINK: "mock_bridge"
+    }
+    op = opcodes.OpClusterSetParams(nicparams=nicparams)
+    self.ExecOpCode(op)
+
+    self.assertEqual("mock_bridge",
+                     self.cluster.nicparams
+                       [constants.PP_DEFAULT][constants.NIC_LINK])
+
+  def testNicparamsInvalidConf(self):
+    nicparams = {
+      constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
+      constants.NIC_LINK: ""
+    }
+    op = opcodes.OpClusterSetParams(nicparams=nicparams)
+    self.ExecOpCodeExpectException(op, errors.ConfigurationError, "NIC link")
+
+  def testNicparamsInvalidInstanceConf(self):
+    nicparams = {
+      constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
+      constants.NIC_LINK: "mock_bridge"
+    }
+    self.cfg.AddNewInstance(nics=[
+      self.cfg.CreateNic(nicparams={constants.NIC_LINK: None})])
+    op = opcodes.OpClusterSetParams(nicparams=nicparams)
+    self.ExecOpCodeExpectOpPrereqError(op, "Missing bridged NIC link")
+
+  def testNicparamsMissingIp(self):
+    nicparams = {
+      constants.NIC_MODE: constants.NIC_MODE_ROUTED
+    }
+    self.cfg.AddNewInstance()
+    op = opcodes.OpClusterSetParams(nicparams=nicparams)
+    self.ExecOpCodeExpectOpPrereqError(op, "routed NIC with no ip address")
+
+  def testNicparamsWithInstance(self):
+    nicparams = {
+      constants.NIC_LINK: "mock_bridge"
+    }
+    self.cfg.AddNewInstance()
+    op = opcodes.OpClusterSetParams(nicparams=nicparams)
+    self.ExecOpCode(op)
+
+  def testDefaultHvparams(self):
+    hvparams = constants.HVC_DEFAULTS
+    op = opcodes.OpClusterSetParams(hvparams=hvparams)
+    self.ExecOpCode(op)
+
+    self.assertEqual(hvparams, self.cluster.hvparams)
+
+  def testMinimalHvparams(self):
+    hvparams = {
+      constants.HT_FAKE: {
+        constants.HV_MIGRATION_MODE: constants.HT_MIGRATION_NONLIVE
+      }
+    }
+    self.cluster.hvparams = {}
+    op = opcodes.OpClusterSetParams(hvparams=hvparams)
+    self.ExecOpCode(op)
+
+    self.assertEqual(hvparams, self.cluster.hvparams)
+
+  def testOsHvp(self):
+    os_hvp = {
+      "mocked_os": {
+        constants.HT_FAKE: {
+          constants.HV_MIGRATION_MODE: constants.HT_MIGRATION_NONLIVE
+        }
+      },
+      "other_os": constants.HVC_DEFAULTS
+    }
+    op = opcodes.OpClusterSetParams(os_hvp=os_hvp)
+    self.ExecOpCode(op)
+
+    self.assertEqual(constants.HT_MIGRATION_NONLIVE,
+                     self.cluster.os_hvp["mocked_os"][constants.HT_FAKE]
+                       [constants.HV_MIGRATION_MODE])
+    self.assertEqual(constants.HVC_DEFAULTS, self.cluster.os_hvp["other_os"])
+
+  def testRemoveOsHvp(self):
+    os_hvp = {"mocked_os": {constants.HT_FAKE: None}}
+    op = opcodes.OpClusterSetParams(os_hvp=os_hvp)
+    self.ExecOpCode(op)
+
+    assert constants.HT_FAKE not in self.cluster.os_hvp["mocked_os"]
+
+  def testDefaultOsHvp(self):
+    os_hvp = {"mocked_os": constants.HVC_DEFAULTS.copy()}
+    self.cluster.os_hvp = {"mocked_os": {}}
+    op = opcodes.OpClusterSetParams(os_hvp=os_hvp)
+    self.ExecOpCode(op)
+
+    self.assertEqual(os_hvp, self.cluster.os_hvp)
+
+  def testOsparams(self):
+    osparams = {
+      "mocked_os": {
+        "param1": "value1",
+        "param2": None
+      },
+      "other_os": {
+        "param1": None
+      }
+    }
+    self.cluster.osparams = {"other_os": {"param1": "value1"}}
+    op = opcodes.OpClusterSetParams(osparams=osparams)
+    self.ExecOpCode(op)
+
+    self.assertEqual({"mocked_os": {"param1": "value1"}}, self.cluster.osparams)
+
+  def testEnabledHypervisors(self):
+    enabled_hypervisors = [constants.HT_XEN_HVM, constants.HT_XEN_PVM]
+    op = opcodes.OpClusterSetParams(enabled_hypervisors=enabled_hypervisors)
+    self.ExecOpCode(op)
+
+    self.assertEqual(enabled_hypervisors, self.cluster.enabled_hypervisors)
+
+  def testEnabledHypervisorsWithoutHypervisorParams(self):
+    enabled_hypervisors = [constants.HT_FAKE]
+    self.cluster.hvparams = {}
+    op = opcodes.OpClusterSetParams(enabled_hypervisors=enabled_hypervisors)
+    self.ExecOpCode(op)
+
+    self.assertEqual(enabled_hypervisors, self.cluster.enabled_hypervisors)
+    self.assertEqual(constants.HVC_DEFAULTS[constants.HT_FAKE],
+                     self.cluster.hvparams[constants.HT_FAKE])
+
+  @testutils.patch_object(utils, "FindFile")
+  def testValidDefaultIallocator(self, find_file_mock):
+    find_file_mock.return_value = "/random/path"
+    default_iallocator = "/random/path"
+    op = opcodes.OpClusterSetParams(default_iallocator=default_iallocator)
+    self.ExecOpCode(op)
+
+    self.assertEqual(default_iallocator, self.cluster.default_iallocator)
+
+  @testutils.patch_object(utils, "FindFile")
+  def testInvalidDefaultIallocator(self, find_file_mock):
+    find_file_mock.return_value = None
+    default_iallocator = "/random/path"
+    op = opcodes.OpClusterSetParams(default_iallocator=default_iallocator)
+    self.ExecOpCodeExpectOpPrereqError(op, "Invalid default iallocator script")
+
+  def testEnabledDiskTemplates(self):
+    enabled_disk_templates = [constants.DT_DISKLESS, constants.DT_PLAIN]
+    op = opcodes.OpClusterSetParams(
+           enabled_disk_templates=enabled_disk_templates,
+           ipolicy={constants.IPOLICY_DTS: enabled_disk_templates})
+    self.ExecOpCode(op)
+
+    self.assertEqual(enabled_disk_templates,
+                     self.cluster.enabled_disk_templates)
+
+  def testEnabledDiskTemplatesVsIpolicy(self):
+    enabled_disk_templates = [constants.DT_DISKLESS, constants.DT_PLAIN]
+    op = opcodes.OpClusterSetParams(
+           enabled_disk_templates=enabled_disk_templates,
+           ipolicy={constants.IPOLICY_DTS: [constants.DT_FILE]})
+    self.ExecOpCodeExpectOpPrereqError(op, "but not enabled on the cluster")
+
+  def testDisablingDiskTemplatesOfInstances(self):
+    old_disk_templates = [constants.DT_DISKLESS, constants.DT_PLAIN]
+    self.cfg.SetEnabledDiskTemplates(old_disk_templates)
+    self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_PLAIN)])
+    new_disk_templates = [constants.DT_DISKLESS, constants.DT_DRBD8]
+    op = opcodes.OpClusterSetParams(
+           enabled_disk_templates=new_disk_templates,
+           ipolicy={constants.IPOLICY_DTS: new_disk_templates})
+    self.ExecOpCodeExpectOpPrereqError(op, "least one instance using it")
+
+  def testEnabledDiskTemplatesWithoutVgName(self):
+    enabled_disk_templates = [constants.DT_PLAIN]
+    self.cluster.volume_group_name = None
+    op = opcodes.OpClusterSetParams(
+           enabled_disk_templates=enabled_disk_templates)
+    self.ExecOpCodeExpectOpPrereqError(op, "specify a volume group")
+
+  def testDisableDiskTemplateWithExistingInstance(self):
+    enabled_disk_templates = [constants.DT_DISKLESS]
+    self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_PLAIN)])
+    op = opcodes.OpClusterSetParams(
+           enabled_disk_templates=enabled_disk_templates,
+           ipolicy={constants.IPOLICY_DTS: enabled_disk_templates})
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot disable disk template")
+
+  def testVgNameNoLvmDiskTemplateEnabled(self):
+    vg_name = "test_vg"
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.ExecOpCode(op)
+
+    self.assertEqual(vg_name, self.cluster.volume_group_name)
+    self.mcpu.assertLogIsEmpty()
+
+  def testUnsetVgNameWithLvmDiskTemplateEnabled(self):
+    vg_name = ""
+    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot unset volume group")
+
+  def testUnsetVgNameWithLvmInstance(self):
+    vg_name = ""
+    self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_PLAIN)])
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot unset volume group")
+
+  def testUnsetVgNameWithNoLvmDiskTemplateEnabled(self):
+    vg_name = ""
+    self.cfg.SetEnabledDiskTemplates([constants.DT_DISKLESS])
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.ExecOpCode(op)
+
+    self.assertEqual(None, self.cluster.volume_group_name)
+
+  def testVgNameToOldName(self):
+    vg_name = self.cluster.volume_group_name
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("already in desired state")
+
+  def testVgNameWithFailingNode(self):
+    vg_name = "test_vg"
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.rpc.call_vg_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddFailedNode(self.master) \
+        .Build()
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("Error while gathering data on node")
+
+  def testVgNameWithValidNode(self):
+    vg_name = "test_vg"
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.rpc.call_vg_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {vg_name: 1024 * 1024}) \
+        .Build()
+    self.ExecOpCode(op)
+
+  def testVgNameWithTooSmallNode(self):
+    vg_name = "test_vg"
+    op = opcodes.OpClusterSetParams(vg_name=vg_name)
+    self.rpc.call_vg_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {vg_name: 1}) \
+        .Build()
+    self.ExecOpCodeExpectOpPrereqError(op, "too small")
+
+  def testMiscParameters(self):
+    op = opcodes.OpClusterSetParams(candidate_pool_size=123,
+                                    maintain_node_health=True,
+                                    modify_etc_hosts=True,
+                                    prealloc_wipe_disks=True,
+                                    reserved_lvs=["/dev/mock_lv"],
+                                    use_external_mip_script=True)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+    self.assertEqual(123, self.cluster.candidate_pool_size)
+    self.assertEqual(True, self.cluster.maintain_node_health)
+    self.assertEqual(True, self.cluster.modify_etc_hosts)
+    self.assertEqual(True, self.cluster.prealloc_wipe_disks)
+    self.assertEqual(["/dev/mock_lv"], self.cluster.reserved_lvs)
+    self.assertEqual(True, self.cluster.use_external_mip_script)
+
+  def testAddHiddenOs(self):
+    self.cluster.hidden_os = ["hidden1", "hidden2"]
+    op = opcodes.OpClusterSetParams(hidden_os=[(constants.DDM_ADD, "hidden2"),
+                                               (constants.DDM_ADD, "hidden3")])
+    self.ExecOpCode(op)
+
+    self.assertEqual(["hidden1", "hidden2", "hidden3"], self.cluster.hidden_os)
+    self.mcpu.assertLogContainsRegex("OS hidden2 already")
+
+  def testRemoveBlacklistedOs(self):
+    self.cluster.blacklisted_os = ["blisted1", "blisted2"]
+    op = opcodes.OpClusterSetParams(blacklisted_os=[
+                                      (constants.DDM_REMOVE, "blisted2"),
+                                      (constants.DDM_REMOVE, "blisted3")])
+    self.ExecOpCode(op)
+
+    self.assertEqual(["blisted1"], self.cluster.blacklisted_os)
+    self.mcpu.assertLogContainsRegex("OS blisted3 not found")
+
+  def testMasterNetdev(self):
+    master_netdev = "test_dev"
+    op = opcodes.OpClusterSetParams(master_netdev=master_netdev)
+    self.ExecOpCode(op)
+
+    self.assertEqual(master_netdev, self.cluster.master_netdev)
+
+  def testMasterNetdevFailNoForce(self):
+    master_netdev = "test_dev"
+    op = opcodes.OpClusterSetParams(master_netdev=master_netdev)
+    self.rpc.call_node_deactivate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master)
+    self.ExecOpCodeExpectOpExecError(op, "Could not disable the master ip")
+
+  def testMasterNetdevFailForce(self):
+    master_netdev = "test_dev"
+    op = opcodes.OpClusterSetParams(master_netdev=master_netdev,
+                                    force=True)
+    self.rpc.call_node_deactivate_master_ip.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master)
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("Could not disable the master ip")
+
+
+class TestLUClusterVerify(CmdlibTestCase):
+  def testVerifyAllGroups(self):
+    op = opcodes.OpClusterVerify()
+    result = self.ExecOpCode(op)
+
+    self.assertEqual(2, len(result["jobs"]))
+
+  def testVerifyDefaultGroups(self):
+    op = opcodes.OpClusterVerify(group_name="default")
+    result = self.ExecOpCode(op)
+
+    self.assertEqual(1, len(result["jobs"]))
+
+
+class TestLUClusterVerifyConfig(CmdlibTestCase):
+
+  def setUp(self):
+    super(TestLUClusterVerifyConfig, self).setUp()
+
+    self._load_cert_patcher = testutils \
+      .patch_object(OpenSSL.crypto, "load_certificate")
+    self._load_cert_mock = self._load_cert_patcher.start()
+    self._verify_cert_patcher = testutils \
+      .patch_object(utils, "VerifyX509Certificate")
+    self._verify_cert_mock = self._verify_cert_patcher.start()
+    self._read_file_patcher = testutils.patch_object(utils, "ReadFile")
+    self._read_file_mock = self._read_file_patcher.start()
+    self._can_read_patcher = testutils.patch_object(utils, "CanRead")
+    self._can_read_mock = self._can_read_patcher.start()
+
+    self._can_read_mock.return_value = True
+    self._read_file_mock.return_value = True
+    self._verify_cert_mock.return_value = (None, "")
+    self._load_cert_mock.return_value = True
+
+  def tearDown(self):
+    super(TestLUClusterVerifyConfig, self).tearDown()
+
+    self._can_read_patcher.stop()
+    self._read_file_patcher.stop()
+    self._verify_cert_patcher.stop()
+    self._load_cert_patcher.stop()
+
+  def testSuccessfulRun(self):
+    self.cfg.AddNewInstance()
+    op = opcodes.OpClusterVerifyConfig()
+    result = self.ExecOpCode(op)
+
+    self.assertTrue(result)
+
+  def testDanglingNode(self):
+    node = self.cfg.AddNewNode()
+    self.cfg.AddNewInstance(primary_node=node)
+    node.group = "invalid"
+    op = opcodes.OpClusterVerifyConfig()
+    result = self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex(
+      "following nodes \(and their instances\) belong to a non existing group")
+    self.assertFalse(result)
+
+  def testDanglingInstance(self):
+    inst = self.cfg.AddNewInstance()
+    inst.primary_node = "invalid"
+    op = opcodes.OpClusterVerifyConfig()
+    result = self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex(
+      "following instances have a non-existing primary-node")
+    self.assertFalse(result)
+
+
+class TestLUClusterVerifyGroup(CmdlibTestCase):
+  def testEmptyNodeGroup(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpClusterVerifyGroup(group_name=group.name, verbose=True)
+
+    result = self.ExecOpCode(op)
+
+    self.assertTrue(result)
+    self.mcpu.assertLogContainsRegex("Empty node group, skipping verification")
+
+  def testSimpleInvocation(self):
+    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
+
+    self.ExecOpCode(op)
+
+  def testSimpleInvocationWithInstance(self):
+    self.cfg.AddNewInstance(disks=[])
+    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
+
+    self.ExecOpCode(op)
+
+  def testGhostNode(self):
+    group = self.cfg.AddNewNodeGroup()
+    node = self.cfg.AddNewNode(group=group.uuid, offline=True)
+    self.master.offline = True
+    self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                            primary_node=self.master,
+                            secondary_node=node)
+
+    self.rpc.call_blockdev_getmirrorstatus_multi.return_value = \
+      RpcResultsBuilder() \
+        .AddOfflineNode(self.master) \
+        .Build()
+
+    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
+
+    self.ExecOpCode(op)
+
+  def testValidRpcResult(self):
+    self.cfg.AddNewInstance(disks=[])
+
+    self.rpc.call_node_verify.return_value = \
+      RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {}) \
+        .Build()
+
+    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
+
+    self.ExecOpCode(op)
+
+
+class TestLUClusterVerifyGroupMethods(CmdlibTestCase):
+  """Base class for testing individual methods in LUClusterVerifyGroup.
+
+  """
+  def setUp(self):
+    super(TestLUClusterVerifyGroupMethods, self).setUp()
+    self.op = opcodes.OpClusterVerifyGroup(group_name="default")
+
+  def PrepareLU(self, lu):
+    lu._exclusive_storage = False
+    lu.master_node = self.master_uuid
+    lu.group_info = self.group
+    cluster.LUClusterVerifyGroup.all_node_info = \
+      property(fget=lambda _: self.cfg.GetAllNodesInfo())
+
+
+class TestLUClusterVerifyGroupVerifyNode(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    self.assertFalse(lu._VerifyNode(self.master, None))
+    self.assertFalse(lu._VerifyNode(self.master, ""))
+
+  @withLockedLU
+  def testInvalidVersion(self, lu):
+    self.assertFalse(lu._VerifyNode(self.master, {"version": None}))
+    self.assertFalse(lu._VerifyNode(self.master, {"version": ""}))
+    self.assertFalse(lu._VerifyNode(self.master, {
+      "version": (constants.PROTOCOL_VERSION - 1, constants.RELEASE_VERSION)
+    }))
+
+    self.mcpu.ClearLogMessages()
+    self.assertTrue(lu._VerifyNode(self.master, {
+      "version": (constants.PROTOCOL_VERSION, constants.RELEASE_VERSION + "x")
+    }))
+    self.mcpu.assertLogContainsRegex("software version mismatch")
+
+  def _GetValidNodeResult(self, additional_fields):
+    ret = {
+      "version": (constants.PROTOCOL_VERSION, constants.RELEASE_VERSION),
+      constants.NV_NODESETUP: []
+    }
+    ret.update(additional_fields)
+    return ret
+
+  @withLockedLU
+  def testHypervisor(self, lu):
+    lu._VerifyNode(self.master, self._GetValidNodeResult({
+      constants.NV_HYPERVISOR: {
+        constants.HT_XEN_PVM: None,
+        constants.HT_XEN_HVM: "mock error"
+      }
+    }))
+    self.mcpu.assertLogContainsRegex(constants.HT_XEN_HVM)
+    self.mcpu.assertLogContainsRegex("mock error")
+
+  @withLockedLU
+  def testHvParams(self, lu):
+    lu._VerifyNode(self.master, self._GetValidNodeResult({
+      constants.NV_HVPARAMS: [("mock item", constants.HT_XEN_HVM, "mock error")]
+    }))
+    self.mcpu.assertLogContainsRegex(constants.HT_XEN_HVM)
+    self.mcpu.assertLogContainsRegex("mock item")
+    self.mcpu.assertLogContainsRegex("mock error")
+
+  @withLockedLU
+  def testSuccessfulResult(self, lu):
+    self.assertTrue(lu._VerifyNode(self.master, self._GetValidNodeResult({})))
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyNodeTime(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    for ndata in [{}, {constants.NV_TIME: "invalid"}]:
+      self.mcpu.ClearLogMessages()
+      lu._VerifyNodeTime(self.master, ndata, None, None)
+
+      self.mcpu.assertLogContainsRegex("Node returned invalid time")
+
+  @withLockedLU
+  def testNodeDiverges(self, lu):
+    for ntime in [(0, 0), (2000, 0)]:
+      self.mcpu.ClearLogMessages()
+      lu._VerifyNodeTime(self.master, {constants.NV_TIME: ntime}, 1000, 1005)
+
+      self.mcpu.assertLogContainsRegex("Node time diverges")
+
+  @withLockedLU
+  def testSuccessfulResult(self, lu):
+    lu._VerifyNodeTime(self.master, {constants.NV_TIME: (0, 0)}, 0, 5)
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupUpdateVerifyNodeLVM(
+        TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupUpdateVerifyNodeLVM, self).setUp()
+    self.VALID_NRESULT = {
+      constants.NV_VGLIST: {"mock_vg": 30000},
+      constants.NV_PVLIST: [
+        {
+          "name": "mock_pv",
+          "vg_name": "mock_vg",
+          "size": 5000,
+          "free": 2500,
+          "attributes": [],
+          "lv_list": []
+        }
+      ]
+    }
+
+  @withLockedLU
+  def testNoVgName(self, lu):
+    lu._UpdateVerifyNodeLVM(self.master, {}, None, None)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testEmptyNodeResult(self, lu):
+    lu._UpdateVerifyNodeLVM(self.master, {}, "mock_vg", None)
+    self.mcpu.assertLogContainsRegex("unable to check volume groups")
+    self.mcpu.assertLogContainsRegex("Can't get PV list from node")
+
+  @withLockedLU
+  def testValidNodeResult(self, lu):
+    lu._UpdateVerifyNodeLVM(self.master, self.VALID_NRESULT, "mock_vg", None)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testValidNodeResultExclusiveStorage(self, lu):
+    lu._exclusive_storage = True
+    lu._UpdateVerifyNodeLVM(self.master, self.VALID_NRESULT, "mock_vg",
+                            cluster.LUClusterVerifyGroup.NodeImage())
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyGroupDRBDVersion(
+        TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testEmptyNodeResult(self, lu):
+    lu._VerifyGroupDRBDVersion({})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testValidNodeResult(self, lu):
+    lu._VerifyGroupDRBDVersion(
+      RpcResultsBuilder()
+        .AddSuccessfulNode(self.master, {
+          constants.NV_DRBDVERSION: "8.3.0"
+        })
+        .Build())
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testDifferentVersions(self, lu):
+    node1 = self.cfg.AddNewNode()
+    lu._VerifyGroupDRBDVersion(
+      RpcResultsBuilder()
+        .AddSuccessfulNode(self.master, {
+          constants.NV_DRBDVERSION: "8.3.0"
+        })
+        .AddSuccessfulNode(node1, {
+          constants.NV_DRBDVERSION: "8.4.0"
+        })
+        .Build())
+    self.mcpu.assertLogContainsRegex("DRBD version mismatch: 8.3.0")
+    self.mcpu.assertLogContainsRegex("DRBD version mismatch: 8.4.0")
+
+
+class TestLUClusterVerifyGroupVerifyGroupLVM(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testNoVgName(self, lu):
+    lu._VerifyGroupLVM(None, None)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testNoExclusiveStorage(self, lu):
+    lu._VerifyGroupLVM(None, "mock_vg")
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testNoPvInfo(self, lu):
+    lu._exclusive_storage = True
+    nimg = cluster.LUClusterVerifyGroup.NodeImage()
+    lu._VerifyGroupLVM({self.master.uuid: nimg}, "mock_vg")
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testValidPvInfos(self, lu):
+    lu._exclusive_storage = True
+    node2 = self.cfg.AddNewNode()
+    nimg1 = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master.uuid)
+    nimg1.pv_min = 10000
+    nimg1.pv_max = 10010
+    nimg2 = cluster.LUClusterVerifyGroup.NodeImage(uuid=node2.uuid)
+    nimg2.pv_min = 9998
+    nimg2.pv_max = 10005
+    lu._VerifyGroupLVM({self.master.uuid: nimg1, node2.uuid: nimg2}, "mock_vg")
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyNodeBridges(
+        TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testNoBridges(self, lu):
+    lu._VerifyNodeBridges(None, None, None)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testInvalidBridges(self, lu):
+    for ndata in [{}, {constants.NV_BRIDGES: ""}]:
+      self.mcpu.ClearLogMessages()
+      lu._VerifyNodeBridges(self.master, ndata, ["mock_bridge"])
+      self.mcpu.assertLogContainsRegex("not return valid bridge information")
+
+    self.mcpu.ClearLogMessages()
+    lu._VerifyNodeBridges(self.master, {constants.NV_BRIDGES: ["mock_bridge"]},
+                          ["mock_bridge"])
+    self.mcpu.assertLogContainsRegex("missing bridge")
+
+
+class TestLUClusterVerifyGroupVerifyNodeUserScripts(
+        TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testNoUserScripts(self, lu):
+    lu._VerifyNodeUserScripts(self.master, {})
+    self.mcpu.assertLogContainsRegex("did not return user scripts information")
+
+  @withLockedLU
+  def testBrokenUserScripts(self, lu):
+    lu._VerifyNodeUserScripts(self.master,
+                              {constants.NV_USERSCRIPTS: ["script"]})
+    self.mcpu.assertLogContainsRegex("scripts not present or not executable")
+
+
+class TestLUClusterVerifyGroupVerifyNodeNetwork(
+        TestLUClusterVerifyGroupMethods):
+
+  def setUp(self):
+    super(TestLUClusterVerifyGroupVerifyNodeNetwork, self).setUp()
+    self.VALID_NRESULT = {
+      constants.NV_NODELIST: {},
+      constants.NV_NODENETTEST: {},
+      constants.NV_MASTERIP: True
+    }
+
+  @withLockedLU
+  def testEmptyNodeResult(self, lu):
+    lu._VerifyNodeNetwork(self.master, {})
+    self.mcpu.assertLogContainsRegex(
+      "node hasn't returned node ssh connectivity data")
+    self.mcpu.assertLogContainsRegex(
+      "node hasn't returned node tcp connectivity data")
+    self.mcpu.assertLogContainsRegex(
+      "node hasn't returned node master IP reachability data")
+
+  @withLockedLU
+  def testValidResult(self, lu):
+    lu._VerifyNodeNetwork(self.master, self.VALID_NRESULT)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testSshProblem(self, lu):
+    self.VALID_NRESULT.update({
+      constants.NV_NODELIST: {
+        "mock_node": "mock_error"
+      }
+    })
+    lu._VerifyNodeNetwork(self.master, self.VALID_NRESULT)
+    self.mcpu.assertLogContainsRegex("ssh communication with node 'mock_node'")
+
+  @withLockedLU
+  def testTcpProblem(self, lu):
+    self.VALID_NRESULT.update({
+      constants.NV_NODENETTEST: {
+        "mock_node": "mock_error"
+      }
+    })
+    lu._VerifyNodeNetwork(self.master, self.VALID_NRESULT)
+    self.mcpu.assertLogContainsRegex("tcp communication with node 'mock_node'")
+
+  @withLockedLU
+  def testMasterIpNotReachable(self, lu):
+    self.VALID_NRESULT.update({
+      constants.NV_MASTERIP: False
+    })
+    node1 = self.cfg.AddNewNode()
+    lu._VerifyNodeNetwork(self.master, self.VALID_NRESULT)
+    self.mcpu.assertLogContainsRegex(
+      "the master node cannot reach the master IP")
+
+    self.mcpu.ClearLogMessages()
+    lu._VerifyNodeNetwork(node1, self.VALID_NRESULT)
+    self.mcpu.assertLogContainsRegex("cannot reach the master IP")
+
+
+class TestLUClusterVerifyGroupVerifyInstance(TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupVerifyInstance, self).setUp()
+
+    self.node1 = self.cfg.AddNewNode()
+    self.drbd_inst = self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                 primary_node=self.master,
+                                 secondary_node=self.node1)])
+    self.running_inst = self.cfg.AddNewInstance(
+      admin_state=constants.ADMINST_UP, disks_active=True)
+    self.diskless_inst = self.cfg.AddNewInstance(disks=[])
+
+    self.master_img = \
+      cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+    self.master_img.volumes = ["/".join(disk.logical_id)
+                               for inst in [self.running_inst,
+                                            self.diskless_inst]
+                               for disk in inst.disks]
+    self.master_img.volumes.extend(
+      ["/".join(disk.logical_id) for disk in self.drbd_inst.disks[0].children])
+    self.master_img.instances = [self.running_inst.uuid]
+    self.node1_img = \
+      cluster.LUClusterVerifyGroup.NodeImage(uuid=self.node1.uuid)
+    self.node1_img.volumes = \
+      ["/".join(disk.logical_id) for disk in self.drbd_inst.disks[0].children]
+    self.node_imgs = {
+      self.master_uuid: self.master_img,
+      self.node1.uuid: self.node1_img
+    }
+    self.diskstatus = {
+      self.master_uuid: [
+        (True, objects.BlockDevStatus(ldisk_status=constants.LDS_OKAY))
+        for _ in self.running_inst.disks
+      ]
+    }
+
+  @withLockedLU
+  def testDisklessInst(self, lu):
+    lu._VerifyInstance(self.diskless_inst, self.node_imgs, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testOfflineNode(self, lu):
+    self.master_img.offline = True
+    lu._VerifyInstance(self.drbd_inst, self.node_imgs, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testRunningOnOfflineNode(self, lu):
+    self.master_img.offline = True
+    lu._VerifyInstance(self.running_inst, self.node_imgs, {})
+    self.mcpu.assertLogContainsRegex(
+      "instance is marked as running and lives on offline node")
+
+  @withLockedLU
+  def testMissingVolume(self, lu):
+    self.master_img.volumes = []
+    lu._VerifyInstance(self.running_inst, self.node_imgs, {})
+    self.mcpu.assertLogContainsRegex("volume .* missing")
+
+  @withLockedLU
+  def testRunningInstanceOnWrongNode(self, lu):
+    self.master_img.instances = []
+    self.diskless_inst.admin_state = constants.ADMINST_UP
+    lu._VerifyInstance(self.running_inst, self.node_imgs, {})
+    self.mcpu.assertLogContainsRegex("instance not running on its primary node")
+
+  @withLockedLU
+  def testRunningInstanceOnRightNode(self, lu):
+    self.master_img.instances = [self.running_inst.uuid]
+    lu._VerifyInstance(self.running_inst, self.node_imgs, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testValidDiskStatus(self, lu):
+    lu._VerifyInstance(self.running_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testDegradedDiskStatus(self, lu):
+    self.diskstatus[self.master_uuid][0][1].is_degraded = True
+    lu._VerifyInstance(self.running_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogContainsRegex("instance .* is degraded")
+
+  @withLockedLU
+  def testNotOkayDiskStatus(self, lu):
+    self.diskstatus[self.master_uuid][0][1].ldisk_status = constants.LDS_FAULTY
+    lu._VerifyInstance(self.running_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogContainsRegex("instance .* state is 'faulty'")
+
+  @withLockedLU
+  def testExclusiveStorageWithInvalidInstance(self, lu):
+    self.master.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    lu._VerifyInstance(self.drbd_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogContainsRegex(
+      "instance has template drbd, which is not supported")
+
+  @withLockedLU
+  def testExclusiveStorageWithValidInstance(self, lu):
+    self.master.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    self.running_inst.disks[0].spindles = 1
+    lu._VerifyInstance(self.running_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testDrbdInTwoGroups(self, lu):
+    group = self.cfg.AddNewNodeGroup()
+    self.node1.group = group.uuid
+    lu._VerifyInstance(self.drbd_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogContainsRegex(
+      "instance has primary and secondary nodes in different groups")
+
+  @withLockedLU
+  def testOfflineSecondary(self, lu):
+    self.node1_img.offline = True
+    lu._VerifyInstance(self.drbd_inst, self.node_imgs, self.diskstatus)
+    self.mcpu.assertLogContainsRegex("instance has offline secondary node\(s\)")
+
+
+class TestLUClusterVerifyGroupVerifyOrphanVolumes(
+        TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testOrphanedVolume(self, lu):
+    master_img = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+    master_img.volumes = ["mock_vg/disk_0", "mock_vg/disk_1", "mock_vg/disk_2"]
+    node_imgs = {
+      self.master_uuid: master_img
+    }
+    node_vol_should = {
+      self.master_uuid: ["mock_vg/disk_0"]
+    }
+
+    lu._VerifyOrphanVolumes(node_vol_should, node_imgs,
+                            utils.FieldSet("mock_vg/disk_2"))
+    self.mcpu.assertLogContainsRegex("volume mock_vg/disk_1 is unknown")
+    self.mcpu.assertLogDoesNotContainRegex("volume mock_vg/disk_0 is unknown")
+    self.mcpu.assertLogDoesNotContainRegex("volume mock_vg/disk_2 is unknown")
+
+
+class TestLUClusterVerifyGroupVerifyNPlusOneMemory(
+        TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testN1Failure(self, lu):
+    group1 = self.cfg.AddNewNodeGroup()
+
+    node1 = self.cfg.AddNewNode()
+    node2 = self.cfg.AddNewNode(group=group1)
+    node3 = self.cfg.AddNewNode()
+
+    inst1 = self.cfg.AddNewInstance()
+    inst2 = self.cfg.AddNewInstance()
+    inst3 = self.cfg.AddNewInstance()
+
+    node1_img = cluster.LUClusterVerifyGroup.NodeImage(uuid=node1.uuid)
+    node1_img.sbp = {
+      self.master_uuid: [inst1.uuid, inst2.uuid, inst3.uuid]
+    }
+
+    node2_img = cluster.LUClusterVerifyGroup.NodeImage(uuid=node2.uuid)
+
+    node3_img = cluster.LUClusterVerifyGroup.NodeImage(uuid=node3.uuid)
+    node3_img.offline = True
+
+    node_imgs = {
+      node1.uuid: node1_img,
+      node2.uuid: node2_img,
+      node3.uuid: node3_img
+    }
+
+    lu._VerifyNPlusOneMemory(node_imgs, self.cfg.GetAllInstancesInfo())
+    self.mcpu.assertLogContainsRegex(
+      "not enough memory to accomodate instance failovers")
+
+    self.mcpu.ClearLogMessages()
+    node1_img.mfree = 1000
+    lu._VerifyNPlusOneMemory(node_imgs, self.cfg.GetAllInstancesInfo())
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyFiles(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def test(self, lu):
+    node1 = self.cfg.AddNewNode(master_candidate=False, offline=False,
+                                vm_capable=True)
+    node2 = self.cfg.AddNewNode(master_candidate=True, vm_capable=False)
+    node3 = self.cfg.AddNewNode(master_candidate=False, offline=False,
+                                vm_capable=True)
+    node4 = self.cfg.AddNewNode(master_candidate=False, offline=False,
+                                vm_capable=True)
+    node5 = self.cfg.AddNewNode(master_candidate=False, offline=True)
+
+    nodeinfo = [self.master, node1, node2, node3, node4, node5]
+    files_all = set([
+      pathutils.CLUSTER_DOMAIN_SECRET_FILE,
+      pathutils.RAPI_CERT_FILE,
+      pathutils.RAPI_USERS_FILE,
+      ])
+    files_opt = set([
+      pathutils.RAPI_USERS_FILE,
+      hv_xen.XL_CONFIG_FILE,
+      pathutils.VNC_PASSWORD_FILE,
+      ])
+    files_mc = set([
+      pathutils.CLUSTER_CONF_FILE,
+      ])
+    files_vm = set([
+      hv_xen.XEND_CONFIG_FILE,
+      hv_xen.XL_CONFIG_FILE,
+      pathutils.VNC_PASSWORD_FILE,
+      ])
+    nvinfo = RpcResultsBuilder() \
+      .AddSuccessfulNode(self.master, {
+        constants.NV_FILELIST: {
+          pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
+          pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
+          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
+        }}) \
+      .AddSuccessfulNode(node1, {
+        constants.NV_FILELIST: {
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
+          }
+        }) \
+      .AddSuccessfulNode(node2, {
+        constants.NV_FILELIST: {
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          }
+        }) \
+      .AddSuccessfulNode(node3, {
+        constants.NV_FILELIST: {
+          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
+          pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
+          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
+          pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
+          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
+          }
+        }) \
+      .AddSuccessfulNode(node4, {}) \
+      .AddOfflineNode(node5) \
+      .Build()
+    assert set(nvinfo.keys()) == set(map(operator.attrgetter("uuid"), nodeinfo))
+
+    lu._VerifyFiles(nodeinfo, self.master_uuid, nvinfo,
+                    (files_all, files_opt, files_mc, files_vm))
+
+    expected_msgs = [
+      "File %s found with 2 different checksums (variant 1 on"
+        " %s, %s, %s; variant 2 on %s)" %
+        (pathutils.RAPI_CERT_FILE, node1.name, node2.name, node3.name,
+         self.master.name),
+      "File %s is missing from node(s) %s" %
+        (pathutils.CLUSTER_DOMAIN_SECRET_FILE, node1.name),
+      "File %s should not exist on node(s) %s" %
+        (pathutils.CLUSTER_CONF_FILE, node3.name),
+      "File %s is missing from node(s) %s" %
+        (hv_xen.XEND_CONFIG_FILE, node3.name),
+      "File %s is missing from node(s) %s" %
+        (pathutils.CLUSTER_CONF_FILE, node2.name),
+      "File %s found with 2 different checksums (variant 1 on"
+        " %s; variant 2 on %s)" %
+        (pathutils.CLUSTER_CONF_FILE, self.master.name, node3.name),
+      "File %s is optional, but it must exist on all or no nodes (not"
+        " found on %s, %s, %s)" %
+        (pathutils.RAPI_USERS_FILE, self.master.name, node1.name, node2.name),
+      "File %s is optional, but it must exist on all or no nodes (not"
+        " found on %s)" % (hv_xen.XL_CONFIG_FILE, node1.name),
+      "Node did not return file checksum data",
+      ]
+
+    self.assertEqual(len(self.mcpu.GetLogMessages()), len(expected_msgs))
+    for expected_msg in expected_msgs:
+      self.mcpu.assertLogContainsInLine(expected_msg)
+
+
+class TestLUClusterVerifyGroupVerifyNodeDrbd(TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupVerifyNodeDrbd, self).setUp()
+
+    self.node1 = self.cfg.AddNewNode()
+    self.node2 = self.cfg.AddNewNode()
+    self.inst = self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                 primary_node=self.node1,
+                                 secondary_node=self.node2)],
+      admin_state=constants.ADMINST_UP)
+
+  @withLockedLU
+  def testNoDrbdHelper(self, lu):
+    lu._VerifyNodeDrbd(self.master, {}, self.cfg.GetAllInstancesInfo(), None,
+                       self.cfg.ComputeDRBDMap())
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testDrbdHelperInvalidNodeResult(self, lu):
+    for ndata, expected in [({}, "no drbd usermode helper returned"),
+                            ({constants.NV_DRBDHELPER: (False, "")},
+                             "drbd usermode helper check unsuccessful"),
+                            ({constants.NV_DRBDHELPER: (True, "/bin/false")},
+                             "wrong drbd usermode helper")]:
+      self.mcpu.ClearLogMessages()
+      lu._VerifyNodeDrbd(self.master, ndata, self.cfg.GetAllInstancesInfo(),
+                         "/bin/true", self.cfg.ComputeDRBDMap())
+      self.mcpu.assertLogContainsRegex(expected)
+
+  @withLockedLU
+  def testNoNodeResult(self, lu):
+    lu._VerifyNodeDrbd(self.node1, {}, self.cfg.GetAllInstancesInfo(),
+                         None, self.cfg.ComputeDRBDMap())
+    self.mcpu.assertLogContainsRegex("drbd minor 1 of .* is not active")
+
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    lu._VerifyNodeDrbd(self.node1, {constants.NV_DRBDLIST: ""},
+                       self.cfg.GetAllInstancesInfo(), None,
+                       self.cfg.ComputeDRBDMap())
+    self.mcpu.assertLogContainsRegex("cannot parse drbd status file")
+
+  @withLockedLU
+  def testWrongMinorInUse(self, lu):
+    lu._VerifyNodeDrbd(self.node1, {constants.NV_DRBDLIST: [2]},
+                       self.cfg.GetAllInstancesInfo(), None,
+                       self.cfg.ComputeDRBDMap())
+    self.mcpu.assertLogContainsRegex("drbd minor 1 of .* is not active")
+    self.mcpu.assertLogContainsRegex("unallocated drbd minor 2 is in use")
+
+  @withLockedLU
+  def testValidResult(self, lu):
+    lu._VerifyNodeDrbd(self.node1, {constants.NV_DRBDLIST: [1]},
+                       self.cfg.GetAllInstancesInfo(), None,
+                       self.cfg.ComputeDRBDMap())
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyNodeOs(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testUpdateNodeOsInvalidNodeResult(self, lu):
+    for ndata in [{}, {constants.NV_OSLIST: ""}, {constants.NV_OSLIST: [""]},
+                  {constants.NV_OSLIST: [["1", "2"]]}]:
+      self.mcpu.ClearLogMessages()
+      nimage = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+      lu._UpdateNodeOS(self.master, ndata, nimage)
+      self.mcpu.assertLogContainsRegex("node hasn't returned valid OS data")
+
+  @withLockedLU
+  def testUpdateNodeOsValidNodeResult(self, lu):
+    ndata = {
+      constants.NV_OSLIST: [
+        ["mock_OS", "/mocked/path", True, "", ["default"], [],
+         [constants.OS_API_V20]],
+        ["Another_Mock", "/random", True, "", ["var1", "var2"],
+         [{"param1": "val1"}, {"param2": "val2"}], constants.OS_API_VERSIONS]
+      ]
+    }
+    nimage = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+    lu._UpdateNodeOS(self.master, ndata, nimage)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testVerifyNodeOs(self, lu):
+    node = self.cfg.AddNewNode()
+    nimg_root = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+    nimg = cluster.LUClusterVerifyGroup.NodeImage(uuid=node.uuid)
+
+    nimg_root.os_fail = False
+    nimg_root.oslist = {
+      "mock_os": [("/mocked/path", True, "", set(["default"]), set(),
+                   set([constants.OS_API_V20]))],
+      "broken_base_os": [("/broken", False, "", set(), set(),
+                         set([constants.OS_API_V20]))],
+      "only_on_root": [("/random", True, "", set(), set(), set())],
+      "diffing_os": [("/pinky", True, "", set(["var1", "var2"]),
+                      set([("param1", "val1"), ("param2", "val2")]),
+                      set([constants.OS_API_V20]))]
+    }
+    nimg.os_fail = False
+    nimg.oslist = {
+      "mock_os": [("/mocked/path", True, "", set(["default"]), set(),
+                   set([constants.OS_API_V20]))],
+      "only_on_test": [("/random", True, "", set(), set(), set())],
+      "diffing_os": [("/bunny", True, "", set(["var1", "var3"]),
+                      set([("param1", "val1"), ("param3", "val3")]),
+                      set([constants.OS_API_V15]))],
+      "broken_os": [("/broken", False, "", set(), set(),
+                     set([constants.OS_API_V20]))],
+      "multi_entries": [
+        ("/multi1", True, "", set(), set(), set([constants.OS_API_V20])),
+        ("/multi2", True, "", set(), set(), set([constants.OS_API_V20]))]
+    }
+
+    lu._VerifyNodeOS(node, nimg, nimg_root)
+
+    expected_msgs = [
+      "Extra OS only_on_test not present on reference node",
+      "OSes present on reference node .* but missing on this node:" +
+        " only_on_root",
+      "OS API version for diffing_os differs",
+      "OS variants list for diffing_os differs",
+      "OS parameters for diffing_os differs",
+      "Invalid OS broken_os",
+      "Extra OS broken_os not present on reference node",
+      "OS 'multi_entries' has multiple entries",
+      "Extra OS multi_entries not present on reference node"
+    ]
+
+    self.assertEqual(len(expected_msgs), len(self.mcpu.GetLogMessages()))
+    for expected_msg in expected_msgs:
+      self.mcpu.assertLogContainsRegex(expected_msg)
+
+
+class TestLUClusterVerifyGroupVerifyAcceptedFileStoragePaths(
+  TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testNotMaster(self, lu):
+    lu._VerifyAcceptedFileStoragePaths(self.master, {}, False)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testNotMasterButRetunedValue(self, lu):
+    lu._VerifyAcceptedFileStoragePaths(
+      self.master, {constants.NV_ACCEPTED_STORAGE_PATHS: []}, False)
+    self.mcpu.assertLogContainsRegex(
+      "Node should not have returned forbidden file storage paths")
+
+  @withLockedLU
+  def testMasterInvalidNodeResult(self, lu):
+    lu._VerifyAcceptedFileStoragePaths(self.master, {}, True)
+    self.mcpu.assertLogContainsRegex(
+      "Node did not return forbidden file storage paths")
+
+  @withLockedLU
+  def testMasterForbiddenPaths(self, lu):
+    lu._VerifyAcceptedFileStoragePaths(
+      self.master, {constants.NV_ACCEPTED_STORAGE_PATHS: ["/forbidden"]}, True)
+    self.mcpu.assertLogContainsRegex("Found forbidden file storage paths")
+
+  @withLockedLU
+  def testMasterSuccess(self, lu):
+    lu._VerifyAcceptedFileStoragePaths(
+      self.master, {constants.NV_ACCEPTED_STORAGE_PATHS: []}, True)
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupVerifyStoragePaths(
+  TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testVerifyFileStoragePathsSuccess(self, lu):
+    lu._VerifyFileStoragePaths(self.master, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testVerifyFileStoragePathsFailure(self, lu):
+    lu._VerifyFileStoragePaths(self.master,
+                               {constants.NV_FILE_STORAGE_PATH: "/fail/path"})
+    self.mcpu.assertLogContainsRegex(
+      "The configured file storage path is unusable")
+
+  @withLockedLU
+  def testVerifySharedFileStoragePathsSuccess(self, lu):
+    lu._VerifySharedFileStoragePaths(self.master, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testVerifySharedFileStoragePathsFailure(self, lu):
+    lu._VerifySharedFileStoragePaths(
+      self.master, {constants.NV_SHARED_FILE_STORAGE_PATH: "/fail/path"})
+    self.mcpu.assertLogContainsRegex(
+      "The configured sharedfile storage path is unusable")
+
+
+class TestLUClusterVerifyGroupVerifyOob(TestLUClusterVerifyGroupMethods):
+  @withLockedLU
+  def testEmptyResult(self, lu):
+    lu._VerifyOob(self.master, {})
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testErrorResults(self, lu):
+    lu._VerifyOob(self.master, {constants.NV_OOB_PATHS: ["path1", "path2"]})
+    self.mcpu.assertLogContainsRegex("path1")
+    self.mcpu.assertLogContainsRegex("path2")
+
+
+class TestLUClusterVerifyGroupUpdateNodeVolumes(
+  TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupUpdateNodeVolumes, self).setUp()
+    self.nimg = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+
+  @withLockedLU
+  def testNoVgName(self, lu):
+    lu._UpdateNodeVolumes(self.master, {}, self.nimg, None)
+    self.mcpu.assertLogIsEmpty()
+    self.assertTrue(self.nimg.lvm_fail)
+
+  @withLockedLU
+  def testErrorMessage(self, lu):
+    lu._UpdateNodeVolumes(self.master, {constants.NV_LVLIST: "mock error"},
+                          self.nimg, "mock_vg")
+    self.mcpu.assertLogContainsRegex("LVM problem on node: mock error")
+    self.assertTrue(self.nimg.lvm_fail)
+
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    lu._UpdateNodeVolumes(self.master, {constants.NV_LVLIST: [1, 2, 3]},
+                          self.nimg, "mock_vg")
+    self.mcpu.assertLogContainsRegex("rpc call to node failed")
+    self.assertTrue(self.nimg.lvm_fail)
+
+  @withLockedLU
+  def testValidNodeResult(self, lu):
+    lu._UpdateNodeVolumes(self.master, {constants.NV_LVLIST: {}},
+                          self.nimg, "mock_vg")
+    self.mcpu.assertLogIsEmpty()
+    self.assertFalse(self.nimg.lvm_fail)
+
+
+class TestLUClusterVerifyGroupUpdateNodeInstances(
+  TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupUpdateNodeInstances, self).setUp()
+    self.nimg = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    lu._UpdateNodeInstances(self.master, {}, self.nimg)
+    self.mcpu.assertLogContainsRegex("rpc call to node failed")
+
+  @withLockedLU
+  def testValidNodeResult(self, lu):
+    inst = self.cfg.AddNewInstance()
+    lu._UpdateNodeInstances(self.master,
+                            {constants.NV_INSTANCELIST: [inst.name]},
+                            self.nimg)
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupUpdateNodeInfo(TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupUpdateNodeInfo, self).setUp()
+    self.nimg = cluster.LUClusterVerifyGroup.NodeImage(uuid=self.master_uuid)
+    self.valid_hvresult = {constants.NV_HVINFO: {"memory_free": 1024}}
+
+  @withLockedLU
+  def testInvalidHvNodeResult(self, lu):
+    for ndata in [{}, {constants.NV_HVINFO: ""}]:
+      self.mcpu.ClearLogMessages()
+      lu._UpdateNodeInfo(self.master, ndata, self.nimg, None)
+      self.mcpu.assertLogContainsRegex("rpc call to node failed")
+
+  @withLockedLU
+  def testInvalidMemoryFreeHvNodeResult(self, lu):
+    lu._UpdateNodeInfo(self.master,
+                       {constants.NV_HVINFO: {"memory_free": "abc"}},
+                       self.nimg, None)
+    self.mcpu.assertLogContainsRegex(
+      "node returned invalid nodeinfo, check hypervisor")
+
+  @withLockedLU
+  def testValidHvNodeResult(self, lu):
+    lu._UpdateNodeInfo(self.master, self.valid_hvresult, self.nimg, None)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testInvalidVgNodeResult(self, lu):
+    for vgdata in [[], ""]:
+      self.mcpu.ClearLogMessages()
+      ndata = {constants.NV_VGLIST: vgdata}
+      ndata.update(self.valid_hvresult)
+      lu._UpdateNodeInfo(self.master, ndata, self.nimg, "mock_vg")
+      self.mcpu.assertLogContainsRegex(
+        "node didn't return data for the volume group 'mock_vg'")
+
+  @withLockedLU
+  def testInvalidDiskFreeVgNodeResult(self, lu):
+    self.valid_hvresult.update({
+      constants.NV_VGLIST: {"mock_vg": "abc"}
+    })
+    lu._UpdateNodeInfo(self.master, self.valid_hvresult, self.nimg, "mock_vg")
+    self.mcpu.assertLogContainsRegex(
+      "node returned invalid LVM info, check LVM status")
+
+  @withLockedLU
+  def testValidVgNodeResult(self, lu):
+    self.valid_hvresult.update({
+      constants.NV_VGLIST: {"mock_vg": 10000}
+    })
+    lu._UpdateNodeInfo(self.master, self.valid_hvresult, self.nimg, "mock_vg")
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupCollectDiskInfo(TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupCollectDiskInfo, self).setUp()
+
+    self.node1 = self.cfg.AddNewNode()
+    self.node2 = self.cfg.AddNewNode()
+    self.node3 = self.cfg.AddNewNode()
+
+    self.diskless_inst = \
+      self.cfg.AddNewInstance(primary_node=self.node1,
+                              disk_template=constants.DT_DISKLESS)
+    self.plain_inst = \
+      self.cfg.AddNewInstance(primary_node=self.node2,
+                              disk_template=constants.DT_PLAIN)
+    self.drbd_inst = \
+      self.cfg.AddNewInstance(primary_node=self.node3,
+                              secondary_node=self.node2,
+                              disk_template=constants.DT_DRBD8)
+
+    self.node1_img = cluster.LUClusterVerifyGroup.NodeImage(
+                       uuid=self.node1.uuid)
+    self.node1_img.pinst = [self.diskless_inst.uuid]
+    self.node1_img.sinst = []
+    self.node2_img = cluster.LUClusterVerifyGroup.NodeImage(
+                       uuid=self.node2.uuid)
+    self.node2_img.pinst = [self.plain_inst.uuid]
+    self.node2_img.sinst = [self.drbd_inst.uuid]
+    self.node3_img = cluster.LUClusterVerifyGroup.NodeImage(
+                       uuid=self.node3.uuid)
+    self.node3_img.pinst = [self.drbd_inst.uuid]
+    self.node3_img.sinst = []
+
+    self.node_images = {
+      self.node1.uuid: self.node1_img,
+      self.node2.uuid: self.node2_img,
+      self.node3.uuid: self.node3_img
+    }
+
+    self.node_uuids = [self.node1.uuid, self.node2.uuid, self.node3.uuid]
+
+  @withLockedLU
+  def testSuccessfulRun(self, lu):
+    self.rpc.call_blockdev_getmirrorstatus_multi.return_value = \
+      RpcResultsBuilder() \
+        .AddSuccessfulNode(self.node2, [(True, ""), (True, "")]) \
+        .AddSuccessfulNode(self.node3, [(True, "")]) \
+        .Build()
+
+    lu._CollectDiskInfo(self.node_uuids, self.node_images,
+                        self.cfg.GetAllInstancesInfo())
+
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testOfflineAndFailingNodes(self, lu):
+    self.rpc.call_blockdev_getmirrorstatus_multi.return_value = \
+      RpcResultsBuilder() \
+        .AddOfflineNode(self.node2) \
+        .AddFailedNode(self.node3) \
+        .Build()
+
+    lu._CollectDiskInfo(self.node_uuids, self.node_images,
+                        self.cfg.GetAllInstancesInfo())
+
+    self.mcpu.assertLogContainsRegex("while getting disk information")
+
+  @withLockedLU
+  def testInvalidNodeResult(self, lu):
+    self.rpc.call_blockdev_getmirrorstatus_multi.return_value = \
+      RpcResultsBuilder() \
+        .AddSuccessfulNode(self.node2, [(True,), (False,)]) \
+        .AddSuccessfulNode(self.node3, [""]) \
+        .Build()
+
+    lu._CollectDiskInfo(self.node_uuids, self.node_images,
+                        self.cfg.GetAllInstancesInfo())
+    # logging is not performed through mcpu
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUClusterVerifyGroupHooksCallBack(TestLUClusterVerifyGroupMethods):
+  def setUp(self):
+    super(TestLUClusterVerifyGroupHooksCallBack, self).setUp()
+
+    self.feedback_fn = lambda _: None
+
+  def PrepareLU(self, lu):
+    super(TestLUClusterVerifyGroupHooksCallBack, self).PrepareLU(lu)
+
+    lu.my_node_uuids = list(self.cfg.GetAllNodesInfo().keys())
+
+  @withLockedLU
+  def testEmptyGroup(self, lu):
+    lu.my_node_uuids = []
+    lu.HooksCallBack(constants.HOOKS_PHASE_POST, None, self.feedback_fn, None)
+
+  @withLockedLU
+  def testFailedResult(self, lu):
+    lu.HooksCallBack(constants.HOOKS_PHASE_POST,
+                     RpcResultsBuilder(use_node_names=True)
+                       .AddFailedNode(self.master).Build(),
+                     self.feedback_fn,
+                     None)
+    self.mcpu.assertLogContainsRegex("Communication failure in hooks execution")
+
+  @withLockedLU
+  def testOfflineNode(self, lu):
+    lu.HooksCallBack(constants.HOOKS_PHASE_POST,
+                     RpcResultsBuilder(use_node_names=True)
+                       .AddOfflineNode(self.master).Build(),
+                     self.feedback_fn,
+                     None)
+
+  @withLockedLU
+  def testValidResult(self, lu):
+    lu.HooksCallBack(constants.HOOKS_PHASE_POST,
+                     RpcResultsBuilder(use_node_names=True)
+                       .AddSuccessfulNode(self.master,
+                                          [("mock_script",
+                                            constants.HKR_SUCCESS,
+                                            "mock output")])
+                       .Build(),
+                     self.feedback_fn,
+                     None)
+
+  @withLockedLU
+  def testFailedScriptResult(self, lu):
+    lu.HooksCallBack(constants.HOOKS_PHASE_POST,
+                     RpcResultsBuilder(use_node_names=True)
+                       .AddSuccessfulNode(self.master,
+                                          [("mock_script",
+                                            constants.HKR_FAIL,
+                                            "mock output")])
+                       .Build(),
+                     self.feedback_fn,
+                     None)
+    self.mcpu.assertLogContainsRegex("Script mock_script failed")
+
+
+class TestLUClusterVerifyDisks(CmdlibTestCase):
+  def testVerifyDisks(self):
+    op = opcodes.OpClusterVerifyDisks()
+    result = self.ExecOpCode(op)
+
+    self.assertEqual(1, len(result["jobs"]))
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/cmdlib_unittest.py b/test/py/cmdlib/cmdlib_unittest.py
new file mode 100755
index 0000000..3f0580e
--- /dev/null
+++ b/test/py/cmdlib/cmdlib_unittest.py
@@ -0,0 +1,1063 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Script for unittesting the cmdlib module"""
+
+
+import unittest
+import operator
+import itertools
+import copy
+
+from ganeti import constants
+from ganeti import mcpu
+from ganeti import cmdlib
+from ganeti.cmdlib import cluster
+from ganeti.cmdlib import instance
+from ganeti.cmdlib import instance_storage
+from ganeti.cmdlib import instance_utils
+from ganeti.cmdlib import common
+from ganeti.cmdlib import query
+from ganeti import opcodes
+from ganeti import errors
+from ganeti import utils
+from ganeti import luxi
+from ganeti import ht
+from ganeti import objects
+from ganeti import compat
+from ganeti import rpc
+from ganeti import locking
+from ganeti.masterd import iallocator
+
+import testutils
+import mocks
+
+
+class TestOpcodeParams(testutils.GanetiTestCase):
+  def testParamsStructures(self):
+    for op in sorted(mcpu.Processor.DISPATCH_TABLE):
+      lu = mcpu.Processor.DISPATCH_TABLE[op]
+      lu_name = lu.__name__
+      self.failIf(hasattr(lu, "_OP_REQP"),
+                  msg=("LU '%s' has old-style _OP_REQP" % lu_name))
+      self.failIf(hasattr(lu, "_OP_DEFS"),
+                  msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
+      self.failIf(hasattr(lu, "_OP_PARAMS"),
+                  msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
+
+
+class TestIAllocatorChecks(testutils.GanetiTestCase):
+  def testFunction(self):
+    class TestLU(object):
+      def __init__(self, opcode):
+        self.cfg = mocks.FakeConfig()
+        self.op = opcode
+
+    class OpTest(opcodes.OpCode):
+       OP_PARAMS = [
+        ("iallocator", None, ht.TAny, None),
+        ("node", None, ht.TAny, None),
+        ]
+
+    default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
+    other_iallocator = default_iallocator + "_not"
+
+    op = OpTest()
+    lu = TestLU(op)
+
+    c_i = lambda: common.CheckIAllocatorOrNode(lu, "iallocator", "node")
+
+    # Neither node nor iallocator given
+    for n in (None, []):
+      op.iallocator = None
+      op.node = n
+      c_i()
+      self.assertEqual(lu.op.iallocator, default_iallocator)
+      self.assertEqual(lu.op.node, n)
+
+    # Both, iallocator and node given
+    for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
+      op.iallocator = a
+      op.node = "test"
+      self.assertRaises(errors.OpPrereqError, c_i)
+
+    # Only iallocator given
+    for n in (None, []):
+      op.iallocator = other_iallocator
+      op.node = n
+      c_i()
+      self.assertEqual(lu.op.iallocator, other_iallocator)
+      self.assertEqual(lu.op.node, n)
+
+    # Only node given
+    op.iallocator = None
+    op.node = "node"
+    c_i()
+    self.assertEqual(lu.op.iallocator, None)
+    self.assertEqual(lu.op.node, "node")
+
+    # Asked for default iallocator, no node given
+    op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
+    op.node = None
+    c_i()
+    self.assertEqual(lu.op.iallocator, default_iallocator)
+    self.assertEqual(lu.op.node, None)
+
+    # No node, iallocator or default iallocator
+    op.iallocator = None
+    op.node = None
+    lu.cfg.GetDefaultIAllocator = lambda: None
+    self.assertRaises(errors.OpPrereqError, c_i)
+
+
+class TestLUTestJqueue(unittest.TestCase):
+  def test(self):
+    self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
+                 (luxi.WFJC_TIMEOUT * 0.75),
+                 msg=("Client timeout too high, might not notice bugs"
+                      " in WaitForJobChange"))
+
+
+class TestLUQuery(unittest.TestCase):
+  def test(self):
+    self.assertEqual(sorted(query._QUERY_IMPL.keys()),
+                     sorted(constants.QR_VIA_OP))
+
+    assert constants.QR_NODE in constants.QR_VIA_OP
+    assert constants.QR_INSTANCE in constants.QR_VIA_OP
+
+    for i in constants.QR_VIA_OP:
+      self.assert_(query._GetQueryImplementation(i))
+
+    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
+                      "")
+    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
+                      "xyz")
+
+
+class _FakeLU:
+  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
+               rpc=NotImplemented):
+    self.warning_log = []
+    self.info_log = []
+    self.cfg = cfg
+    self.proc = proc
+    self.rpc = rpc
+
+  def LogWarning(self, text, *args):
+    self.warning_log.append((text, args))
+
+  def LogInfo(self, text, *args):
+    self.info_log.append((text, args))
+
+
+class TestLoadNodeEvacResult(unittest.TestCase):
+  def testSuccess(self):
+    for moved in [[], [
+      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
+      ]]:
+      for early_release in [False, True]:
+        for use_nodes in [False, True]:
+          jobs = [
+            [opcodes.OpInstanceReplaceDisks().__getstate__()],
+            [opcodes.OpInstanceMigrate().__getstate__()],
+            ]
+
+          alloc_result = (moved, [], jobs)
+          assert iallocator._NEVAC_RESULT(alloc_result)
+
+          lu = _FakeLU()
+          result = common.LoadNodeEvacResult(lu, alloc_result,
+                                             early_release, use_nodes)
+
+          if moved:
+            (_, (info_args, )) = lu.info_log.pop(0)
+            for (instname, instgroup, instnodes) in moved:
+              self.assertTrue(instname in info_args)
+              if use_nodes:
+                for i in instnodes:
+                  self.assertTrue(i in info_args)
+              else:
+                self.assertTrue(instgroup in info_args)
+
+          self.assertFalse(lu.info_log)
+          self.assertFalse(lu.warning_log)
+
+          for op in itertools.chain(*result):
+            if hasattr(op.__class__, "early_release"):
+              self.assertEqual(op.early_release, early_release)
+            else:
+              self.assertFalse(hasattr(op, "early_release"))
+
+  def testFailed(self):
+    alloc_result = ([], [
+      ("inst5191.example.com", "errormsg21178"),
+      ], [])
+    assert iallocator._NEVAC_RESULT(alloc_result)
+
+    lu = _FakeLU()
+    self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult,
+                      lu, alloc_result, False, False)
+    self.assertFalse(lu.info_log)
+    (_, (args, )) = lu.warning_log.pop(0)
+    self.assertTrue("inst5191.example.com" in args)
+    self.assertTrue("errormsg21178" in args)
+    self.assertFalse(lu.warning_log)
+
+
+class TestUpdateAndVerifySubDict(unittest.TestCase):
+  def setUp(self):
+    self.type_check = {
+        "a": constants.VTYPE_INT,
+        "b": constants.VTYPE_STRING,
+        "c": constants.VTYPE_BOOL,
+        "d": constants.VTYPE_STRING,
+        }
+
+  def test(self):
+    old_test = {
+      "foo": {
+        "d": "blubb",
+        "a": 321,
+        },
+      "baz": {
+        "a": 678,
+        "b": "678",
+        "c": True,
+        },
+      }
+    test = {
+      "foo": {
+        "a": 123,
+        "b": "123",
+        "c": True,
+        },
+      "bar": {
+        "a": 321,
+        "b": "321",
+        "c": False,
+        },
+      }
+
+    mv = {
+      "foo": {
+        "a": 123,
+        "b": "123",
+        "c": True,
+        "d": "blubb"
+        },
+      "bar": {
+        "a": 321,
+        "b": "321",
+        "c": False,
+        },
+      "baz": {
+        "a": 678,
+        "b": "678",
+        "c": True,
+        },
+      }
+
+    verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
+    self.assertEqual(verified, mv)
+
+  def testWrong(self):
+    test = {
+      "foo": {
+        "a": "blubb",
+        "b": "123",
+        "c": True,
+        },
+      "bar": {
+        "a": 321,
+        "b": "321",
+        "c": False,
+        },
+      }
+
+    self.assertRaises(errors.TypeEnforcementError,
+                      common._UpdateAndVerifySubDict, {}, test,
+                      self.type_check)
+
+
+class TestHvStateHelper(unittest.TestCase):
+  def testWithoutOpData(self):
+    self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented),
+                     None)
+
+  def testWithoutOldData(self):
+    new = {
+      constants.HT_XEN_PVM: {
+        constants.HVST_MEMORY_TOTAL: 4096,
+        },
+      }
+    self.assertEqual(common.MergeAndVerifyHvState(new, None), new)
+
+  def testWithWrongHv(self):
+    new = {
+      "i-dont-exist": {
+        constants.HVST_MEMORY_TOTAL: 4096,
+        },
+      }
+    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState,
+                      new, None)
+
+class TestDiskStateHelper(unittest.TestCase):
+  def testWithoutOpData(self):
+    self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented),
+                     None)
+
+  def testWithoutOldData(self):
+    new = {
+      constants.DT_PLAIN: {
+        "xenvg": {
+          constants.DS_DISK_RESERVED: 1024,
+          },
+        },
+      }
+    self.assertEqual(common.MergeAndVerifyDiskState(new, None), new)
+
+  def testWithWrongStorageType(self):
+    new = {
+      "i-dont-exist": {
+        "xenvg": {
+          constants.DS_DISK_RESERVED: 1024,
+          },
+        },
+      }
+    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState,
+                      new, None)
+
+
+class TestComputeMinMaxSpec(unittest.TestCase):
+  def setUp(self):
+    self.ispecs = {
+      constants.ISPECS_MAX: {
+        constants.ISPEC_MEM_SIZE: 512,
+        constants.ISPEC_DISK_SIZE: 1024,
+        },
+      constants.ISPECS_MIN: {
+        constants.ISPEC_MEM_SIZE: 128,
+        constants.ISPEC_DISK_COUNT: 1,
+        },
+      }
+
+  def testNoneValue(self):
+    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
+                                              self.ispecs, None) is None)
+
+  def testAutoValue(self):
+    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
+                                              self.ispecs,
+                                              constants.VALUE_AUTO) is None)
+
+  def testNotDefined(self):
+    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
+                                              self.ispecs, 3) is None)
+
+  def testNoMinDefined(self):
+    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
+                                              self.ispecs, 128) is None)
+
+  def testNoMaxDefined(self):
+    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
+                                              None, self.ispecs, 16) is None)
+
+  def testOutOfRange(self):
+    for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
+                        (constants.ISPEC_MEM_SIZE, 768),
+                        (constants.ISPEC_DISK_SIZE, 4096),
+                        (constants.ISPEC_DISK_COUNT, 0)):
+      min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
+      max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
+      self.assertEqual(common._ComputeMinMaxSpec(name, None,
+                                                 self.ispecs, val),
+                       "%s value %s is not in range [%s, %s]" %
+                       (name, val,min_v, max_v))
+      self.assertEqual(common._ComputeMinMaxSpec(name, "1",
+                                                 self.ispecs, val),
+                       "%s/1 value %s is not in range [%s, %s]" %
+                       (name, val,min_v, max_v))
+
+  def test(self):
+    for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
+                        (constants.ISPEC_MEM_SIZE, 128),
+                        (constants.ISPEC_MEM_SIZE, 512),
+                        (constants.ISPEC_DISK_SIZE, 1024),
+                        (constants.ISPEC_DISK_SIZE, 0),
+                        (constants.ISPEC_DISK_COUNT, 1),
+                        (constants.ISPEC_DISK_COUNT, 5)):
+      self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
+                      is None)
+
+
+def _ValidateComputeMinMaxSpec(name, *_):
+  assert name in constants.ISPECS_PARAMETERS
+  return None
+
+
+def _NoDiskComputeMinMaxSpec(name, *_):
+  if name == constants.ISPEC_DISK_COUNT:
+    return name
+  else:
+    return None
+
+
+class _SpecWrapper:
+  def __init__(self, spec):
+    self.spec = spec
+
+  def ComputeMinMaxSpec(self, *args):
+    return self.spec.pop(0)
+
+
+class TestComputeIPolicySpecViolation(unittest.TestCase):
+  # Minimal policy accepted by _ComputeIPolicySpecViolation()
+  _MICRO_IPOL = {
+    constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
+    constants.ISPECS_MINMAX: [NotImplemented],
+    }
+
+  def test(self):
+    compute_fn = _ValidateComputeMinMaxSpec
+    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
+                                             [1024], 1, constants.DT_PLAIN,
+                                             _compute_fn=compute_fn)
+    self.assertEqual(ret, [])
+
+  def testDiskFull(self):
+    compute_fn = _NoDiskComputeMinMaxSpec
+    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
+                                             [1024], 1, constants.DT_PLAIN,
+                                             _compute_fn=compute_fn)
+    self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
+
+  def testDiskLess(self):
+    compute_fn = _NoDiskComputeMinMaxSpec
+    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
+                                             [1024], 1, constants.DT_DISKLESS,
+                                             _compute_fn=compute_fn)
+    self.assertEqual(ret, [])
+
+  def testWrongTemplates(self):
+    compute_fn = _ValidateComputeMinMaxSpec
+    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
+                                             [1024], 1, constants.DT_DRBD8,
+                                             _compute_fn=compute_fn)
+    self.assertEqual(len(ret), 1)
+    self.assertTrue("Disk template" in ret[0])
+
+  def testInvalidArguments(self):
+    self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation,
+                      self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
+                      constants.DT_PLAIN,)
+
+  def testInvalidSpec(self):
+    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
+    compute_fn = spec.ComputeMinMaxSpec
+    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
+                                             [1024], 1, constants.DT_PLAIN,
+                                             _compute_fn=compute_fn)
+    self.assertEqual(ret, ["foo", "bar"])
+    self.assertFalse(spec.spec)
+
+  def testWithIPolicy(self):
+    mem_size = 2048
+    cpu_count = 2
+    disk_count = 1
+    disk_sizes = [512]
+    nic_count = 1
+    spindle_use = 4
+    disk_template = "mytemplate"
+    ispec = {
+      constants.ISPEC_MEM_SIZE: mem_size,
+      constants.ISPEC_CPU_COUNT: cpu_count,
+      constants.ISPEC_DISK_COUNT: disk_count,
+      constants.ISPEC_DISK_SIZE: disk_sizes[0],
+      constants.ISPEC_NIC_COUNT: nic_count,
+      constants.ISPEC_SPINDLE_USE: spindle_use,
+      }
+    ipolicy1 = {
+      constants.ISPECS_MINMAX: [{
+        constants.ISPECS_MIN: ispec,
+        constants.ISPECS_MAX: ispec,
+        }],
+      constants.IPOLICY_DTS: [disk_template],
+      }
+    ispec_copy = copy.deepcopy(ispec)
+    ipolicy2 = {
+      constants.ISPECS_MINMAX: [
+        {
+          constants.ISPECS_MIN: ispec_copy,
+          constants.ISPECS_MAX: ispec_copy,
+          },
+        {
+          constants.ISPECS_MIN: ispec,
+          constants.ISPECS_MAX: ispec,
+          },
+        ],
+      constants.IPOLICY_DTS: [disk_template],
+      }
+    ipolicy3 = {
+      constants.ISPECS_MINMAX: [
+        {
+          constants.ISPECS_MIN: ispec,
+          constants.ISPECS_MAX: ispec,
+          },
+        {
+          constants.ISPECS_MIN: ispec_copy,
+          constants.ISPECS_MAX: ispec_copy,
+          },
+        ],
+      constants.IPOLICY_DTS: [disk_template],
+      }
+    def AssertComputeViolation(ipolicy, violations):
+      ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
+                                               disk_count, nic_count,
+                                               disk_sizes, spindle_use,
+                                               disk_template)
+      self.assertEqual(len(ret), violations)
+
+    AssertComputeViolation(ipolicy1, 0)
+    AssertComputeViolation(ipolicy2, 0)
+    AssertComputeViolation(ipolicy3, 0)
+    for par in constants.ISPECS_PARAMETERS:
+      ispec[par] += 1
+      AssertComputeViolation(ipolicy1, 1)
+      AssertComputeViolation(ipolicy2, 0)
+      AssertComputeViolation(ipolicy3, 0)
+      ispec[par] -= 2
+      AssertComputeViolation(ipolicy1, 1)
+      AssertComputeViolation(ipolicy2, 0)
+      AssertComputeViolation(ipolicy3, 0)
+      ispec[par] += 1 # Restore
+    ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
+    AssertComputeViolation(ipolicy1, 1)
+
+
+class _StubComputeIPolicySpecViolation:
+  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
+               spindle_use, disk_template):
+    self.mem_size = mem_size
+    self.cpu_count = cpu_count
+    self.disk_count = disk_count
+    self.nic_count = nic_count
+    self.disk_sizes = disk_sizes
+    self.spindle_use = spindle_use
+    self.disk_template = disk_template
+
+  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
+               spindle_use, disk_template):
+    assert self.mem_size == mem_size
+    assert self.cpu_count == cpu_count
+    assert self.disk_count == disk_count
+    assert self.nic_count == nic_count
+    assert self.disk_sizes == disk_sizes
+    assert self.spindle_use == spindle_use
+    assert self.disk_template == disk_template
+
+    return []
+
+
+class _FakeConfigForComputeIPolicyInstanceViolation:
+  def __init__(self, be, excl_stor):
+    self.cluster = objects.Cluster(beparams={"default": be})
+    self.excl_stor = excl_stor
+
+  def GetClusterInfo(self):
+    return self.cluster
+
+  def GetNodeInfo(self, _):
+    return {}
+
+  def GetNdParams(self, _):
+    return {
+      constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
+      }
+
+
+class TestComputeIPolicyInstanceViolation(unittest.TestCase):
+  def test(self):
+    beparams = {
+      constants.BE_MAXMEM: 2048,
+      constants.BE_VCPUS: 2,
+      constants.BE_SPINDLE_USE: 4,
+      }
+    disks = [objects.Disk(size=512, spindles=13)]
+    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
+    instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
+                                disk_template=constants.DT_PLAIN)
+    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
+                                            constants.DT_PLAIN)
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
+                                                 cfg, _compute_fn=stub)
+    self.assertEqual(ret, [])
+    instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
+                                 disk_template=constants.DT_PLAIN)
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
+                                                 cfg, _compute_fn=stub)
+    self.assertEqual(ret, [])
+    cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
+    stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
+                                               constants.DT_PLAIN)
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
+                                                 cfg_es, _compute_fn=stub_es)
+    self.assertEqual(ret, [])
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
+                                                 cfg_es, _compute_fn=stub_es)
+    self.assertEqual(ret, [])
+
+
+class _CallRecorder:
+  def __init__(self, return_value=None):
+    self.called = False
+    self.return_value = return_value
+
+  def __call__(self, *args):
+    self.called = True
+    return self.return_value
+
+
+class TestComputeIPolicyNodeViolation(unittest.TestCase):
+  def setUp(self):
+    self.recorder = _CallRecorder(return_value=[])
+
+  def testSameGroup(self):
+    ret = instance_utils._ComputeIPolicyNodeViolation(
+      NotImplemented,
+      NotImplemented,
+      "foo", "foo", NotImplemented,
+      _compute_fn=self.recorder)
+    self.assertFalse(self.recorder.called)
+    self.assertEqual(ret, [])
+
+  def testDifferentGroup(self):
+    ret = instance_utils._ComputeIPolicyNodeViolation(
+      NotImplemented,
+      NotImplemented,
+      "foo", "bar", NotImplemented,
+      _compute_fn=self.recorder)
+    self.assertTrue(self.recorder.called)
+    self.assertEqual(ret, [])
+
+
+class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
+  def testLessThanOneMebibyte(self):
+    for i in [1, 2, 7, 512, 1000, 1023]:
+      lu = _FakeLU()
+      result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
+      self.assertEqual(result, 1)
+      self.assertEqual(len(lu.warning_log), 1)
+      self.assertEqual(len(lu.warning_log[0]), 2)
+      (_, (warnsize, )) = lu.warning_log[0]
+      self.assertEqual(warnsize, (1024 * 1024) - i)
+
+  def testEven(self):
+    for i in [1, 2, 7, 512, 1000, 1023]:
+      lu = _FakeLU()
+      result = instance_storage._DiskSizeInBytesToMebibytes(lu,
+                                                            i * 1024 * 1024)
+      self.assertEqual(result, i)
+      self.assertFalse(lu.warning_log)
+
+  def testLargeNumber(self):
+    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
+      for j in [1, 2, 486, 326, 986, 1023]:
+        lu = _FakeLU()
+        size = (1024 * 1024 * i) + j
+        result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
+        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
+        self.assertEqual(len(lu.warning_log), 1)
+        self.assertEqual(len(lu.warning_log[0]), 2)
+        (_, (warnsize, )) = lu.warning_log[0]
+        self.assertEqual(warnsize, (1024 * 1024) - j)
+
+
+class _OpTestVerifyErrors(opcodes.OpCode):
+  OP_PARAMS = [
+    ("debug_simulate_errors", False, ht.TBool, ""),
+    ("error_codes", False, ht.TBool, ""),
+    ("ignore_errors",
+     [],
+     ht.TListOf(ht.TElemOf(constants.CV_ALL_ECODES_STRINGS)),
+     "")
+    ]
+
+
+class _LuTestVerifyErrors(cluster._VerifyErrors):
+  def __init__(self, **kwargs):
+    cluster._VerifyErrors.__init__(self)
+    self.op = _OpTestVerifyErrors(**kwargs)
+    self.op.Validate(True)
+    self.msglist = []
+    self._feedback_fn = self.msglist.append
+    self.bad = False
+
+  def DispatchCallError(self, which, *args, **kwargs):
+    if which:
+      self._Error(*args, **kwargs)
+    else:
+      self._ErrorIf(True, *args, **kwargs)
+
+  def CallErrorIf(self, c, *args, **kwargs):
+    self._ErrorIf(c, *args, **kwargs)
+
+
+class TestVerifyErrors(unittest.TestCase):
+  # Fake cluster-verify error code structures; we use two arbitary real error
+  # codes to pass validation of ignore_errors
+  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
+  _NODESTR = "node"
+  _NODENAME = "mynode"
+  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
+  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
+  _INSTSTR = "instance"
+  _INSTNAME = "myinstance"
+  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
+  # Arguments used to call _Error() or _ErrorIf()
+  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
+  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
+  # Expected error messages
+  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
+  _ERR2MSG = _ERR2ARGS[2]
+
+  def testNoError(self):
+    lu = _LuTestVerifyErrors()
+    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
+    self.assertFalse(lu.bad)
+    self.assertFalse(lu.msglist)
+
+  def _InitTest(self, **kwargs):
+    self.lu1 = _LuTestVerifyErrors(**kwargs)
+    self.lu2 = _LuTestVerifyErrors(**kwargs)
+
+  def _CallError(self, *args, **kwargs):
+    # Check that _Error() and _ErrorIf() produce the same results
+    self.lu1.DispatchCallError(True, *args, **kwargs)
+    self.lu2.DispatchCallError(False, *args, **kwargs)
+    self.assertEqual(self.lu1.bad, self.lu2.bad)
+    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
+    # Test-specific checks are made on one LU
+    return self.lu1
+
+  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
+    self.assertTrue(errmsg in logstr)
+    if warning:
+      self.assertTrue("WARNING" in logstr)
+    else:
+      self.assertTrue("ERROR" in logstr)
+    self.assertTrue(itype in logstr)
+    self.assertTrue(item in logstr)
+
+  def _checkMsg1(self, logstr, warning=False):
+    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
+                         self._NODENAME, warning)
+
+  def _checkMsg2(self, logstr, warning=False):
+    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
+                         self._INSTNAME, warning)
+
+  def testPlain(self):
+    self._InitTest()
+    lu = self._CallError(*self._ERR1ARGS)
+    self.assertTrue(lu.bad)
+    self.assertEqual(len(lu.msglist), 1)
+    self._checkMsg1(lu.msglist[0])
+
+  def testMultiple(self):
+    self._InitTest()
+    self._CallError(*self._ERR1ARGS)
+    lu = self._CallError(*self._ERR2ARGS)
+    self.assertTrue(lu.bad)
+    self.assertEqual(len(lu.msglist), 2)
+    self._checkMsg1(lu.msglist[0])
+    self._checkMsg2(lu.msglist[1])
+
+  def testIgnore(self):
+    self._InitTest(ignore_errors=[self._ERR1ID])
+    lu = self._CallError(*self._ERR1ARGS)
+    self.assertFalse(lu.bad)
+    self.assertEqual(len(lu.msglist), 1)
+    self._checkMsg1(lu.msglist[0], warning=True)
+
+  def testWarning(self):
+    self._InitTest()
+    lu = self._CallError(*self._ERR1ARGS,
+                         code=_LuTestVerifyErrors.ETYPE_WARNING)
+    self.assertFalse(lu.bad)
+    self.assertEqual(len(lu.msglist), 1)
+    self._checkMsg1(lu.msglist[0], warning=True)
+
+  def testWarning2(self):
+    self._InitTest()
+    self._CallError(*self._ERR1ARGS)
+    lu = self._CallError(*self._ERR2ARGS,
+                         code=_LuTestVerifyErrors.ETYPE_WARNING)
+    self.assertTrue(lu.bad)
+    self.assertEqual(len(lu.msglist), 2)
+    self._checkMsg1(lu.msglist[0])
+    self._checkMsg2(lu.msglist[1], warning=True)
+
+  def testDebugSimulate(self):
+    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
+    lu.CallErrorIf(False, *self._ERR1ARGS)
+    self.assertTrue(lu.bad)
+    self.assertEqual(len(lu.msglist), 1)
+    self._checkMsg1(lu.msglist[0])
+
+  def testErrCodes(self):
+    self._InitTest(error_codes=True)
+    lu = self._CallError(*self._ERR1ARGS)
+    self.assertTrue(lu.bad)
+    self.assertEqual(len(lu.msglist), 1)
+    self._checkMsg1(lu.msglist[0])
+    self.assertTrue(self._ERR1ID in lu.msglist[0])
+
+
+class TestGetUpdatedIPolicy(unittest.TestCase):
+  """Tests for cmdlib._GetUpdatedIPolicy()"""
+  _OLD_CLUSTER_POLICY = {
+    constants.IPOLICY_VCPU_RATIO: 1.5,
+    constants.ISPECS_MINMAX: [
+      {
+        constants.ISPECS_MIN: {
+          constants.ISPEC_MEM_SIZE: 32768,
+          constants.ISPEC_CPU_COUNT: 8,
+          constants.ISPEC_DISK_COUNT: 1,
+          constants.ISPEC_DISK_SIZE: 1024,
+          constants.ISPEC_NIC_COUNT: 1,
+          constants.ISPEC_SPINDLE_USE: 1,
+          },
+        constants.ISPECS_MAX: {
+          constants.ISPEC_MEM_SIZE: 65536,
+          constants.ISPEC_CPU_COUNT: 10,
+          constants.ISPEC_DISK_COUNT: 5,
+          constants.ISPEC_DISK_SIZE: 1024 * 1024,
+          constants.ISPEC_NIC_COUNT: 3,
+          constants.ISPEC_SPINDLE_USE: 12,
+          },
+        },
+      constants.ISPECS_MINMAX_DEFAULTS,
+      ],
+    constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
+    }
+  _OLD_GROUP_POLICY = {
+    constants.IPOLICY_SPINDLE_RATIO: 2.5,
+    constants.ISPECS_MINMAX: [{
+      constants.ISPECS_MIN: {
+        constants.ISPEC_MEM_SIZE: 128,
+        constants.ISPEC_CPU_COUNT: 1,
+        constants.ISPEC_DISK_COUNT: 1,
+        constants.ISPEC_DISK_SIZE: 1024,
+        constants.ISPEC_NIC_COUNT: 1,
+        constants.ISPEC_SPINDLE_USE: 1,
+        },
+      constants.ISPECS_MAX: {
+        constants.ISPEC_MEM_SIZE: 32768,
+        constants.ISPEC_CPU_COUNT: 8,
+        constants.ISPEC_DISK_COUNT: 5,
+        constants.ISPEC_DISK_SIZE: 1024 * 1024,
+        constants.ISPEC_NIC_COUNT: 3,
+        constants.ISPEC_SPINDLE_USE: 12,
+        },
+      }],
+    }
+
+  def _TestSetSpecs(self, old_policy, isgroup):
+    diff_minmax = [{
+      constants.ISPECS_MIN: {
+        constants.ISPEC_MEM_SIZE: 64,
+        constants.ISPEC_CPU_COUNT: 1,
+        constants.ISPEC_DISK_COUNT: 2,
+        constants.ISPEC_DISK_SIZE: 64,
+        constants.ISPEC_NIC_COUNT: 1,
+        constants.ISPEC_SPINDLE_USE: 1,
+        },
+      constants.ISPECS_MAX: {
+        constants.ISPEC_MEM_SIZE: 16384,
+        constants.ISPEC_CPU_COUNT: 10,
+        constants.ISPEC_DISK_COUNT: 12,
+        constants.ISPEC_DISK_SIZE: 1024,
+        constants.ISPEC_NIC_COUNT: 9,
+        constants.ISPEC_SPINDLE_USE: 18,
+        },
+      }]
+    diff_std = {
+        constants.ISPEC_DISK_COUNT: 10,
+        constants.ISPEC_DISK_SIZE: 512,
+        }
+    diff_policy = {
+      constants.ISPECS_MINMAX: diff_minmax
+      }
+    if not isgroup:
+      diff_policy[constants.ISPECS_STD] = diff_std
+    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
+                                          group_policy=isgroup)
+
+    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
+    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
+    for key in old_policy:
+      if not key in diff_policy:
+        self.assertTrue(key in new_policy)
+        self.assertEqual(new_policy[key], old_policy[key])
+
+    if not isgroup:
+      new_std = new_policy[constants.ISPECS_STD]
+      for key in diff_std:
+        self.assertTrue(key in new_std)
+        self.assertEqual(new_std[key], diff_std[key])
+      old_std = old_policy.get(constants.ISPECS_STD, {})
+      for key in old_std:
+        self.assertTrue(key in new_std)
+        if key not in diff_std:
+          self.assertEqual(new_std[key], old_std[key])
+
+  def _TestSet(self, old_policy, diff_policy, isgroup):
+    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
+                                           group_policy=isgroup)
+    for key in diff_policy:
+      self.assertTrue(key in new_policy)
+      self.assertEqual(new_policy[key], diff_policy[key])
+    for key in old_policy:
+      if not key in diff_policy:
+        self.assertTrue(key in new_policy)
+        self.assertEqual(new_policy[key], old_policy[key])
+
+  def testSet(self):
+    diff_policy = {
+      constants.IPOLICY_VCPU_RATIO: 3,
+      constants.IPOLICY_DTS: [constants.DT_FILE],
+      }
+    self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
+    self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
+    self._TestSet({}, diff_policy, True)
+    self._TestSetSpecs({}, True)
+    self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
+    self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
+
+  def testUnset(self):
+    old_policy = self._OLD_GROUP_POLICY
+    diff_policy = {
+      constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
+      }
+    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
+                                          group_policy=True)
+    for key in diff_policy:
+      self.assertFalse(key in new_policy)
+    for key in old_policy:
+      if not key in diff_policy:
+        self.assertTrue(key in new_policy)
+        self.assertEqual(new_policy[key], old_policy[key])
+
+    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
+                      old_policy, diff_policy, group_policy=False)
+
+  def testUnsetEmpty(self):
+    old_policy = {}
+    for key in constants.IPOLICY_ALL_KEYS:
+      diff_policy = {
+        key: constants.VALUE_DEFAULT,
+        }
+    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
+                                          group_policy=True)
+    self.assertEqual(new_policy, old_policy)
+
+  def _TestInvalidKeys(self, old_policy, isgroup):
+    INVALID_KEY = "this_key_shouldnt_be_allowed"
+    INVALID_DICT = {
+      INVALID_KEY: 3,
+      }
+    invalid_policy = INVALID_DICT
+    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
+                      old_policy, invalid_policy, group_policy=isgroup)
+    invalid_ispecs = {
+      constants.ISPECS_MINMAX: [INVALID_DICT],
+      }
+    self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
+                      old_policy, invalid_ispecs, group_policy=isgroup)
+    if isgroup:
+      invalid_for_group = {
+        constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
+        }
+      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
+                        old_policy, invalid_for_group, group_policy=isgroup)
+    good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
+    invalid_ispecs = copy.deepcopy(good_ispecs)
+    invalid_policy = {
+      constants.ISPECS_MINMAX: invalid_ispecs,
+      }
+    for minmax in invalid_ispecs:
+      for key in constants.ISPECS_MINMAX_KEYS:
+        ispec = minmax[key]
+        ispec[INVALID_KEY] = None
+        self.assertRaises(errors.TypeEnforcementError,
+                          common.GetUpdatedIPolicy, old_policy,
+                          invalid_policy, group_policy=isgroup)
+        del ispec[INVALID_KEY]
+        for par in constants.ISPECS_PARAMETERS:
+          oldv = ispec[par]
+          ispec[par] = "this_is_not_good"
+          self.assertRaises(errors.TypeEnforcementError,
+                            common.GetUpdatedIPolicy,
+                            old_policy, invalid_policy, group_policy=isgroup)
+          ispec[par] = oldv
+    # This is to make sure that no two errors were present during the tests
+    common.GetUpdatedIPolicy(old_policy, invalid_policy,
+                             group_policy=isgroup)
+
+  def testInvalidKeys(self):
+    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
+    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
+
+  def testInvalidValues(self):
+    for par in (constants.IPOLICY_PARAMETERS |
+                frozenset([constants.IPOLICY_DTS])):
+      bad_policy = {
+        par: "invalid_value",
+        }
+      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
+                        bad_policy, group_policy=True)
+
+
+class TestCopyLockList(unittest.TestCase):
+  def test(self):
+    self.assertEqual(instance_utils.CopyLockList([]), [])
+    self.assertEqual(instance_utils.CopyLockList(None), None)
+    self.assertEqual(instance_utils.CopyLockList(locking.ALL_SET),
+                     locking.ALL_SET)
+
+    names = ["foo", "bar"]
+    output = instance_utils.CopyLockList(names)
+    self.assertEqual(names, output)
+    self.assertNotEqual(id(names), id(output), msg="List was not copied")
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/group_unittest.py b/test/py/cmdlib/group_unittest.py
new file mode 100644
index 0000000..3f76c24
--- /dev/null
+++ b/test/py/cmdlib/group_unittest.py
@@ -0,0 +1,466 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUGroup*
+
+"""
+
+import itertools
+
+from ganeti import constants
+from ganeti import opcodes
+from ganeti import query
+
+from testsupport import *
+
+import testutils
+
+
+class TestLUGroupAdd(CmdlibTestCase):
+  def testAddExistingGroup(self):
+    self.cfg.AddNewNodeGroup(name="existing_group")
+
+    op = opcodes.OpGroupAdd(group_name="existing_group")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Desired group name 'existing_group' already exists")
+
+  def testAddNewGroup(self):
+    op = opcodes.OpGroupAdd(group_name="new_group")
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def testAddNewGroupParams(self):
+    ndparams = {constants.ND_EXCLUSIVE_STORAGE: True}
+    hv_state = {constants.HT_FAKE: {constants.HVST_CPU_TOTAL: 8}}
+    disk_state = {
+      constants.DT_PLAIN: {
+        "mock_vg": {constants.DS_DISK_TOTAL: 10}
+      }
+    }
+    diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool"}}
+    ipolicy = constants.IPOLICY_DEFAULTS
+    op = opcodes.OpGroupAdd(group_name="new_group",
+                            ndparams=ndparams,
+                            hv_state=hv_state,
+                            disk_state=disk_state,
+                            diskparams=diskparams,
+                            ipolicy=ipolicy)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def testAddNewGroupInvalidDiskparams(self):
+    diskparams = {constants.DT_RBD: {constants.LV_STRIPES: 1}}
+    op = opcodes.OpGroupAdd(group_name="new_group",
+                            diskparams=diskparams)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Provided option keys not supported")
+
+  def testAddNewGroupInvalidIPolic(self):
+    ipolicy = {"invalid_key": "value"}
+    op = opcodes.OpGroupAdd(group_name="new_group",
+                            ipolicy=ipolicy)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Invalid keys in ipolicy")
+
+
+class TestLUGroupAssignNodes(CmdlibTestCase):
+  def __init__(self, methodName='runTest'):
+    super(TestLUGroupAssignNodes, self).__init__(methodName)
+
+    self.op = opcodes.OpGroupAssignNodes(group_name="default",
+                                         nodes=[])
+
+  def testAssignSingleNode(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.op, nodes=[node.name])
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def _BuildSplitInstanceSituation(self):
+    node = self.cfg.AddNewNode()
+    self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                            primary_node=self.master,
+                            secondary_node=node)
+    group = self.cfg.AddNewNodeGroup()
+
+    return (node, group)
+
+  def testSplitInstanceNoForce(self):
+    (node, group) = self._BuildSplitInstanceSituation()
+    op = opcodes.OpGroupAssignNodes(group_name=group.name,
+                                    nodes=[node.name])
+
+    self.ExecOpCodeExpectOpExecError(
+      op, "instances get split by this change and --force was not given")
+
+  def testSplitInstanceForce(self):
+    (node, group) = self._BuildSplitInstanceSituation()
+
+    node2 = self.cfg.AddNewNode(group=group)
+    self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                            primary_node=self.master,
+                            secondary_node=node2)
+
+    op = opcodes.OpGroupAssignNodes(group_name=group.name,
+                                    nodes=[node.name],
+                                    force=True)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogContainsRegex("will split the following instances")
+    self.mcpu.assertLogContainsRegex(
+      "instances continue to be split across groups")
+
+
+  @withLockedLU
+  def testCheckAssignmentForSplitInstances(self, lu):
+    g1 = self.cfg.AddNewNodeGroup()
+    g2 = self.cfg.AddNewNodeGroup()
+    g3 = self.cfg.AddNewNodeGroup()
+
+    for (n, g) in [("n1a", g1), ("n1b", g1), ("n2a", g2), ("n2b", g2),
+                   ("n3a", g3), ("n3b", g3), ("n3c", g3)]:
+      self.cfg.AddNewNode(uuid=n, group=g.uuid)
+
+    for uuid, pnode, snode in [("inst1a", "n1a", "n1b"),
+                               ("inst1b", "n1b", "n1a"),
+                               ("inst2a", "n2a", "n2b"),
+                               ("inst3a", "n3a", None),
+                               ("inst3b", "n3b", "n1b"),
+                               ("inst3c", "n3b", "n2b")]:
+      dt = constants.DT_DISKLESS if snode is None else constants.DT_DRBD8
+      self.cfg.AddNewInstance(uuid=uuid,
+                              disk_template=dt,
+                              primary_node=pnode,
+                              secondary_node=snode)
+
+    # Test first with the existing state.
+    (new, prev) = lu.CheckAssignmentForSplitInstances(
+      [], self.cfg.GetAllNodesInfo(), self.cfg.GetAllInstancesInfo())
+
+    self.assertEqual([], new)
+    self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
+
+    # And now some changes.
+    (new, prev) = lu.CheckAssignmentForSplitInstances(
+      [("n1b", g3.uuid)],
+      self.cfg.GetAllNodesInfo(),
+      self.cfg.GetAllInstancesInfo())
+
+    self.assertEqual(set(["inst1a", "inst1b"]), set(new))
+    self.assertEqual(set(["inst3c"]), set(prev))
+
+
+class TestLUGroupQuery(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUGroupQuery, self).setUp()
+    self.fields = query._BuildGroupFields().keys()
+
+  def testInvalidGroupName(self):
+    op = opcodes.OpGroupQuery(names=["does_not_exist"],
+                              output_fields=self.fields)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Some groups do not exist")
+
+  def testQueryAllGroups(self):
+    op = opcodes.OpGroupQuery(output_fields=self.fields)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def testQueryGroupsByNameAndUuid(self):
+    group1 = self.cfg.AddNewNodeGroup()
+    group2 = self.cfg.AddNewNodeGroup()
+
+    node1 = self.cfg.AddNewNode(group=group1)
+    node2 = self.cfg.AddNewNode(group=group1)
+    self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                            primary_node=node1,
+                            secondary_node=node2)
+    self.cfg.AddNewInstance(primary_node=node2)
+
+    op = opcodes.OpGroupQuery(names=[group1.name, group2.uuid],
+                              output_fields=self.fields)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUGroupSetParams(CmdlibTestCase):
+  def testNoModifications(self):
+    op = opcodes.OpGroupSetParams(group_name=self.group.name)
+
+    self.ExecOpCodeExpectOpPrereqError(op,
+                                       "Please pass at least one modification")
+
+  def testModifyingAll(self):
+    ndparams = {constants.ND_EXCLUSIVE_STORAGE: True}
+    hv_state = {constants.HT_FAKE: {constants.HVST_CPU_TOTAL: 8}}
+    disk_state = {
+      constants.DT_PLAIN: {
+        "mock_vg": {constants.DS_DISK_TOTAL: 10}
+      }
+    }
+    diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool"}}
+    ipolicy = {constants.IPOLICY_DTS: [constants.DT_DRBD8]}
+    op = opcodes.OpGroupSetParams(group_name=self.group.name,
+                                  ndparams=ndparams,
+                                  hv_state=hv_state,
+                                  disk_state=disk_state,
+                                  diskparams=diskparams,
+                                  ipolicy=ipolicy)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def testInvalidDiskparams(self):
+    diskparams = {constants.DT_RBD: {constants.LV_STRIPES: 1}}
+    op = opcodes.OpGroupSetParams(group_name=self.group.name,
+                                  diskparams=diskparams)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Provided option keys not supported")
+
+  def testIPolicyNewViolations(self):
+    self.cfg.AddNewInstance(beparams={constants.BE_VCPUS: 8})
+
+    min_max = dict(constants.ISPECS_MINMAX_DEFAULTS)
+    min_max[constants.ISPECS_MAX].update({constants.ISPEC_CPU_COUNT: 2})
+    ipolicy = {constants.ISPECS_MINMAX: [min_max]}
+    op = opcodes.OpGroupSetParams(group_name=self.group.name,
+                                  ipolicy=ipolicy)
+
+    self.ExecOpCode(op)
+
+    self.assertLogContainsRegex(
+      "After the ipolicy change the following instances violate them")
+
+
+class TestLUGroupRemove(CmdlibTestCase):
+  def testNonEmptyGroup(self):
+    group = self.cfg.AddNewNodeGroup()
+    self.cfg.AddNewNode(group=group)
+    op = opcodes.OpGroupRemove(group_name=group.name)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Group .* not empty")
+
+  def testRemoveLastGroup(self):
+    self.master.group = "invalid_group"
+    op = opcodes.OpGroupRemove(group_name=self.group.name)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Group .* is the only group, cannot be removed")
+
+  def testRemoveGroup(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupRemove(group_name=group.name)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUGroupRename(CmdlibTestCase):
+  def testRenameToExistingName(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupRename(group_name=group.name,
+                               new_name=self.group.name)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Desired new name .* clashes with existing node group")
+
+  def testRename(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupRename(group_name=group.name,
+                               new_name="new_group_name")
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+
+class TestLUGroupEvacuate(CmdlibTestCase):
+  def testEvacuateEmptyGroup(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupEvacuate(group_name=group.name)
+
+    self.iallocator_cls.return_value.result = ([], [], [])
+
+    self.ExecOpCode(op)
+
+  def testEvacuateOnlyGroup(self):
+    op = opcodes.OpGroupEvacuate(group_name=self.group.name)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "There are no possible target groups")
+
+  def testEvacuateWithTargetGroups(self):
+    group = self.cfg.AddNewNodeGroup()
+    self.cfg.AddNewNode(group=group)
+    self.cfg.AddNewNode(group=group)
+
+    target_group1 = self.cfg.AddNewNodeGroup()
+    target_group2 = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupEvacuate(group_name=group.name,
+                                 target_groups=[target_group1.name,
+                                                target_group2.name])
+
+    self.iallocator_cls.return_value.result = ([], [], [])
+
+    self.ExecOpCode(op)
+
+  def testFailingIAllocator(self):
+    group = self.cfg.AddNewNodeGroup()
+    op = opcodes.OpGroupEvacuate(group_name=group.name)
+
+    self.iallocator_cls.return_value.success = False
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't compute group evacuation using iallocator")
+
+
+class TestLUGroupVerifyDisks(CmdlibTestCase):
+  def testNoInstances(self):
+    op = opcodes.OpGroupVerifyDisks(group_name=self.group.name)
+
+    self.ExecOpCode(op)
+
+    self.mcpu.assertLogIsEmpty()
+
+  def testOfflineAndFailingNode(self):
+    node = self.cfg.AddNewNode(offline=True)
+    self.cfg.AddNewInstance(primary_node=node,
+                            admin_state=constants.ADMINST_UP)
+    self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddFailedNode(self.master) \
+        .AddOfflineNode(node) \
+        .Build()
+
+    op = opcodes.OpGroupVerifyDisks(group_name=self.group.name)
+
+    (nerrors, offline, missing) = self.ExecOpCode(op)
+
+    self.assertEqual(1, len(nerrors))
+    self.assertEqual(0, len(offline))
+    self.assertEqual(2, len(missing))
+
+  def testValidNodeResult(self):
+    self.cfg.AddNewInstance(
+      disks=[self.cfg.CreateDisk(dev_type=constants.DT_PLAIN),
+             self.cfg.CreateDisk(dev_type=constants.DT_PLAIN)
+             ],
+      admin_state=constants.ADMINST_UP)
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {
+          "mockvg/mock_disk_1": (None, None, True),
+          "mockvg/mock_disk_2": (None, None, False)
+        }) \
+        .Build()
+
+    op = opcodes.OpGroupVerifyDisks(group_name=self.group.name)
+
+    (nerrors, offline, missing) = self.ExecOpCode(op)
+
+    self.assertEqual(0, len(nerrors))
+    self.assertEqual(1, len(offline))
+    self.assertEqual(0, len(missing))
+
+  def testDrbdDisk(self):
+    node1 = self.cfg.AddNewNode()
+    node2 = self.cfg.AddNewNode()
+    node3 = self.cfg.AddNewNode()
+    node4 = self.cfg.AddNewNode()
+
+    valid_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                     primary_node=node1,
+                                     secondary_node=node2)
+    broken_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                      primary_node=node1,
+                                      secondary_node=node2)
+    failing_node_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                            primary_node=node3,
+                                            secondary_node=node4)
+
+    self.cfg.AddNewInstance(disks=[valid_disk, broken_disk],
+                            primary_node=node1,
+                            admin_state=constants.ADMINST_UP)
+    self.cfg.AddNewInstance(disks=[failing_node_disk],
+                            primary_node=node3,
+                            admin_state=constants.ADMINST_UP)
+
+    lv_list_result = dict(("/".join(disk.logical_id), (None, None, True))
+                          for disk in itertools.chain(valid_disk.children,
+                                                      broken_disk.children))
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(node1, lv_list_result) \
+        .AddSuccessfulNode(node2, lv_list_result) \
+        .AddFailedNode(node3) \
+        .AddFailedNode(node4) \
+        .Build()
+
+    def GetDrbdNeedsActivationResult(node_uuid, *_):
+      if node_uuid == node1.uuid:
+        return self.RpcResultsBuilder() \
+                 .CreateSuccessfulNodeResult(node1, [])
+      elif node_uuid == node2.uuid:
+        return self.RpcResultsBuilder() \
+                 .CreateSuccessfulNodeResult(node2, [broken_disk.uuid])
+      elif node_uuid == node3.uuid or node_uuid == node4.uuid:
+        return self.RpcResultsBuilder() \
+                 .CreateFailedNodeResult(node_uuid)
+
+    self.rpc.call_drbd_needs_activation.side_effect = \
+      GetDrbdNeedsActivationResult
+
+    op = opcodes.OpGroupVerifyDisks(group_name=self.group.name)
+
+    (nerrors, offline, missing) = self.ExecOpCode(op)
+
+    self.assertEqual(2, len(nerrors))
+    self.assertEqual(1, len(offline))
+    self.assertEqual(1, len(missing))
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/instance_migration_unittest.py b/test/py/cmdlib/instance_migration_unittest.py
new file mode 100644
index 0000000..091f760
--- /dev/null
+++ b/test/py/cmdlib/instance_migration_unittest.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUInstanceFailover and LUInstanceMigrate
+
+"""
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import opcodes
+
+from testsupport import *
+
+import testutils
+
+
+class TestLUInstanceMigrate(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceMigrate, self).setUp()
+
+    self.snode = self.cfg.AddNewNode()
+
+    hv_info = ("bootid",
+               [{
+                 "type": constants.ST_LVM_VG,
+                 "storage_free": 10000
+               }],
+               ({"memory_free": 10000}, ))
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, hv_info) \
+        .AddSuccessfulNode(self.snode, hv_info) \
+        .Build()
+
+    self.rpc.call_blockdev_find.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, objects.BlockDevStatus())
+
+    self.rpc.call_migration_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_accept_instance.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.snode, True)
+    self.rpc.call_instance_migrate.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_instance_get_migration_status.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, objects.MigrationStatus())
+    self.rpc.call_instance_finalize_migration_dst.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.snode, True)
+    self.rpc.call_instance_finalize_migration_src.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+
+    self.inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                                        admin_state=constants.ADMINST_UP,
+                                        secondary_node=self.snode)
+    self.op = opcodes.OpInstanceMigrate(instance_name=self.inst.name)
+
+  def testPlainDisk(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_PLAIN)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance's disk layout 'plain' does not allow migrations")
+
+  def testMigrationToWrongNode(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.op,
+                         target_node=node.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instances with disk template drbd cannot be migrated to"
+          " arbitrary nodes")
+
+  def testMigration(self):
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCode(op)
+
+
+class TestLUInstanceFailover(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceFailover, self).setUp()
+
+    self.snode = self.cfg.AddNewNode()
+
+    hv_info = ("bootid",
+               [{
+                 "type": constants.ST_LVM_VG,
+                 "storage_free": 10000
+               }],
+               ({"memory_free": 10000}, ))
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, hv_info) \
+        .AddSuccessfulNode(self.snode, hv_info) \
+        .Build()
+
+    self.rpc.call_blockdev_find.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, objects.BlockDevStatus())
+
+    self.rpc.call_instance_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.snode,
+                                    ("/dev/mock", "/var/mock", None))
+    self.rpc.call_instance_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.snode, True)
+
+    self.inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                                        admin_state=constants.ADMINST_UP,
+                                        secondary_node=self.snode)
+    self.op = opcodes.OpInstanceFailover(instance_name=self.inst.name)
+
+  def testPlainDisk(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_PLAIN)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance's disk layout 'plain' does not allow failovers")
+
+  def testMigrationToWrongNode(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.op,
+                         target_node=node.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instances with disk template drbd cannot be failed over to"
+          " arbitrary nodes")
+
+  def testMigration(self):
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCode(op)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/instance_query_unittest.py b/test/py/cmdlib/instance_query_unittest.py
new file mode 100644
index 0000000..a2a8c4c
--- /dev/null
+++ b/test/py/cmdlib/instance_query_unittest.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUInstanceFailover and LUInstanceMigrate
+
+"""
+
+from ganeti import opcodes
+from ganeti import query
+
+from testsupport import *
+
+import testutils
+
+
+class TestLUInstanceQuery(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceQuery, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+    self.fields = query._BuildInstanceFields().keys()
+
+  def testInvalidInstance(self):
+    op = opcodes.OpInstanceQuery(names=["does_not_exist"],
+                                 output_fields=self.fields)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance 'does_not_exist' not known")
+
+  def testQueryInstance(self):
+    op = opcodes.OpInstanceQuery(output_fields=self.fields)
+    self.ExecOpCode(op)
+
+
+class TestLUInstanceQueryData(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceQueryData, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+
+  def testQueryInstanceData(self):
+    op = opcodes.OpInstanceQueryData()
+    self.ExecOpCode(op)
+
+  def testQueryStaticInstanceData(self):
+    op = opcodes.OpInstanceQueryData(static=True)
+    self.ExecOpCode(op)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.cmdlib.instance_storage_unittest.py b/test/py/cmdlib/instance_storage_unittest.py
similarity index 67%
rename from test/py/ganeti.cmdlib.instance_storage_unittest.py
rename to test/py/cmdlib/instance_storage_unittest.py
index 7874b5d..722546d 100755
--- a/test/py/ganeti.cmdlib.instance_storage_unittest.py
+++ b/test/py/cmdlib/instance_storage_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the cmdlib module 'instance_storage'"""
@@ -73,7 +82,8 @@
   def testPerformNodeInfoCall(self):
     expected_hv_arg = [(self.hvname, self.hvparams)]
     expected_storage_arg = {self.node_uuid:
-        [(constants.ST_LVM_VG, self.vg, [self.es])]}
+        [(constants.ST_LVM_VG, self.vg, [self.es]),
+         (constants.ST_LVM_PV, self.vg, [self.es])]}
     instance_storage._PerformNodeInfoCall(self.lu, self.node_uuids, self.vg)
     self.lu.rpc.call_node_info.assert_called_with(
         self.node_uuids, expected_storage_arg, expected_hv_arg)
diff --git a/test/py/cmdlib/instance_unittest.py b/test/py/cmdlib/instance_unittest.py
new file mode 100644
index 0000000..336fccb
--- /dev/null
+++ b/test/py/cmdlib/instance_unittest.py
@@ -0,0 +1,2421 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUInstance*
+
+"""
+
+import copy
+import itertools
+import re
+import unittest
+import mock
+import operator
+
+from ganeti import compat
+from ganeti import constants
+from ganeti import errors
+from ganeti import ht
+from ganeti import opcodes
+from ganeti import objects
+from ganeti import rpc
+from ganeti import utils
+from ganeti.cmdlib import instance
+
+from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
+
+from testsupport import *
+
+import testutils
+
+
+class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
+  def test(self):
+    ispec = {
+      constants.ISPEC_MEM_SIZE: 2048,
+      constants.ISPEC_CPU_COUNT: 2,
+      constants.ISPEC_DISK_COUNT: 1,
+      constants.ISPEC_DISK_SIZE: [512],
+      constants.ISPEC_NIC_COUNT: 0,
+      constants.ISPEC_SPINDLE_USE: 1,
+      }
+    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
+                                            constants.DT_PLAIN)
+    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
+                                                        constants.DT_PLAIN,
+                                                        _compute_fn=stub)
+    self.assertEqual(ret, [])
+
+
+class TestLUInstanceCreate(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceCreate, self).setUp()
+
+    self.net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(self.net, self.group)
+
+    self.node1 = self.cfg.AddNewNode()
+    self.node2 = self.cfg.AddNewNode()
+
+    self.rpc.call_os_get.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, self.os)
+
+    hv_info = ("bootid",
+               [{
+                 "type": constants.ST_LVM_VG,
+                 "storage_free": 10000
+               }],
+               ({"memory_free": 10000}, ))
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, hv_info) \
+        .AddSuccessfulNode(self.node1, hv_info) \
+        .AddSuccessfulNode(self.node2, hv_info) \
+        .Build()
+
+    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, [])
+
+    self.iallocator_cls.return_value.result = [self.node1.name, self.node2.name]
+
+    self.diskless_op = opcodes.OpInstanceCreate(
+      instance_name="diskless.test.com",
+      pnode=self.master.name,
+      disk_template=constants.DT_DISKLESS,
+      mode=constants.INSTANCE_CREATE,
+      nics=[{}],
+      disks=[],
+      os_type=self.os_name_variant)
+
+    self.plain_op = opcodes.OpInstanceCreate(
+      instance_name="plain.test.com",
+      pnode=self.master.name,
+      disk_template=constants.DT_PLAIN,
+      mode=constants.INSTANCE_CREATE,
+      nics=[{}],
+      disks=[{
+        constants.IDISK_SIZE: 1024
+      }],
+      os_type=self.os_name_variant)
+
+    self.block_op = opcodes.OpInstanceCreate(
+      instance_name="block.test.com",
+      pnode=self.master.name,
+      disk_template=constants.DT_BLOCK,
+      mode=constants.INSTANCE_CREATE,
+      nics=[{}],
+      disks=[{
+        constants.IDISK_SIZE: 1024,
+        constants.IDISK_ADOPT: "/dev/disk/block0"
+      }],
+      os_type=self.os_name_variant)
+
+    self.drbd_op = opcodes.OpInstanceCreate(
+      instance_name="drbd.test.com",
+      pnode=self.node1.name,
+      snode=self.node2.name,
+      disk_template=constants.DT_DRBD8,
+      mode=constants.INSTANCE_CREATE,
+      nics=[{}],
+      disks=[{
+        constants.IDISK_SIZE: 1024
+      }],
+      os_type=self.os_name_variant)
+
+    self.file_op = opcodes.OpInstanceCreate(
+      instance_name="file.test.com",
+      pnode=self.node1.name,
+      disk_template=constants.DT_FILE,
+      mode=constants.INSTANCE_CREATE,
+      nics=[{}],
+      disks=[{
+        constants.IDISK_SIZE: 1024
+      }],
+      os_type=self.os_name_variant)
+
+  def testSimpleCreate(self):
+    op = self.CopyOpCode(self.diskless_op)
+    self.ExecOpCode(op)
+
+  def testStrangeHostnameResolve(self):
+    op = self.CopyOpCode(self.diskless_op)
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock("random.host.example.com", "203.0.113.1")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Resolved hostname .* does not look the same as given hostname")
+
+  def testOpportunisticLockingNoIAllocator(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         opportunistic_locking=True,
+                         iallocator=None)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Opportunistic locking is only available in combination with an"
+          " instance allocator")
+
+  def testNicWithNetAndMode(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_NETWORK: self.net.name,
+                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "If network is given, no mode or link is allowed to be passed")
+
+  def testAutoIpNoNameCheck(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: constants.VALUE_AUTO
+                         }],
+                         ip_check=False,
+                         name_check=False)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "IP address set to auto but name checks have been skipped")
+
+  def testAutoIp(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: constants.VALUE_AUTO
+                         }])
+    self.ExecOpCode(op)
+
+  def testPoolIpNoNetwork(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: constants.NIC_IP_POOL
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "if ip=pool, parameter network must be passed too")
+
+  def testValidIp(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: "203.0.113.1"
+                         }])
+    self.ExecOpCode(op)
+
+  def testRoutedNoIp(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_MODE: constants.NIC_MODE_ROUTED
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Routed nic mode requires an ip address")
+
+  def testValicMac(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
+                         }])
+    self.ExecOpCode(op)
+
+  def testValidNicParams(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
+                           constants.INIC_LINK: "br_mock"
+                         }])
+    self.ExecOpCode(op)
+
+  def testValidNicParamsOpenVSwitch(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_MODE: constants.NIC_MODE_OVS,
+                           constants.INIC_VLAN: "1"
+                         }])
+    self.ExecOpCode(op)
+
+  def testNicNoneName(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_NAME: constants.VALUE_NONE
+                         }])
+    self.ExecOpCode(op)
+
+  def testConflictingIP(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: self.net.gateway[:-1] + "2"
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "The requested IP address .* belongs to network .*, but the target"
+          " NIC does not.")
+
+  def testVLanFormat(self):
+    for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
+      self.ResetMocks()
+      op = self.CopyOpCode(self.diskless_op,
+                           nics=[{
+                             constants.INIC_VLAN: vlan
+                           }])
+      self.ExecOpCodeExpectOpPrereqError(
+        op, "Specified VLAN parameter is invalid")
+
+  def testPoolIp(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: constants.NIC_IP_POOL,
+                           constants.INIC_NETWORK: self.net.name
+                         }])
+    self.ExecOpCode(op)
+
+  def testPoolIpUnconnectedNetwork(self):
+    net = self.cfg.AddNewNetwork()
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: constants.NIC_IP_POOL,
+                           constants.INIC_NETWORK: net.name
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "No netparams found for network .*.")
+
+  def testIpNotInNetwork(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         nics=[{
+                           constants.INIC_IP: "203.0.113.1",
+                           constants.INIC_NETWORK: self.net.name
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "IP address .* already in use or does not belong to network .*")
+
+  def testMixAdoptAndNotAdopt(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=constants.DT_PLAIN,
+                         disks=[{
+                           constants.IDISK_ADOPT: "lv1"
+                         }, {}])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Either all disks are adopted or none is")
+
+  def testMustAdoptWithoutAdopt(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=constants.DT_BLOCK,
+                         disks=[{}])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template blockdev requires disk adoption, but no 'adopt'"
+          " parameter given")
+
+  def testDontAdoptWithAdopt(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=constants.DT_DRBD8,
+                         disks=[{
+                           constants.IDISK_ADOPT: "lv1"
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk adoption is not supported for the 'drbd' disk template")
+
+  def testAdoptWithIAllocator(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=constants.DT_PLAIN,
+                         disks=[{
+                           constants.IDISK_ADOPT: "lv1"
+                         }],
+                         iallocator="mock")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk adoption not allowed with an iallocator script")
+
+  def testAdoptWithImport(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=constants.DT_PLAIN,
+                         disks=[{
+                           constants.IDISK_ADOPT: "lv1"
+                         }],
+                         mode=constants.INSTANCE_IMPORT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk adoption not allowed for instance import")
+
+  def testArgumentCombinations(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         # start flag will be flipped
+                         no_install=True,
+                         start=True,
+                         # no allowed combination
+                         ip_check=True,
+                         name_check=False)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Cannot do IP address check without a name check")
+
+  def testInvalidFileDriver(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         file_driver="invalid_file_driver")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
+
+  def testMissingSecondaryNode(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         pnode=self.master.name,
+                         disk_template=constants.DT_DRBD8)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "The networked disk templates need a mirror node")
+
+  def testIgnoredSecondaryNode(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         pnode=self.master.name,
+                         snode=self.node1.name,
+                         disk_template=constants.DT_PLAIN)
+    try:
+      self.ExecOpCode(op)
+    except Exception:
+      pass
+    self.mcpu.assertLogContainsRegex(
+      "Secondary node will be ignored on non-mirrored disk template")
+
+  def testMissingOsType(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         os_type=self.REMOVE)
+    self.ExecOpCodeExpectOpPrereqError(op, "No guest OS specified")
+
+  def testBlacklistedOs(self):
+    self.cluster.blacklisted_os = [self.os_name_variant]
+    op = self.CopyOpCode(self.diskless_op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Guest OS .* is not allowed for installation")
+
+  def testMissingDiskTemplate(self):
+    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
+    op = self.CopyOpCode(self.diskless_op,
+                         disk_template=self.REMOVE)
+    self.ExecOpCode(op)
+
+  def testExistingInstance(self):
+    inst = self.cfg.AddNewInstance()
+    op = self.CopyOpCode(self.diskless_op,
+                         instance_name=inst.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance .* is already in the cluster")
+
+  def testPlainInstance(self):
+    op = self.CopyOpCode(self.plain_op)
+    self.ExecOpCode(op)
+
+  def testPlainIAllocator(self):
+    op = self.CopyOpCode(self.plain_op,
+                         pnode=self.REMOVE,
+                         iallocator="mock")
+    self.ExecOpCode(op)
+
+  def testIAllocatorOpportunisticLocking(self):
+    op = self.CopyOpCode(self.plain_op,
+                         pnode=self.REMOVE,
+                         iallocator="mock",
+                         opportunistic_locking=True)
+    self.ExecOpCode(op)
+
+  def testFailingIAllocator(self):
+    self.iallocator_cls.return_value.success = False
+    op = self.CopyOpCode(self.plain_op,
+                         pnode=self.REMOVE,
+                         iallocator="mock")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't compute nodes using iallocator")
+
+  def testDrbdInstance(self):
+    op = self.CopyOpCode(self.drbd_op)
+    self.ExecOpCode(op)
+
+  def testDrbdIAllocator(self):
+    op = self.CopyOpCode(self.drbd_op,
+                         pnode=self.REMOVE,
+                         snode=self.REMOVE,
+                         iallocator="mock")
+    self.ExecOpCode(op)
+
+  def testFileInstance(self):
+    op = self.CopyOpCode(self.file_op)
+    self.ExecOpCode(op)
+
+  def testFileInstanceNoClusterStorage(self):
+    self.cluster.file_storage_dir = None
+    op = self.CopyOpCode(self.file_op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Cluster file storage dir not defined")
+
+  def testFileInstanceAdditionalPath(self):
+    op = self.CopyOpCode(self.file_op,
+                         file_storage_dir="mock_dir")
+    self.ExecOpCode(op)
+
+  def testIdentifyDefaults(self):
+    op = self.CopyOpCode(self.plain_op,
+                         hvparams={
+                           constants.HV_BOOT_ORDER: "cd"
+                         },
+                         beparams=constants.BEC_DEFAULTS.copy(),
+                         nics=[{
+                           constants.NIC_MODE: constants.NIC_MODE_BRIDGED
+                         }],
+                         osparams={
+                           self.os_name_variant: {}
+                         },
+                         identify_defaults=True)
+    self.ExecOpCode(op)
+
+    inst = self.cfg.GetAllInstancesInfo().values()[0]
+    self.assertEqual(0, len(inst.hvparams))
+    self.assertEqual(0, len(inst.beparams))
+    assert self.os_name_variant not in inst.osparams or \
+            len(inst.osparams[self.os_name_variant]) == 0
+
+  def testOfflineNode(self):
+    self.node1.offline = True
+    op = self.CopyOpCode(self.diskless_op,
+                         pnode=self.node1.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
+
+  def testDrainedNode(self):
+    self.node1.drained = True
+    op = self.CopyOpCode(self.diskless_op,
+                         pnode=self.node1.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
+
+  def testNonVmCapableNode(self):
+    self.node1.vm_capable = False
+    op = self.CopyOpCode(self.diskless_op,
+                         pnode=self.node1.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Cannot use non-vm_capable primary node")
+
+  def testNonEnabledHypervisor(self):
+    self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
+    op = self.CopyOpCode(self.diskless_op,
+                         hypervisor=constants.HT_FAKE)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Selected hypervisor .* not enabled in the cluster")
+
+  def testAddTag(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         tags=["tag"])
+    self.ExecOpCode(op)
+
+  def testInvalidTag(self):
+    op = self.CopyOpCode(self.diskless_op,
+                         tags=["too_long" * 20])
+    self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
+
+  def testPingableInstanceName(self):
+    self.netutils_mod.TcpPing.return_value = True
+    op = self.CopyOpCode(self.diskless_op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "IP .* of instance diskless.test.com already in use")
+
+  def testPrimaryIsSecondaryNode(self):
+    op = self.CopyOpCode(self.drbd_op,
+                         snode=self.drbd_op.pnode)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "The secondary node cannot be the primary node")
+
+  def testPrimarySecondaryDifferentNodeGroups(self):
+    group = self.cfg.AddNewNodeGroup()
+    self.node2.group = group.uuid
+    op = self.CopyOpCode(self.drbd_op)
+    self.ExecOpCode(op)
+    self.mcpu.assertLogContainsRegex(
+      "The primary and secondary nodes are in two different node groups")
+
+  def testExclusiveStorageUnsupportedDiskTemplate(self):
+    self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
+    op = self.CopyOpCode(self.drbd_op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template drbd not supported with exclusive storage")
+
+  def testAdoptPlain(self):
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {
+          "xenvg/mock_disk_1": (10000, None, False)
+        }) \
+        .Build()
+    op = self.CopyOpCode(self.plain_op)
+    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
+    self.ExecOpCode(op)
+
+  def testAdoptPlainMissingLv(self):
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {}) \
+        .Build()
+    op = self.CopyOpCode(self.plain_op)
+    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
+    self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
+
+  def testAdoptPlainOnlineLv(self):
+    self.rpc.call_lv_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {
+          "xenvg/mock_disk_1": (10000, None, True)
+        }) \
+        .Build()
+    op = self.CopyOpCode(self.plain_op)
+    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Online logical volumes found, cannot adopt")
+
+  def testAdoptBlock(self):
+    self.rpc.call_bdev_sizes.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {
+          "/dev/disk/block0": 10000
+        }) \
+        .Build()
+    op = self.CopyOpCode(self.block_op)
+    self.ExecOpCode(op)
+
+  def testAdoptBlockDuplicateNames(self):
+    op = self.CopyOpCode(self.block_op,
+                         disks=[{
+                           constants.IDISK_SIZE: 0,
+                           constants.IDISK_ADOPT: "/dev/disk/block0"
+                         }, {
+                           constants.IDISK_SIZE: 0,
+                           constants.IDISK_ADOPT: "/dev/disk/block0"
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Duplicate disk names given for adoption")
+
+  def testAdoptBlockInvalidNames(self):
+    op = self.CopyOpCode(self.block_op,
+                         disks=[{
+                           constants.IDISK_SIZE: 0,
+                           constants.IDISK_ADOPT: "/invalid/block0"
+                         }])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Device node.* lie outside .* and cannot be adopted")
+
+  def testAdoptBlockMissingDisk(self):
+    self.rpc.call_bdev_sizes.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {}) \
+        .Build()
+    op = self.CopyOpCode(self.block_op)
+    self.ExecOpCodeExpectOpPrereqError(op, "Missing block device")
+
+  def testNoWaitForSyncDrbd(self):
+    op = self.CopyOpCode(self.drbd_op,
+                         wait_for_sync=False)
+    self.ExecOpCode(op)
+
+  def testNoWaitForSyncPlain(self):
+    op = self.CopyOpCode(self.plain_op,
+                         wait_for_sync=False)
+    self.ExecOpCode(op)
+
+  def testImportPlainFromGivenSrcNode(self):
+    exp_info = """
+[export]
+version=0
+os=mock_os
+[instance]
+name=old_name.example.com
+"""
+
+    self.rpc.call_export_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, exp_info)
+    op = self.CopyOpCode(self.plain_op,
+                         mode=constants.INSTANCE_IMPORT,
+                         src_node=self.master.name)
+    self.ExecOpCode(op)
+
+  def testImportPlainWithoutSrcNodeNotFound(self):
+    op = self.CopyOpCode(self.plain_op,
+                         mode=constants.INSTANCE_IMPORT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "No export found for relative path")
+
+  def testImportPlainWithoutSrcNode(self):
+    exp_info = """
+[export]
+version=0
+os=mock_os
+[instance]
+name=old_name.example.com
+"""
+
+    self.rpc.call_export_list.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master, {"mock_path": {}}) \
+        .Build()
+    self.rpc.call_export_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, exp_info)
+
+    op = self.CopyOpCode(self.plain_op,
+                         mode=constants.INSTANCE_IMPORT,
+                         src_path="mock_path")
+    self.ExecOpCode(op)
+
+  def testImportPlainCorruptExportInfo(self):
+    exp_info = ""
+    self.rpc.call_export_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, exp_info)
+    op = self.CopyOpCode(self.plain_op,
+                         mode=constants.INSTANCE_IMPORT,
+                         src_node=self.master.name)
+    self.ExecOpCodeExpectException(op, errors.ProgrammerError,
+                                   "Corrupted export config")
+
+  def testImportPlainWrongExportInfoVersion(self):
+    exp_info = """
+[export]
+version=1
+"""
+    self.rpc.call_export_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, exp_info)
+    op = self.CopyOpCode(self.plain_op,
+                         mode=constants.INSTANCE_IMPORT,
+                         src_node=self.master.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Wrong export version")
+
+  def testImportPlainWithParametersAndImport(self):
+    exp_info = """
+[export]
+version=0
+os=mock_os
+[instance]
+name=old_name.example.com
+disk0_size=1024
+disk1_size=1500
+disk1_dump=mock_path
+nic0_mode=bridged
+nic0_link=br_mock
+nic0_mac=f6:ab:f4:45:d1:af
+nic0_ip=192.0.2.1
+tags=tag1 tag2
+hypervisor=xen-hvm
+[hypervisor]
+boot_order=cd
+[backend]
+memory=1024
+vcpus=8
+[os]
+param1=val1
+"""
+
+    self.rpc.call_export_info.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, exp_info)
+    self.rpc.call_import_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, "daemon_name")
+    self.rpc.call_impexp_status.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master,
+                                    [
+                                      objects.ImportExportStatus(exit_status=0)
+                                    ])
+    self.rpc.call_impexp_cleanup.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+
+    op = self.CopyOpCode(self.plain_op,
+                         disks=[],
+                         nics=[],
+                         tags=[],
+                         hypervisor=None,
+                         hvparams={},
+                         mode=constants.INSTANCE_IMPORT,
+                         src_node=self.master.name)
+    self.ExecOpCode(op)
+
+
+class TestCheckOSVariant(CmdlibTestCase):
+  def testNoVariantsSupported(self):
+    os = self.cfg.CreateOs(supported_variants=[])
+    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
+                      os, "os+variant")
+
+  def testNoVariantGiven(self):
+    os = self.cfg.CreateOs(supported_variants=["default"])
+    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
+                      os, "os")
+
+  def testWrongVariantGiven(self):
+    os = self.cfg.CreateOs(supported_variants=["default"])
+    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
+                      os, "os+wrong_variant")
+
+  def testOkWithVariant(self):
+    os = self.cfg.CreateOs(supported_variants=["default"])
+    instance._CheckOSVariant(os, "os+default")
+
+  def testOkWithoutVariant(self):
+    os = self.cfg.CreateOs(supported_variants=[])
+    instance._CheckOSVariant(os, "os")
+
+
+class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
+  def setUp(self):
+    super(TestCheckTargetNodeIPolicy, self).setUp()
+
+    self.op = self.diskless_op
+
+    self.instance = self.cfg.AddNewInstance()
+    self.target_group = self.cfg.AddNewNodeGroup()
+    self.target_node = self.cfg.AddNewNode(group=self.target_group)
+
+  @withLockedLU
+  def testNoViolation(self, lu):
+    compute_recoder = mock.Mock(return_value=[])
+    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
+                                    self.target_node, NotImplemented,
+                                    _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testNoIgnore(self, lu):
+    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
+    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
+                      lu, NotImplemented, self.instance,
+                      self.target_node, NotImplemented,
+                      _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    self.mcpu.assertLogIsEmpty()
+
+  @withLockedLU
+  def testIgnoreViolation(self, lu):
+    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
+    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
+                                    self.target_node, NotImplemented,
+                                    ignore=True, _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    msg = ("Instance does not meet target node group's .* instance policy:"
+           " mem_size not in range")
+    self.mcpu.assertLogContainsRegex(msg)
+
+
+class TestApplyContainerMods(unittest.TestCase):
+  def testEmptyContainer(self):
+    container = []
+    chgdesc = []
+    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
+                                 None)
+    self.assertEqual(container, [])
+    self.assertEqual(chgdesc, [])
+
+  def testAdd(self):
+    container = []
+    chgdesc = []
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_ADD, -1, "Hello"),
+      (constants.DDM_ADD, -1, "World"),
+      (constants.DDM_ADD, 0, "Start"),
+      (constants.DDM_ADD, -1, "End"),
+      ], None)
+    instance._ApplyContainerMods("test", container, chgdesc, mods,
+                                 None, None, None)
+    self.assertEqual(container, ["Start", "Hello", "World", "End"])
+    self.assertEqual(chgdesc, [])
+
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_ADD, 0, "zero"),
+      (constants.DDM_ADD, 3, "Added"),
+      (constants.DDM_ADD, 5, "four"),
+      (constants.DDM_ADD, 7, "xyz"),
+      ], None)
+    instance._ApplyContainerMods("test", container, chgdesc, mods,
+                                 None, None, None)
+    self.assertEqual(container,
+                     ["zero", "Start", "Hello", "Added", "World", "four",
+                      "End", "xyz"])
+    self.assertEqual(chgdesc, [])
+
+    for idx in [-2, len(container) + 1]:
+      mods = instance._PrepareContainerMods([
+        (constants.DDM_ADD, idx, "error"),
+        ], None)
+      self.assertRaises(IndexError, instance._ApplyContainerMods,
+                        "test", container, None, mods, None, None, None)
+
+  def testRemoveError(self):
+    for idx in [0, 1, 2, 100, -1, -4]:
+      mods = instance._PrepareContainerMods([
+        (constants.DDM_REMOVE, idx, None),
+        ], None)
+      self.assertRaises(IndexError, instance._ApplyContainerMods,
+                        "test", [], None, mods, None, None, None)
+
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_REMOVE, 0, object()),
+      ], None)
+    self.assertRaises(AssertionError, instance._ApplyContainerMods,
+                      "test", [""], None, mods, None, None, None)
+
+  def testAddError(self):
+    for idx in range(-100, -1) + [100]:
+      mods = instance._PrepareContainerMods([
+        (constants.DDM_ADD, idx, None),
+        ], None)
+      self.assertRaises(IndexError, instance._ApplyContainerMods,
+                        "test", [], None, mods, None, None, None)
+
+  def testRemove(self):
+    container = ["item 1", "item 2"]
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_ADD, -1, "aaa"),
+      (constants.DDM_REMOVE, -1, None),
+      (constants.DDM_ADD, -1, "bbb"),
+      ], None)
+    chgdesc = []
+    instance._ApplyContainerMods("test", container, chgdesc, mods,
+                                 None, None, None)
+    self.assertEqual(container, ["item 1", "item 2", "bbb"])
+    self.assertEqual(chgdesc, [
+      ("test/2", "remove"),
+      ])
+
+  def testModify(self):
+    container = ["item 1", "item 2"]
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_MODIFY, -1, "a"),
+      (constants.DDM_MODIFY, 0, "b"),
+      (constants.DDM_MODIFY, 1, "c"),
+      ], None)
+    chgdesc = []
+    instance._ApplyContainerMods("test", container, chgdesc, mods,
+                                 None, None, None)
+    self.assertEqual(container, ["item 1", "item 2"])
+    self.assertEqual(chgdesc, [])
+
+    for idx in [-2, len(container) + 1]:
+      mods = instance._PrepareContainerMods([
+        (constants.DDM_MODIFY, idx, "error"),
+        ], None)
+      self.assertRaises(IndexError, instance._ApplyContainerMods,
+                        "test", container, None, mods, None, None, None)
+
+  @staticmethod
+  def _CreateTestFn(idx, params, private):
+    private.data = ("add", idx, params)
+    return ((100 * idx, params), [
+      ("test/%s" % idx, hex(idx)),
+      ])
+
+  @staticmethod
+  def _ModifyTestFn(idx, item, params, private):
+    private.data = ("modify", idx, params)
+    return [
+      ("test/%s" % idx, "modify %s" % params),
+      ]
+
+  @staticmethod
+  def _RemoveTestFn(idx, item, private):
+    private.data = ("remove", idx, item)
+
+  def testAddWithCreateFunction(self):
+    container = []
+    chgdesc = []
+    mods = instance._PrepareContainerMods([
+      (constants.DDM_ADD, -1, "Hello"),
+      (constants.DDM_ADD, -1, "World"),
+      (constants.DDM_ADD, 0, "Start"),
+      (constants.DDM_ADD, -1, "End"),
+      (constants.DDM_REMOVE, 2, None),
+      (constants.DDM_MODIFY, -1, "foobar"),
+      (constants.DDM_REMOVE, 2, None),
+      (constants.DDM_ADD, 1, "More"),
+      ], mock.Mock)
+    instance._ApplyContainerMods("test", container, chgdesc, mods,
+                                 self._CreateTestFn, self._ModifyTestFn,
+                                 self._RemoveTestFn)
+    self.assertEqual(container, [
+      (000, "Start"),
+      (100, "More"),
+      (000, "Hello"),
+      ])
+    self.assertEqual(chgdesc, [
+      ("test/0", "0x0"),
+      ("test/1", "0x1"),
+      ("test/0", "0x0"),
+      ("test/3", "0x3"),
+      ("test/2", "remove"),
+      ("test/2", "modify foobar"),
+      ("test/2", "remove"),
+      ("test/1", "0x1")
+      ])
+    self.assertTrue(compat.all(op == private.data[0]
+                               for (op, _, _, private) in mods))
+    self.assertEqual([private.data for (op, _, _, private) in mods], [
+      ("add", 0, "Hello"),
+      ("add", 1, "World"),
+      ("add", 0, "Start"),
+      ("add", 3, "End"),
+      ("remove", 2, (100, "World")),
+      ("modify", 2, "foobar"),
+      ("remove", 2, (300, "End")),
+      ("add", 1, "More"),
+      ])
+
+
+class _FakeConfigForGenDiskTemplate(ConfigMock):
+  def __init__(self):
+    super(_FakeConfigForGenDiskTemplate, self).__init__()
+
+    self._unique_id = itertools.count()
+    self._drbd_minor = itertools.count(20)
+    self._port = itertools.count(constants.FIRST_DRBD_PORT)
+    self._secret = itertools.count()
+
+  def GenerateUniqueID(self, ec_id):
+    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
+
+  def AllocateDRBDMinor(self, nodes, instance):
+    return [self._drbd_minor.next()
+            for _ in nodes]
+
+  def AllocatePort(self):
+    return self._port.next()
+
+  def GenerateDRBDSecret(self, ec_id):
+    return "ec%s-secret%s" % (ec_id, self._secret.next())
+
+
+class TestGenerateDiskTemplate(CmdlibTestCase):
+  def setUp(self):
+    super(TestGenerateDiskTemplate, self).setUp()
+
+    self.cfg = _FakeConfigForGenDiskTemplate()
+    self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
+
+    self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
+
+    self.lu = self.GetMockLU()
+
+  @staticmethod
+  def GetDiskParams():
+    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
+
+  def testWrongDiskTemplate(self):
+    gdt = instance.GenerateDiskTemplate
+    disk_template = "##unknown##"
+
+    assert disk_template not in constants.DISK_TEMPLATES
+
+    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
+                      "inst26831.example.com", "node30113.example.com", [], [],
+                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
+                      self.GetDiskParams())
+
+  def testDiskless(self):
+    gdt = instance.GenerateDiskTemplate
+
+    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
+                 "node30113.example.com", [], [],
+                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
+                 self.GetDiskParams())
+    self.assertEqual(result, [])
+
+  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
+                       file_storage_dir=NotImplemented,
+                       file_driver=NotImplemented):
+    gdt = instance.GenerateDiskTemplate
+
+    map(lambda params: utils.ForceDictType(params,
+                                           constants.IDISK_PARAMS_TYPES),
+        disk_info)
+
+    # Check if non-empty list of secondaries is rejected
+    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
+                      template, "inst25088.example.com",
+                      "node185.example.com", ["node323.example.com"], [],
+                      NotImplemented, NotImplemented, base_index,
+                      self.lu.LogInfo, self.GetDiskParams())
+
+    result = gdt(self.lu, template, "inst21662.example.com",
+                 "node21741.example.com", [],
+                 disk_info, file_storage_dir, file_driver, base_index,
+                 self.lu.LogInfo, self.GetDiskParams())
+
+    for (idx, disk) in enumerate(result):
+      self.assertTrue(isinstance(disk, objects.Disk))
+      self.assertEqual(disk.dev_type, exp_dev_type)
+      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
+      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
+      self.assertTrue(disk.children is None)
+
+    self._CheckIvNames(result, base_index, base_index + len(disk_info))
+    instance._UpdateIvNames(base_index, result)
+    self._CheckIvNames(result, base_index, base_index + len(disk_info))
+
+    return result
+
+  def _CheckIvNames(self, disks, base_index, end_index):
+    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
+                     ["disk/%s" % i for i in range(base_index, end_index)])
+
+  def testPlain(self):
+    disk_info = [{
+      constants.IDISK_SIZE: 1024,
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      }, {
+      constants.IDISK_SIZE: 4096,
+      constants.IDISK_VG: "othervg",
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      }]
+
+    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
+                                   constants.DT_PLAIN)
+
+    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
+      ("xenvg", "ec1-uq0.disk3"),
+      ("othervg", "ec1-uq1.disk4"),
+      ])
+
+  def testFile(self):
+    # anything != DT_FILE would do here
+    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
+    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
+                      constants.DT_FILE, [], 0, NotImplemented)
+    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
+                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
+
+    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
+      disk_info = [{
+        constants.IDISK_SIZE: 80 * 1024,
+        constants.IDISK_MODE: constants.DISK_RDONLY,
+        }, {
+        constants.IDISK_SIZE: 4096,
+        constants.IDISK_MODE: constants.DISK_RDWR,
+        }, {
+        constants.IDISK_SIZE: 6 * 1024,
+        constants.IDISK_MODE: constants.DISK_RDWR,
+        }]
+
+      self.cluster.enabled_disk_templates = [disk_template]
+      result = self._TestTrivialDisk(
+        disk_template, disk_info, 2, disk_template,
+        file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
+
+      for (idx, disk) in enumerate(result):
+        (file_driver, file_storage_dir) = disk.logical_id
+        dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
+        self.assertEqual(file_driver, constants.FD_BLKTAP)
+        # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
+        self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
+
+  def testBlock(self):
+    disk_info = [{
+      constants.IDISK_SIZE: 8 * 1024,
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      constants.IDISK_ADOPT: "/tmp/some/block/dev",
+      }]
+
+    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
+                                   constants.DT_BLOCK)
+
+    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
+      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
+      ])
+
+  def testRbd(self):
+    disk_info = [{
+      constants.IDISK_SIZE: 8 * 1024,
+      constants.IDISK_MODE: constants.DISK_RDONLY,
+      }, {
+      constants.IDISK_SIZE: 100 * 1024,
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      }]
+
+    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
+                                   constants.DT_RBD)
+
+    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
+      ("rbd", "ec1-uq0.rbd.disk0"),
+      ("rbd", "ec1-uq1.rbd.disk1"),
+      ])
+
+  def testDrbd8(self):
+    gdt = instance.GenerateDiskTemplate
+    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
+    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
+
+    disk_info = [{
+      constants.IDISK_SIZE: 1024,
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      }, {
+      constants.IDISK_SIZE: 100 * 1024,
+      constants.IDISK_MODE: constants.DISK_RDONLY,
+      constants.IDISK_METAVG: "metavg",
+      }, {
+      constants.IDISK_SIZE: 4096,
+      constants.IDISK_MODE: constants.DISK_RDWR,
+      constants.IDISK_VG: "vgxyz",
+      },
+      ]
+
+    exp_logical_ids = [
+      [
+        (self.lu.cfg.GetVGName(), "ec1-uq0.disk0_data"),
+        (drbd8_default_metavg, "ec1-uq0.disk0_meta"),
+      ], [
+        (self.lu.cfg.GetVGName(), "ec1-uq1.disk1_data"),
+        ("metavg", "ec1-uq1.disk1_meta"),
+      ], [
+        ("vgxyz", "ec1-uq2.disk2_data"),
+        (drbd8_default_metavg, "ec1-uq2.disk2_meta"),
+      ]]
+
+    assert len(exp_logical_ids) == len(disk_info)
+
+    map(lambda params: utils.ForceDictType(params,
+                                           constants.IDISK_PARAMS_TYPES),
+        disk_info)
+
+    # Check if empty list of secondaries is rejected
+    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
+                      "inst827.example.com", "node1334.example.com", [],
+                      disk_info, NotImplemented, NotImplemented, 0,
+                      self.lu.LogInfo, self.GetDiskParams())
+
+    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
+                 "node1334.example.com", ["node12272.example.com"],
+                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
+                 self.GetDiskParams())
+
+    for (idx, disk) in enumerate(result):
+      self.assertTrue(isinstance(disk, objects.Disk))
+      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
+      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
+      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
+
+      for child in disk.children:
+        self.assertTrue(isinstance(disk, objects.Disk))
+        self.assertEqual(child.dev_type, constants.DT_PLAIN)
+        self.assertTrue(child.children is None)
+
+      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
+                       exp_logical_ids[idx])
+
+      self.assertEqual(len(disk.children), 2)
+      self.assertEqual(disk.children[0].size, disk.size)
+      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
+
+    self._CheckIvNames(result, 0, len(disk_info))
+    instance._UpdateIvNames(0, result)
+    self._CheckIvNames(result, 0, len(disk_info))
+
+    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
+      ("node1334.example.com", "node12272.example.com",
+       constants.FIRST_DRBD_PORT, 20, 21, "ec1-secret0"),
+      ("node1334.example.com", "node12272.example.com",
+       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec1-secret1"),
+      ("node1334.example.com", "node12272.example.com",
+       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec1-secret2"),
+      ])
+
+
+class _DiskPauseTracker:
+  def __init__(self):
+    self.history = []
+
+  def __call__(self, (disks, instance), pause):
+    assert not (set(disks) - set(instance.disks))
+
+    self.history.extend((i.logical_id, i.size, pause)
+                        for i in disks)
+
+    return (True, [True] * len(disks))
+
+
+class _ConfigForDiskWipe:
+  def __init__(self, exp_node_uuid):
+    self._exp_node_uuid = exp_node_uuid
+
+  def GetNodeName(self, node_uuid):
+    assert node_uuid == self._exp_node_uuid
+    return "name.of.expected.node"
+
+
+class _RpcForDiskWipe:
+  def __init__(self, exp_node, pause_cb, wipe_cb):
+    self._exp_node = exp_node
+    self._pause_cb = pause_cb
+    self._wipe_cb = wipe_cb
+
+  def call_blockdev_pause_resume_sync(self, node, disks, pause):
+    assert node == self._exp_node
+    return rpc.RpcResult(data=self._pause_cb(disks, pause))
+
+  def call_blockdev_wipe(self, node, bdev, offset, size):
+    assert node == self._exp_node
+    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
+
+
+class _DiskWipeProgressTracker:
+  def __init__(self, start_offset):
+    self._start_offset = start_offset
+    self.progress = {}
+
+  def __call__(self, (disk, _), offset, size):
+    assert isinstance(offset, (long, int))
+    assert isinstance(size, (long, int))
+
+    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
+
+    assert offset >= self._start_offset
+    assert (offset + size) <= disk.size
+
+    assert size > 0
+    assert size <= constants.MAX_WIPE_CHUNK
+    assert size <= max_chunk_size
+
+    assert offset == self._start_offset or disk.logical_id in self.progress
+
+    # Keep track of progress
+    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
+
+    assert cur_progress == offset
+
+    # Record progress
+    self.progress[disk.logical_id] += size
+
+    return (True, None)
+
+
+class TestWipeDisks(unittest.TestCase):
+  def _FailingPauseCb(self, (disks, _), pause):
+    self.assertEqual(len(disks), 3)
+    self.assertTrue(pause)
+    # Simulate an RPC error
+    return (False, "error")
+
+  def testPauseFailure(self):
+    node_name = "node1372.example.com"
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
+                                     NotImplemented),
+                 cfg=_ConfigForDiskWipe(node_name))
+
+    disks = [
+      objects.Disk(dev_type=constants.DT_PLAIN),
+      objects.Disk(dev_type=constants.DT_PLAIN),
+      objects.Disk(dev_type=constants.DT_PLAIN),
+      ]
+
+    inst = objects.Instance(name="inst21201",
+                            primary_node=node_name,
+                            disk_template=constants.DT_PLAIN,
+                            disks=disks)
+
+    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
+
+  def _FailingWipeCb(self, (disk, _), offset, size):
+    # This should only ever be called for the first disk
+    self.assertEqual(disk.logical_id, "disk0")
+    return (False, None)
+
+  def testFailingWipe(self):
+    node_uuid = "node13445-uuid"
+    pt = _DiskPauseTracker()
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
+                 cfg=_ConfigForDiskWipe(node_uuid))
+
+    disks = [
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
+                   size=100 * 1024),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
+                   size=500 * 1024),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
+      ]
+
+    inst = objects.Instance(name="inst562",
+                            primary_node=node_uuid,
+                            disk_template=constants.DT_PLAIN,
+                            disks=disks)
+
+    try:
+      instance.WipeDisks(lu, inst)
+    except errors.OpExecError, err:
+      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
+    else:
+      self.fail("Did not raise exception")
+
+    # Check if all disks were paused and resumed
+    self.assertEqual(pt.history, [
+      ("disk0", 100 * 1024, True),
+      ("disk1", 500 * 1024, True),
+      ("disk2", 256, True),
+      ("disk0", 100 * 1024, False),
+      ("disk1", 500 * 1024, False),
+      ("disk2", 256, False),
+      ])
+
+  def _PrepareWipeTest(self, start_offset, disks):
+    node_name = "node-with-offset%s.example.com" % start_offset
+    pauset = _DiskPauseTracker()
+    progresst = _DiskWipeProgressTracker(start_offset)
+
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
+                 cfg=_ConfigForDiskWipe(node_name))
+
+    instance = objects.Instance(name="inst3560",
+                                primary_node=node_name,
+                                disk_template=constants.DT_PLAIN,
+                                disks=disks)
+
+    return (lu, instance, pauset, progresst)
+
+  def testNormalWipe(self):
+    disks = [
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
+                   size=500 * 1024),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
+      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
+                   size=constants.MAX_WIPE_CHUNK),
+      ]
+
+    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
+
+    instance.WipeDisks(lu, inst)
+
+    self.assertEqual(pauset.history, [
+      ("disk0", 1024, True),
+      ("disk1", 500 * 1024, True),
+      ("disk2", 128, True),
+      ("disk3", constants.MAX_WIPE_CHUNK, True),
+      ("disk0", 1024, False),
+      ("disk1", 500 * 1024, False),
+      ("disk2", 128, False),
+      ("disk3", constants.MAX_WIPE_CHUNK, False),
+      ])
+
+    # Ensure the complete disk has been wiped
+    self.assertEqual(progresst.progress,
+                     dict((i.logical_id, i.size) for i in disks))
+
+  def testWipeWithStartOffset(self):
+    for start_offset in [0, 280, 8895, 1563204]:
+      disks = [
+        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
+                     size=128),
+        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
+                     size=start_offset + (100 * 1024)),
+        ]
+
+      (lu, inst, pauset, progresst) = \
+        self._PrepareWipeTest(start_offset, disks)
+
+      # Test start offset with only one disk
+      instance.WipeDisks(lu, inst,
+                         disks=[(1, disks[1], start_offset)])
+
+      # Only the second disk may have been paused and wiped
+      self.assertEqual(pauset.history, [
+        ("disk1", start_offset + (100 * 1024), True),
+        ("disk1", start_offset + (100 * 1024), False),
+        ])
+      self.assertEqual(progresst.progress, {
+        "disk1": disks[1].size,
+        })
+
+
+class TestCheckOpportunisticLocking(unittest.TestCase):
+  class OpTest(opcodes.OpCode):
+    OP_PARAMS = [
+      ("opportunistic_locking", False, ht.TBool, None),
+      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
+      ]
+
+  @classmethod
+  def _MakeOp(cls, **kwargs):
+    op = cls.OpTest(**kwargs)
+    op.Validate(True)
+    return op
+
+  def testMissingAttributes(self):
+    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
+                      object())
+
+  def testDefaults(self):
+    op = self._MakeOp()
+    instance._CheckOpportunisticLocking(op)
+
+  def test(self):
+    for iallocator in [None, "something", "other"]:
+      for opplock in [False, True]:
+        op = self._MakeOp(iallocator=iallocator,
+                          opportunistic_locking=opplock)
+        if opplock and not iallocator:
+          self.assertRaises(errors.OpPrereqError,
+                            instance._CheckOpportunisticLocking, op)
+        else:
+          instance._CheckOpportunisticLocking(op)
+
+
+class TestLUInstanceRemove(CmdlibTestCase):
+  def testRemoveMissingInstance(self):
+    op = opcodes.OpInstanceRemove(instance_name="missing.inst")
+    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
+
+  def testRemoveInst(self):
+    inst = self.cfg.AddNewInstance(disks=[])
+    op = opcodes.OpInstanceRemove(instance_name=inst.name)
+    self.ExecOpCode(op)
+
+
+class TestLUInstanceMove(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceMove, self).setUp()
+
+    self.node = self.cfg.AddNewNode()
+
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.node, ("/dev/mocked_path",
+                                    "/var/run/ganeti/instance-disks/mocked_d",
+                                    None))
+    self.rpc.call_blockdev_export.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, "")
+    self.rpc.call_blockdev_remove.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, "")
+
+  def testMissingInstance(self):
+    op = opcodes.OpInstanceMove(instance_name="missing.inst",
+                                target_node=self.node.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
+
+  def testUncopyableDiskTemplate(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template sharedfile not suitable for copying")
+
+  def testAlreadyOnTargetNode(self):
+    inst = self.cfg.AddNewInstance()
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.master.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance .* is already on the node .*")
+
+  def testMoveStoppedInstance(self):
+    inst = self.cfg.AddNewInstance()
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCode(op)
+
+  def testMoveRunningInstance(self):
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.node,
+                           (NotImplemented, NotImplemented,
+                            ({"memory_free": 10000}, ))) \
+        .Build()
+    self.rpc.call_instance_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.node, "")
+
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCode(op)
+
+  def testMoveFailingStart(self):
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.node,
+                           (NotImplemented, NotImplemented,
+                            ({"memory_free": 10000}, ))) \
+        .Build()
+    self.rpc.call_instance_start.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.node)
+
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCodeExpectOpExecError(
+      op, "Could not start instance .* on node .*")
+
+  def testMoveFailingBlockdevAssemble(self):
+    inst = self.cfg.AddNewInstance()
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.node)
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
+
+  def testMoveFailingBlockdevExport(self):
+    inst = self.cfg.AddNewInstance()
+    self.rpc.call_blockdev_export.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.node)
+    op = opcodes.OpInstanceMove(instance_name=inst.name,
+                                target_node=self.node.name)
+    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
+
+
+class TestLUInstanceRename(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceRename, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+
+    self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
+                                       new_name="new_name.example.com")
+
+  def testIpCheckWithoutNameCheck(self):
+    op = self.CopyOpCode(self.op,
+                         ip_check=True,
+                         name_check=False)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "IP address check requires a name check")
+
+  def testIpAlreadyInUse(self):
+    self.netutils_mod.TcpPing.return_value = True
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "IP .* of instance .* already in use")
+
+  def testExistingInstanceName(self):
+    self.cfg.AddNewInstance(name="new_name.example.com")
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance .* is already in the cluster")
+
+  def testFileInstance(self):
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, (None, None, None))
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, None)
+
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name)
+    self.ExecOpCode(op)
+
+
+class TestLUInstanceMultiAlloc(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceMultiAlloc, self).setUp()
+
+    self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
+                                            disk_template=constants.DT_DRBD8,
+                                            disks=[],
+                                            nics=[],
+                                            os_type="mock_os",
+                                            hypervisor=constants.HT_XEN_HVM,
+                                            mode=constants.INSTANCE_CREATE)
+
+  def testInstanceWithIAllocator(self):
+    inst = self.CopyOpCode(self.inst_op,
+                           iallocator="mock")
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "iallocator are not allowed to be set on instance objects")
+
+  def testOnlySomeNodesGiven(self):
+    inst1 = self.CopyOpCode(self.inst_op,
+                            pnode=self.master.name)
+    inst2 = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "There are instance objects providing pnode/snode while others"
+          " do not")
+
+  def testMissingIAllocator(self):
+    self.cluster.default_iallocator = None
+    inst = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "No iallocator or nodes on the instances given and no cluster-wide"
+          " default iallocator found")
+
+  def testDuplicateInstanceNames(self):
+    inst1 = self.CopyOpCode(self.inst_op)
+    inst2 = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "There are duplicate instance names")
+
+  def testWithGivenNodes(self):
+    snode = self.cfg.AddNewNode()
+    inst = self.CopyOpCode(self.inst_op,
+                           pnode=self.master.name,
+                           snode=snode.name)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
+    self.ExecOpCode(op)
+
+  def testDryRun(self):
+    snode = self.cfg.AddNewNode()
+    inst = self.CopyOpCode(self.inst_op,
+                           pnode=self.master.name,
+                           snode=snode.name)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
+                                      dry_run=True)
+    self.ExecOpCode(op)
+
+  def testWithIAllocator(self):
+    snode = self.cfg.AddNewNode()
+    self.iallocator_cls.return_value.result = \
+      ([("inst.example.com", [self.master.name, snode.name])], [])
+
+    inst = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
+                                      iallocator="mock_ialloc")
+    self.ExecOpCode(op)
+
+  def testManyInstancesWithIAllocator(self):
+    snode = self.cfg.AddNewNode()
+
+    inst1 = self.CopyOpCode(self.inst_op)
+    inst2 = self.CopyOpCode(self.inst_op, instance_name="inst2.example.com")
+
+    self.iallocator_cls.return_value.result = \
+      ([("inst.example.com",  [self.master.name, snode.name]),
+        ("inst2.example.com", [self.master.name, snode.name])],
+       [])
+
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2],
+                                      iallocator="mock_ialloc")
+    self.ExecOpCode(op)
+
+  def testWithIAllocatorOpportunisticLocking(self):
+    snode = self.cfg.AddNewNode()
+    self.iallocator_cls.return_value.result = \
+      ([("inst.example.com", [self.master.name, snode.name])], [])
+
+    inst = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
+                                      iallocator="mock_ialloc",
+                                      opportunistic_locking=True)
+    self.ExecOpCode(op)
+
+  def testFailingIAllocator(self):
+    self.iallocator_cls.return_value.success = False
+
+    inst = self.CopyOpCode(self.inst_op)
+    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
+                                      iallocator="mock_ialloc")
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't compute nodes using iallocator")
+
+
+class TestLUInstanceSetParams(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceSetParams, self).setUp()
+
+    self.inst = self.cfg.AddNewInstance()
+    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
+
+    self.running_inst = \
+      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
+    self.running_op = \
+      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
+
+    self.snode = self.cfg.AddNewNode()
+
+    self.mocked_storage_type = constants.ST_LVM_VG
+    self.mocked_storage_free = 10000
+    self.mocked_master_cpu_total = 16
+    self.mocked_master_memory_free = 2048
+    self.mocked_snode_cpu_total = 16
+    self.mocked_snode_memory_free = 512
+
+    self.mocked_running_inst_memory = 1024
+    self.mocked_running_inst_vcpus = 8
+    self.mocked_running_inst_state = "running"
+    self.mocked_running_inst_time = 10938474
+
+    bootid = "mock_bootid"
+    storage_info = [
+      {
+        "type": self.mocked_storage_type,
+        "storage_free": self.mocked_storage_free
+      }
+    ]
+    hv_info_master = {
+      "cpu_total": self.mocked_master_cpu_total,
+      "memory_free": self.mocked_master_memory_free
+    }
+    hv_info_snode = {
+      "cpu_total": self.mocked_snode_cpu_total,
+      "memory_free": self.mocked_snode_memory_free
+    }
+
+    self.rpc.call_node_info.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master,
+                           (bootid, storage_info, (hv_info_master, ))) \
+        .AddSuccessfulNode(self.snode,
+                           (bootid, storage_info, (hv_info_snode, ))) \
+        .Build()
+
+    def _InstanceInfo(_, instance, __, ___):
+      if instance == self.inst.name:
+        return self.RpcResultsBuilder() \
+          .CreateSuccessfulNodeResult(self.master, None)
+      elif instance == self.running_inst.name:
+        return self.RpcResultsBuilder() \
+          .CreateSuccessfulNodeResult(
+            self.master, {
+              "memory": self.mocked_running_inst_memory,
+              "vcpus": self.mocked_running_inst_vcpus,
+              "state": self.mocked_running_inst_state,
+              "time": self.mocked_running_inst_time
+            })
+      else:
+        raise AssertionError()
+    self.rpc.call_instance_info.side_effect = _InstanceInfo
+
+    self.rpc.call_bridges_exist.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+
+    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, [])
+
+    self.rpc.call_blockdev_shutdown.side_effect = \
+      lambda node, _: self.RpcResultsBuilder() \
+                        .CreateSuccessfulNodeResult(node, [])
+
+  def testNoChanges(self):
+    op = self.CopyOpCode(self.op)
+    self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
+
+  def testGlobalHvparams(self):
+    op = self.CopyOpCode(self.op,
+                         hvparams={constants.HV_MIGRATION_PORT: 1234})
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "hypervisor parameters are global and cannot be customized")
+
+  def testHvparams(self):
+    op = self.CopyOpCode(self.op,
+                         hvparams={constants.HV_BOOT_ORDER: "cd"})
+    self.ExecOpCode(op)
+
+  def testDisksAndDiskTemplate(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_PLAIN,
+                         disks=[[constants.DDM_ADD, -1, {}]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template conversion and other disk changes not supported at"
+          " the same time")
+
+  def testDiskTemplateToMirroredNoRemoteNode(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Changing the disk template to a mirrored one requires specifying"
+          " a secondary node")
+
+  def testPrimaryNodeToOldPrimaryNode(self):
+    op = self.CopyOpCode(self.op,
+                         pnode=self.master.name)
+    self.ExecOpCode(op)
+
+  def testPrimaryNodeChange(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.op,
+                         pnode=node.name)
+    self.ExecOpCode(op)
+
+  def testPrimaryNodeChangeRunningInstance(self):
+    node = self.cfg.AddNewNode()
+    op = self.CopyOpCode(self.running_op,
+                         pnode=node.name)
+    self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
+
+  def testOsChange(self):
+    os = self.cfg.CreateOs(supported_variants=[])
+    self.rpc.call_os_get.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, os)
+    op = self.CopyOpCode(self.op,
+                         os_name=os.name)
+    self.ExecOpCode(op)
+
+  def testVCpuChange(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         })
+    self.ExecOpCode(op)
+
+  def testWrongCpuMask(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         },
+                         hvparams={
+                           constants.HV_CPU_MASK: "1,2:3,4"
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Number of vCPUs .* does not match the CPU mask .*")
+
+  def testCorrectCpuMask(self):
+    op = self.CopyOpCode(self.op,
+                         beparams={
+                           constants.BE_VCPUS: 4
+                         },
+                         hvparams={
+                           constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
+                         })
+    self.ExecOpCode(op)
+
+  def testOsParams(self):
+    op = self.CopyOpCode(self.op,
+                         osparams={
+                           self.os.supported_parameters[0]: "test_param_val"
+                         })
+    self.ExecOpCode(op)
+
+  def testIncreaseMemoryTooMuch(self):
+    op = self.CopyOpCode(self.running_op,
+                         beparams={
+                           constants.BE_MAXMEM:
+                             self.mocked_master_memory_free * 2
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "This change will prevent the instance from starting")
+
+  def testIncreaseMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         beparams={
+                           constants.BE_MAXMEM: self.mocked_master_memory_free
+                         })
+    self.ExecOpCode(op)
+
+  def testIncreaseMemoryTooMuchForSecondary(self):
+    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
+                                   disk_template=constants.DT_DRBD8,
+                                   secondary_node=self.snode)
+    self.rpc.call_instance_info.side_effect = [
+      self.RpcResultsBuilder()
+        .CreateSuccessfulNodeResult(self.master,
+                                    {
+                                      "memory":
+                                        self.mocked_snode_memory_free * 2,
+                                      "vcpus": self.mocked_running_inst_vcpus,
+                                      "state": self.mocked_running_inst_state,
+                                      "time": self.mocked_running_inst_time
+                                    })]
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         beparams={
+                           constants.BE_MAXMEM:
+                             self.mocked_snode_memory_free * 2,
+                           constants.BE_AUTO_BALANCE: True
+                         })
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "This change will prevent the instance from failover to its"
+          " secondary node")
+
+  def testInvalidRuntimeMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         runtime_mem=self.mocked_master_memory_free * 2)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance .* must have memory between .* and .* of memory")
+
+  def testIncreaseRuntimeMemory(self):
+    op = self.CopyOpCode(self.running_op,
+                         runtime_mem=self.mocked_master_memory_free,
+                         beparams={
+                           constants.BE_MAXMEM: self.mocked_master_memory_free
+                         })
+    self.ExecOpCode(op)
+
+  def testAddNicWithPoolIpNoNetwork(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "If ip=pool, parameter network cannot be none")
+
+  def testAddNicWithPoolIp(self):
+    net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(net, self.group)
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL,
+                                  constants.INIC_NETWORK: net.name
+                                })])
+    self.ExecOpCode(op)
+
+  def testAddNicWithInvalidIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: "invalid"
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Invalid IP address")
+
+  def testAddNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1, {})])
+    self.ExecOpCode(op)
+
+  def testNoHotplugSupport(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1, {})],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master)
+    self.ExecOpCodeExpectOpPrereqError(op, "Hotplug is not possible")
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+
+  def testHotplugIfPossible(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1, {})],
+                         hotplug_if_possible=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateFailedNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertFalse(self.rpc.call_hotplug_device.called)
+
+  def testHotAddNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1, {})],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertTrue(self.rpc.call_hotplug_device.called)
+
+  def testAddNicWithIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_ADD, -1,
+                                {
+                                  constants.INIC_IP: "2.3.1.4"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicRoutedWithoutIp(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_MODE: constants.NIC_MODE_ROUTED
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Cannot set the NIC IP address to None on a routed NIC")
+
+  def testModifyNicSetMac(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_MAC: "0a:12:95:15:bf:75"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicWithPoolIpNoNetwork(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, -1,
+                                {
+                                  constants.INIC_IP: constants.NIC_IP_POOL
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "ip=pool, but no network found")
+
+  def testModifyNicSetNet(self):
+    old_net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(old_net, self.group)
+    inst = self.cfg.AddNewInstance(nics=[
+      self.cfg.CreateNic(network=old_net,
+                         ip="198.51.100.2")])
+
+    new_net = self.cfg.AddNewNetwork(mac_prefix="be")
+    self.cfg.ConnectNetworkToGroup(new_net, self.group)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_NETWORK: new_net.name
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNicSetLinkWhileConnected(self):
+    old_net = self.cfg.AddNewNetwork()
+    self.cfg.ConnectNetworkToGroup(old_net, self.group)
+    inst = self.cfg.AddNewInstance(nics=[
+      self.cfg.CreateNic(network=old_net)])
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_LINK: "mock_link"
+                                })])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Not allowed to change link or mode of a NIC that is connected"
+          " to a network")
+
+  def testModifyNicSetNetAndIp(self):
+    net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
+    self.cfg.ConnectNetworkToGroup(net, self.group)
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0,
+                                {
+                                  constants.INIC_NETWORK: net.name,
+                                  constants.INIC_IP: "123.123.123.1"
+                                })])
+    self.ExecOpCode(op)
+
+  def testModifyNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0, {})])
+    self.ExecOpCode(op)
+
+  def testHotModifyNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_MODIFY, 0, {})],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertTrue(self.rpc.call_hotplug_device.called)
+
+  def testRemoveLastNic(self):
+    op = self.CopyOpCode(self.op,
+                         nics=[(constants.DDM_REMOVE, 0, {})])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "violates policy")
+
+  def testRemoveNic(self):
+    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
+                                         self.cfg.CreateNic()])
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_REMOVE, 0, {})])
+    self.ExecOpCode(op)
+
+  def testHotRemoveNic(self):
+    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
+                                         self.cfg.CreateNic()])
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         nics=[(constants.DDM_REMOVE, 0, {})],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertTrue(self.rpc.call_hotplug_device.called)
+
+  def testSetOffline(self):
+    op = self.CopyOpCode(self.op,
+                         offline=True)
+    self.ExecOpCode(op)
+
+  def testUnsetOffline(self):
+    op = self.CopyOpCode(self.op,
+                         offline=False)
+    self.ExecOpCode(op)
+
+  def testAddDiskInvalidMode(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_MODE: "invalid"
+                                 }]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Invalid disk access mode 'invalid'")
+
+  def testAddDiskMissingSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1, {}]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Required disk parameter 'size' missing")
+
+  def testAddDiskInvalidSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: "invalid"
+                                 }]])
+    self.ExecOpCodeExpectException(
+      op, errors.TypeEnforcementError, "is not a valid size")
+
+  def testAddDiskRunningInstanceNoWaitForSync(self):
+    op = self.CopyOpCode(self.running_op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]],
+                         wait_for_sync=False)
+    self.ExecOpCode(op)
+    self.assertFalse(self.rpc.call_blockdev_shutdown.called)
+
+  def testAddDiskDownInstance(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCode(op)
+
+    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
+
+  def testAddDiskDownInstanceNoWaitForSync(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]],
+                         wait_for_sync=False)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't add a disk to an instance with deactivated disks"
+          " and --no-wait-for-sync given.")
+
+  def testAddDiskRunningInstance(self):
+    op = self.CopyOpCode(self.running_op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCode(op)
+
+    self.assertFalse(self.rpc.call_blockdev_shutdown.called)
+
+  def testAddDiskNoneName(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024,
+                                   constants.IDISK_NAME: constants.VALUE_NONE
+                                 }]])
+    self.ExecOpCode(op)
+
+  def testHotAddDisk(self):
+    self.rpc.call_blockdev_assemble.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, ("/dev/mocked_path",
+                                    "/var/run/ganeti/instance-disks/mocked_d",
+                                    None))
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_ADD, -1,
+                                 {
+                                   constants.IDISK_SIZE: 1024,
+                                 }]],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertTrue(self.rpc.call_blockdev_create.called)
+    self.assertTrue(self.rpc.call_blockdev_assemble.called)
+    self.assertTrue(self.rpc.call_hotplug_device.called)
+
+  def testHotRemoveDisk(self):
+    inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
+                                          self.cfg.CreateDisk()])
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         disks=[[constants.DDM_REMOVE, -1,
+                                 {}]],
+                         hotplug=True)
+    self.rpc.call_hotplug_supported.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
+    self.assertTrue(self.rpc.call_hotplug_device.called)
+    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
+    self.assertTrue(self.rpc.call_blockdev_remove.called)
+
+  def testModifyDiskWithSize(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                 {
+                                   constants.IDISK_SIZE: 1024
+                                 }]])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk size change not possible, use grow-disk")
+
+  def testModifyDiskWithRandomParams(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                 {
+                                   constants.IDISK_METAVG: "new_meta_vg",
+                                   constants.IDISK_MODE: "invalid",
+                                   constants.IDISK_NAME: "new_name"
+                                 }]])
+    self.ExecOpCodeExpectException(op, errors.TypeEnforcementError,
+                                   "Unknown parameter 'metavg'")
+
+  def testModifyDiskUnsetName(self):
+    op = self.CopyOpCode(self.op,
+                         disks=[[constants.DDM_MODIFY, 0,
+                                  {
+                                    constants.IDISK_NAME: constants.VALUE_NONE
+                                  }]])
+    self.ExecOpCode(op)
+
+  def testSetOldDiskTemplate(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=self.inst.disk_template)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Instance already has disk template")
+
+  def testSetDisabledDiskTemplate(self):
+    self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_EXT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Disk template .* is not enabled for this cluster")
+
+  def testInvalidDiskTemplateConversion(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_EXT)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Unsupported disk template conversion from .* to .*")
+
+  def testConvertToDRBDWithSecondarySameAsPrimary(self):
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8,
+                         remote_node=self.master.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Given new secondary node .* is the same as the primary node"
+          " of the instance")
+
+  def testConvertPlainToDRBD(self):
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_getmirrorstatus.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8,
+                         remote_node=self.snode.name)
+    self.ExecOpCode(op)
+
+  def testConvertDRBDToPlain(self):
+    self.inst.disks = [self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
+                                           primary_node=self.master,
+                                           secondary_node=self.snode)]
+    self.inst.disk_template = constants.DT_DRBD8
+    self.rpc.call_blockdev_shutdown.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, True)
+    self.rpc.call_blockdev_remove.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master)
+    self.rpc.call_blockdev_getmirrorstatus.return_value = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_PLAIN)
+    self.ExecOpCode(op)
+
+  def testConvertDisklessDRBDToPlain(self):
+    self.cfg.SetIPolicyField(
+      constants.ISPECS_MIN, constants.ISPEC_DISK_COUNT, 0)
+    self.inst.disks = []
+    self.inst.disk_template = constants.DT_DRBD8
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_PLAIN)
+    self.ExecOpCode(op)
+
+    self.assertEqual(self.inst.disk_template, constants.DT_PLAIN)
+
+  def testConvertDisklessPlainToDRBD(self):
+    self.cfg.SetIPolicyField(
+      constants.ISPECS_MIN, constants.ISPEC_DISK_COUNT, 0)
+    self.inst.disks = []
+    self.inst.disk_template = constants.DT_PLAIN
+
+    op = self.CopyOpCode(self.op,
+                         disk_template=constants.DT_DRBD8,
+                         remote_node=self.snode.name)
+    self.ExecOpCode(op)
+
+    self.assertEqual(self.inst.disk_template, constants.DT_DRBD8)
+
+class TestLUInstanceChangeGroup(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUInstanceChangeGroup, self).setUp()
+
+    self.group2 = self.cfg.AddNewNodeGroup()
+    self.node2 = self.cfg.AddNewNode(group=self.group2)
+    self.inst = self.cfg.AddNewInstance()
+    self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
+
+  def testTargetGroupIsInstanceGroup(self):
+    op = self.CopyOpCode(self.op,
+                         target_groups=[self.group.name])
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't use group\(s\) .* as targets, they are used by the"
+          " instance .*")
+
+  def testNoTargetGroups(self):
+    inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
+                                   primary_node=self.master,
+                                   secondary_node=self.node2)
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name)
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "There are no possible target groups")
+
+  def testFailingIAllocator(self):
+    self.iallocator_cls.return_value.success = False
+    op = self.CopyOpCode(self.op)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "Can't compute solution for changing group of instance .*"
+          " using iallocator .*")
+
+  def testChangeGroup(self):
+    self.iallocator_cls.return_value.success = True
+    self.iallocator_cls.return_value.result = ([], [], [])
+    op = self.CopyOpCode(self.op)
+
+    self.ExecOpCode(op)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/node_unittest.py b/test/py/cmdlib/node_unittest.py
new file mode 100644
index 0000000..757d6ed
--- /dev/null
+++ b/test/py/cmdlib/node_unittest.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUNode*
+
+"""
+
+from collections import defaultdict
+
+from ganeti import compat
+from ganeti import constants
+from ganeti import objects
+from ganeti import opcodes
+from ganeti import errors
+
+from testsupport import *
+
+import testutils
+
+
+# pylint: disable=W0613
+def _TcpPingFailSecondary(cfg, mock_fct, target, port, timeout=None,
+                          live_port_needed=None, source=None):
+  # This will return True if target is in 192.0.2.0/24 (primary range)
+  # and False if not.
+  return "192.0.2." in target
+
+
+class TestLUNodeAdd(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUNodeAdd, self).setUp()
+
+    # One node for testing readding:
+    self.node_readd = self.cfg.AddNewNode()
+    self.op_readd = opcodes.OpNodeAdd(node_name=self.node_readd.name,
+                                      readd=True,
+                                      primary_ip=self.node_readd.primary_ip,
+                                      secondary_ip=self.node_readd.secondary_ip)
+
+    # One node for testing adding:
+    # don't add to configuration now!
+    self.node_add = objects.Node(name="node_add",
+                                 primary_ip="192.0.2.200",
+                                 secondary_ip="203.0.113.200")
+
+    self.op_add = opcodes.OpNodeAdd(node_name=self.node_add.name,
+                                    primary_ip=self.node_add.primary_ip,
+                                    secondary_ip=self.node_add.secondary_ip)
+
+    self.netutils_mod.TcpPing.return_value = True
+
+    self.mocked_dns_rpc = self.rpc_mod.DnsOnlyRunner.return_value
+
+    self.mocked_dns_rpc.call_version.return_value = \
+      self.RpcResultsBuilder(use_node_names=True) \
+        .AddSuccessfulNode(self.node_add, constants.CONFIG_VERSION) \
+        .AddSuccessfulNode(self.node_readd, constants.CONFIG_VERSION) \
+        .Build()
+
+    node_verify_result = \
+      self.RpcResultsBuilder() \
+        .CreateSuccessfulNodeResult(self.node_add, {constants.NV_NODELIST: []})
+    # we can't know the node's UUID in advance, so use defaultdict here
+    self.rpc.call_node_verify.return_value = \
+      defaultdict(lambda: node_verify_result, {})
+
+  def testOvsNoLink(self):
+    ndparams = {
+      constants.ND_OVS: True,
+      constants.ND_OVS_NAME: "testswitch",
+      constants.ND_OVS_LINK: None,
+    }
+
+    op = self.CopyOpCode(self.op_add,
+                         ndparams=ndparams)
+
+    self.ExecOpCode(op)
+    self.assertLogContainsRegex(
+      "No physical interface for OpenvSwitch was given."
+      " OpenvSwitch will not have an outside connection."
+      " This might not be what you want")
+
+    created_node = self.cfg.GetNodeInfoByName(op.node_name)
+    self.assertEqual(ndparams[constants.ND_OVS],
+                     created_node.ndparams.get(constants.ND_OVS, None))
+    self.assertEqual(ndparams[constants.ND_OVS_NAME],
+                     created_node.ndparams.get(constants.ND_OVS_NAME, None))
+    self.assertEqual(ndparams[constants.ND_OVS_LINK],
+                     created_node.ndparams.get(constants.ND_OVS_LINK, None))
+
+  def testWithoutOVS(self):
+    self.ExecOpCode(self.op_add)
+
+    created_node = self.cfg.GetNodeInfoByName(self.op_add.node_name)
+    self.assertEqual(None,
+                     created_node.ndparams.get(constants.ND_OVS, None))
+
+  def testWithOVS(self):
+    ndparams = {
+      constants.ND_OVS: True,
+      constants.ND_OVS_LINK: "eth2",
+    }
+
+    op = self.CopyOpCode(self.op_add,
+                         ndparams=ndparams)
+
+    self.ExecOpCode(op)
+
+    created_node = self.cfg.GetNodeInfoByName(op.node_name)
+    self.assertEqual(ndparams[constants.ND_OVS],
+                     created_node.ndparams.get(constants.ND_OVS, None))
+    self.assertEqual(ndparams[constants.ND_OVS_LINK],
+                     created_node.ndparams.get(constants.ND_OVS_LINK, None))
+
+  def testReaddingMaster(self):
+    op = opcodes.OpNodeAdd(node_name=self.cfg.GetMasterNodeName(),
+                           readd=True)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot readd the master node")
+
+  def testReaddNotVmCapableNode(self):
+    self.cfg.AddNewInstance(primary_node=self.node_readd)
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.node_readd.name, self.node_readd.primary_ip)
+
+    op = self.CopyOpCode(self.op_readd, vm_capable=False)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Node .* being re-added with"
+                                       " vm_capable flag set to false, but it"
+                                       " already holds instances")
+
+  def testReaddAndPassNodeGroup(self):
+    op = self.CopyOpCode(self.op_readd,group="groupname")
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Cannot pass a node group when a"
+                                       " node is being readded")
+
+  def testPrimaryIPv6(self):
+    self.master.secondary_ip = self.master.primary_ip
+
+    op = self.CopyOpCode(self.op_add, primary_ip="2001:DB8::1",
+                         secondary_ip=self.REMOVE)
+
+    self.ExecOpCode(op)
+
+  def testInvalidSecondaryIP(self):
+    op = self.CopyOpCode(self.op_add, secondary_ip="333.444.555.777")
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Secondary IP .* needs to be a valid"
+                                       " IPv4 address")
+
+  def testNodeAlreadyInCluster(self):
+    op = self.CopyOpCode(self.op_readd, readd=False)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Node %s is already in the"
+                                       " configuration" % self.node_readd.name)
+
+  def testReaddNodeNotInConfiguration(self):
+    op = self.CopyOpCode(self.op_add, readd=True)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Node %s is not in the"
+                                       " configuration" % self.node_add.name)
+
+  def testPrimaryIpConflict(self):
+    # In LUNodeAdd, DNS will resolve the node name to an IP address, that is
+    # used to overwrite any given primary_ip value!
+    # Thus we need to mock this DNS resolver here!
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.node_add.name, self.node_readd.primary_ip)
+
+    op = self.CopyOpCode(self.op_add)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "New node ip address.* conflict with"
+                                       " existing node")
+
+  def testSecondaryIpConflict(self):
+    op = self.CopyOpCode(self.op_add, secondary_ip=self.node_readd.secondary_ip)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "New node ip address.* conflict with"
+                                       " existing node")
+
+  def testReaddWithDifferentIP(self):
+    op = self.CopyOpCode(self.op_readd, primary_ip="192.0.2.100",
+                         secondary_ip="230.0.113.100")
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Readded node doesn't have the same"
+                                       " IP address configuration as before")
+
+
+  def testNodeHasSecondaryIpButNotMaster(self):
+    self.master.secondary_ip = self.master.primary_ip
+
+    self.ExecOpCodeExpectOpPrereqError(self.op_add, "The master has no"
+                                       " secondary ip but the new node has one")
+
+  def testMasterHasSecondaryIpButNotNode(self):
+    op = self.CopyOpCode(self.op_add, secondary_ip=None)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "The master has a secondary ip but"
+                                       " the new node doesn't have one")
+
+  def testNodeNotReachableByPing(self):
+    self.netutils_mod.TcpPing.return_value = False
+
+    op = self.CopyOpCode(self.op_add)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Node not reachable by ping")
+
+  def testNodeNotReachableByPingOnSecondary(self):
+    self.netutils_mod.GetHostname.return_value = \
+      HostnameMock(self.node_add.name, self.node_add.primary_ip)
+    self.netutils_mod.TcpPing.side_effect = \
+      compat.partial(_TcpPingFailSecondary, self.cfg, self.netutils_mod.TcpPing)
+
+    op = self.CopyOpCode(self.op_add)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Node secondary ip not reachable by"
+                                       " TCP based ping to node daemon port")
+
+  def testCantGetVersion(self):
+    self.mocked_dns_rpc.call_version.return_value = \
+      self.RpcResultsBuilder(use_node_names=True) \
+        .AddErrorNode(self.node_add) \
+        .Build()
+
+    op = self.CopyOpCode(self.op_add)
+    self.ExecOpCodeExpectOpPrereqError(op, "Can't get version information from"
+                                       " node %s" % self.node_add.name)
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/test_unittest.py b/test/py/cmdlib/test_unittest.py
new file mode 100644
index 0000000..f25accc
--- /dev/null
+++ b/test/py/cmdlib/test_unittest.py
@@ -0,0 +1,259 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for LUTest*"""
+
+import mock
+
+from ganeti import constants
+from ganeti import opcodes
+
+from testsupport import *
+
+import testutils
+
+DELAY_DURATION = 0.01
+
+
+class TestLUTestDelay(CmdlibTestCase):
+  def testRepeatedInvocation(self):
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             repeat=3)
+    self.ExecOpCode(op)
+
+    self.assertLogContainsMessage(" - INFO: Test delay iteration 0/2")
+    self.mcpu.assertLogContainsEntry(constants.ELOG_MESSAGE,
+                                     " - INFO: Test delay iteration 1/2")
+    self.assertLogContainsRegex("2/2$")
+
+  def testInvalidDuration(self):
+    op = opcodes.OpTestDelay(duration=-1)
+
+    self.ExecOpCodeExpectOpExecError(op)
+
+  def testOnNodeUuid(self):
+    node_uuids = [self.master_uuid]
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             on_node_uuids=node_uuids)
+    self.ExecOpCode(op)
+
+    self.rpc.call_test_delay.assert_called_once_with(node_uuids, DELAY_DURATION)
+
+  def testOnNodeName(self):
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             on_nodes=[self.master.name])
+    self.ExecOpCode(op)
+
+    self.rpc.call_test_delay.assert_called_once_with([self.master_uuid],
+                                                     DELAY_DURATION)
+
+  def testSuccessfulRpc(self):
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             on_nodes=[self.master.name])
+
+    self.rpc.call_test_delay.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(self.master) \
+        .Build()
+
+    self.ExecOpCode(op)
+
+    self.rpc.call_test_delay.assert_called_once()
+
+  def testFailingRpc(self):
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             on_nodes=[self.master.name])
+
+    self.rpc.call_test_delay.return_value = \
+      self.RpcResultsBuilder() \
+        .AddFailedNode(self.master) \
+        .Build()
+
+    self.ExecOpCodeExpectOpExecError(op)
+
+  def testMultipleNodes(self):
+    node1 = self.cfg.AddNewNode()
+    node2 = self.cfg.AddNewNode()
+    op = opcodes.OpTestDelay(duration=DELAY_DURATION,
+                             on_nodes=[node1.name, node2.name])
+
+    self.rpc.call_test_delay.return_value = \
+      self.RpcResultsBuilder() \
+        .AddSuccessfulNode(node1) \
+        .AddSuccessfulNode(node2) \
+        .Build()
+
+    self.ExecOpCode(op)
+
+    self.rpc.call_test_delay.assert_called_once_with([node1.uuid, node2.uuid],
+                                                     DELAY_DURATION)
+
+
+class TestLUTestAllocator(CmdlibTestCase):
+  def setUp(self):
+    super(TestLUTestAllocator, self).setUp()
+
+    self.base_op = opcodes.OpTestAllocator(
+                      name="new-instance.example.com",
+                      nics=[],
+                      disks=[],
+                      disk_template=constants.DT_DISKLESS,
+                      direction=constants.IALLOCATOR_DIR_OUT,
+                      iallocator="test")
+
+    self.valid_alloc_op = \
+      self.CopyOpCode(self.base_op,
+                      mode=constants.IALLOCATOR_MODE_ALLOC,
+                      memory=0,
+                      disk_template=constants.DT_DISKLESS,
+                      os="mock_os",
+                      vcpus=1)
+    self.valid_multi_alloc_op = \
+      self.CopyOpCode(self.base_op,
+                      mode=constants.IALLOCATOR_MODE_MULTI_ALLOC,
+                      instances=["new-instance.example.com"],
+                      memory=0,
+                      disk_template=constants.DT_DISKLESS,
+                      os="mock_os",
+                      vcpus=1)
+    self.valid_reloc_op = \
+      self.CopyOpCode(self.base_op,
+                      mode=constants.IALLOCATOR_MODE_RELOC)
+    self.valid_chg_group_op = \
+      self.CopyOpCode(self.base_op,
+                      mode=constants.IALLOCATOR_MODE_CHG_GROUP,
+                      instances=["new-instance.example.com"],
+                      target_groups=["default"])
+    self.valid_node_evac_op = \
+      self.CopyOpCode(self.base_op,
+                      mode=constants.IALLOCATOR_MODE_NODE_EVAC,
+                      instances=["new-instance.example.com"],
+                      evac_mode=constants.NODE_EVAC_PRI)
+
+    self.iallocator_cls.return_value.in_text = "mock in text"
+    self.iallocator_cls.return_value.out_text = "mock out text"
+
+  def testMissingDirection(self):
+    op = self.CopyOpCode(self.base_op,
+                         direction=self.REMOVE)
+
+    self.ExecOpCodeExpectOpPrereqError(
+      op, "'OP_TEST_ALLOCATOR.direction' fails validation")
+
+  def testAllocWrongDisks(self):
+    op = self.CopyOpCode(self.valid_alloc_op,
+                         disks=[0, "test"])
+
+    self.ExecOpCodeExpectOpPrereqError(op, "Invalid contents")
+
+  def testAllocWithExistingInstance(self):
+    inst = self.cfg.AddNewInstance()
+    op = self.CopyOpCode(self.valid_alloc_op, name=inst.name)
+
+    self.ExecOpCodeExpectOpPrereqError(op, "already in the cluster")
+
+  def testAllocMultiAllocMissingIAllocator(self):
+    for mode in [constants.IALLOCATOR_MODE_ALLOC,
+                 constants.IALLOCATOR_MODE_MULTI_ALLOC]:
+      op = self.CopyOpCode(self.base_op,
+                           mode=mode,
+                           iallocator=None)
+
+      self.ResetMocks()
+      self.ExecOpCodeExpectOpPrereqError(op, "Missing allocator name")
+
+  def testChgGroupNodeEvacMissingInstances(self):
+    for mode in [constants.IALLOCATOR_MODE_CHG_GROUP,
+                 constants.IALLOCATOR_MODE_NODE_EVAC]:
+      op = self.CopyOpCode(self.base_op,
+                           mode=mode)
+
+      self.ResetMocks()
+      self.ExecOpCodeExpectOpPrereqError(op, "Missing instances")
+
+  def testAlloc(self):
+    op = self.valid_alloc_op
+
+    self.ExecOpCode(op)
+
+    assert self.iallocator_cls.call_count == 1
+    self.iallocator_cls.return_value.Run \
+      .assert_called_once_with("test", validate=False)
+
+  def testReloc(self):
+    op = self.valid_reloc_op
+    self.cfg.AddNewInstance(name=op.name)
+
+    self.ExecOpCode(op)
+
+    assert self.iallocator_cls.call_count == 1
+    self.iallocator_cls.return_value.Run \
+      .assert_called_once_with("test", validate=False)
+
+  def testChgGroup(self):
+    op = self.valid_chg_group_op
+    for inst_name in op.instances:
+      self.cfg.AddNewInstance(name=inst_name)
+
+    self.ExecOpCode(op)
+
+    assert self.iallocator_cls.call_count == 1
+    self.iallocator_cls.return_value.Run \
+      .assert_called_once_with("test", validate=False)
+
+  def testNodeEvac(self):
+    op = self.valid_node_evac_op
+    for inst_name in op.instances:
+      self.cfg.AddNewInstance(name=inst_name)
+
+    self.ExecOpCode(op)
+
+    assert self.iallocator_cls.call_count == 1
+    self.iallocator_cls.return_value.Run \
+      .assert_called_once_with("test", validate=False)
+
+  def testMultiAlloc(self):
+    op = self.valid_multi_alloc_op
+
+    self.ExecOpCode(op)
+
+    assert self.iallocator_cls.call_count == 1
+    self.iallocator_cls.return_value.Run \
+      .assert_called_once_with("test", validate=False)
+
+  def testAllocDirectionIn(self):
+    op = self.CopyOpCode(self.valid_alloc_op,
+                         direction=constants.IALLOCATOR_DIR_IN)
+
+    self.ExecOpCode(op)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/cmdlib/testsupport/__init__.py b/test/py/cmdlib/testsupport/__init__.py
new file mode 100644
index 0000000..38370d3
--- /dev/null
+++ b/test/py/cmdlib/testsupport/__init__.py
@@ -0,0 +1,59 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support classes and functions for testing the cmdlib module.
+
+"""
+
+from cmdlib.testsupport.cmdlib_testcase import CmdlibTestCase, \
+  withLockedLU
+from cmdlib.testsupport.config_mock import ConfigMock
+from cmdlib.testsupport.iallocator_mock import patchIAllocator
+from cmdlib.testsupport.utils_mock import patchUtils
+from cmdlib.testsupport.lock_manager_mock import LockManagerMock
+from cmdlib.testsupport.netutils_mock import patchNetutils, HostnameMock
+from cmdlib.testsupport.processor_mock import ProcessorMock
+from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock, \
+  RpcResultsBuilder
+from cmdlib.testsupport.ssh_mock import patchSsh
+
+__all__ = ["CmdlibTestCase",
+           "withLockedLU",
+           "ConfigMock",
+           "CreateRpcRunnerMock",
+           "HostnameMock",
+           "patchIAllocator",
+           "patchUtils",
+           "patchNetutils",
+           "patchSsh",
+           "LockManagerMock",
+           "ProcessorMock",
+           "RpcResultsBuilder",
+           ]
diff --git a/test/py/cmdlib/testsupport/cmdlib_testcase.py b/test/py/cmdlib/testsupport/cmdlib_testcase.py
new file mode 100644
index 0000000..2beef08
--- /dev/null
+++ b/test/py/cmdlib/testsupport/cmdlib_testcase.py
@@ -0,0 +1,426 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Main module of the cmdlib test framework"""
+
+
+import inspect
+import mock
+import re
+import traceback
+
+from cmdlib.testsupport.config_mock import ConfigMock
+from cmdlib.testsupport.iallocator_mock import patchIAllocator
+from cmdlib.testsupport.lock_manager_mock import LockManagerMock
+from cmdlib.testsupport.netutils_mock import patchNetutils, \
+  SetupDefaultNetutilsMock
+from cmdlib.testsupport.processor_mock import ProcessorMock
+from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock, \
+  RpcResultsBuilder, patchRpc, SetupDefaultRpcModuleMock
+from cmdlib.testsupport.ssh_mock import patchSsh
+
+from ganeti.cmdlib.base import LogicalUnit
+from ganeti import errors
+from ganeti import locking
+from ganeti import objects
+from ganeti import opcodes
+from ganeti import runtime
+
+import testutils
+
+
+class GanetiContextMock(object):
+  # pylint: disable=W0212
+  cfg = property(fget=lambda self: self._test_case.cfg)
+  # pylint: disable=W0212
+  glm = property(fget=lambda self: self._test_case.glm)
+  # pylint: disable=W0212
+  rpc = property(fget=lambda self: self._test_case.rpc)
+
+  def __init__(self, test_case):
+    self._test_case = test_case
+
+  def AddNode(self, node, ec_id):
+    self._test_case.cfg.AddNode(node, ec_id)
+    self._test_case.glm.add(locking.LEVEL_NODE, node.uuid)
+    self._test_case.glm.add(locking.LEVEL_NODE_RES, node.uuid)
+
+  def ReaddNode(self, node):
+    pass
+
+  def RemoveNode(self, node):
+    self._test_case.cfg.RemoveNode(node.uuid)
+    self._test_case.glm.remove(locking.LEVEL_NODE, node.uuid)
+    self._test_case.glm.remove(locking.LEVEL_NODE_RES, node.uuid)
+
+
+class MockLU(LogicalUnit):
+  def BuildHooksNodes(self):
+    pass
+
+  def BuildHooksEnv(self):
+    pass
+
+
+# pylint: disable=R0904
+class CmdlibTestCase(testutils.GanetiTestCase):
+  """Base class for cmdlib tests.
+
+  This class sets up a mocked environment for the execution of
+  L{ganeti.cmdlib.base.LogicalUnit} subclasses.
+
+  The environment can be customized via the following fields:
+
+    * C{cfg}: @see L{ConfigMock}
+    * C{glm}: @see L{LockManagerMock}
+    * C{rpc}: @see L{CreateRpcRunnerMock}
+    * C{iallocator_cls}: @see L{patchIAllocator}
+    * C{mcpu}: @see L{ProcessorMock}
+    * C{netutils_mod}: @see L{patchNetutils}
+    * C{ssh_mod}: @see L{patchSsh}
+
+  """
+
+  REMOVE = object()
+
+  cluster = property(fget=lambda self: self.cfg.GetClusterInfo(),
+                     doc="Cluster configuration object")
+  master = property(fget=lambda self: self.cfg.GetMasterNodeInfo(),
+                    doc="Master node")
+  master_uuid = property(fget=lambda self: self.cfg.GetMasterNode(),
+                         doc="Master node UUID")
+  # pylint: disable=W0212
+  group = property(fget=lambda self: self._GetDefaultGroup(),
+                   doc="Default node group")
+
+  os = property(fget=lambda self: self.cfg.GetDefaultOs(),
+                doc="Default OS")
+  os_name_variant = property(
+    fget=lambda self: self.os.name + objects.OS.VARIANT_DELIM +
+      self.os.supported_variants[0],
+    doc="OS name and variant string")
+
+  def setUp(self):
+    super(CmdlibTestCase, self).setUp()
+    self._iallocator_patcher = None
+    self._netutils_patcher = None
+    self._ssh_patcher = None
+    self._rpc_patcher = None
+
+    try:
+      runtime.InitArchInfo()
+    except errors.ProgrammerError:
+      # during tests, the arch info can be initialized multiple times
+      pass
+
+    self.ResetMocks()
+
+  def _StopPatchers(self):
+    if self._iallocator_patcher is not None:
+      self._iallocator_patcher.stop()
+      self._iallocator_patcher = None
+    if self._netutils_patcher is not None:
+      self._netutils_patcher.stop()
+      self._netutils_patcher = None
+    if self._ssh_patcher is not None:
+      self._ssh_patcher.stop()
+      self._ssh_patcher = None
+    if self._rpc_patcher is not None:
+      self._rpc_patcher.stop()
+      self._rpc_patcher = None
+
+  def tearDown(self):
+    super(CmdlibTestCase, self).tearDown()
+
+    self._StopPatchers()
+
+  def _GetTestModule(self):
+    module = inspect.getsourcefile(self.__class__).split("/")[-1]
+    suffix = "_unittest.py"
+    assert module.endswith(suffix), "Naming convention for cmdlib test" \
+                                    " modules is: <module>%s (found '%s')"\
+                                    % (suffix, module)
+    return module[:-len(suffix)]
+
+  def ResetMocks(self):
+    """Resets all mocks back to their initial state.
+
+    This is useful if you want to execute more than one opcode in a single
+    test.
+
+    """
+    self.cfg = ConfigMock()
+    self.glm = LockManagerMock()
+    self.rpc = CreateRpcRunnerMock()
+    self.ctx = GanetiContextMock(self)
+    self.mcpu = ProcessorMock(self.ctx)
+
+    self._StopPatchers()
+    try:
+      self._iallocator_patcher = patchIAllocator(self._GetTestModule())
+      self.iallocator_cls = self._iallocator_patcher.start()
+    except (ImportError, AttributeError):
+      # this test module does not use iallocator, no patching performed
+      self._iallocator_patcher = None
+
+    try:
+      self._netutils_patcher = patchNetutils(self._GetTestModule())
+      self.netutils_mod = self._netutils_patcher.start()
+      SetupDefaultNetutilsMock(self.netutils_mod, self.cfg)
+    except (ImportError, AttributeError):
+      # this test module does not use netutils, no patching performed
+      self._netutils_patcher = None
+
+    try:
+      self._ssh_patcher = patchSsh(self._GetTestModule())
+      self.ssh_mod = self._ssh_patcher.start()
+    except (ImportError, AttributeError):
+      # this test module does not use ssh, no patching performed
+      self._ssh_patcher = None
+
+    try:
+      self._rpc_patcher = patchRpc(self._GetTestModule())
+      self.rpc_mod = self._rpc_patcher.start()
+      SetupDefaultRpcModuleMock(self.rpc_mod)
+    except (ImportError, AttributeError):
+      # this test module does not use rpc, no patching performed
+      self._rpc_patcher = None
+
+  def GetMockLU(self):
+    """Creates a mock L{LogialUnit} with access to the mocked config etc.
+
+    @rtype: L{LogialUnit}
+    @return: A mock LU
+
+    """
+    return MockLU(self.mcpu, mock.MagicMock(), self.ctx, self.rpc)
+
+  def RpcResultsBuilder(self, use_node_names=False):
+    """Creates a pre-configured L{RpcResultBuilder}
+
+    @type use_node_names: bool
+    @param use_node_names: @see L{RpcResultBuilder}
+    @rtype: L{RpcResultBuilder}
+    @return: a pre-configured builder for RPC results
+
+    """
+    return RpcResultsBuilder(cfg=self.cfg, use_node_names=use_node_names)
+
+  def ExecOpCode(self, opcode):
+    """Executes the given opcode.
+
+    @param opcode: the opcode to execute
+    @return: the result of the LU's C{Exec} method
+
+    """
+    self.glm.AddLocksFromConfig(self.cfg)
+
+    return self.mcpu.ExecOpCodeAndRecordOutput(opcode)
+
+  def ExecOpCodeExpectException(self, opcode,
+                                expected_exception,
+                                expected_regex=None):
+    """Executes the given opcode and expects an exception.
+
+    @param opcode: @see L{ExecOpCode}
+    @type expected_exception: class
+    @param expected_exception: the exception which must be raised
+    @type expected_regex: string
+    @param expected_regex: if not C{None}, a regular expression which must be
+          present in the string representation of the exception
+
+    """
+    try:
+      self.ExecOpCode(opcode)
+    except expected_exception, e:
+      if expected_regex is not None:
+        assert re.search(expected_regex, str(e)) is not None, \
+                "Caught exception '%s' did not match '%s'" % \
+                  (str(e), expected_regex)
+    except Exception, e:
+      tb = traceback.format_exc()
+      raise AssertionError("%s\n(See original exception above)\n"
+                           "Expected exception '%s' was not raised,"
+                           " got '%s' of class '%s' instead." %
+                           (tb, expected_exception, e, e.__class__))
+    else:
+      raise AssertionError("Expected exception '%s' was not raised" %
+                           expected_exception)
+
+  def ExecOpCodeExpectOpPrereqError(self, opcode, expected_regex=None):
+    """Executes the given opcode and expects a L{errors.OpPrereqError}
+
+    @see L{ExecOpCodeExpectException}
+
+    """
+    self.ExecOpCodeExpectException(opcode, errors.OpPrereqError, expected_regex)
+
+  def ExecOpCodeExpectOpExecError(self, opcode, expected_regex=None):
+    """Executes the given opcode and expects a L{errors.OpExecError}
+
+    @see L{ExecOpCodeExpectException}
+
+    """
+    self.ExecOpCodeExpectException(opcode, errors.OpExecError, expected_regex)
+
+  def RunWithLockedLU(self, opcode, test_func):
+    """Takes the given opcode, creates a LU and runs func on it.
+
+    The passed LU did already perform locking, but no methods which actually
+    require locking are executed on the LU.
+
+    @param opcode: the opcode to get the LU for.
+    @param test_func: the function to execute with the LU as parameter.
+    @return: the result of test_func
+
+    """
+    self.glm.AddLocksFromConfig(self.cfg)
+
+    return self.mcpu.RunWithLockedLU(opcode, test_func)
+
+  def assertLogContainsMessage(self, expected_msg):
+    """Shortcut for L{ProcessorMock.assertLogContainsMessage}
+
+    """
+    self.mcpu.assertLogContainsMessage(expected_msg)
+
+  def assertLogContainsRegex(self, expected_regex):
+    """Shortcut for L{ProcessorMock.assertLogContainsRegex}
+
+    """
+    self.mcpu.assertLogContainsRegex(expected_regex)
+
+  def assertHooksCall(self, nodes, hook_path, phase,
+                      environment=None, count=None, index=0):
+    """Asserts a call to C{rpc.call_hooks_runner}
+
+    @type nodes: list of string
+    @param nodes: node UUID's or names hooks run on
+    @type hook_path: string
+    @param hook_path: path (or name) of the hook run
+    @type phase: string
+    @param phase: phase in which the hook runs in
+    @type environment: dict
+    @param environment: the environment passed to the hooks. C{None} to skip
+            asserting it
+    @type count: int
+    @param count: the number of hook invocations. C{None} to skip asserting it
+    @type index: int
+    @param index: the index of the hook invocation to assert
+
+    """
+    if count is not None:
+      self.assertEqual(count, self.rpc.call_hooks_runner.call_count)
+
+    args = self.rpc.call_hooks_runner.call_args[index]
+
+    self.assertEqual(set(nodes), set(args[0]))
+    self.assertEqual(hook_path, args[1])
+    self.assertEqual(phase, args[2])
+    if environment is not None:
+      self.assertEqual(environment, args[3])
+
+  def assertSingleHooksCall(self, nodes, hook_path, phase,
+                            environment=None):
+    """Asserts a single call to C{rpc.call_hooks_runner}
+
+    @see L{assertHooksCall} for parameter description.
+
+    """
+    self.assertHooksCall(nodes, hook_path, phase,
+                         environment=environment, count=1)
+
+  def CopyOpCode(self, opcode, **kwargs):
+    """Creates a copy of the given opcode and applies modifications to it
+
+    @type opcode: opcode.OpCode
+    @param opcode: the opcode to copy
+    @type kwargs: dict
+    @param kwargs: dictionary of fields to overwrite in the copy. The special
+          value L{REMOVE} can be used to remove fields from the copy.
+    @return: a copy of the given opcode
+
+    """
+    state = opcode.__getstate__()
+
+    for key, value in kwargs.items():
+      if value == self.REMOVE and key in state:
+        del state[key]
+      else:
+        state[key] = value
+
+    return opcodes.OpCode.LoadOpCode(state)
+
+  def _GetDefaultGroup(self):
+    for group in self.cfg.GetAllNodeGroupsInfo().values():
+      if group.name == "default":
+        return group
+    assert False
+
+
+# pylint: disable=C0103
+def withLockedLU(func):
+  """Convenience decorator which runs the decorated method with the LU.
+
+  This uses L{CmdlibTestCase.RunWithLockedLU} to run the decorated method.
+  For this to work, the opcode to run has to be an instance field named "op",
+  "_op", "opcode" or "_opcode".
+
+  If the instance has a method called "PrepareLU", this method is invoked with
+  the LU right before the test method is called.
+
+  """
+  def wrapper(*args, **kwargs):
+    test = args[0]
+    assert isinstance(test, CmdlibTestCase)
+
+    op = None
+    for attr_name in ["op", "_op", "opcode", "_opcode"]:
+      if hasattr(test, attr_name):
+        op = getattr(test, attr_name)
+        break
+    assert op is not None
+
+    prepare_fn = None
+    if hasattr(test, "PrepareLU"):
+      prepare_fn = getattr(test, "PrepareLU")
+      assert callable(prepare_fn)
+
+    # pylint: disable=W0142
+    def callWithLU(lu):
+      if prepare_fn:
+        prepare_fn(lu)
+
+      new_args = list(args)
+      new_args.append(lu)
+      func(*new_args, **kwargs)
+
+    return test.RunWithLockedLU(op, callWithLU)
+  return wrapper
diff --git a/test/py/cmdlib/testsupport/config_mock.py b/test/py/cmdlib/testsupport/config_mock.py
new file mode 100644
index 0000000..0859bb0
--- /dev/null
+++ b/test/py/cmdlib/testsupport/config_mock.py
@@ -0,0 +1,632 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the cluster configuration"""
+
+
+import time
+import uuid as uuid_module
+
+from ganeti import config
+from ganeti import constants
+from ganeti import objects
+from ganeti.network import AddressPool
+
+import mocks
+
+
+def _StubGetEntResolver():
+  return mocks.FakeGetentResolver()
+
+
+# pylint: disable=R0904
+class ConfigMock(config.ConfigWriter):
+  """A mocked cluster configuration with added methods for easy customization.
+
+  """
+
+  def __init__(self):
+    self._cur_group_id = 1
+    self._cur_node_id = 1
+    self._cur_inst_id = 1
+    self._cur_disk_id = 1
+    self._cur_os_id = 1
+    self._cur_nic_id = 1
+    self._cur_net_id = 1
+    self._default_os = None
+
+    super(ConfigMock, self).__init__(cfg_file="/dev/null",
+                                     _getents=_StubGetEntResolver())
+
+  def _GetUuid(self):
+    return str(uuid_module.uuid4())
+
+  def _GetObjUuid(self, obj):
+    if obj is None:
+      return None
+    elif isinstance(obj, objects.ConfigObject):
+      return obj.uuid
+    else:
+      return obj
+
+  def AddNewNodeGroup(self,
+                      uuid=None,
+                      name=None,
+                      ndparams=None,
+                      diskparams=None,
+                      ipolicy=None,
+                      hv_state_static=None,
+                      disk_state_static=None,
+                      alloc_policy=None,
+                      networks=None):
+    """Add a new L{objects.NodeGroup} to the cluster configuration
+
+    See L{objects.NodeGroup} for parameter documentation.
+
+    @rtype: L{objects.NodeGroup}
+    @return: the newly added node group
+
+    """
+    group_id = self._cur_group_id
+    self._cur_group_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_group_%d" % group_id
+    if networks is None:
+      networks = {}
+
+    group = objects.NodeGroup(uuid=uuid,
+                              name=name,
+                              ndparams=ndparams,
+                              diskparams=diskparams,
+                              ipolicy=ipolicy,
+                              hv_state_static=hv_state_static,
+                              disk_state_static=disk_state_static,
+                              alloc_policy=alloc_policy,
+                              networks=networks,
+                              members=[])
+
+    self.AddNodeGroup(group, None)
+    return group
+
+  # pylint: disable=R0913
+  def AddNewNode(self,
+                 uuid=None,
+                 name=None,
+                 primary_ip=None,
+                 secondary_ip=None,
+                 master_candidate=True,
+                 offline=False,
+                 drained=False,
+                 group=None,
+                 master_capable=True,
+                 vm_capable=True,
+                 ndparams=None,
+                 powered=True,
+                 hv_state=None,
+                 hv_state_static=None,
+                 disk_state=None,
+                 disk_state_static=None):
+    """Add a new L{objects.Node} to the cluster configuration
+
+    See L{objects.Node} for parameter documentation.
+
+    @rtype: L{objects.Node}
+    @return: the newly added node
+
+    """
+    node_id = self._cur_node_id
+    self._cur_node_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_node_%d.example.com" % node_id
+    if primary_ip is None:
+      primary_ip = "192.0.2.%d" % node_id
+    if secondary_ip is None:
+      secondary_ip = "203.0.113.%d" % node_id
+    if group is None:
+      group = self._default_group.uuid
+    group = self._GetObjUuid(group)
+    if ndparams is None:
+      ndparams = {}
+
+    node = objects.Node(uuid=uuid,
+                        name=name,
+                        primary_ip=primary_ip,
+                        secondary_ip=secondary_ip,
+                        master_candidate=master_candidate,
+                        offline=offline,
+                        drained=drained,
+                        group=group,
+                        master_capable=master_capable,
+                        vm_capable=vm_capable,
+                        ndparams=ndparams,
+                        powered=powered,
+                        hv_state=hv_state,
+                        hv_state_static=hv_state_static,
+                        disk_state=disk_state,
+                        disk_state_static=disk_state_static)
+
+    self.AddNode(node, None)
+    return node
+
+  def AddNewInstance(self,
+                     uuid=None,
+                     name=None,
+                     primary_node=None,
+                     os=None,
+                     hypervisor=None,
+                     hvparams=None,
+                     beparams=None,
+                     osparams=None,
+                     admin_state=None,
+                     nics=None,
+                     disks=None,
+                     disk_template=None,
+                     disks_active=None,
+                     network_port=None,
+                     secondary_node=None):
+    """Add a new L{objects.Instance} to the cluster configuration
+
+    See L{objects.Instance} for parameter documentation.
+
+    @rtype: L{objects.Instance}
+    @return: the newly added instance
+
+    """
+    inst_id = self._cur_inst_id
+    self._cur_inst_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_inst_%d.example.com" % inst_id
+    if primary_node is None:
+      primary_node = self._master_node.uuid
+    primary_node = self._GetObjUuid(primary_node)
+    if os is None:
+      os = self.GetDefaultOs().name + objects.OS.VARIANT_DELIM +\
+           self.GetDefaultOs().supported_variants[0]
+    if hypervisor is None:
+      hypervisor = self.GetClusterInfo().enabled_hypervisors[0]
+    if hvparams is None:
+      hvparams = {}
+    if beparams is None:
+      beparams = {}
+    if osparams is None:
+      osparams = {}
+    if admin_state is None:
+      admin_state = constants.ADMINST_DOWN
+    if nics is None:
+      nics = [self.CreateNic()]
+    if disk_template is None:
+      if disks is None:
+        # user chose nothing, so create a plain disk for him
+        disk_template = constants.DT_PLAIN
+      elif len(disks) == 0:
+        disk_template = constants.DT_DISKLESS
+      else:
+        disk_template = disks[0].dev_type
+    if disks is None:
+      if disk_template == constants.DT_DISKLESS:
+        disks = []
+      else:
+        disks = [self.CreateDisk(dev_type=disk_template,
+                                 primary_node=primary_node,
+                                 secondary_node=secondary_node)]
+    if disks_active is None:
+      disks_active = admin_state == constants.ADMINST_UP
+
+    inst = objects.Instance(uuid=uuid,
+                            name=name,
+                            primary_node=primary_node,
+                            os=os,
+                            hypervisor=hypervisor,
+                            hvparams=hvparams,
+                            beparams=beparams,
+                            osparams=osparams,
+                            admin_state=admin_state,
+                            nics=nics,
+                            disks=disks,
+                            disk_template=disk_template,
+                            disks_active=disks_active,
+                            network_port=network_port)
+    self.AddInstance(inst, None)
+    return inst
+
+  def AddNewNetwork(self,
+                    uuid=None,
+                    name=None,
+                    mac_prefix=None,
+                    network=None,
+                    network6=None,
+                    gateway=None,
+                    gateway6=None,
+                    reservations=None,
+                    ext_reservations=None):
+    """Add a new L{objects.Network} to the cluster configuration
+
+    See L{objects.Network} for parameter documentation.
+
+    @rtype: L{objects.Network}
+    @return: the newly added network
+
+    """
+    net_id = self._cur_net_id
+    self._cur_net_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_net_%d" % net_id
+    if network is None:
+      network = "198.51.100.0/24"
+    if gateway is None:
+      if network[-3:] == "/24":
+        gateway = network[:-4] + "1"
+      else:
+        gateway = "198.51.100.1"
+    if network[-3:] == "/24" and gateway == network[:-4] + "1":
+      if reservations is None:
+        reservations = "0" * 256
+      if ext_reservations:
+        ext_reservations = "11" + ("0" * 253) + "1"
+    elif reservations is None or ext_reservations is None:
+      raise AssertionError("You have to specify 'reservations' and"
+                           " 'ext_reservations'!")
+
+    net = objects.Network(uuid=uuid,
+                          name=name,
+                          mac_prefix=mac_prefix,
+                          network=network,
+                          network6=network6,
+                          gateway=gateway,
+                          gateway6=gateway6,
+                          reservations=reservations,
+                          ext_reservations=ext_reservations)
+    self.AddNetwork(net, None)
+    return net
+
+  def ConnectNetworkToGroup(self, net, group, netparams=None):
+    """Connect the given network to the group.
+
+    @type net: string or L{objects.Network}
+    @param net: network object or UUID
+    @type group: string of L{objects.NodeGroup}
+    @param group: node group object of UUID
+    @type netparams: dict
+    @param netparams: network parameters for this connection
+
+    """
+    net_obj = None
+    if isinstance(net, objects.Network):
+      net_obj = net
+    else:
+      net_obj = self.GetNetwork(net)
+
+    group_obj = None
+    if isinstance(group, objects.NodeGroup):
+      group_obj = group
+    else:
+      group_obj = self.GetNodeGroup(group)
+
+    if net_obj is None or group_obj is None:
+      raise AssertionError("Failed to get network or node group")
+
+    if netparams is None:
+      netparams = {
+        constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
+        constants.NIC_LINK: "br_mock"
+      }
+
+    group_obj.networks[net_obj.uuid] = netparams
+
+  def CreateDisk(self,
+                 uuid=None,
+                 name=None,
+                 dev_type=constants.DT_PLAIN,
+                 logical_id=None,
+                 children=None,
+                 iv_name=None,
+                 size=1024,
+                 mode=constants.DISK_RDWR,
+                 params=None,
+                 spindles=None,
+                 primary_node=None,
+                 secondary_node=None,
+                 create_nodes=False,
+                 instance_disk_index=0):
+    """Create a new L{objecs.Disk} object
+
+    @rtype: L{objects.Disk}
+    @return: the newly create disk object
+
+    """
+    disk_id = self._cur_disk_id
+    self._cur_disk_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_disk_%d" % disk_id
+
+    if dev_type == constants.DT_DRBD8:
+      pnode_uuid = self._GetObjUuid(primary_node)
+      snode_uuid = self._GetObjUuid(secondary_node)
+      if logical_id is not None:
+        pnode_uuid = logical_id[0]
+        snode_uuid = logical_id[1]
+
+      if pnode_uuid is None and create_nodes:
+        pnode_uuid = self.AddNewNode().uuid
+      if snode_uuid is None and create_nodes:
+        snode_uuid = self.AddNewNode().uuid
+
+      if pnode_uuid is None or snode_uuid is None:
+        raise AssertionError("Trying to create DRBD disk without nodes!")
+
+      if logical_id is None:
+        logical_id = (pnode_uuid, snode_uuid,
+                      constants.FIRST_DRBD_PORT + disk_id,
+                      disk_id, disk_id, "mock_secret")
+      if children is None:
+        data_child = self.CreateDisk(dev_type=constants.DT_PLAIN,
+                                     size=size)
+        meta_child = self.CreateDisk(dev_type=constants.DT_PLAIN,
+                                     size=constants.DRBD_META_SIZE)
+        children = [data_child, meta_child]
+    elif dev_type == constants.DT_PLAIN:
+      if logical_id is None:
+        logical_id = ("mockvg", "mock_disk_%d" % disk_id)
+    elif dev_type in (constants.DT_FILE, constants.DT_SHARED_FILE):
+      if logical_id is None:
+        logical_id = (constants.FD_LOOP, "/file/storage/disk%d" % disk_id)
+    elif dev_type == constants.DT_BLOCK:
+      if logical_id is None:
+        logical_id = (constants.BLOCKDEV_DRIVER_MANUAL,
+                      "/dev/disk/disk%d" % disk_id)
+    elif logical_id is None:
+      raise NotImplementedError
+    if children is None:
+      children = []
+    if iv_name is None:
+      iv_name = "disk/%d" % instance_disk_index
+    if params is None:
+      params = {}
+
+    return objects.Disk(uuid=uuid,
+                        name=name,
+                        dev_type=dev_type,
+                        logical_id=logical_id,
+                        children=children,
+                        iv_name=iv_name,
+                        size=size,
+                        mode=mode,
+                        params=params,
+                        spindles=spindles)
+
+  def GetDefaultOs(self):
+    if self._default_os is None:
+      self._default_os = self.CreateOs(name="mocked_os")
+    return self._default_os
+
+  def CreateOs(self,
+               name=None,
+               path=None,
+               api_versions=None,
+               create_script=None,
+               export_script=None,
+               import_script=None,
+               rename_script=None,
+               verify_script=None,
+               supported_variants=None,
+               supported_parameters=None):
+    """Create a new L{objects.OS} object
+
+    @rtype: L{object.OS}
+    @return: the newly create OS objects
+
+    """
+    os_id = self._cur_os_id
+    self._cur_os_id += 1
+
+    if name is None:
+      name = "mock_os_%d" % os_id
+    if path is None:
+      path = "/mocked/path/%d" % os_id
+    if api_versions is None:
+      api_versions = [constants.OS_API_V20]
+    if create_script is None:
+      create_script = "mock_create.sh"
+    if export_script is None:
+      export_script = "mock_export.sh"
+    if import_script is None:
+      import_script = "mock_import.sh"
+    if rename_script is None:
+      rename_script = "mock_rename.sh"
+    if verify_script is None:
+      verify_script = "mock_verify.sh"
+    if supported_variants is None:
+      supported_variants = ["default"]
+    if supported_parameters is None:
+      supported_parameters = ["mock_param"]
+
+    return objects.OS(name=name,
+                      path=path,
+                      api_versions=api_versions,
+                      create_script=create_script,
+                      export_script=export_script,
+                      import_script=import_script,
+                      rename_script=rename_script,
+                      verify_script=verify_script,
+                      supported_variants=supported_variants,
+                      supported_parameters=supported_parameters)
+
+  def CreateNic(self,
+                uuid=None,
+                name=None,
+                mac=None,
+                ip=None,
+                network=None,
+                nicparams=None,
+                netinfo=None):
+    """Create a new L{objecs.NIC} object
+
+    @rtype: L{objects.NIC}
+    @return: the newly create NIC object
+
+    """
+    nic_id = self._cur_nic_id
+    self._cur_nic_id += 1
+
+    if uuid is None:
+      uuid = self._GetUuid()
+    if name is None:
+      name = "mock_nic_%d" % nic_id
+    if mac is None:
+      mac = "aa:00:00:aa:%02x:%02x" % (nic_id / 0xff, nic_id % 0xff)
+    if isinstance(network, objects.Network):
+      if ip:
+        pool = AddressPool(network)
+        pool.Reserve(ip)
+      network = network.uuid
+    if nicparams is None:
+      nicparams = {}
+
+    return objects.NIC(uuid=uuid,
+                       name=name,
+                       mac=mac,
+                       ip=ip,
+                       network=network,
+                       nicparams=nicparams,
+                       netinfo=netinfo)
+
+  def SetEnabledDiskTemplates(self, enabled_disk_templates):
+    """Set the enabled disk templates in the cluster.
+
+    This also takes care of required IPolicy updates.
+
+    @type enabled_disk_templates: list of string
+    @param enabled_disk_templates: list of disk templates to enable
+
+    """
+    cluster = self.GetClusterInfo()
+    cluster.enabled_disk_templates = list(enabled_disk_templates)
+    cluster.ipolicy[constants.IPOLICY_DTS] = list(enabled_disk_templates)
+
+  def SetIPolicyField(self, category, field, value):
+    """Set a value of a desired ipolicy field.
+
+    @type category: one of L{constants.ISPECS_MAX}, L{constants.ISPECS_MIN},
+      L{constants.ISPECS_STD}
+    @param category: Whether to change the default value, or the upper or lower
+      bound.
+    @type field: string
+    @param field: The field to change.
+    @type value: any
+    @param value: The value to assign.
+
+    """
+    if category not in [constants.ISPECS_MAX, constants.ISPECS_MIN,
+                        constants.ISPECS_STD]:
+      raise ValueError("Invalid ipolicy category %s" % category)
+
+    ipolicy_dict = self.GetClusterInfo().ipolicy[constants.ISPECS_MINMAX][0]
+    ipolicy_dict[category][field] = value
+
+  def _OpenConfig(self, accept_foreign):
+    self._config_data = objects.ConfigData(
+      version=constants.CONFIG_VERSION,
+      cluster=None,
+      nodegroups={},
+      nodes={},
+      instances={},
+      networks={})
+
+    master_node_uuid = self._GetUuid()
+
+    self._cluster = objects.Cluster(
+      serial_no=1,
+      rsahostkeypub="",
+      highest_used_port=(constants.FIRST_DRBD_PORT - 1),
+      tcpudp_port_pool=set(),
+      mac_prefix="aa:00:00",
+      volume_group_name="xenvg",
+      reserved_lvs=None,
+      drbd_usermode_helper="/bin/true",
+      master_node=master_node_uuid,
+      master_ip="192.0.2.254",
+      master_netdev=constants.DEFAULT_BRIDGE,
+      master_netmask=None,
+      use_external_mip_script=None,
+      cluster_name="cluster.example.com",
+      file_storage_dir="/tmp",
+      shared_file_storage_dir=None,
+      enabled_hypervisors=[constants.HT_XEN_HVM, constants.HT_XEN_PVM,
+                           constants.HT_KVM],
+      hvparams=constants.HVC_DEFAULTS.copy(),
+      ipolicy=None,
+      os_hvp={self.GetDefaultOs().name: constants.HVC_DEFAULTS.copy()},
+      beparams=None,
+      osparams=None,
+      nicparams={constants.PP_DEFAULT: constants.NICC_DEFAULTS},
+      ndparams=None,
+      diskparams=None,
+      candidate_pool_size=3,
+      modify_etc_hosts=False,
+      modify_ssh_setup=False,
+      maintain_node_health=False,
+      uid_pool=None,
+      default_iallocator="mock_iallocator",
+      hidden_os=None,
+      blacklisted_os=None,
+      primary_ip_family=None,
+      prealloc_wipe_disks=None,
+      enabled_disk_templates=list(constants.DISK_TEMPLATE_PREFERENCE),
+      )
+    self._cluster.ctime = self._cluster.mtime = time.time()
+    self._cluster.UpgradeConfig()
+    self._config_data.cluster = self._cluster
+
+    self._default_group = self.AddNewNodeGroup(name="default")
+    self._master_node = self.AddNewNode(uuid=master_node_uuid)
+
+  def _WriteConfig(self, destination=None, feedback_fn=None):
+    pass
+
+  def _DistributeConfig(self, feedback_fn):
+    pass
+
+  def _GetRpc(self, address_list):
+    raise AssertionError("This should not be used during tests!")
diff --git a/test/py/cmdlib/testsupport/iallocator_mock.py b/test/py/cmdlib/testsupport/iallocator_mock.py
new file mode 100644
index 0000000..a83ec1a
--- /dev/null
+++ b/test/py/cmdlib/testsupport/iallocator_mock.py
@@ -0,0 +1,48 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the IAllocator interface"""
+
+
+from cmdlib.testsupport.util import patchModule
+
+
+# pylint: disable=C0103
+def patchIAllocator(module_under_test):
+  """Patches the L{ganeti.masterd.iallocator.IAllocator} class for tests.
+
+  This function is meant to be used as a decorator for test methods.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+
+  """
+  return patchModule(module_under_test, "iallocator.IAllocator")
diff --git a/test/py/cmdlib/testsupport/lock_manager_mock.py b/test/py/cmdlib/testsupport/lock_manager_mock.py
new file mode 100644
index 0000000..4ca7cfa
--- /dev/null
+++ b/test/py/cmdlib/testsupport/lock_manager_mock.py
@@ -0,0 +1,66 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the lock manager"""
+
+
+from ganeti import locking
+
+
+class LockManagerMock(locking.GanetiLockManager):
+  """Mocked lock manager for tests.
+
+  """
+  def __init__(self):
+    # reset singleton instance, there is a separate lock manager for every test
+    # pylint: disable=W0212
+    self.__class__._instance = None
+
+    super(LockManagerMock, self).__init__([], [], [], [])
+
+  def AddLocksFromConfig(self, cfg):
+    """Create locks for all entities in the given configuration.
+
+    @type cfg: ganeti.config.ConfigWriter
+    """
+    try:
+      self.acquire(locking.LEVEL_CLUSTER, locking.BGL)
+
+      for node_uuid in cfg.GetNodeList():
+        self.add(locking.LEVEL_NODE, node_uuid)
+        self.add(locking.LEVEL_NODE_RES, node_uuid)
+      for group_uuid in cfg.GetNodeGroupList():
+        self.add(locking.LEVEL_NODEGROUP, group_uuid)
+      for inst in cfg.GetAllInstancesInfo().values():
+        self.add(locking.LEVEL_INSTANCE, inst.name)
+      for net_uuid in cfg.GetNetworkList():
+        self.add(locking.LEVEL_NETWORK, net_uuid)
+    finally:
+      self.release(locking.LEVEL_CLUSTER, locking.BGL)
diff --git a/test/py/cmdlib/testsupport/netutils_mock.py b/test/py/cmdlib/testsupport/netutils_mock.py
new file mode 100644
index 0000000..5be6a4a
--- /dev/null
+++ b/test/py/cmdlib/testsupport/netutils_mock.py
@@ -0,0 +1,125 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the netutils module"""
+
+import mock
+
+from ganeti import compat
+from ganeti import netutils
+from cmdlib.testsupport.util import patchModule
+
+
+# pylint: disable=C0103
+def patchNetutils(module_under_test):
+  """Patches the L{ganeti.netutils} module for tests.
+
+  This function is meant to be used as a decorator for test methods.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+
+  """
+  return patchModule(module_under_test, "netutils")
+
+
+class HostnameMock(object):
+  """Simple mocked version of L{netutils.Hostname}.
+
+  """
+  def __init__(self, name, ip):
+    self.name = name
+    self.ip = ip
+
+
+def _IsOverwrittenReturnValue(value):
+  return value is not None and value != mock.DEFAULT and \
+      not isinstance(value, mock.Mock)
+
+
+# pylint: disable=W0613
+def _GetHostnameMock(cfg, mock_fct, name=None, family=None):
+  if _IsOverwrittenReturnValue(mock_fct.return_value):
+    return mock.DEFAULT
+
+  if name is None:
+    name = cfg.GetMasterNodeName()
+
+  if name == cfg.GetClusterName():
+    cluster = cfg.GetClusterInfo()
+    return HostnameMock(cluster.cluster_name, cluster.master_ip)
+
+  node = cfg.GetNodeInfoByName(name)
+  if node is not None:
+    return HostnameMock(node.name, node.primary_ip)
+
+  return HostnameMock(name, "203.0.113.253")
+
+
+# pylint: disable=W0613
+def _TcpPingMock(cfg, mock_fct, target, port, timeout=None,
+                 live_port_needed=None, source=None):
+  if _IsOverwrittenReturnValue(mock_fct.return_value):
+    return mock.DEFAULT
+
+  if target == cfg.GetClusterName():
+    return True
+  if cfg.GetNodeInfoByName(target) is not None:
+    return True
+  if target in [node.primary_ip for node in cfg.GetAllNodesInfo().values()]:
+    return True
+  if target in [node.secondary_ip for node in cfg.GetAllNodesInfo().values()]:
+    return True
+  return False
+
+
+def SetupDefaultNetutilsMock(netutils_mod, cfg):
+  """Configures the given netutils_mod mock to work with the given config.
+
+  All relevant functions in netutils_mod are stubbed in such a way that they
+  are consistent with the configuration.
+
+  @param netutils_mod: the mock module to configure
+  @type cfg: cmdlib.testsupport.ConfigMock
+  @param cfg: the configuration to query for netutils request
+
+  """
+  netutils_mod.GetHostname.side_effect = \
+    compat.partial(_GetHostnameMock, cfg, netutils_mod.GetHostname)
+  netutils_mod.TcpPing.side_effect = \
+    compat.partial(_TcpPingMock, cfg, netutils_mod.TcpPing)
+  netutils_mod.GetDaemonPort.side_effect = netutils.GetDaemonPort
+  netutils_mod.FormatAddress.side_effect = netutils.FormatAddress
+  netutils_mod.Hostname.GetNormalizedName.side_effect = \
+    netutils.Hostname.GetNormalizedName
+  netutils_mod.IPAddress = netutils.IPAddress
+  netutils_mod.IP4Address = netutils.IP4Address
+  netutils_mod.IP6Address = netutils.IP6Address
diff --git a/test/py/cmdlib/testsupport/processor_mock.py b/test/py/cmdlib/testsupport/processor_mock.py
new file mode 100644
index 0000000..449580c
--- /dev/null
+++ b/test/py/cmdlib/testsupport/processor_mock.py
@@ -0,0 +1,239 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the opcode processor"""
+
+
+import re
+
+from ganeti import constants
+from ganeti import mcpu
+
+
+class LogRecordingCallback(mcpu.OpExecCbBase):
+  """Helper class for log output recording.
+
+  """
+  def __init__(self, processor):
+    super(LogRecordingCallback, self).__init__()
+
+    self.processor = processor
+
+  def Feedback(self, *args):
+    assert len(args) < 3
+
+    if len(args) == 1:
+      log_type = constants.ELOG_MESSAGE
+      log_msg = args[0]
+    else:
+      (log_type, log_msg) = args
+
+    self.processor.log_entries.append((log_type, log_msg))
+
+  def SubmitManyJobs(self, jobs):
+    results = []
+    for idx, _ in enumerate(jobs):
+      results.append((True, idx))
+    return results
+
+
+class ProcessorMock(mcpu.Processor):
+  """Mocked opcode processor for tests.
+
+  This class actually performs much more than a mock, as it drives the
+  execution of LU's. But it also provides access to the log output of the LU
+  the result of the execution.
+
+  See L{ExecOpCodeAndRecordOutput} for the main method of this class.
+
+  """
+
+  def __init__(self, context):
+    super(ProcessorMock, self).__init__(context, 1, True)
+    self.log_entries = []
+    self._lu_test_func = None
+
+  def ExecOpCodeAndRecordOutput(self, op):
+    """Executes the given opcode and records the output for further inspection.
+
+    @param op: the opcode to execute.
+    @return: see L{mcpu.Processor.ExecOpCode}
+
+    """
+    return self.ExecOpCode(op, LogRecordingCallback(self))
+
+  def _ExecLU(self, lu):
+    # pylint: disable=W0212
+    if not self._lu_test_func:
+      return super(ProcessorMock, self)._ExecLU(lu)
+    else:
+      # required by a lot LU's, and usually passed in Exec
+      lu._feedback_fn = self.Log
+      return self._lu_test_func(lu)
+
+  def _CheckLUResult(self, op, result):
+    # pylint: disable=W0212
+    if not self._lu_test_func:
+      return super(ProcessorMock, self)._CheckLUResult(op, result)
+    else:
+      pass
+
+  def RunWithLockedLU(self, op, func):
+    """Takes the given opcode, creates a LU and runs func with it.
+
+    @param op: the opcode to get the LU for.
+    @param func: the function to run with the created and locked LU.
+    @return: the result of func.
+
+    """
+    self._lu_test_func = func
+    try:
+      return self.ExecOpCodeAndRecordOutput(op)
+    finally:
+      self._lu_test_func = None
+
+  def GetLogEntries(self):
+    """Return the list of recorded log entries.
+
+    @rtype: list of (string, string) tuples
+    @return: the list of recorded log entries
+
+    """
+    return self.log_entries
+
+  def GetLogMessages(self):
+    """Return the list of recorded log messages.
+
+    @rtype: list of string
+    @return: the list of recorded log messages
+
+    """
+    return [msg for _, msg in self.log_entries]
+
+  def GetLogEntriesString(self):
+    """Return a string with all log entries separated by a newline.
+
+    """
+    return "\n".join("%s: %s" % (log_type, msg)
+                     for log_type, msg in self.GetLogEntries())
+
+  def GetLogMessagesString(self):
+    """Return a string with all log messages separated by a newline.
+
+    """
+    return "\n".join("%s" % msg for _, msg in self.GetLogEntries())
+
+  def assertLogContainsEntry(self, expected_type, expected_msg):
+    """Asserts that the log contains the exact given entry.
+
+    @type expected_type: string
+    @param expected_type: the expected type
+    @type expected_msg: string
+    @param expected_msg: the expected message
+
+    """
+    for log_type, msg in self.log_entries:
+      if log_type == expected_type and msg == expected_msg:
+        return
+
+    raise AssertionError(
+      "Could not find '%s' (type '%s') in LU log messages. Log is:\n%s" %
+      (expected_msg, expected_type, self.GetLogEntriesString()))
+
+  def assertLogContainsMessage(self, expected_msg):
+    """Asserts that the log contains the exact given message.
+
+    @type expected_msg: string
+    @param expected_msg: the expected message
+
+    """
+    for msg in self.GetLogMessages():
+      if msg == expected_msg:
+        return
+
+    raise AssertionError(
+      "Could not find '%s' in LU log messages. Log is:\n%s" %
+      (expected_msg, self.GetLogMessagesString()))
+
+  def assertLogContainsRegex(self, expected_regex):
+    """Asserts that the log contains a message which matches the regex.
+
+    @type expected_regex: string
+    @param expected_regex: regular expression to match messages with.
+
+    """
+    for msg in self.GetLogMessages():
+      if re.search(expected_regex, msg) is not None:
+        return
+
+    raise AssertionError(
+      "Could not find '%s' in LU log messages. Log is:\n%s" %
+      (expected_regex, self.GetLogMessagesString())
+    )
+
+  def assertLogContainsInLine(self, expected):
+    """Asserts that the log contains a message which contains a string.
+
+    @type expected: string
+    @param expected: string to search in messages.
+
+    """
+    self.assertLogContainsRegex(re.escape(expected))
+
+  def assertLogDoesNotContainRegex(self, expected_regex):
+    """Asserts that the log does not contain a message which matches the regex.
+
+    @type expected_regex: string
+    @param expected_regex: regular expression to match messages with.
+
+    """
+    for msg in self.GetLogMessages():
+      if re.search(expected_regex, msg) is not None:
+        raise AssertionError(
+          "Found '%s' in LU log messages. Log is:\n%s" %
+          (expected_regex, self.GetLogMessagesString())
+        )
+
+  def assertLogIsEmpty(self):
+    """Asserts that the log does not contain any message.
+
+    """
+    if len(self.GetLogMessages()) > 0:
+      raise AssertionError("Log is not empty. Log is:\n%s" %
+                           self.GetLogMessagesString())
+
+  def ClearLogMessages(self):
+    """Clears all recorded log messages.
+
+    This is useful if you use L{GetLockedLU} and want to test multiple calls
+    on it.
+
+    """
+    self.log_entries = []
diff --git a/test/py/cmdlib/testsupport/rpc_runner_mock.py b/test/py/cmdlib/testsupport/rpc_runner_mock.py
new file mode 100644
index 0000000..4e25661
--- /dev/null
+++ b/test/py/cmdlib/testsupport/rpc_runner_mock.py
@@ -0,0 +1,217 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the RPC runner"""
+
+
+import mock
+
+from ganeti import objects
+from ganeti import rpc
+
+from cmdlib.testsupport.util import patchModule
+
+
+def CreateRpcRunnerMock():
+  """Creates a new L{mock.MagicMock} tailored for L{rpc.RpcRunner}
+
+  """
+  ret = mock.MagicMock(spec=rpc.RpcRunner)
+  return ret
+
+
+class RpcResultsBuilder(object):
+  """Helper class which assists in constructing L{rpc.RpcResult} objects.
+
+  This class provides some convenience methods for constructing L{rpc.RpcResult}
+  objects. It is possible to create single results with the C{Create*} methods
+  or to create multi-node results by repeatedly calling the C{Add*} methods and
+  then obtaining the final result with C{Build}.
+
+  The C{node} parameter of all the methods can either be a L{objects.Node}
+  object, a node UUID or a node name. You have to provide the cluster config
+  in the constructor if you want to use node UUID's/names.
+
+  A typical usage of this class is as follows::
+
+    self.rpc.call_some_rpc.return_value = \
+      RpcResultsBuilder(cfg=self.cfg) \
+        .AddSuccessfulNode(node1,
+                           {
+                             "result_key": "result_data",
+                             "another_key": "other_data",
+                           }) \
+        .AddErrorNode(node2) \
+        .Build()
+
+  """
+
+  def __init__(self, cfg=None, use_node_names=False):
+    """Constructor.
+
+    @type cfg: L{ganeti.config.ConfigWriter}
+    @param cfg: used to resolve nodes if not C{None}
+    @type use_node_names: bool
+    @param use_node_names: if set to C{True}, the node field in the RPC results
+          will contain the node name instead of the node UUID.
+    """
+    self._cfg = cfg
+    self._use_node_names = use_node_names
+    self._results = []
+
+  def _GetNode(self, node_id):
+    if isinstance(node_id, objects.Node):
+      return node_id
+
+    node = None
+    if self._cfg is not None:
+      node = self._cfg.GetNodeInfo(node_id)
+      if node is None:
+        node = self._cfg.GetNodeInfoByName(node_id)
+
+    assert node is not None, "Failed to find '%s' in configuration" % node_id
+    return node
+
+  def _GetNodeId(self, node_id):
+    node = self._GetNode(node_id)
+    if self._use_node_names:
+      return node.name
+    else:
+      return node.uuid
+
+  def CreateSuccessfulNodeResult(self, node, data=None):
+    """@see L{RpcResultsBuilder}
+
+    @param node: @see L{RpcResultsBuilder}.
+    @type data: dict
+    @param data: the data as returned by the RPC
+    @rtype: L{rpc.RpcResult}
+    """
+    if data is None:
+      data = {}
+    return rpc.RpcResult(data=(True, data), node=self._GetNodeId(node))
+
+  def CreateFailedNodeResult(self, node):
+    """@see L{RpcResultsBuilder}
+
+    @param node: @see L{RpcResultsBuilder}.
+    @rtype: L{rpc.RpcResult}
+    """
+    return rpc.RpcResult(failed=True, node=self._GetNodeId(node))
+
+  def CreateOfflineNodeResult(self, node):
+    """@see L{RpcResultsBuilder}
+
+    @param node: @see L{RpcResultsBuilder}.
+    @rtype: L{rpc.RpcResult}
+    """
+    return rpc.RpcResult(failed=True, offline=True, node=self._GetNodeId(node))
+
+  def CreateErrorNodeResult(self, node, error_msg=None):
+    """@see L{RpcResultsBuilder}
+
+    @param node: @see L{RpcResultsBuilder}.
+    @type error_msg: string
+    @param error_msg: the error message as returned by the RPC
+    @rtype: L{rpc.RpcResult}
+    """
+    return rpc.RpcResult(data=(False, error_msg), node=self._GetNodeId(node))
+
+  def AddSuccessfulNode(self, node, data=None):
+    """@see L{CreateSuccessfulNode}
+
+    @rtype: L{RpcResultsBuilder}
+    @return: self for chaining
+
+    """
+    self._results.append(self.CreateSuccessfulNodeResult(node, data))
+    return self
+
+  def AddFailedNode(self, node):
+    """@see L{CreateFailedNode}
+
+    @rtype: L{RpcResultsBuilder}
+    @return: self for chaining
+
+    """
+    self._results.append(self.CreateFailedNodeResult(node))
+    return self
+
+  def AddOfflineNode(self, node):
+    """@see L{CreateOfflineNode}
+
+    @rtype: L{RpcResultsBuilder}
+    @return: self for chaining
+
+    """
+    self._results.append(self.CreateOfflineNodeResult(node))
+    return self
+
+  def AddErrorNode(self, node, error_msg=None):
+    """@see L{CreateErrorNode}
+
+    @rtype: L{RpcResultsBuilder}
+    @return: self for chaining
+
+    """
+    self._results.append(self.CreateErrorNodeResult(node, error_msg=error_msg))
+    return self
+
+  def Build(self):
+    """Creates a dictionary holding multi-node results
+
+    @rtype: dict
+    """
+    return dict((result.node, result) for result in self._results)
+
+
+# pylint: disable=C0103
+def patchRpc(module_under_test):
+  """Patches the L{ganeti.rpc} module for tests.
+
+  This function is meant to be used as a decorator for test methods.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+
+  """
+  return patchModule(module_under_test, "rpc", wraps=rpc)
+
+
+def SetupDefaultRpcModuleMock(rpc_mod):
+  """Configures the given rpc_mod.
+
+  All relevant functions in rpc_mod are stubbed in a sensible way.
+
+  @param rpc_mod: the mock module to configure
+
+  """
+  rpc_mod.DnsOnlyRunner.return_value = CreateRpcRunnerMock()
diff --git a/test/py/cmdlib/testsupport/ssh_mock.py b/test/py/cmdlib/testsupport/ssh_mock.py
new file mode 100644
index 0000000..5b7268a
--- /dev/null
+++ b/test/py/cmdlib/testsupport/ssh_mock.py
@@ -0,0 +1,48 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the ssh module"""
+
+
+from cmdlib.testsupport.util import patchModule
+
+
+# pylint: disable=C0103
+def patchSsh(module_under_test):
+  """Patches the L{ganeti.ssh} module for tests.
+
+  This function is meant to be used as a decorator for test methods.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+
+  """
+  return patchModule(module_under_test, "ssh")
diff --git a/test/py/cmdlib/testsupport/util.py b/test/py/cmdlib/testsupport/util.py
new file mode 100644
index 0000000..4d5aa07
--- /dev/null
+++ b/test/py/cmdlib/testsupport/util.py
@@ -0,0 +1,50 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Utility functions or the cmdlib test framework"""
+
+
+import mock
+
+
+# pylint: disable=C0103
+def patchModule(module_under_test, mock_module, **kwargs):
+  """Computes the module prefix required to mock parts of the Ganeti code.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+  @type mock_module
+  @param mock_module: the module which should be mocked.
+
+  """
+  if not module_under_test.startswith("ganeti.cmdlib"):
+    module_under_test = "ganeti.cmdlib." + module_under_test
+  return mock.patch("%s.%s" % (module_under_test, mock_module), **kwargs)
diff --git a/test/py/cmdlib/testsupport/utils_mock.py b/test/py/cmdlib/testsupport/utils_mock.py
new file mode 100644
index 0000000..edc76d9
--- /dev/null
+++ b/test/py/cmdlib/testsupport/utils_mock.py
@@ -0,0 +1,48 @@
+#
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Support for mocking the utils module"""
+
+
+from cmdlib.testsupport.util import patchModule
+
+
+# pylint: disable=C0103
+def patchUtils(module_under_test):
+  """Patches the L{ganeti.utils} module for tests.
+
+  This function is meant to be used as a decorator for test methods.
+
+  @type module_under_test: string
+  @param module_under_test: the module within cmdlib which is tested. The
+        "ganeti.cmdlib" prefix is optional.
+
+  """
+  return patchModule(module_under_test, "utils")
diff --git a/test/py/daemon-util_unittest.bash b/test/py/daemon-util_unittest.bash
index ec7b50d..310b86f 100755
--- a/test/py/daemon-util_unittest.bash
+++ b/test/py/daemon-util_unittest.bash
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 
@@ -28,23 +37,23 @@
   exit 1
 }
 
-if ! grep -q '^ENABLE_CONFD = ' lib/_autoconf.py; then
+if ! grep -q '^ENABLE_CONFD = ' lib/_constants.py; then
   err "Please update $0, confd enable feature is missing"
 fi
 
-if ! grep -q '^ENABLE_MOND = ' lib/_autoconf.py; then
+if ! grep -q '^ENABLE_MOND = ' lib/_constants.py; then
   err "Please update $0, mond enable feature is missing"
 fi
 
 DAEMONS_LIST="noded masterd rapi"
 STOPDAEMONS_LIST="rapi masterd noded"
 
-if grep -q '^ENABLE_CONFD = True' lib/_autoconf.py; then
+if grep -q '^ENABLE_CONFD = True' lib/_constants.py; then
   DAEMONS_LIST="$DAEMONS_LIST confd luxid"
   STOPDAEMONS_LIST="luxid confd $STOPDAEMONS_LIST"
 fi
 
-if grep -q '^ENABLE_MOND = True' lib/_autoconf.py; then
+if grep -q '^ENABLE_MOND = True' lib/_constants.py; then
   DAEMONS_LIST="$DAEMONS_LIST mond"
   STOPDAEMONS_LIST="mond $STOPDAEMONS_LIST"
 fi
diff --git a/test/py/docs_unittest.py b/test/py/docs_unittest.py
index 8c9d4fe..c2d7189 100755
--- a/test/py/docs_unittest.py
+++ b/test/py/docs_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting documentation"""
@@ -26,7 +35,7 @@
 import itertools
 import operator
 
-from ganeti import _autoconf
+from ganeti import _constants
 from ganeti import utils
 from ganeti import cmdlib
 from ganeti import build
@@ -314,7 +323,7 @@
     return build.LoadModule("scripts/%s" % name)
 
   def test(self):
-    for script in _autoconf.GNT_SCRIPTS:
+    for script in _constants.GNT_SCRIPTS:
       self._CheckManpage(script,
                          self._ReadManFile(script),
                          self._LoadScript(script).commands.keys())
diff --git a/test/py/ganeti-cleaner_unittest.bash b/test/py/ganeti-cleaner_unittest.bash
index cf38f73..e163740 100755
--- a/test/py/ganeti-cleaner_unittest.bash
+++ b/test/py/ganeti-cleaner_unittest.bash
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u
 set -o pipefail
@@ -26,8 +35,9 @@
 GNTC=daemons/ganeti-cleaner
 CCE=$PWD/tools/check-cert-expired
 
-if [ "x$PYTHONPATH" = "x." ]
-then export PYTHONPATH=$PWD
+# Expand relative PYTHONPATH passed as passed by the test environment.
+if [ "x$PYTHONPATH" = "x.:./test/py" ]
+then export PYTHONPATH=$PWD:$PWD/test/py
 fi
 
 err() {
diff --git a/test/py/ganeti.asyncnotifier_unittest.py b/test/py/ganeti.asyncnotifier_unittest.py
index 0a376e6..2b3098f 100755
--- a/test/py/ganeti.asyncnotifier_unittest.py
+++ b/test/py/ganeti.asyncnotifier_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the asyncnotifier module"""
diff --git a/test/py/ganeti.backend_unittest-runasroot.py b/test/py/ganeti.backend_unittest-runasroot.py
index 4266247..e0f1690 100755
--- a/test/py/ganeti.backend_unittest-runasroot.py
+++ b/test/py/ganeti.backend_unittest-runasroot.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.backend (tests requiring root access)"""
diff --git a/test/py/ganeti.backend_unittest.py b/test/py/ganeti.backend_unittest.py
index 44f45c5..225fa89 100755
--- a/test/py/ganeti.backend_unittest.py
+++ b/test/py/ganeti.backend_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.backend"""
diff --git a/test/py/ganeti.bootstrap_unittest.py b/test/py/ganeti.bootstrap_unittest.py
index 82c4a16..059b059 100755
--- a/test/py/ganeti.bootstrap_unittest.py
+++ b/test/py/ganeti.bootstrap_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.bootstrap"""
@@ -27,6 +36,7 @@
 
 from ganeti import bootstrap
 from ganeti import constants
+from ganeti.storage import drbd
 from ganeti import errors
 from ganeti import pathutils
 
@@ -130,5 +140,47 @@
     self.assertEqual(ipolicy[constants.IPOLICY_DTS], [constants.DT_PLAIN])
 
 
+class TestInitCheckDrbdHelper(unittest.TestCase):
+
+  @testutils.patch_object(drbd.DRBD8, "GetUsermodeHelper")
+  def testNoDrbd(self, drbd_mock_get_usermode_helper):
+    drbd_enabled = False
+    drbd_helper = None
+    bootstrap._InitCheckDrbdHelper(drbd_helper, drbd_enabled)
+
+  @testutils.patch_object(drbd.DRBD8, "GetUsermodeHelper")
+  def testHelperNone(self, drbd_mock_get_usermode_helper):
+    drbd_enabled = True
+    current_helper = "/bin/helper"
+    drbd_helper = None
+    drbd_mock_get_usermode_helper.return_value = current_helper
+    bootstrap._InitCheckDrbdHelper(drbd_helper, drbd_enabled)
+
+  @testutils.patch_object(drbd.DRBD8, "GetUsermodeHelper")
+  def testHelperOk(self, drbd_mock_get_usermode_helper):
+    drbd_enabled = True
+    current_helper = "/bin/helper"
+    drbd_helper = "/bin/helper"
+    drbd_mock_get_usermode_helper.return_value = current_helper
+    bootstrap._InitCheckDrbdHelper(drbd_helper, drbd_enabled)
+
+  @testutils.patch_object(drbd.DRBD8, "GetUsermodeHelper")
+  def testWrongHelper(self, drbd_mock_get_usermode_helper):
+    drbd_enabled = True
+    current_helper = "/bin/otherhelper"
+    drbd_helper = "/bin/helper"
+    drbd_mock_get_usermode_helper.return_value = current_helper
+    self.assertRaises(errors.OpPrereqError,
+        bootstrap._InitCheckDrbdHelper, drbd_helper, drbd_enabled)
+
+  @testutils.patch_object(drbd.DRBD8, "GetUsermodeHelper")
+  def testHelperCheckFails(self, drbd_mock_get_usermode_helper):
+    drbd_enabled = True
+    drbd_helper = "/bin/helper"
+    drbd_mock_get_usermode_helper.side_effect=errors.BlockDeviceError
+    self.assertRaises(errors.OpPrereqError,
+        bootstrap._InitCheckDrbdHelper, drbd_helper, drbd_enabled)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.cli_unittest.py b/test/py/ganeti.cli_unittest.py
index 0c221ee..7670a1b 100755
--- a/test/py/ganeti.cli_unittest.py
+++ b/test/py/ganeti.cli_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the cli module"""
diff --git a/test/py/ganeti.client.gnt_cluster_unittest.py b/test/py/ganeti.client.gnt_cluster_unittest.py
index 1908f87..c22c26d 100755
--- a/test/py/ganeti.client.gnt_cluster_unittest.py
+++ b/test/py/ganeti.client.gnt_cluster_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.client.gnt_cluster"""
@@ -24,11 +33,13 @@
 import unittest
 import optparse
 
+from ganeti import errors
 from ganeti.client import gnt_cluster
 from ganeti import utils
 from ganeti import compat
 from ganeti import constants
 
+import mock
 import testutils
 
 
@@ -258,5 +269,99 @@
     self.assertEqual(result, constants.EXIT_FAILURE)
 
 
+class DrbdHelperTestCase(unittest.TestCase):
+
+  def setUp(self):
+    unittest.TestCase.setUp(self)
+    self.enabled_disk_templates = []
+
+  def enableDrbd(self):
+    self.enabled_disk_templates = [constants.DT_DRBD8]
+
+  def disableDrbd(self):
+    self.enabled_disk_templates = [constants.DT_DISKLESS]
+
+
+class InitDrbdHelper(DrbdHelperTestCase):
+
+  def testNoDrbdNoHelper(self):
+    opts = mock.Mock()
+    opts.drbd_helper = None
+    self.disableDrbd()
+    helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(None, helper)
+
+  def testNoDrbdHelper(self):
+    opts = mock.Mock()
+    self.disableDrbd()
+    opts.drbd_helper = "/bin/true"
+    helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(opts.drbd_helper, helper)
+
+  def testDrbdHelperNone(self):
+    opts = mock.Mock()
+    self.enableDrbd()
+    opts.drbd_helper = None
+    helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(constants.DEFAULT_DRBD_HELPER, helper)
+
+  def testDrbdHelperEmpty(self):
+    opts = mock.Mock()
+    self.enableDrbd()
+    opts.drbd_helper = ''
+    self.assertRaises(errors.OpPrereqError, gnt_cluster._InitDrbdHelper, opts,
+        self.enabled_disk_templates)
+
+  def testDrbdHelper(self):
+    opts = mock.Mock()
+    self.enableDrbd()
+    opts.drbd_helper = "/bin/true"
+    helper = gnt_cluster._InitDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(opts.drbd_helper, helper)
+
+
+class GetDrbdHelper(DrbdHelperTestCase):
+
+  def testNoDrbdNoHelper(self):
+    opts = mock.Mock()
+    self.disableDrbd()
+    opts.drbd_helper = None
+    helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(None, helper)
+
+  def testNoTemplateInfoNoHelper(self):
+    opts = mock.Mock()
+    opts.drbd_helper = None
+    helper = gnt_cluster._GetDrbdHelper(opts, None)
+    self.assertEquals(None, helper)
+
+  def testNoTemplateInfoHelper(self):
+    opts = mock.Mock()
+    opts.drbd_helper = "/bin/true"
+    helper = gnt_cluster._GetDrbdHelper(opts, None)
+    self.assertEquals(opts.drbd_helper, helper)
+
+  def testNoDrbdHelper(self):
+    opts = mock.Mock()
+    self.disableDrbd()
+    opts.drbd_helper = "/bin/true"
+    helper = gnt_cluster._GetDrbdHelper(opts, None)
+    self.assertEquals(opts.drbd_helper, helper)
+
+  def testDrbdNoHelper(self):
+    opts = mock.Mock()
+    self.enableDrbd()
+    opts.drbd_helper = None
+    helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(None, helper)
+
+  def testDrbdHelper(self):
+    opts = mock.Mock()
+    self.enableDrbd()
+    opts.drbd_helper = "/bin/true"
+    helper = gnt_cluster._GetDrbdHelper(opts, self.enabled_disk_templates)
+    self.assertEquals(opts.drbd_helper, helper)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.client.gnt_instance_unittest.py b/test/py/ganeti.client.gnt_instance_unittest.py
index 20d3d86..3349c1a 100755
--- a/test/py/ganeti.client.gnt_instance_unittest.py
+++ b/test/py/ganeti.client.gnt_instance_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.client.gnt_instance"""
diff --git a/test/py/ganeti.client.gnt_job_unittest.py b/test/py/ganeti.client.gnt_job_unittest.py
index b7a86cc..5ef411b 100755
--- a/test/py/ganeti.client.gnt_job_unittest.py
+++ b/test/py/ganeti.client.gnt_job_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.client.gnt_job"""
diff --git a/test/py/ganeti.cmdlib.cluster_unittest.py b/test/py/ganeti.cmdlib.cluster_unittest.py
deleted file mode 100755
index 819b401..0000000
--- a/test/py/ganeti.cmdlib.cluster_unittest.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Script for unittesting the cmdlib module 'cluster'"""
-
-
-import unittest
-
-from ganeti.cmdlib import cluster
-from ganeti import constants
-from ganeti import errors
-
-import testutils
-import mock
-
-
-class TestCheckFileStoragePath(unittest.TestCase):
-
-  def setUp(self):
-    unittest.TestCase.setUp(self)
-    self.log_warning = mock.Mock()
-
-  def enableFileStorage(self, file_storage_enabled):
-    if file_storage_enabled:
-      self.enabled_disk_templates = [constants.DT_FILE]
-    else:
-      # anything != 'file' would do here
-      self.enabled_disk_templates = [constants.DT_DISKLESS]
-
-  def testNone(self):
-    self.enableFileStorage(True)
-    self.assertRaises(
-        errors.ProgrammerError,
-        cluster.CheckFileStoragePathVsEnabledDiskTemplates,
-        self.log_warning, None, self.enabled_disk_templates)
-
-  def testNotEmptyAndEnabled(self):
-    self.enableFileStorage(True)
-    cluster.CheckFileStoragePathVsEnabledDiskTemplates(
-        self.log_warning, "/some/path", self.enabled_disk_templates)
-
-  def testNotEnabled(self):
-    self.enableFileStorage(False)
-    cluster.CheckFileStoragePathVsEnabledDiskTemplates(
-        self.log_warning, "/some/path", self.enabled_disk_templates)
-    self.assertTrue(self.log_warning.called)
-
-  def testEmptyAndEnabled(self):
-    self.enableFileStorage(True)
-    self.assertRaises(
-        errors.OpPrereqError,
-        cluster.CheckFileStoragePathVsEnabledDiskTemplates,
-        self.log_warning, "", self.enabled_disk_templates)
-
-  def testEmptyAndDisabled(self):
-    self.enableFileStorage(False)
-    cluster.CheckFileStoragePathVsEnabledDiskTemplates(
-        NotImplemented, "", self.enabled_disk_templates)
-
-
-class TestGetEnabledDiskTemplates(unittest.TestCase):
-
-  def testNoNew(self):
-    op_dts = [constants.DT_DISKLESS]
-    old_dts = [constants.DT_DISKLESS]
-    (enabled_dts, new_dts) =\
-        cluster.LUClusterSetParams._GetEnabledDiskTemplatesInner(
-            op_dts, old_dts)
-    self.assertEqual(enabled_dts, old_dts)
-    self.assertEqual(new_dts, [])
-
-
-if __name__ == "__main__":
-  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.cmdlib.common_unittest.py b/test/py/ganeti.cmdlib.common_unittest.py
deleted file mode 100755
index 626afe6..0000000
--- a/test/py/ganeti.cmdlib.common_unittest.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Script for unittesting the cmdlib module 'common'"""
-
-
-import unittest
-
-from ganeti.cmdlib import common
-from ganeti import constants
-from ganeti import errors
-
-import testutils
-
-
-class TestCheckIpolicy(unittest.TestCase):
-
-  def testAllTemplatesEnabled(self):
-    allowed_disk_templates = [constants.DT_PLAIN]
-    ipolicy = {constants.IPOLICY_DTS: allowed_disk_templates}
-    enabled_disk_templates = [constants.DT_PLAIN, constants.DT_DRBD8]
-    common.CheckIpolicyVsDiskTemplates(
-        ipolicy, enabled_disk_templates)
-
-  def testSomeTemplatesUnenabled(self):
-    allowed_disk_templates = [constants.DT_PLAIN, constants.DT_DISKLESS]
-    ipolicy = {constants.IPOLICY_DTS: allowed_disk_templates}
-    enabled_disk_templates = [constants.DT_PLAIN, constants.DT_DRBD8]
-    self.assertRaises(
-        errors.OpPrereqError,
-        common.CheckIpolicyVsDiskTemplates,
-        ipolicy, enabled_disk_templates)
-
-
-if __name__ == "__main__":
-  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.cmdlib_unittest.py b/test/py/ganeti.cmdlib_unittest.py
deleted file mode 100755
index 55dea3c..0000000
--- a/test/py/ganeti.cmdlib_unittest.py
+++ /dev/null
@@ -1,2077 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 2008, 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Script for unittesting the cmdlib module"""
-
-
-import os
-import re
-import unittest
-import tempfile
-import shutil
-import operator
-import itertools
-import copy
-
-from ganeti import constants
-from ganeti import mcpu
-from ganeti import cmdlib
-from ganeti.cmdlib import cluster
-from ganeti.cmdlib import group
-from ganeti.cmdlib import instance
-from ganeti.cmdlib import instance_storage
-from ganeti.cmdlib import instance_utils
-from ganeti.cmdlib import common
-from ganeti.cmdlib import query
-from ganeti import opcodes
-from ganeti import errors
-from ganeti import utils
-from ganeti import luxi
-from ganeti import ht
-from ganeti import objects
-from ganeti import compat
-from ganeti import rpc
-from ganeti import locking
-from ganeti import pathutils
-from ganeti.masterd import iallocator
-from ganeti.hypervisor import hv_xen
-
-import testutils
-import mocks
-
-
-class TestCertVerification(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-    self.tmpdir = tempfile.mkdtemp()
-
-  def tearDown(self):
-    shutil.rmtree(self.tmpdir)
-
-  def testVerifyCertificate(self):
-    cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
-
-    nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
-
-    (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
-    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
-
-    # Try to load non-certificate file
-    invalid_cert = testutils.TestDataFilename("bdev-net.txt")
-    (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
-    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
-
-
-class TestOpcodeParams(testutils.GanetiTestCase):
-  def testParamsStructures(self):
-    for op in sorted(mcpu.Processor.DISPATCH_TABLE):
-      lu = mcpu.Processor.DISPATCH_TABLE[op]
-      lu_name = lu.__name__
-      self.failIf(hasattr(lu, "_OP_REQP"),
-                  msg=("LU '%s' has old-style _OP_REQP" % lu_name))
-      self.failIf(hasattr(lu, "_OP_DEFS"),
-                  msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
-      self.failIf(hasattr(lu, "_OP_PARAMS"),
-                  msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
-
-
-class TestIAllocatorChecks(testutils.GanetiTestCase):
-  def testFunction(self):
-    class TestLU(object):
-      def __init__(self, opcode):
-        self.cfg = mocks.FakeConfig()
-        self.op = opcode
-
-    class OpTest(opcodes.OpCode):
-       OP_PARAMS = [
-        ("iallocator", None, ht.NoType, None),
-        ("node", None, ht.NoType, None),
-        ]
-
-    default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
-    other_iallocator = default_iallocator + "_not"
-
-    op = OpTest()
-    lu = TestLU(op)
-
-    c_i = lambda: common.CheckIAllocatorOrNode(lu, "iallocator", "node")
-
-    # Neither node nor iallocator given
-    for n in (None, []):
-      op.iallocator = None
-      op.node = n
-      c_i()
-      self.assertEqual(lu.op.iallocator, default_iallocator)
-      self.assertEqual(lu.op.node, n)
-
-    # Both, iallocator and node given
-    for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
-      op.iallocator = a
-      op.node = "test"
-      self.assertRaises(errors.OpPrereqError, c_i)
-
-    # Only iallocator given
-    for n in (None, []):
-      op.iallocator = other_iallocator
-      op.node = n
-      c_i()
-      self.assertEqual(lu.op.iallocator, other_iallocator)
-      self.assertEqual(lu.op.node, n)
-
-    # Only node given
-    op.iallocator = None
-    op.node = "node"
-    c_i()
-    self.assertEqual(lu.op.iallocator, None)
-    self.assertEqual(lu.op.node, "node")
-
-    # Asked for default iallocator, no node given
-    op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
-    op.node = None
-    c_i()
-    self.assertEqual(lu.op.iallocator, default_iallocator)
-    self.assertEqual(lu.op.node, None)
-
-    # No node, iallocator or default iallocator
-    op.iallocator = None
-    op.node = None
-    lu.cfg.GetDefaultIAllocator = lambda: None
-    self.assertRaises(errors.OpPrereqError, c_i)
-
-
-class TestLUTestJqueue(unittest.TestCase):
-  def test(self):
-    self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
-                 (luxi.WFJC_TIMEOUT * 0.75),
-                 msg=("Client timeout too high, might not notice bugs"
-                      " in WaitForJobChange"))
-
-
-class TestLUQuery(unittest.TestCase):
-  def test(self):
-    self.assertEqual(sorted(query._QUERY_IMPL.keys()),
-                     sorted(constants.QR_VIA_OP))
-
-    assert constants.QR_NODE in constants.QR_VIA_OP
-    assert constants.QR_INSTANCE in constants.QR_VIA_OP
-
-    for i in constants.QR_VIA_OP:
-      self.assert_(query._GetQueryImplementation(i))
-
-    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
-                      "")
-    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
-                      "xyz")
-
-
-class TestLUGroupAssignNodes(unittest.TestCase):
-
-  def testCheckAssignmentForSplitInstances(self):
-    node_data = dict((n, objects.Node(name=n, group=g))
-                     for (n, g) in [("n1a", "g1"), ("n1b", "g1"),
-                                    ("n2a", "g2"), ("n2b", "g2"),
-                                    ("n3a", "g3"), ("n3b", "g3"),
-                                    ("n3c", "g3"),
-                                    ])
-
-    def Instance(uuid, pnode, snode):
-      if snode is None:
-        disks = []
-        disk_template = constants.DT_DISKLESS
-      else:
-        disks = [objects.Disk(dev_type=constants.DT_DRBD8,
-                              logical_id=[pnode, snode, 1, 17, 17])]
-        disk_template = constants.DT_DRBD8
-
-      return objects.Instance(name="%s-name" % uuid, uuid="%s" % uuid,
-                              primary_node=pnode, disks=disks,
-                              disk_template=disk_template)
-
-    instance_data = dict((uuid, Instance(uuid, pnode, snode))
-                         for uuid, pnode, snode in [("inst1a", "n1a", "n1b"),
-                                                    ("inst1b", "n1b", "n1a"),
-                                                    ("inst2a", "n2a", "n2b"),
-                                                    ("inst3a", "n3a", None),
-                                                    ("inst3b", "n3b", "n1b"),
-                                                    ("inst3c", "n3b", "n2b"),
-                                                    ])
-
-    # Test first with the existing state.
-    (new, prev) = \
-      group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
-                                                                node_data,
-                                                                instance_data)
-
-    self.assertEqual([], new)
-    self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
-
-    # And now some changes.
-    (new, prev) = \
-      group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
-                                                                  "g3")],
-                                                                node_data,
-                                                                instance_data)
-
-    self.assertEqual(set(["inst1a", "inst1b"]), set(new))
-    self.assertEqual(set(["inst3c"]), set(prev))
-
-
-class TestClusterVerifySsh(unittest.TestCase):
-  def testMultipleGroups(self):
-    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
-    mygroupnodes = [
-      objects.Node(name="node20", group="my", offline=False),
-      objects.Node(name="node21", group="my", offline=False),
-      objects.Node(name="node22", group="my", offline=False),
-      objects.Node(name="node23", group="my", offline=False),
-      objects.Node(name="node24", group="my", offline=False),
-      objects.Node(name="node25", group="my", offline=False),
-      objects.Node(name="node26", group="my", offline=True),
-      ]
-    nodes = [
-      objects.Node(name="node1", group="g1", offline=True),
-      objects.Node(name="node2", group="g1", offline=False),
-      objects.Node(name="node3", group="g1", offline=False),
-      objects.Node(name="node4", group="g1", offline=True),
-      objects.Node(name="node5", group="g1", offline=False),
-      objects.Node(name="node10", group="xyz", offline=False),
-      objects.Node(name="node11", group="xyz", offline=False),
-      objects.Node(name="node40", group="alloff", offline=True),
-      objects.Node(name="node41", group="alloff", offline=True),
-      objects.Node(name="node50", group="aaa", offline=False),
-      ] + mygroupnodes
-    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
-
-    (online, perhost) = fn(mygroupnodes, "my", nodes)
-    self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
-    self.assertEqual(set(perhost.keys()), set(online))
-
-    self.assertEqual(perhost, {
-      "node20": ["node10", "node2", "node50"],
-      "node21": ["node11", "node3", "node50"],
-      "node22": ["node10", "node5", "node50"],
-      "node23": ["node11", "node2", "node50"],
-      "node24": ["node10", "node3", "node50"],
-      "node25": ["node11", "node5", "node50"],
-      })
-
-  def testSingleGroup(self):
-    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
-    nodes = [
-      objects.Node(name="node1", group="default", offline=True),
-      objects.Node(name="node2", group="default", offline=False),
-      objects.Node(name="node3", group="default", offline=False),
-      objects.Node(name="node4", group="default", offline=True),
-      ]
-    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
-
-    (online, perhost) = fn(nodes, "default", nodes)
-    self.assertEqual(online, ["node2", "node3"])
-    self.assertEqual(set(perhost.keys()), set(online))
-
-    self.assertEqual(perhost, {
-      "node2": [],
-      "node3": [],
-      })
-
-
-class TestClusterVerifyFiles(unittest.TestCase):
-  @staticmethod
-  def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
-    assert ((ecode == constants.CV_ENODEFILECHECK and
-             ht.TNonEmptyString(item)) or
-            (ecode == constants.CV_ECLUSTERFILECHECK and
-             item is None))
-
-    if args:
-      msg = msg % args
-
-    if cond:
-      errors.append((item, msg))
-
-  def test(self):
-    errors = []
-    nodeinfo = [
-      objects.Node(name="master.example.com",
-                   uuid="master-uuid",
-                   offline=False,
-                   vm_capable=True),
-      objects.Node(name="node2.example.com",
-                   uuid="node2-uuid",
-                   offline=False,
-                   vm_capable=True),
-      objects.Node(name="node3.example.com",
-                   uuid="node3-uuid",
-                   master_candidate=True,
-                   vm_capable=False),
-      objects.Node(name="node4.example.com",
-                   uuid="node4-uuid",
-                   offline=False,
-                   vm_capable=True),
-      objects.Node(name="nodata.example.com",
-                   uuid="nodata-uuid",
-                   offline=False,
-                   vm_capable=True),
-      objects.Node(name="offline.example.com",
-                   uuid="offline-uuid",
-                   offline=True),
-      ]
-    files_all = set([
-      pathutils.CLUSTER_DOMAIN_SECRET_FILE,
-      pathutils.RAPI_CERT_FILE,
-      pathutils.RAPI_USERS_FILE,
-      ])
-    files_opt = set([
-      pathutils.RAPI_USERS_FILE,
-      hv_xen.XL_CONFIG_FILE,
-      pathutils.VNC_PASSWORD_FILE,
-      ])
-    files_mc = set([
-      pathutils.CLUSTER_CONF_FILE,
-      ])
-    files_vm = set([
-      hv_xen.XEND_CONFIG_FILE,
-      hv_xen.XL_CONFIG_FILE,
-      pathutils.VNC_PASSWORD_FILE,
-      ])
-    nvinfo = {
-      "master-uuid": rpc.RpcResult(data=(True, {
-        constants.NV_FILELIST: {
-          pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
-          pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
-          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
-          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
-          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
-        }})),
-      "node2-uuid": rpc.RpcResult(data=(True, {
-        constants.NV_FILELIST: {
-          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
-          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
-          }
-        })),
-      "node3-uuid": rpc.RpcResult(data=(True, {
-        constants.NV_FILELIST: {
-          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
-          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
-          }
-        })),
-      "node4-uuid": rpc.RpcResult(data=(True, {
-        constants.NV_FILELIST: {
-          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
-          pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
-          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
-          pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
-          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
-          }
-        })),
-      "nodata-uuid": rpc.RpcResult(data=(True, {})),
-      "offline-uuid": rpc.RpcResult(offline=True),
-      }
-    assert set(nvinfo.keys()) == set(map(operator.attrgetter("uuid"), nodeinfo))
-
-    verify_lu = cluster.LUClusterVerifyGroup(mocks.FakeProc(),
-                                             opcodes.OpClusterVerify(),
-                                             mocks.FakeContext(),
-                                             None)
-
-    verify_lu._ErrorIf = compat.partial(self._FakeErrorIf, errors)
-
-    # TODO: That's a bit hackish to mock only this single method. We should
-    # build a better FakeConfig which provides such a feature already.
-    def GetNodeName(node_uuid):
-      for node in nodeinfo:
-        if node.uuid == node_uuid:
-          return node.name
-      return None
-
-    verify_lu.cfg.GetNodeName = GetNodeName
-
-    verify_lu._VerifyFiles(nodeinfo, "master-uuid", nvinfo,
-                           (files_all, files_opt, files_mc, files_vm))
-    self.assertEqual(sorted(errors), sorted([
-      (None, ("File %s found with 2 different checksums (variant 1 on"
-              " node2.example.com, node3.example.com, node4.example.com;"
-              " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
-      (None, ("File %s is missing from node(s) node2.example.com" %
-              pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
-      (None, ("File %s should not exist on node(s) node4.example.com" %
-              pathutils.CLUSTER_CONF_FILE)),
-      (None, ("File %s is missing from node(s) node4.example.com" %
-              hv_xen.XEND_CONFIG_FILE)),
-      (None, ("File %s is missing from node(s) node3.example.com" %
-              pathutils.CLUSTER_CONF_FILE)),
-      (None, ("File %s found with 2 different checksums (variant 1 on"
-              " master.example.com; variant 2 on node4.example.com)" %
-              pathutils.CLUSTER_CONF_FILE)),
-      (None, ("File %s is optional, but it must exist on all or no nodes (not"
-              " found on master.example.com, node2.example.com,"
-              " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
-      (None, ("File %s is optional, but it must exist on all or no nodes (not"
-              " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
-      ("nodata.example.com", "Node did not return file checksum data"),
-      ]))
-
-
-class _FakeLU:
-  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
-               rpc=NotImplemented):
-    self.warning_log = []
-    self.info_log = []
-    self.cfg = cfg
-    self.proc = proc
-    self.rpc = rpc
-
-  def LogWarning(self, text, *args):
-    self.warning_log.append((text, args))
-
-  def LogInfo(self, text, *args):
-    self.info_log.append((text, args))
-
-
-class TestLoadNodeEvacResult(unittest.TestCase):
-  def testSuccess(self):
-    for moved in [[], [
-      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
-      ]]:
-      for early_release in [False, True]:
-        for use_nodes in [False, True]:
-          jobs = [
-            [opcodes.OpInstanceReplaceDisks().__getstate__()],
-            [opcodes.OpInstanceMigrate().__getstate__()],
-            ]
-
-          alloc_result = (moved, [], jobs)
-          assert iallocator._NEVAC_RESULT(alloc_result)
-
-          lu = _FakeLU()
-          result = common.LoadNodeEvacResult(lu, alloc_result,
-                                             early_release, use_nodes)
-
-          if moved:
-            (_, (info_args, )) = lu.info_log.pop(0)
-            for (instname, instgroup, instnodes) in moved:
-              self.assertTrue(instname in info_args)
-              if use_nodes:
-                for i in instnodes:
-                  self.assertTrue(i in info_args)
-              else:
-                self.assertTrue(instgroup in info_args)
-
-          self.assertFalse(lu.info_log)
-          self.assertFalse(lu.warning_log)
-
-          for op in itertools.chain(*result):
-            if hasattr(op.__class__, "early_release"):
-              self.assertEqual(op.early_release, early_release)
-            else:
-              self.assertFalse(hasattr(op, "early_release"))
-
-  def testFailed(self):
-    alloc_result = ([], [
-      ("inst5191.example.com", "errormsg21178"),
-      ], [])
-    assert iallocator._NEVAC_RESULT(alloc_result)
-
-    lu = _FakeLU()
-    self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult,
-                      lu, alloc_result, False, False)
-    self.assertFalse(lu.info_log)
-    (_, (args, )) = lu.warning_log.pop(0)
-    self.assertTrue("inst5191.example.com" in args)
-    self.assertTrue("errormsg21178" in args)
-    self.assertFalse(lu.warning_log)
-
-
-class TestUpdateAndVerifySubDict(unittest.TestCase):
-  def setUp(self):
-    self.type_check = {
-        "a": constants.VTYPE_INT,
-        "b": constants.VTYPE_STRING,
-        "c": constants.VTYPE_BOOL,
-        "d": constants.VTYPE_STRING,
-        }
-
-  def test(self):
-    old_test = {
-      "foo": {
-        "d": "blubb",
-        "a": 321,
-        },
-      "baz": {
-        "a": 678,
-        "b": "678",
-        "c": True,
-        },
-      }
-    test = {
-      "foo": {
-        "a": 123,
-        "b": "123",
-        "c": True,
-        },
-      "bar": {
-        "a": 321,
-        "b": "321",
-        "c": False,
-        },
-      }
-
-    mv = {
-      "foo": {
-        "a": 123,
-        "b": "123",
-        "c": True,
-        "d": "blubb"
-        },
-      "bar": {
-        "a": 321,
-        "b": "321",
-        "c": False,
-        },
-      "baz": {
-        "a": 678,
-        "b": "678",
-        "c": True,
-        },
-      }
-
-    verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
-    self.assertEqual(verified, mv)
-
-  def testWrong(self):
-    test = {
-      "foo": {
-        "a": "blubb",
-        "b": "123",
-        "c": True,
-        },
-      "bar": {
-        "a": 321,
-        "b": "321",
-        "c": False,
-        },
-      }
-
-    self.assertRaises(errors.TypeEnforcementError,
-                      common._UpdateAndVerifySubDict, {}, test,
-                      self.type_check)
-
-
-class TestHvStateHelper(unittest.TestCase):
-  def testWithoutOpData(self):
-    self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented),
-                     None)
-
-  def testWithoutOldData(self):
-    new = {
-      constants.HT_XEN_PVM: {
-        constants.HVST_MEMORY_TOTAL: 4096,
-        },
-      }
-    self.assertEqual(common.MergeAndVerifyHvState(new, None), new)
-
-  def testWithWrongHv(self):
-    new = {
-      "i-dont-exist": {
-        constants.HVST_MEMORY_TOTAL: 4096,
-        },
-      }
-    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState,
-                      new, None)
-
-class TestDiskStateHelper(unittest.TestCase):
-  def testWithoutOpData(self):
-    self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented),
-                     None)
-
-  def testWithoutOldData(self):
-    new = {
-      constants.DT_PLAIN: {
-        "xenvg": {
-          constants.DS_DISK_RESERVED: 1024,
-          },
-        },
-      }
-    self.assertEqual(common.MergeAndVerifyDiskState(new, None), new)
-
-  def testWithWrongStorageType(self):
-    new = {
-      "i-dont-exist": {
-        "xenvg": {
-          constants.DS_DISK_RESERVED: 1024,
-          },
-        },
-      }
-    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState,
-                      new, None)
-
-
-class TestComputeMinMaxSpec(unittest.TestCase):
-  def setUp(self):
-    self.ispecs = {
-      constants.ISPECS_MAX: {
-        constants.ISPEC_MEM_SIZE: 512,
-        constants.ISPEC_DISK_SIZE: 1024,
-        },
-      constants.ISPECS_MIN: {
-        constants.ISPEC_MEM_SIZE: 128,
-        constants.ISPEC_DISK_COUNT: 1,
-        },
-      }
-
-  def testNoneValue(self):
-    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
-                                              self.ispecs, None) is None)
-
-  def testAutoValue(self):
-    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
-                                              self.ispecs,
-                                              constants.VALUE_AUTO) is None)
-
-  def testNotDefined(self):
-    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
-                                              self.ispecs, 3) is None)
-
-  def testNoMinDefined(self):
-    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
-                                              self.ispecs, 128) is None)
-
-  def testNoMaxDefined(self):
-    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
-                                              None, self.ispecs, 16) is None)
-
-  def testOutOfRange(self):
-    for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
-                        (constants.ISPEC_MEM_SIZE, 768),
-                        (constants.ISPEC_DISK_SIZE, 4096),
-                        (constants.ISPEC_DISK_COUNT, 0)):
-      min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
-      max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
-      self.assertEqual(common._ComputeMinMaxSpec(name, None,
-                                                 self.ispecs, val),
-                       "%s value %s is not in range [%s, %s]" %
-                       (name, val,min_v, max_v))
-      self.assertEqual(common._ComputeMinMaxSpec(name, "1",
-                                                 self.ispecs, val),
-                       "%s/1 value %s is not in range [%s, %s]" %
-                       (name, val,min_v, max_v))
-
-  def test(self):
-    for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
-                        (constants.ISPEC_MEM_SIZE, 128),
-                        (constants.ISPEC_MEM_SIZE, 512),
-                        (constants.ISPEC_DISK_SIZE, 1024),
-                        (constants.ISPEC_DISK_SIZE, 0),
-                        (constants.ISPEC_DISK_COUNT, 1),
-                        (constants.ISPEC_DISK_COUNT, 5)):
-      self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
-                      is None)
-
-
-def _ValidateComputeMinMaxSpec(name, *_):
-  assert name in constants.ISPECS_PARAMETERS
-  return None
-
-
-def _NoDiskComputeMinMaxSpec(name, *_):
-  if name == constants.ISPEC_DISK_COUNT:
-    return name
-  else:
-    return None
-
-
-class _SpecWrapper:
-  def __init__(self, spec):
-    self.spec = spec
-
-  def ComputeMinMaxSpec(self, *args):
-    return self.spec.pop(0)
-
-
-class TestComputeIPolicySpecViolation(unittest.TestCase):
-  # Minimal policy accepted by _ComputeIPolicySpecViolation()
-  _MICRO_IPOL = {
-    constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
-    constants.ISPECS_MINMAX: [NotImplemented],
-    }
-
-  def test(self):
-    compute_fn = _ValidateComputeMinMaxSpec
-    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
-                                             [1024], 1, constants.DT_PLAIN,
-                                             _compute_fn=compute_fn)
-    self.assertEqual(ret, [])
-
-  def testDiskFull(self):
-    compute_fn = _NoDiskComputeMinMaxSpec
-    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
-                                             [1024], 1, constants.DT_PLAIN,
-                                             _compute_fn=compute_fn)
-    self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
-
-  def testDiskLess(self):
-    compute_fn = _NoDiskComputeMinMaxSpec
-    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
-                                             [1024], 1, constants.DT_DISKLESS,
-                                             _compute_fn=compute_fn)
-    self.assertEqual(ret, [])
-
-  def testWrongTemplates(self):
-    compute_fn = _ValidateComputeMinMaxSpec
-    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
-                                             [1024], 1, constants.DT_DRBD8,
-                                             _compute_fn=compute_fn)
-    self.assertEqual(len(ret), 1)
-    self.assertTrue("Disk template" in ret[0])
-
-  def testInvalidArguments(self):
-    self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation,
-                      self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
-                      constants.DT_PLAIN,)
-
-  def testInvalidSpec(self):
-    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
-    compute_fn = spec.ComputeMinMaxSpec
-    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
-                                             [1024], 1, constants.DT_PLAIN,
-                                             _compute_fn=compute_fn)
-    self.assertEqual(ret, ["foo", "bar"])
-    self.assertFalse(spec.spec)
-
-  def testWithIPolicy(self):
-    mem_size = 2048
-    cpu_count = 2
-    disk_count = 1
-    disk_sizes = [512]
-    nic_count = 1
-    spindle_use = 4
-    disk_template = "mytemplate"
-    ispec = {
-      constants.ISPEC_MEM_SIZE: mem_size,
-      constants.ISPEC_CPU_COUNT: cpu_count,
-      constants.ISPEC_DISK_COUNT: disk_count,
-      constants.ISPEC_DISK_SIZE: disk_sizes[0],
-      constants.ISPEC_NIC_COUNT: nic_count,
-      constants.ISPEC_SPINDLE_USE: spindle_use,
-      }
-    ipolicy1 = {
-      constants.ISPECS_MINMAX: [{
-        constants.ISPECS_MIN: ispec,
-        constants.ISPECS_MAX: ispec,
-        }],
-      constants.IPOLICY_DTS: [disk_template],
-      }
-    ispec_copy = copy.deepcopy(ispec)
-    ipolicy2 = {
-      constants.ISPECS_MINMAX: [
-        {
-          constants.ISPECS_MIN: ispec_copy,
-          constants.ISPECS_MAX: ispec_copy,
-          },
-        {
-          constants.ISPECS_MIN: ispec,
-          constants.ISPECS_MAX: ispec,
-          },
-        ],
-      constants.IPOLICY_DTS: [disk_template],
-      }
-    ipolicy3 = {
-      constants.ISPECS_MINMAX: [
-        {
-          constants.ISPECS_MIN: ispec,
-          constants.ISPECS_MAX: ispec,
-          },
-        {
-          constants.ISPECS_MIN: ispec_copy,
-          constants.ISPECS_MAX: ispec_copy,
-          },
-        ],
-      constants.IPOLICY_DTS: [disk_template],
-      }
-    def AssertComputeViolation(ipolicy, violations):
-      ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
-                                               disk_count, nic_count,
-                                               disk_sizes, spindle_use,
-                                               disk_template)
-      self.assertEqual(len(ret), violations)
-
-    AssertComputeViolation(ipolicy1, 0)
-    AssertComputeViolation(ipolicy2, 0)
-    AssertComputeViolation(ipolicy3, 0)
-    for par in constants.ISPECS_PARAMETERS:
-      ispec[par] += 1
-      AssertComputeViolation(ipolicy1, 1)
-      AssertComputeViolation(ipolicy2, 0)
-      AssertComputeViolation(ipolicy3, 0)
-      ispec[par] -= 2
-      AssertComputeViolation(ipolicy1, 1)
-      AssertComputeViolation(ipolicy2, 0)
-      AssertComputeViolation(ipolicy3, 0)
-      ispec[par] += 1 # Restore
-    ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
-    AssertComputeViolation(ipolicy1, 1)
-
-
-class _StubComputeIPolicySpecViolation:
-  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
-               spindle_use, disk_template):
-    self.mem_size = mem_size
-    self.cpu_count = cpu_count
-    self.disk_count = disk_count
-    self.nic_count = nic_count
-    self.disk_sizes = disk_sizes
-    self.spindle_use = spindle_use
-    self.disk_template = disk_template
-
-  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
-               spindle_use, disk_template):
-    assert self.mem_size == mem_size
-    assert self.cpu_count == cpu_count
-    assert self.disk_count == disk_count
-    assert self.nic_count == nic_count
-    assert self.disk_sizes == disk_sizes
-    assert self.spindle_use == spindle_use
-    assert self.disk_template == disk_template
-
-    return []
-
-
-class _FakeConfigForComputeIPolicyInstanceViolation:
-  def __init__(self, be, excl_stor):
-    self.cluster = objects.Cluster(beparams={"default": be})
-    self.excl_stor = excl_stor
-
-  def GetClusterInfo(self):
-    return self.cluster
-
-  def GetNodeInfo(self, _):
-    return {}
-
-  def GetNdParams(self, _):
-    return {
-      constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
-      }
-
-
-class TestComputeIPolicyInstanceViolation(unittest.TestCase):
-  def test(self):
-    beparams = {
-      constants.BE_MAXMEM: 2048,
-      constants.BE_VCPUS: 2,
-      constants.BE_SPINDLE_USE: 4,
-      }
-    disks = [objects.Disk(size=512, spindles=13)]
-    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
-    instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
-                                disk_template=constants.DT_PLAIN)
-    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
-                                            constants.DT_PLAIN)
-    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
-                                                 cfg, _compute_fn=stub)
-    self.assertEqual(ret, [])
-    instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
-                                 disk_template=constants.DT_PLAIN)
-    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
-                                                 cfg, _compute_fn=stub)
-    self.assertEqual(ret, [])
-    cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
-    stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
-                                               constants.DT_PLAIN)
-    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
-                                                 cfg_es, _compute_fn=stub_es)
-    self.assertEqual(ret, [])
-    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
-                                                 cfg_es, _compute_fn=stub_es)
-    self.assertEqual(ret, [])
-
-
-class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
-  def test(self):
-    ispec = {
-      constants.ISPEC_MEM_SIZE: 2048,
-      constants.ISPEC_CPU_COUNT: 2,
-      constants.ISPEC_DISK_COUNT: 1,
-      constants.ISPEC_DISK_SIZE: [512],
-      constants.ISPEC_NIC_COUNT: 0,
-      constants.ISPEC_SPINDLE_USE: 1,
-      }
-    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
-                                            constants.DT_PLAIN)
-    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
-                                                        constants.DT_PLAIN,
-                                                        _compute_fn=stub)
-    self.assertEqual(ret, [])
-
-
-class _CallRecorder:
-  def __init__(self, return_value=None):
-    self.called = False
-    self.return_value = return_value
-
-  def __call__(self, *args):
-    self.called = True
-    return self.return_value
-
-
-class TestComputeIPolicyNodeViolation(unittest.TestCase):
-  def setUp(self):
-    self.recorder = _CallRecorder(return_value=[])
-
-  def testSameGroup(self):
-    ret = instance_utils._ComputeIPolicyNodeViolation(
-      NotImplemented,
-      NotImplemented,
-      "foo", "foo", NotImplemented,
-      _compute_fn=self.recorder)
-    self.assertFalse(self.recorder.called)
-    self.assertEqual(ret, [])
-
-  def testDifferentGroup(self):
-    ret = instance_utils._ComputeIPolicyNodeViolation(
-      NotImplemented,
-      NotImplemented,
-      "foo", "bar", NotImplemented,
-      _compute_fn=self.recorder)
-    self.assertTrue(self.recorder.called)
-    self.assertEqual(ret, [])
-
-
-class _FakeConfigForTargetNodeIPolicy:
-  def __init__(self, node_info=NotImplemented):
-    self._node_info = node_info
-
-  def GetNodeInfo(self, _):
-    return self._node_info
-
-
-class TestCheckTargetNodeIPolicy(unittest.TestCase):
-  def setUp(self):
-    self.instance = objects.Instance(primary_node="blubb")
-    self.target_node = objects.Node(group="bar")
-    node_info = objects.Node(group="foo")
-    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
-    self.lu = _FakeLU(cfg=fake_cfg)
-
-  def testNoViolation(self):
-    compute_recoder = _CallRecorder(return_value=[])
-    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
-                                    self.target_node, NotImplemented,
-                                    _compute_fn=compute_recoder)
-    self.assertTrue(compute_recoder.called)
-    self.assertEqual(self.lu.warning_log, [])
-
-  def testNoIgnore(self):
-    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
-    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
-                      self.lu, NotImplemented, self.instance,
-                      self.target_node, NotImplemented,
-                      _compute_fn=compute_recoder)
-    self.assertTrue(compute_recoder.called)
-    self.assertEqual(self.lu.warning_log, [])
-
-  def testIgnoreViolation(self):
-    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
-    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
-                                     self.target_node, NotImplemented,
-                                     ignore=True, _compute_fn=compute_recoder)
-    self.assertTrue(compute_recoder.called)
-    msg = ("Instance does not meet target node group's (bar) instance policy:"
-           " mem_size not in range")
-    self.assertEqual(self.lu.warning_log, [(msg, ())])
-
-
-class TestApplyContainerMods(unittest.TestCase):
-  def testEmptyContainer(self):
-    container = []
-    chgdesc = []
-    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
-                                None)
-    self.assertEqual(container, [])
-    self.assertEqual(chgdesc, [])
-
-  def testAdd(self):
-    container = []
-    chgdesc = []
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_ADD, -1, "Hello"),
-      (constants.DDM_ADD, -1, "World"),
-      (constants.DDM_ADD, 0, "Start"),
-      (constants.DDM_ADD, -1, "End"),
-      ], None)
-    instance._ApplyContainerMods("test", container, chgdesc, mods,
-                                None, None, None)
-    self.assertEqual(container, ["Start", "Hello", "World", "End"])
-    self.assertEqual(chgdesc, [])
-
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_ADD, 0, "zero"),
-      (constants.DDM_ADD, 3, "Added"),
-      (constants.DDM_ADD, 5, "four"),
-      (constants.DDM_ADD, 7, "xyz"),
-      ], None)
-    instance._ApplyContainerMods("test", container, chgdesc, mods,
-                                None, None, None)
-    self.assertEqual(container,
-                     ["zero", "Start", "Hello", "Added", "World", "four",
-                      "End", "xyz"])
-    self.assertEqual(chgdesc, [])
-
-    for idx in [-2, len(container) + 1]:
-      mods = instance._PrepareContainerMods([
-        (constants.DDM_ADD, idx, "error"),
-        ], None)
-      self.assertRaises(IndexError, instance._ApplyContainerMods,
-                        "test", container, None, mods, None, None, None)
-
-  def testRemoveError(self):
-    for idx in [0, 1, 2, 100, -1, -4]:
-      mods = instance._PrepareContainerMods([
-        (constants.DDM_REMOVE, idx, None),
-        ], None)
-      self.assertRaises(IndexError, instance._ApplyContainerMods,
-                        "test", [], None, mods, None, None, None)
-
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_REMOVE, 0, object()),
-      ], None)
-    self.assertRaises(AssertionError, instance._ApplyContainerMods,
-                      "test", [""], None, mods, None, None, None)
-
-  def testAddError(self):
-    for idx in range(-100, -1) + [100]:
-      mods = instance._PrepareContainerMods([
-        (constants.DDM_ADD, idx, None),
-        ], None)
-      self.assertRaises(IndexError, instance._ApplyContainerMods,
-                        "test", [], None, mods, None, None, None)
-
-  def testRemove(self):
-    container = ["item 1", "item 2"]
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_ADD, -1, "aaa"),
-      (constants.DDM_REMOVE, -1, None),
-      (constants.DDM_ADD, -1, "bbb"),
-      ], None)
-    chgdesc = []
-    instance._ApplyContainerMods("test", container, chgdesc, mods,
-                                None, None, None)
-    self.assertEqual(container, ["item 1", "item 2", "bbb"])
-    self.assertEqual(chgdesc, [
-      ("test/2", "remove"),
-      ])
-
-  def testModify(self):
-    container = ["item 1", "item 2"]
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_MODIFY, -1, "a"),
-      (constants.DDM_MODIFY, 0, "b"),
-      (constants.DDM_MODIFY, 1, "c"),
-      ], None)
-    chgdesc = []
-    instance._ApplyContainerMods("test", container, chgdesc, mods,
-                                None, None, None)
-    self.assertEqual(container, ["item 1", "item 2"])
-    self.assertEqual(chgdesc, [])
-
-    for idx in [-2, len(container) + 1]:
-      mods = instance._PrepareContainerMods([
-        (constants.DDM_MODIFY, idx, "error"),
-        ], None)
-      self.assertRaises(IndexError, instance._ApplyContainerMods,
-                        "test", container, None, mods, None, None, None)
-
-  class _PrivateData:
-    def __init__(self):
-      self.data = None
-
-  @staticmethod
-  def _CreateTestFn(idx, params, private):
-    private.data = ("add", idx, params)
-    return ((100 * idx, params), [
-      ("test/%s" % idx, hex(idx)),
-      ])
-
-  @staticmethod
-  def _ModifyTestFn(idx, item, params, private):
-    private.data = ("modify", idx, params)
-    return [
-      ("test/%s" % idx, "modify %s" % params),
-      ]
-
-  @staticmethod
-  def _RemoveTestFn(idx, item, private):
-    private.data = ("remove", idx, item)
-
-  def testAddWithCreateFunction(self):
-    container = []
-    chgdesc = []
-    mods = instance._PrepareContainerMods([
-      (constants.DDM_ADD, -1, "Hello"),
-      (constants.DDM_ADD, -1, "World"),
-      (constants.DDM_ADD, 0, "Start"),
-      (constants.DDM_ADD, -1, "End"),
-      (constants.DDM_REMOVE, 2, None),
-      (constants.DDM_MODIFY, -1, "foobar"),
-      (constants.DDM_REMOVE, 2, None),
-      (constants.DDM_ADD, 1, "More"),
-      ], self._PrivateData)
-    instance._ApplyContainerMods("test", container, chgdesc, mods,
-                                self._CreateTestFn, self._ModifyTestFn,
-                                self._RemoveTestFn)
-    self.assertEqual(container, [
-      (000, "Start"),
-      (100, "More"),
-      (000, "Hello"),
-      ])
-    self.assertEqual(chgdesc, [
-      ("test/0", "0x0"),
-      ("test/1", "0x1"),
-      ("test/0", "0x0"),
-      ("test/3", "0x3"),
-      ("test/2", "remove"),
-      ("test/2", "modify foobar"),
-      ("test/2", "remove"),
-      ("test/1", "0x1")
-      ])
-    self.assertTrue(compat.all(op == private.data[0]
-                               for (op, _, _, private) in mods))
-    self.assertEqual([private.data for (op, _, _, private) in mods], [
-      ("add", 0, "Hello"),
-      ("add", 1, "World"),
-      ("add", 0, "Start"),
-      ("add", 3, "End"),
-      ("remove", 2, (100, "World")),
-      ("modify", 2, "foobar"),
-      ("remove", 2, (300, "End")),
-      ("add", 1, "More"),
-      ])
-
-
-class _FakeConfigForGenDiskTemplate:
-  def __init__(self, enabled_disk_templates):
-    self._unique_id = itertools.count()
-    self._drbd_minor = itertools.count(20)
-    self._port = itertools.count(constants.FIRST_DRBD_PORT)
-    self._secret = itertools.count()
-    self._enabled_disk_templates = enabled_disk_templates
-
-  def GetVGName(self):
-    return "testvg"
-
-  def GenerateUniqueID(self, ec_id):
-    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
-
-  def AllocateDRBDMinor(self, nodes, instance):
-    return [self._drbd_minor.next()
-            for _ in nodes]
-
-  def AllocatePort(self):
-    return self._port.next()
-
-  def GenerateDRBDSecret(self, ec_id):
-    return "ec%s-secret%s" % (ec_id, self._secret.next())
-
-  def GetInstanceInfo(self, _):
-    return "foobar"
-
-  def GetClusterInfo(self):
-    cluster = objects.Cluster()
-    cluster.enabled_disk_templates = self._enabled_disk_templates
-    return cluster
-
-
-class _FakeProcForGenDiskTemplate:
-  def GetECId(self):
-    return 0
-
-
-class TestGenerateDiskTemplate(unittest.TestCase):
-
-  def _SetUpLUWithTemplates(self, enabled_disk_templates):
-    self._enabled_disk_templates = enabled_disk_templates
-    cfg = _FakeConfigForGenDiskTemplate(self._enabled_disk_templates)
-    proc = _FakeProcForGenDiskTemplate()
-
-    self.lu = _FakeLU(cfg=cfg, proc=proc)
-
-  def setUp(self):
-    nodegroup = objects.NodeGroup(name="ng")
-    nodegroup.UpgradeConfig()
-
-    self._enabled_disk_templates = list(constants.DISK_TEMPLATES)
-    self._SetUpLUWithTemplates(self._enabled_disk_templates)
-    self.nodegroup = nodegroup
-
-  @staticmethod
-  def GetDiskParams():
-    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
-
-  def testWrongDiskTemplate(self):
-    gdt = instance.GenerateDiskTemplate
-    disk_template = "##unknown##"
-
-    assert disk_template not in constants.DISK_TEMPLATES
-
-    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
-                      "inst26831.example.com", "node30113.example.com", [], [],
-                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                      self.GetDiskParams())
-
-  def testDiskless(self):
-    gdt = instance.GenerateDiskTemplate
-
-    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
-                 "node30113.example.com", [], [],
-                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                 self.GetDiskParams())
-    self.assertEqual(result, [])
-
-  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
-                       file_storage_dir=NotImplemented,
-                       file_driver=NotImplemented):
-    gdt = instance.GenerateDiskTemplate
-
-    map(lambda params: utils.ForceDictType(params,
-                                           constants.IDISK_PARAMS_TYPES),
-        disk_info)
-
-    # Check if non-empty list of secondaries is rejected
-    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
-                      template, "inst25088.example.com",
-                      "node185.example.com", ["node323.example.com"], [],
-                      NotImplemented, NotImplemented, base_index,
-                      self.lu.LogInfo, self.GetDiskParams())
-
-    result = gdt(self.lu, template, "inst21662.example.com",
-                 "node21741.example.com", [],
-                 disk_info, file_storage_dir, file_driver, base_index,
-                 self.lu.LogInfo, self.GetDiskParams())
-
-    for (idx, disk) in enumerate(result):
-      self.assertTrue(isinstance(disk, objects.Disk))
-      self.assertEqual(disk.dev_type, exp_dev_type)
-      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
-      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
-      self.assertTrue(disk.children is None)
-
-    self._CheckIvNames(result, base_index, base_index + len(disk_info))
-    instance._UpdateIvNames(base_index, result)
-    self._CheckIvNames(result, base_index, base_index + len(disk_info))
-
-    return result
-
-  def _CheckIvNames(self, disks, base_index, end_index):
-    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
-                     ["disk/%s" % i for i in range(base_index, end_index)])
-
-  def testPlain(self):
-    disk_info = [{
-      constants.IDISK_SIZE: 1024,
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      }, {
-      constants.IDISK_SIZE: 4096,
-      constants.IDISK_VG: "othervg",
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      }]
-
-    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
-                                   constants.DT_PLAIN)
-
-    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
-      ("testvg", "ec0-uq0.disk3"),
-      ("othervg", "ec0-uq1.disk4"),
-      ])
-
-  def testFile(self):
-    # anything != DT_FILE would do here
-    self._SetUpLUWithTemplates([constants.DT_PLAIN])
-    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
-                      constants.DT_FILE, [], 0, NotImplemented)
-    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
-                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
-
-    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
-      disk_info = [{
-        constants.IDISK_SIZE: 80 * 1024,
-        constants.IDISK_MODE: constants.DISK_RDONLY,
-        }, {
-        constants.IDISK_SIZE: 4096,
-        constants.IDISK_MODE: constants.DISK_RDWR,
-        }, {
-        constants.IDISK_SIZE: 6 * 1024,
-        constants.IDISK_MODE: constants.DISK_RDWR,
-        }]
-
-      self._SetUpLUWithTemplates([disk_template])
-      result = self._TestTrivialDisk(disk_template, disk_info, 2,
-        disk_template, file_storage_dir="/tmp",
-        file_driver=constants.FD_BLKTAP)
-
-      for (idx, disk) in enumerate(result):
-        (file_driver, file_storage_dir) = disk.logical_id
-        dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
-        self.assertEqual(file_driver, constants.FD_BLKTAP)
-        # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
-        self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
-
-  def testBlock(self):
-    disk_info = [{
-      constants.IDISK_SIZE: 8 * 1024,
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      constants.IDISK_ADOPT: "/tmp/some/block/dev",
-      }]
-
-    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
-                                   constants.DT_BLOCK)
-
-    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
-      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
-      ])
-
-  def testRbd(self):
-    disk_info = [{
-      constants.IDISK_SIZE: 8 * 1024,
-      constants.IDISK_MODE: constants.DISK_RDONLY,
-      }, {
-      constants.IDISK_SIZE: 100 * 1024,
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      }]
-
-    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
-                                   constants.DT_RBD)
-
-    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
-      ("rbd", "ec0-uq0.rbd.disk0"),
-      ("rbd", "ec0-uq1.rbd.disk1"),
-      ])
-
-  def testDrbd8(self):
-    gdt = instance.GenerateDiskTemplate
-    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
-    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
-
-    disk_info = [{
-      constants.IDISK_SIZE: 1024,
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      }, {
-      constants.IDISK_SIZE: 100 * 1024,
-      constants.IDISK_MODE: constants.DISK_RDONLY,
-      constants.IDISK_METAVG: "metavg",
-      }, {
-      constants.IDISK_SIZE: 4096,
-      constants.IDISK_MODE: constants.DISK_RDWR,
-      constants.IDISK_VG: "vgxyz",
-      },
-      ]
-
-    exp_logical_ids = [[
-      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
-      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
-      ], [
-      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
-      ("metavg", "ec0-uq1.disk1_meta"),
-      ], [
-      ("vgxyz", "ec0-uq2.disk2_data"),
-      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
-      ]]
-
-    assert len(exp_logical_ids) == len(disk_info)
-
-    map(lambda params: utils.ForceDictType(params,
-                                           constants.IDISK_PARAMS_TYPES),
-        disk_info)
-
-    # Check if empty list of secondaries is rejected
-    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
-                      "inst827.example.com", "node1334.example.com", [],
-                      disk_info, NotImplemented, NotImplemented, 0,
-                      self.lu.LogInfo, self.GetDiskParams())
-
-    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
-                 "node1334.example.com", ["node12272.example.com"],
-                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
-                 self.GetDiskParams())
-
-    for (idx, disk) in enumerate(result):
-      self.assertTrue(isinstance(disk, objects.Disk))
-      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
-      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
-      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
-
-      for child in disk.children:
-        self.assertTrue(isinstance(disk, objects.Disk))
-        self.assertEqual(child.dev_type, constants.DT_PLAIN)
-        self.assertTrue(child.children is None)
-
-      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
-                       exp_logical_ids[idx])
-
-      self.assertEqual(len(disk.children), 2)
-      self.assertEqual(disk.children[0].size, disk.size)
-      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
-
-    self._CheckIvNames(result, 0, len(disk_info))
-    instance._UpdateIvNames(0, result)
-    self._CheckIvNames(result, 0, len(disk_info))
-
-    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
-      ("node1334.example.com", "node12272.example.com",
-       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
-      ("node1334.example.com", "node12272.example.com",
-       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
-      ("node1334.example.com", "node12272.example.com",
-       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
-      ])
-
-
-class _ConfigForDiskWipe:
-  def __init__(self, exp_node_uuid):
-    self._exp_node_uuid = exp_node_uuid
-
-  def SetDiskID(self, device, node_uuid):
-    assert isinstance(device, objects.Disk)
-    assert node_uuid == self._exp_node_uuid
-
-  def GetNodeName(self, node_uuid):
-    assert node_uuid == self._exp_node_uuid
-    return "name.of.expected.node"
-
-
-class _RpcForDiskWipe:
-  def __init__(self, exp_node, pause_cb, wipe_cb):
-    self._exp_node = exp_node
-    self._pause_cb = pause_cb
-    self._wipe_cb = wipe_cb
-
-  def call_blockdev_pause_resume_sync(self, node, disks, pause):
-    assert node == self._exp_node
-    return rpc.RpcResult(data=self._pause_cb(disks, pause))
-
-  def call_blockdev_wipe(self, node, bdev, offset, size):
-    assert node == self._exp_node
-    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
-
-
-class _DiskPauseTracker:
-  def __init__(self):
-    self.history = []
-
-  def __call__(self, (disks, instance), pause):
-    assert not (set(disks) - set(instance.disks))
-
-    self.history.extend((i.logical_id, i.size, pause)
-                        for i in disks)
-
-    return (True, [True] * len(disks))
-
-
-class _DiskWipeProgressTracker:
-  def __init__(self, start_offset):
-    self._start_offset = start_offset
-    self.progress = {}
-
-  def __call__(self, (disk, _), offset, size):
-    assert isinstance(offset, (long, int))
-    assert isinstance(size, (long, int))
-
-    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
-
-    assert offset >= self._start_offset
-    assert (offset + size) <= disk.size
-
-    assert size > 0
-    assert size <= constants.MAX_WIPE_CHUNK
-    assert size <= max_chunk_size
-
-    assert offset == self._start_offset or disk.logical_id in self.progress
-
-    # Keep track of progress
-    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
-
-    assert cur_progress == offset
-
-    # Record progress
-    self.progress[disk.logical_id] += size
-
-    return (True, None)
-
-
-class TestWipeDisks(unittest.TestCase):
-  def _FailingPauseCb(self, (disks, _), pause):
-    self.assertEqual(len(disks), 3)
-    self.assertTrue(pause)
-    # Simulate an RPC error
-    return (False, "error")
-
-  def testPauseFailure(self):
-    node_name = "node1372.example.com"
-
-    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
-                                     NotImplemented),
-                 cfg=_ConfigForDiskWipe(node_name))
-
-    disks = [
-      objects.Disk(dev_type=constants.DT_PLAIN),
-      objects.Disk(dev_type=constants.DT_PLAIN),
-      objects.Disk(dev_type=constants.DT_PLAIN),
-      ]
-
-    inst = objects.Instance(name="inst21201",
-                            primary_node=node_name,
-                            disk_template=constants.DT_PLAIN,
-                            disks=disks)
-
-    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
-
-  def _FailingWipeCb(self, (disk, _), offset, size):
-    # This should only ever be called for the first disk
-    self.assertEqual(disk.logical_id, "disk0")
-    return (False, None)
-
-  def testFailingWipe(self):
-    node_uuid = "node13445-uuid"
-    pt = _DiskPauseTracker()
-
-    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
-                 cfg=_ConfigForDiskWipe(node_uuid))
-
-    disks = [
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
-                   size=100 * 1024),
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
-                   size=500 * 1024),
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
-      ]
-
-    inst = objects.Instance(name="inst562",
-                            primary_node=node_uuid,
-                            disk_template=constants.DT_PLAIN,
-                            disks=disks)
-
-    try:
-      instance.WipeDisks(lu, inst)
-    except errors.OpExecError, err:
-      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
-    else:
-      self.fail("Did not raise exception")
-
-    # Check if all disks were paused and resumed
-    self.assertEqual(pt.history, [
-      ("disk0", 100 * 1024, True),
-      ("disk1", 500 * 1024, True),
-      ("disk2", 256, True),
-      ("disk0", 100 * 1024, False),
-      ("disk1", 500 * 1024, False),
-      ("disk2", 256, False),
-      ])
-
-  def _PrepareWipeTest(self, start_offset, disks):
-    node_name = "node-with-offset%s.example.com" % start_offset
-    pauset = _DiskPauseTracker()
-    progresst = _DiskWipeProgressTracker(start_offset)
-
-    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
-                 cfg=_ConfigForDiskWipe(node_name))
-
-    instance = objects.Instance(name="inst3560",
-                                primary_node=node_name,
-                                disk_template=constants.DT_PLAIN,
-                                disks=disks)
-
-    return (lu, instance, pauset, progresst)
-
-  def testNormalWipe(self):
-    disks = [
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
-                   size=500 * 1024),
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
-      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
-                   size=constants.MAX_WIPE_CHUNK),
-      ]
-
-    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
-
-    instance.WipeDisks(lu, inst)
-
-    self.assertEqual(pauset.history, [
-      ("disk0", 1024, True),
-      ("disk1", 500 * 1024, True),
-      ("disk2", 128, True),
-      ("disk3", constants.MAX_WIPE_CHUNK, True),
-      ("disk0", 1024, False),
-      ("disk1", 500 * 1024, False),
-      ("disk2", 128, False),
-      ("disk3", constants.MAX_WIPE_CHUNK, False),
-      ])
-
-    # Ensure the complete disk has been wiped
-    self.assertEqual(progresst.progress,
-                     dict((i.logical_id, i.size) for i in disks))
-
-  def testWipeWithStartOffset(self):
-    for start_offset in [0, 280, 8895, 1563204]:
-      disks = [
-        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
-                     size=128),
-        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
-                     size=start_offset + (100 * 1024)),
-        ]
-
-      (lu, inst, pauset, progresst) = \
-        self._PrepareWipeTest(start_offset, disks)
-
-      # Test start offset with only one disk
-      instance.WipeDisks(lu, inst,
-                         disks=[(1, disks[1], start_offset)])
-
-      # Only the second disk may have been paused and wiped
-      self.assertEqual(pauset.history, [
-        ("disk1", start_offset + (100 * 1024), True),
-        ("disk1", start_offset + (100 * 1024), False),
-        ])
-      self.assertEqual(progresst.progress, {
-        "disk1": disks[1].size,
-        })
-
-
-class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
-  def testLessThanOneMebibyte(self):
-    for i in [1, 2, 7, 512, 1000, 1023]:
-      lu = _FakeLU()
-      result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
-      self.assertEqual(result, 1)
-      self.assertEqual(len(lu.warning_log), 1)
-      self.assertEqual(len(lu.warning_log[0]), 2)
-      (_, (warnsize, )) = lu.warning_log[0]
-      self.assertEqual(warnsize, (1024 * 1024) - i)
-
-  def testEven(self):
-    for i in [1, 2, 7, 512, 1000, 1023]:
-      lu = _FakeLU()
-      result = instance_storage._DiskSizeInBytesToMebibytes(lu,
-                                                            i * 1024 * 1024)
-      self.assertEqual(result, i)
-      self.assertFalse(lu.warning_log)
-
-  def testLargeNumber(self):
-    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
-      for j in [1, 2, 486, 326, 986, 1023]:
-        lu = _FakeLU()
-        size = (1024 * 1024 * i) + j
-        result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
-        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
-        self.assertEqual(len(lu.warning_log), 1)
-        self.assertEqual(len(lu.warning_log[0]), 2)
-        (_, (warnsize, )) = lu.warning_log[0]
-        self.assertEqual(warnsize, (1024 * 1024) - j)
-
-
-class TestCopyLockList(unittest.TestCase):
-  def test(self):
-    self.assertEqual(instance.CopyLockList([]), [])
-    self.assertEqual(instance.CopyLockList(None), None)
-    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
-
-    names = ["foo", "bar"]
-    output = instance.CopyLockList(names)
-    self.assertEqual(names, output)
-    self.assertNotEqual(id(names), id(output), msg="List was not copied")
-
-
-class TestCheckOpportunisticLocking(unittest.TestCase):
-  class OpTest(opcodes.OpCode):
-    OP_PARAMS = [
-      opcodes._POpportunisticLocking,
-      opcodes._PIAllocFromDesc(""),
-      ]
-
-  @classmethod
-  def _MakeOp(cls, **kwargs):
-    op = cls.OpTest(**kwargs)
-    op.Validate(True)
-    return op
-
-  def testMissingAttributes(self):
-    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
-                      object())
-
-  def testDefaults(self):
-    op = self._MakeOp()
-    instance._CheckOpportunisticLocking(op)
-
-  def test(self):
-    for iallocator in [None, "something", "other"]:
-      for opplock in [False, True]:
-        op = self._MakeOp(iallocator=iallocator,
-                          opportunistic_locking=opplock)
-        if opplock and not iallocator:
-          self.assertRaises(errors.OpPrereqError,
-                            instance._CheckOpportunisticLocking, op)
-        else:
-          instance._CheckOpportunisticLocking(op)
-
-
-class _OpTestVerifyErrors(opcodes.OpCode):
-  OP_PARAMS = [
-    opcodes._PDebugSimulateErrors,
-    opcodes._PErrorCodes,
-    opcodes._PIgnoreErrors,
-    ]
-
-
-class _LuTestVerifyErrors(cluster._VerifyErrors):
-  def __init__(self, **kwargs):
-    cluster._VerifyErrors.__init__(self)
-    self.op = _OpTestVerifyErrors(**kwargs)
-    self.op.Validate(True)
-    self.msglist = []
-    self._feedback_fn = self.msglist.append
-    self.bad = False
-
-  def DispatchCallError(self, which, *args, **kwargs):
-    if which:
-      self._Error(*args, **kwargs)
-    else:
-      self._ErrorIf(True, *args, **kwargs)
-
-  def CallErrorIf(self, c, *args, **kwargs):
-    self._ErrorIf(c, *args, **kwargs)
-
-
-class TestVerifyErrors(unittest.TestCase):
-  # Fake cluster-verify error code structures; we use two arbitary real error
-  # codes to pass validation of ignore_errors
-  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
-  _NODESTR = "node"
-  _NODENAME = "mynode"
-  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
-  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
-  _INSTSTR = "instance"
-  _INSTNAME = "myinstance"
-  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
-  # Arguments used to call _Error() or _ErrorIf()
-  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
-  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
-  # Expected error messages
-  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
-  _ERR2MSG = _ERR2ARGS[2]
-
-  def testNoError(self):
-    lu = _LuTestVerifyErrors()
-    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
-    self.assertFalse(lu.bad)
-    self.assertFalse(lu.msglist)
-
-  def _InitTest(self, **kwargs):
-    self.lu1 = _LuTestVerifyErrors(**kwargs)
-    self.lu2 = _LuTestVerifyErrors(**kwargs)
-
-  def _CallError(self, *args, **kwargs):
-    # Check that _Error() and _ErrorIf() produce the same results
-    self.lu1.DispatchCallError(True, *args, **kwargs)
-    self.lu2.DispatchCallError(False, *args, **kwargs)
-    self.assertEqual(self.lu1.bad, self.lu2.bad)
-    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
-    # Test-specific checks are made on one LU
-    return self.lu1
-
-  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
-    self.assertTrue(errmsg in logstr)
-    if warning:
-      self.assertTrue("WARNING" in logstr)
-    else:
-      self.assertTrue("ERROR" in logstr)
-    self.assertTrue(itype in logstr)
-    self.assertTrue(item in logstr)
-
-  def _checkMsg1(self, logstr, warning=False):
-    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
-                         self._NODENAME, warning)
-
-  def _checkMsg2(self, logstr, warning=False):
-    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
-                         self._INSTNAME, warning)
-
-  def testPlain(self):
-    self._InitTest()
-    lu = self._CallError(*self._ERR1ARGS)
-    self.assertTrue(lu.bad)
-    self.assertEqual(len(lu.msglist), 1)
-    self._checkMsg1(lu.msglist[0])
-
-  def testMultiple(self):
-    self._InitTest()
-    self._CallError(*self._ERR1ARGS)
-    lu = self._CallError(*self._ERR2ARGS)
-    self.assertTrue(lu.bad)
-    self.assertEqual(len(lu.msglist), 2)
-    self._checkMsg1(lu.msglist[0])
-    self._checkMsg2(lu.msglist[1])
-
-  def testIgnore(self):
-    self._InitTest(ignore_errors=[self._ERR1ID])
-    lu = self._CallError(*self._ERR1ARGS)
-    self.assertFalse(lu.bad)
-    self.assertEqual(len(lu.msglist), 1)
-    self._checkMsg1(lu.msglist[0], warning=True)
-
-  def testWarning(self):
-    self._InitTest()
-    lu = self._CallError(*self._ERR1ARGS,
-                         code=_LuTestVerifyErrors.ETYPE_WARNING)
-    self.assertFalse(lu.bad)
-    self.assertEqual(len(lu.msglist), 1)
-    self._checkMsg1(lu.msglist[0], warning=True)
-
-  def testWarning2(self):
-    self._InitTest()
-    self._CallError(*self._ERR1ARGS)
-    lu = self._CallError(*self._ERR2ARGS,
-                         code=_LuTestVerifyErrors.ETYPE_WARNING)
-    self.assertTrue(lu.bad)
-    self.assertEqual(len(lu.msglist), 2)
-    self._checkMsg1(lu.msglist[0])
-    self._checkMsg2(lu.msglist[1], warning=True)
-
-  def testDebugSimulate(self):
-    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
-    lu.CallErrorIf(False, *self._ERR1ARGS)
-    self.assertTrue(lu.bad)
-    self.assertEqual(len(lu.msglist), 1)
-    self._checkMsg1(lu.msglist[0])
-
-  def testErrCodes(self):
-    self._InitTest(error_codes=True)
-    lu = self._CallError(*self._ERR1ARGS)
-    self.assertTrue(lu.bad)
-    self.assertEqual(len(lu.msglist), 1)
-    self._checkMsg1(lu.msglist[0])
-    self.assertTrue(self._ERR1ID in lu.msglist[0])
-
-
-class TestGetUpdatedIPolicy(unittest.TestCase):
-  """Tests for cmdlib._GetUpdatedIPolicy()"""
-  _OLD_CLUSTER_POLICY = {
-    constants.IPOLICY_VCPU_RATIO: 1.5,
-    constants.ISPECS_MINMAX: [
-      {
-        constants.ISPECS_MIN: {
-          constants.ISPEC_MEM_SIZE: 32768,
-          constants.ISPEC_CPU_COUNT: 8,
-          constants.ISPEC_DISK_COUNT: 1,
-          constants.ISPEC_DISK_SIZE: 1024,
-          constants.ISPEC_NIC_COUNT: 1,
-          constants.ISPEC_SPINDLE_USE: 1,
-          },
-        constants.ISPECS_MAX: {
-          constants.ISPEC_MEM_SIZE: 65536,
-          constants.ISPEC_CPU_COUNT: 10,
-          constants.ISPEC_DISK_COUNT: 5,
-          constants.ISPEC_DISK_SIZE: 1024 * 1024,
-          constants.ISPEC_NIC_COUNT: 3,
-          constants.ISPEC_SPINDLE_USE: 12,
-          },
-        },
-      constants.ISPECS_MINMAX_DEFAULTS,
-      ],
-    constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
-    }
-  _OLD_GROUP_POLICY = {
-    constants.IPOLICY_SPINDLE_RATIO: 2.5,
-    constants.ISPECS_MINMAX: [{
-      constants.ISPECS_MIN: {
-        constants.ISPEC_MEM_SIZE: 128,
-        constants.ISPEC_CPU_COUNT: 1,
-        constants.ISPEC_DISK_COUNT: 1,
-        constants.ISPEC_DISK_SIZE: 1024,
-        constants.ISPEC_NIC_COUNT: 1,
-        constants.ISPEC_SPINDLE_USE: 1,
-        },
-      constants.ISPECS_MAX: {
-        constants.ISPEC_MEM_SIZE: 32768,
-        constants.ISPEC_CPU_COUNT: 8,
-        constants.ISPEC_DISK_COUNT: 5,
-        constants.ISPEC_DISK_SIZE: 1024 * 1024,
-        constants.ISPEC_NIC_COUNT: 3,
-        constants.ISPEC_SPINDLE_USE: 12,
-        },
-      }],
-    }
-
-  def _TestSetSpecs(self, old_policy, isgroup):
-    diff_minmax = [{
-      constants.ISPECS_MIN: {
-        constants.ISPEC_MEM_SIZE: 64,
-        constants.ISPEC_CPU_COUNT: 1,
-        constants.ISPEC_DISK_COUNT: 2,
-        constants.ISPEC_DISK_SIZE: 64,
-        constants.ISPEC_NIC_COUNT: 1,
-        constants.ISPEC_SPINDLE_USE: 1,
-        },
-      constants.ISPECS_MAX: {
-        constants.ISPEC_MEM_SIZE: 16384,
-        constants.ISPEC_CPU_COUNT: 10,
-        constants.ISPEC_DISK_COUNT: 12,
-        constants.ISPEC_DISK_SIZE: 1024,
-        constants.ISPEC_NIC_COUNT: 9,
-        constants.ISPEC_SPINDLE_USE: 18,
-        },
-      }]
-    diff_std = {
-        constants.ISPEC_DISK_COUNT: 10,
-        constants.ISPEC_DISK_SIZE: 512,
-        }
-    diff_policy = {
-      constants.ISPECS_MINMAX: diff_minmax
-      }
-    if not isgroup:
-      diff_policy[constants.ISPECS_STD] = diff_std
-    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
-                                          group_policy=isgroup)
-
-    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
-    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
-    for key in old_policy:
-      if not key in diff_policy:
-        self.assertTrue(key in new_policy)
-        self.assertEqual(new_policy[key], old_policy[key])
-
-    if not isgroup:
-      new_std = new_policy[constants.ISPECS_STD]
-      for key in diff_std:
-        self.assertTrue(key in new_std)
-        self.assertEqual(new_std[key], diff_std[key])
-      old_std = old_policy.get(constants.ISPECS_STD, {})
-      for key in old_std:
-        self.assertTrue(key in new_std)
-        if key not in diff_std:
-          self.assertEqual(new_std[key], old_std[key])
-
-  def _TestSet(self, old_policy, diff_policy, isgroup):
-    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
-                                           group_policy=isgroup)
-    for key in diff_policy:
-      self.assertTrue(key in new_policy)
-      self.assertEqual(new_policy[key], diff_policy[key])
-    for key in old_policy:
-      if not key in diff_policy:
-        self.assertTrue(key in new_policy)
-        self.assertEqual(new_policy[key], old_policy[key])
-
-  def testSet(self):
-    diff_policy = {
-      constants.IPOLICY_VCPU_RATIO: 3,
-      constants.IPOLICY_DTS: [constants.DT_FILE],
-      }
-    self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
-    self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
-    self._TestSet({}, diff_policy, True)
-    self._TestSetSpecs({}, True)
-    self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
-    self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
-
-  def testUnset(self):
-    old_policy = self._OLD_GROUP_POLICY
-    diff_policy = {
-      constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
-      }
-    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
-                                          group_policy=True)
-    for key in diff_policy:
-      self.assertFalse(key in new_policy)
-    for key in old_policy:
-      if not key in diff_policy:
-        self.assertTrue(key in new_policy)
-        self.assertEqual(new_policy[key], old_policy[key])
-
-    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
-                      old_policy, diff_policy, group_policy=False)
-
-  def testUnsetEmpty(self):
-    old_policy = {}
-    for key in constants.IPOLICY_ALL_KEYS:
-      diff_policy = {
-        key: constants.VALUE_DEFAULT,
-        }
-    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
-                                          group_policy=True)
-    self.assertEqual(new_policy, old_policy)
-
-  def _TestInvalidKeys(self, old_policy, isgroup):
-    INVALID_KEY = "this_key_shouldnt_be_allowed"
-    INVALID_DICT = {
-      INVALID_KEY: 3,
-      }
-    invalid_policy = INVALID_DICT
-    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
-                      old_policy, invalid_policy, group_policy=isgroup)
-    invalid_ispecs = {
-      constants.ISPECS_MINMAX: [INVALID_DICT],
-      }
-    self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
-                      old_policy, invalid_ispecs, group_policy=isgroup)
-    if isgroup:
-      invalid_for_group = {
-        constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
-        }
-      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
-                        old_policy, invalid_for_group, group_policy=isgroup)
-    good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
-    invalid_ispecs = copy.deepcopy(good_ispecs)
-    invalid_policy = {
-      constants.ISPECS_MINMAX: invalid_ispecs,
-      }
-    for minmax in invalid_ispecs:
-      for key in constants.ISPECS_MINMAX_KEYS:
-        ispec = minmax[key]
-        ispec[INVALID_KEY] = None
-        self.assertRaises(errors.TypeEnforcementError,
-                          common.GetUpdatedIPolicy, old_policy,
-                          invalid_policy, group_policy=isgroup)
-        del ispec[INVALID_KEY]
-        for par in constants.ISPECS_PARAMETERS:
-          oldv = ispec[par]
-          ispec[par] = "this_is_not_good"
-          self.assertRaises(errors.TypeEnforcementError,
-                            common.GetUpdatedIPolicy,
-                            old_policy, invalid_policy, group_policy=isgroup)
-          ispec[par] = oldv
-    # This is to make sure that no two errors were present during the tests
-    common.GetUpdatedIPolicy(old_policy, invalid_policy,
-                             group_policy=isgroup)
-
-  def testInvalidKeys(self):
-    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
-    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
-
-  def testInvalidValues(self):
-    for par in (constants.IPOLICY_PARAMETERS |
-                frozenset([constants.IPOLICY_DTS])):
-      bad_policy = {
-        par: "invalid_value",
-        }
-      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
-                        bad_policy, group_policy=True)
-
-if __name__ == "__main__":
-  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.compat_unittest.py b/test/py/ganeti.compat_unittest.py
index 8ed54fe..144141d 100755
--- a/test/py/ganeti.compat_unittest.py
+++ b/test/py/ganeti.compat_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the compat module"""
diff --git a/test/py/ganeti.confd.client_unittest.py b/test/py/ganeti.confd.client_unittest.py
index 215e75a..871e849 100755
--- a/test/py/ganeti.confd.client_unittest.py
+++ b/test/py/ganeti.confd.client_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the confd client module"""
diff --git a/test/py/ganeti.config_unittest.py b/test/py/ganeti.config_unittest.py
index 381db00..391d7c2 100755
--- a/test/py/ganeti.config_unittest.py
+++ b/test/py/ganeti.config_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the config module"""
@@ -242,6 +251,9 @@
         constants.ND_OOB_PROGRAM: "/bin/node-oob",
         constants.ND_SPINDLE_COUNT: 1,
         constants.ND_EXCLUSIVE_STORAGE: False,
+        constants.ND_OVS: True,
+        constants.ND_OVS_NAME: "openvswitch",
+        constants.ND_OVS_LINK: "eth1"
         }
 
     cfg = self._get_object()
@@ -253,15 +265,21 @@
   def testGetNdParamsInheritance(self):
     node_ndparams = {
       constants.ND_OOB_PROGRAM: "/bin/node-oob",
+      constants.ND_OVS_LINK: "eth3"
       }
     group_ndparams = {
       constants.ND_SPINDLE_COUNT: 10,
+      constants.ND_OVS: True,
+      constants.ND_OVS_NAME: "openvswitch",
       }
     expected_ndparams = {
       constants.ND_OOB_PROGRAM: "/bin/node-oob",
       constants.ND_SPINDLE_COUNT: 10,
       constants.ND_EXCLUSIVE_STORAGE:
         constants.NDC_DEFAULTS[constants.ND_EXCLUSIVE_STORAGE],
+      constants.ND_OVS: True,
+      constants.ND_OVS_NAME: "openvswitch",
+      constants.ND_OVS_LINK: "eth3"
       }
     cfg = self._get_object()
     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
diff --git a/test/py/ganeti.constants_unittest.py b/test/py/ganeti.constants_unittest.py
index cb1cdf1..55772e5 100755
--- a/test/py/ganeti.constants_unittest.py
+++ b/test/py/ganeti.constants_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the constants module"""
@@ -30,6 +39,8 @@
 from ganeti import locking
 from ganeti import utils
 
+from ganeti.utils import version
+
 import testutils
 
 
@@ -46,16 +57,16 @@
     self.failUnless(constants.CONFIG_VERSION >= 0 and
                     constants.CONFIG_VERSION <= 99999999)
 
-    self.failUnless(constants.BuildVersion(0, 0, 0) == 0)
-    self.failUnless(constants.BuildVersion(10, 10, 1010) == 10101010)
-    self.failUnless(constants.BuildVersion(12, 34, 5678) == 12345678)
-    self.failUnless(constants.BuildVersion(99, 99, 9999) == 99999999)
+    self.failUnless(version.BuildVersion(0, 0, 0) == 0)
+    self.failUnless(version.BuildVersion(10, 10, 1010) == 10101010)
+    self.failUnless(version.BuildVersion(12, 34, 5678) == 12345678)
+    self.failUnless(version.BuildVersion(99, 99, 9999) == 99999999)
 
-    self.failUnless(constants.SplitVersion(00000000) == (0, 0, 0))
-    self.failUnless(constants.SplitVersion(10101010) == (10, 10, 1010))
-    self.failUnless(constants.SplitVersion(12345678) == (12, 34, 5678))
-    self.failUnless(constants.SplitVersion(99999999) == (99, 99, 9999))
-    self.failUnless(constants.SplitVersion(constants.CONFIG_VERSION) ==
+    self.failUnless(version.SplitVersion(00000000) == (0, 0, 0))
+    self.failUnless(version.SplitVersion(10101010) == (10, 10, 1010))
+    self.failUnless(version.SplitVersion(12345678) == (12, 34, 5678))
+    self.failUnless(version.SplitVersion(99999999) == (99, 99, 9999))
+    self.failUnless(version.SplitVersion(constants.CONFIG_VERSION) ==
                     (constants.CONFIG_MAJOR, constants.CONFIG_MINOR,
                      constants.CONFIG_REVISION))
 
@@ -99,12 +110,6 @@
     self.assertTrue(constants.DEFAULT_ENABLED_HYPERVISOR in
                     constants.HYPER_TYPES)
 
-  def testExtraLogfiles(self):
-    for daemon in constants.DAEMONS_EXTRA_LOGBASE:
-      self.assertTrue(daemon in constants.DAEMONS)
-      for log_reason in constants.DAEMONS_EXTRA_LOGBASE[daemon]:
-        self.assertTrue(log_reason in constants.VALID_EXTRA_LOGREASONS)
-
 
 class TestExportedNames(unittest.TestCase):
   _VALID_NAME_RE = re.compile(r"^[A-Z][A-Z0-9_]+$")
@@ -168,6 +173,12 @@
       self.assertTrue(
           constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template] is not None)
 
+  def testLvmDiskTemplates(self):
+    lvm_by_storage_type = [
+        dt for dt in constants.DISK_TEMPLATES
+        if constants.ST_LVM_VG == constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[dt]]
+    self.assertEqual(set(lvm_by_storage_type), set(constants.DTS_LVM))
+
 
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.daemon_unittest.py b/test/py/ganeti.daemon_unittest.py
index e337065..ed0f992 100755
--- a/test/py/ganeti.daemon_unittest.py
+++ b/test/py/ganeti.daemon_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the daemon module"""
diff --git a/test/py/ganeti.errors_unittest.py b/test/py/ganeti.errors_unittest.py
index f10f661..a619760 100755
--- a/test/py/ganeti.errors_unittest.py
+++ b/test/py/ganeti.errors_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.backend"""
diff --git a/test/py/ganeti.hooks_unittest.py b/test/py/ganeti.hooks_unittest.py
index d7cf32f..b6884fd 100755
--- a/test/py/ganeti.hooks_unittest.py
+++ b/test/py/ganeti.hooks_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the hooks module"""
@@ -495,7 +504,7 @@
     assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
 
 
-class FakeEnvWithNodeNameLU(cmdlib.LogicalUnit):
+class FakeEnvWithCustomPostHookNodesLU(cmdlib.LogicalUnit):
   HPATH = "env_test_lu"
   HTYPE = constants.HTYPE_GROUP
 
@@ -506,7 +515,10 @@
     return {}
 
   def BuildHooksNodes(self):
-    return (["a"], ["a"], ["explicit.node1.com", "explicit.node2.com"])
+    return (["a"], ["a"])
+
+  def PreparePostHookNodes(self, post_hook_node_uuids):
+    return post_hook_node_uuids + ["b"]
 
 
 class TestHooksRunnerEnv(unittest.TestCase):
@@ -514,7 +526,8 @@
     self._rpcs = []
 
     self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
-    self.lu = FakeEnvWithNodeNameLU(FakeProc(), self.op, FakeContext(), None)
+    self.lu = FakeEnvWithCustomPostHookNodesLU(FakeProc(), self.op,
+                                               FakeContext(), None)
 
   def _HooksRpc(self, *args):
     self._rpcs.append(args)
@@ -526,15 +539,13 @@
     hm.RunPhase(constants.HOOKS_PHASE_PRE)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["node_a.example.com"]))
+    self.assertEqual(node_list, set(["a"]))
 
     # Check post-phase hook
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["node_a.example.com",
-                                     "explicit.node1.com",
-                                     "explicit.node2.com"]))
+    self.assertEqual(node_list, set(["a", "b"]))
 
     self.assertRaises(IndexError, self._rpcs.pop)
 
diff --git a/test/py/ganeti.ht_unittest.py b/test/py/ganeti.ht_unittest.py
index e99a810..aaa69fc 100755
--- a/test/py/ganeti.ht_unittest.py
+++ b/test/py/ganeti.ht_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.ht"""
diff --git a/test/py/ganeti.http_unittest.py b/test/py/ganeti.http_unittest.py
index d958499..518f817 100755
--- a/test/py/ganeti.http_unittest.py
+++ b/test/py/ganeti.http_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the http module"""
diff --git a/test/py/ganeti.hypervisor.hv_chroot_unittest.py b/test/py/ganeti.hypervisor.hv_chroot_unittest.py
index 548d8d1..39124c5 100755
--- a/test/py/ganeti.hypervisor.hv_chroot_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_chroot_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.hypervisor.hv_chroot"""
diff --git a/test/py/ganeti.hypervisor.hv_fake_unittest.py b/test/py/ganeti.hypervisor.hv_fake_unittest.py
index 700e0e9..4d8e893 100755
--- a/test/py/ganeti.hypervisor.hv_fake_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_fake_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.hypervisor.hv_fake"""
diff --git a/test/py/ganeti.hypervisor.hv_kvm_unittest.py b/test/py/ganeti.hypervisor.hv_kvm_unittest.py
index c8eaffc..a9aacc6 100755
--- a/test/py/ganeti.hypervisor.hv_kvm_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_kvm_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing the hypervisor.hv_kvm module"""
@@ -27,6 +36,7 @@
 import socket
 import os
 import struct
+import re
 
 from ganeti import serializer
 from ganeti import constants
@@ -384,5 +394,52 @@
     self.assertFalse(hv_kvm._ProbeTapVnetHdr(fd, _features_fn=lambda _: None))
 
 
+class TestGenerateDeviceKVMId(unittest.TestCase):
+  def test(self):
+    device = objects.NIC()
+    target = constants.HOTPLUG_TARGET_NIC
+    fn = hv_kvm._GenerateDeviceKVMId
+    self.assertRaises(errors.HotplugError, fn, target, device)
+
+    device.pci = 5
+    device.uuid = "003fc157-66a8-4e6d-8b7e-ec4f69751396"
+    self.assertTrue(re.match("hotnic-003fc157-pci-5", fn(target, device)))
+
+
+class TestGetRuntimeInfo(unittest.TestCase):
+  @classmethod
+  def _GetRuntime(cls):
+    data = testutils.ReadTestData("kvm_runtime.json")
+    return hv_kvm._AnalyzeSerializedRuntime(data)
+
+  def _fail(self, target, device, runtime):
+    device.uuid = "aaaaaaaa-66a8-4e6d-8b7e-ec4f69751396"
+    self.assertRaises(errors.HotplugError,
+                      hv_kvm._GetExistingDeviceInfo,
+                      target, device, runtime)
+
+  def testNIC(self):
+    device = objects.NIC()
+    target = constants.HOTPLUG_TARGET_NIC
+    runtime = self._GetRuntime()
+
+    self._fail(target, device, runtime)
+
+    device.uuid = "003fc157-66a8-4e6d-8b7e-ec4f69751396"
+    devinfo = hv_kvm._GetExistingDeviceInfo(target, device, runtime)
+    self.assertTrue(devinfo.pci==6)
+
+  def testDisk(self):
+    device = objects.Disk()
+    target = constants.HOTPLUG_TARGET_DISK
+    runtime = self._GetRuntime()
+
+    self._fail(target, device, runtime)
+
+    device.uuid = "9f5c5bd4-6f60-480b-acdc-9bb1a4b7df79"
+    (devinfo, _, __) = hv_kvm._GetExistingDeviceInfo(target, device, runtime)
+    self.assertTrue(devinfo.pci==5)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.hypervisor.hv_lxc_unittest.py b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
index 62ba58e..7b09204 100755
--- a/test/py/ganeti.hypervisor.hv_lxc_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_lxc_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.hypervisor.hv_lxc"""
diff --git a/test/py/ganeti.hypervisor.hv_xen_unittest.py b/test/py/ganeti.hypervisor.hv_xen_unittest.py
index 767eae8..7e4fe08 100755
--- a/test/py/ganeti.hypervisor.hv_xen_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_xen_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.hypervisor.hv_xen"""
@@ -269,7 +278,9 @@
 
   def testManyDisks(self):
     for offset in [0, 1, 10]:
-      disks = [(objects.Disk(dev_type=constants.DT_PLAIN), "/tmp/disk/%s" % idx)
+      disks = [(objects.Disk(dev_type=constants.DT_PLAIN),
+               "/tmp/disk/%s" % idx,
+               NotImplemented)
                for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
 
       if offset == 0:
@@ -289,9 +300,11 @@
   def testTwoLvDisksWithMode(self):
     disks = [
       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
-       "/tmp/diskFirst"),
+       "/tmp/diskFirst",
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
-       "/tmp/diskLast"),
+       "/tmp/diskLast",
+       NotImplemented),
       ]
 
     result = hv_xen._GetConfigFileDiskData(disks, "hd")
@@ -303,20 +316,25 @@
   def testFileDisks(self):
     disks = [
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
-                    physical_id=[constants.FD_LOOP]),
-       "/tmp/diskFirst"),
+                    logical_id=[constants.FD_LOOP]),
+       "/tmp/diskFirst",
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
-                    physical_id=[constants.FD_BLKTAP]),
-       "/tmp/diskTwo"),
+                    logical_id=[constants.FD_BLKTAP]),
+       "/tmp/diskTwo",
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
-                    physical_id=[constants.FD_LOOP]),
-       "/tmp/diskThree"),
+                    logical_id=[constants.FD_LOOP]),
+       "/tmp/diskThree",
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
-                    physical_id=[constants.FD_BLKTAP2]),
-       "/tmp/diskFour"),
+                    logical_id=[constants.FD_BLKTAP2]),
+       "/tmp/diskFour",
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
-                    physical_id=[constants.FD_BLKTAP]),
-       "/tmp/diskLast"),
+                    logical_id=[constants.FD_BLKTAP]),
+       "/tmp/diskLast",
+       NotImplemented),
       ]
 
     result = hv_xen._GetConfigFileDiskData(disks, "sd")
@@ -331,8 +349,9 @@
   def testInvalidFileDisk(self):
     disks = [
       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
-                    physical_id=["#unknown#"]),
-       "/tmp/diskinvalid"),
+                    logical_id=["#unknown#"]),
+       "/tmp/diskinvalid",
+       NotImplemented),
       ]
 
     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
@@ -683,9 +702,11 @@
 
     disks = [
       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
-       utils.PathJoin(self.tmpdir, "disk0")),
+       utils.PathJoin(self.tmpdir, "disk0"),
+       NotImplemented),
       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
-       utils.PathJoin(self.tmpdir, "disk1")),
+       utils.PathJoin(self.tmpdir, "disk1"),
+       NotImplemented),
       ]
 
     inst = objects.Instance(name="server01.example.com",
diff --git a/test/py/ganeti.hypervisor_unittest.py b/test/py/ganeti.hypervisor_unittest.py
index 73bf571..780e1f1 100755
--- a/test/py/ganeti.hypervisor_unittest.py
+++ b/test/py/ganeti.hypervisor_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing hypervisor functionality"""
diff --git a/test/py/ganeti.impexpd_unittest.py b/test/py/ganeti.impexpd_unittest.py
index 17c2489..129a1ec 100755
--- a/test/py/ganeti.impexpd_unittest.py
+++ b/test/py/ganeti.impexpd_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.impexpd"""
diff --git a/test/py/ganeti.jqueue_unittest.py b/test/py/ganeti.jqueue_unittest.py
index 4f0b964..5313d54 100755
--- a/test/py/ganeti.jqueue_unittest.py
+++ b/test/py/ganeti.jqueue_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.jqueue"""
diff --git a/test/py/ganeti.jstore_unittest.py b/test/py/ganeti.jstore_unittest.py
index bc24415..44e9cf4 100755
--- a/test/py/ganeti.jstore_unittest.py
+++ b/test/py/ganeti.jstore_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.jstore"""
diff --git a/test/py/ganeti.locking_unittest.py b/test/py/ganeti.locking_unittest.py
index 705103f..607b704 100755
--- a/test/py/ganeti.locking_unittest.py
+++ b/test/py/ganeti.locking_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the locking module"""
diff --git a/test/py/ganeti.luxi_unittest.py b/test/py/ganeti.luxi_unittest.py
index 66116d1..a550b9e 100755
--- a/test/py/ganeti.luxi_unittest.py
+++ b/test/py/ganeti.luxi_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the luxi module"""
diff --git a/test/py/ganeti.masterd.iallocator_unittest.py b/test/py/ganeti.masterd.iallocator_unittest.py
index d3144b0..0b04788 100755
--- a/test/py/ganeti.masterd.iallocator_unittest.py
+++ b/test/py/ganeti.masterd.iallocator_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.masterd.iallocator"""
@@ -217,6 +226,32 @@
     self.assertEqual(self.free_storage_lvm, free_disk)
     self.assertEqual(self.total_storage_lvm, total_disk)
 
+  def testComputeStorageDataFromSpaceInfoByTemplate(self):
+    disk_template = constants.DT_FILE
+    node_name = "mynode"
+    (total_disk, free_disk, total_spindles, free_spindles) = \
+        iallocator.IAllocator._ComputeStorageDataFromSpaceInfoByTemplate(
+            self.space_info, node_name, disk_template)
+    self.assertEqual(self.free_storage_file, free_disk)
+    self.assertEqual(self.total_storage_file, total_disk)
+
+  def testComputeStorageDataFromSpaceInfoByTemplateLvm(self):
+    disk_template = constants.DT_PLAIN
+    node_name = "mynode"
+    (total_disk, free_disk, total_spindles, free_spindles) = \
+        iallocator.IAllocator._ComputeStorageDataFromSpaceInfoByTemplate(
+            self.space_info, node_name, disk_template)
+    self.assertEqual(self.free_storage_lvm, free_disk)
+    self.assertEqual(self.total_storage_lvm, total_disk)
+
+  def testComputeStorageDataFromSpaceInfoByTemplateNoReport(self):
+    disk_template = constants.DT_DISKLESS
+    node_name = "mynode"
+    (total_disk, free_disk, total_spindles, free_spindles) = \
+        iallocator.IAllocator._ComputeStorageDataFromSpaceInfoByTemplate(
+            self.space_info, node_name, disk_template)
+    self.assertEqual(0, free_disk)
+    self.assertEqual(0, total_disk)
 
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.masterd.instance_unittest.py b/test/py/ganeti.masterd.instance_unittest.py
index 4dda147..f1c7b3c 100755
--- a/test/py/ganeti.masterd.instance_unittest.py
+++ b/test/py/ganeti.masterd.instance_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.masterd.instance"""
diff --git a/test/py/ganeti.mcpu_unittest.py b/test/py/ganeti.mcpu_unittest.py
index 8a997a0..8621425 100755
--- a/test/py/ganeti.mcpu_unittest.py
+++ b/test/py/ganeti.mcpu_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2009, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the mcpu module"""
diff --git a/test/py/ganeti.netutils_unittest.py b/test/py/ganeti.netutils_unittest.py
index 79aeed8..586c8e8 100755
--- a/test/py/ganeti.netutils_unittest.py
+++ b/test/py/ganeti.netutils_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the netutils module"""
diff --git a/test/py/ganeti.objects_unittest.py b/test/py/ganeti.objects_unittest.py
index 97594f8..afd9770 100755
--- a/test/py/ganeti.objects_unittest.py
+++ b/test/py/ganeti.objects_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the objects module"""
@@ -174,6 +183,9 @@
         constants.ND_OOB_PROGRAM: "/bin/group-oob",
         constants.ND_SPINDLE_COUNT: 10,
         constants.ND_EXCLUSIVE_STORAGE: True,
+        constants.ND_OVS: True,
+        constants.ND_OVS_LINK: "eth2",
+        constants.ND_OVS_NAME: "openvswitch",
         }
     fake_group = objects.NodeGroup(name="testgroup",
                                    ndparams=group_ndparams)
@@ -185,6 +197,9 @@
         constants.ND_OOB_PROGRAM: "/bin/node-oob",
         constants.ND_SPINDLE_COUNT: 2,
         constants.ND_EXCLUSIVE_STORAGE: True,
+        constants.ND_OVS: True,
+        constants.ND_OVS_LINK: "eth2",
+        constants.ND_OVS_NAME: "openvswitch",
         }
     fake_node = objects.Node(name="test",
                              ndparams=node_ndparams,
@@ -199,6 +214,9 @@
         constants.ND_OOB_PROGRAM: "/bin/node-oob",
         constants.ND_SPINDLE_COUNT: 5,
         constants.ND_EXCLUSIVE_STORAGE: True,
+        constants.ND_OVS: True,
+        constants.ND_OVS_LINK: "eth2",
+        constants.ND_OVS_NAME: "openvswitch",
         }
     fake_node = objects.Node(name="test",
                              ndparams=node_ndparams,
@@ -347,10 +365,10 @@
 
   def testNodesDrbdDisks(self):
     inst = objects.Instance(name="fakeinstdrbd.example.com",
-      primary_node="node10.example.com",
+      primary_node="node20.example.com",
       disks=[
         objects.Disk(dev_type=constants.DT_DRBD8, size=786432,
-          logical_id=("node10.example.com", "node15.example.com",
+          logical_id=("node20.example.com", "node15.example.com",
                       12300, 0, 0, "secret"),
           children=[
             objects.Disk(dev_type=constants.DT_PLAIN, size=786432,
diff --git a/test/py/ganeti.opcodes_unittest.py b/test/py/ganeti.opcodes_unittest.py
index 98ec769..56847d4 100755
--- a/test/py/ganeti.opcodes_unittest.py
+++ b/test/py/ganeti.opcodes_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.backend"""
@@ -27,6 +36,7 @@
 
 from ganeti import utils
 from ganeti import opcodes
+from ganeti import opcodes_base
 from ganeti import ht
 from ganeti import constants
 from ganeti import errors
@@ -35,16 +45,6 @@
 import testutils
 
 
-#: Unless an opcode is included in the following list it must have a result
-#: check of some sort
-MISSING_RESULT_CHECK = compat.UniqueFrozenset([
-  opcodes.OpTestAllocator,
-  opcodes.OpTestDelay,
-  opcodes.OpTestDummy,
-  opcodes.OpTestJqueue,
-  ])
-
-
 class TestOpcodes(unittest.TestCase):
   def test(self):
     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, None)
@@ -56,16 +56,12 @@
       self.assert_(cls.OP_ID.startswith("OP_"))
       self.assert_(len(cls.OP_ID) > 3)
       self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
-      self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
-      self.assertFalse(compat.any(cls.OP_ID.startswith(prefix)
-                                  for prefix in opcodes._SUMMARY_PREFIX.keys()))
-      if cls in MISSING_RESULT_CHECK:
-        self.assertTrue(cls.OP_RESULT is None,
-                        msg=("%s is listed to not have a result check" %
-                             cls.OP_ID))
-      else:
-        self.assertTrue(callable(cls.OP_RESULT),
-                        msg=("%s should have a result check" % cls.OP_ID))
+      self.assertEqual(cls.OP_ID, opcodes_base._NameToId(cls.__name__))
+      self.assertFalse(
+        compat.any(cls.OP_ID.startswith(prefix)
+                   for prefix in opcodes_base.SUMMARY_PREFIX.keys()))
+      self.assertTrue(callable(cls.OP_RESULT),
+                      msg=("%s should have a result check" % cls.OP_ID))
 
       self.assertRaises(TypeError, cls, unsupported_parameter="some value")
 
@@ -132,10 +128,11 @@
     self.assertEqual(OpTest(data="b").Summary(), "TEST(a)")
 
   def testTinySummary(self):
-    self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
+    self.assertFalse(
+      utils.FindDuplicates(opcodes_base.SUMMARY_PREFIX.values()))
     self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
                                for (prefix, supplement) in
-                                 opcodes._SUMMARY_PREFIX.items()))
+                                 opcodes_base.SUMMARY_PREFIX.items()))
 
     self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
     self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
@@ -164,7 +161,7 @@
   def testParams(self):
     supported_by_all = set(["debug_level", "dry_run", "priority"])
 
-    self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
+    self.assertTrue(opcodes_base.BaseOpCode not in opcodes.OP_MAPPING.values())
     self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
 
     for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
@@ -195,7 +192,7 @@
       # Check parameter definitions
       for attr_name, aval, test, doc in cls.GetAllParams():
         self.assert_(attr_name)
-        self.assert_(test is None or test is ht.NoType or callable(test),
+        self.assertTrue(callable(test),
                      msg=("Invalid type check for %s.%s" %
                           (cls.OP_ID, attr_name)))
         self.assertTrue(doc is None or isinstance(doc, basestring))
@@ -206,13 +203,9 @@
                            msg=("Default value of %s.%s returned by function"
                                 " is callable" % (cls.OP_ID, attr_name)))
         else:
-          self.assertFalse(isinstance(aval, (list, dict, set)),
-                           msg=("Default value of %s.%s is mutable (%s)" %
-                                (cls.OP_ID, attr_name, repr(aval))))
-
           default_value = aval
 
-        if aval is not ht.NoDefault and test is not ht.NoType:
+        if aval is not ht.NoDefault and aval is not None:
           self.assertTrue(test(default_value),
                           msg=("Default value of %s.%s does not verify" %
                                (cls.OP_ID, attr_name)))
@@ -225,10 +218,10 @@
   def testValidateNoModification(self):
     class OpTest(opcodes.OpCode):
       OP_PARAMS = [
-        ("nodef", ht.NoDefault, ht.TMaybeString, None),
+        ("nodef", None, ht.TString, None),
         ("wdef", "default", ht.TMaybeString, None),
         ("number", 0, ht.TInt, None),
-        ("notype", None, ht.NoType, None),
+        ("notype", None, ht.TAny, None),
         ]
 
     # Missing required parameter "nodef"
@@ -285,11 +278,8 @@
   def testValidateSetDefaults(self):
     class OpTest(opcodes.OpCode):
       OP_PARAMS = [
-        # Static default value
         ("value1", "default", ht.TMaybeString, None),
-
-        # Default value callback
-        ("value2", lambda: "result", ht.TMaybeString, None),
+        ("value2", "result", ht.TMaybeString, None),
         ]
 
     op = OpTest()
@@ -328,8 +318,8 @@
 
 class TestOpcodeDepends(unittest.TestCase):
   def test(self):
-    check_relative = opcodes._BuildJobDepCheck(True)
-    check_norelative = opcodes.TNoRelativeJobDependencies
+    check_relative = opcodes_base.BuildJobDepCheck(True)
+    check_norelative = opcodes_base.TNoRelativeJobDependencies
 
     for fn in [check_relative, check_norelative]:
       self.assertTrue(fn(None))
@@ -362,60 +352,34 @@
   def testJobIdList(self):
     for i in [[], [(False, "error")], [(False, "")],
               [(True, 123), (True, "999")]]:
-      self.assertTrue(opcodes.TJobIdList(i))
+      self.assertTrue(ht.TJobIdList(i))
 
     for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
-      self.assertFalse(opcodes.TJobIdList(i))
+      self.assertFalse(ht.TJobIdList(i))
 
   def testJobIdListOnly(self):
-    self.assertTrue(opcodes.TJobIdListOnly({
+    self.assertTrue(ht.TJobIdListOnly({
       constants.JOB_IDS_KEY: [],
       }))
-    self.assertTrue(opcodes.TJobIdListOnly({
+    self.assertTrue(ht.TJobIdListOnly({
       constants.JOB_IDS_KEY: [(True, "9282")],
       }))
 
-    self.assertFalse(opcodes.TJobIdListOnly({
+    self.assertFalse(ht.TJobIdListOnly({
       "x": None,
       }))
-    self.assertFalse(opcodes.TJobIdListOnly({
+    self.assertFalse(ht.TJobIdListOnly({
       constants.JOB_IDS_KEY: [],
       "x": None,
       }))
-    self.assertFalse(opcodes.TJobIdListOnly({
+    self.assertFalse(ht.TJobIdListOnly({
       constants.JOB_IDS_KEY: [("foo", "bar")],
       }))
-    self.assertFalse(opcodes.TJobIdListOnly({
+    self.assertFalse(ht.TJobIdListOnly({
       constants.JOB_IDS_KEY: [("one", "two", "three")],
       }))
 
 
-class TestClusterOsList(unittest.TestCase):
-  def test(self):
-    good = [
-      None,
-      [],
-      [(constants.DDM_ADD, "dos"),
-       (constants.DDM_REMOVE, "linux")],
-      ]
-
-    for i in good:
-      self.assertTrue(opcodes._TestClusterOsList(i))
-
-    wrong = ["", 0, "xy", ["Hello World"], object(),
-      [("foo", "bar")],
-      [("", "")],
-      [[constants.DDM_ADD]],
-      [(constants.DDM_ADD, "")],
-      [(constants.DDM_REMOVE, "")],
-      [(constants.DDM_ADD, None)],
-      [(constants.DDM_REMOVE, None)],
-      ]
-
-    for i in wrong:
-      self.assertFalse(opcodes._TestClusterOsList(i))
-
-
 class TestOpInstanceSetParams(unittest.TestCase):
   def _GenericTests(self, fn):
     self.assertTrue(fn([]))
@@ -433,7 +397,7 @@
     self.assertFalse(fn([[constants.DDM_ADD]]))
 
   def testNicModifications(self):
-    fn = opcodes.OpInstanceSetParams.TestNicModifications
+    fn = ht.TSetParamsMods(ht.TINicParams)
     self._GenericTests(fn)
 
     for param in constants.INIC_PARAMS:
@@ -441,7 +405,7 @@
       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
 
   def testDiskModifications(self):
-    fn = opcodes.OpInstanceSetParams.TestDiskModifications
+    fn = ht.TSetParamsMods(ht.TIDiskParams)
     self._GenericTests(fn)
 
     for param in constants.IDISK_PARAMS:
diff --git a/test/py/ganeti.outils_unittest.py b/test/py/ganeti.outils_unittest.py
index 22d2177..83c4d46 100755
--- a/test/py/ganeti.outils_unittest.py
+++ b/test/py/ganeti.outils_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the outils module"""
diff --git a/test/py/ganeti.ovf_unittest.py b/test/py/ganeti.ovf_unittest.py
index bc72496..882c051 100755
--- a/test/py/ganeti.ovf_unittest.py
+++ b/test/py/ganeti.ovf_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.ovf.
diff --git a/test/py/ganeti.qlang_unittest.py b/test/py/ganeti.qlang_unittest.py
index df249ec..8b259b8 100755
--- a/test/py/ganeti.qlang_unittest.py
+++ b/test/py/ganeti.qlang_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.qlang"""
diff --git a/test/py/ganeti.query_unittest.py b/test/py/ganeti.query_unittest.py
index 0d6e4a4..afcf921 100755
--- a/test/py/ganeti.query_unittest.py
+++ b/test/py/ganeti.query_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.query"""
diff --git a/test/py/ganeti.rapi.baserlib_unittest.py b/test/py/ganeti.rapi.baserlib_unittest.py
index c971577..254e82f 100755
--- a/test/py/ganeti.rapi.baserlib_unittest.py
+++ b/test/py/ganeti.rapi.baserlib_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.rapi.baserlib"""
@@ -107,7 +116,7 @@
   @staticmethod
   def _GetMethodAttributes(method):
     attrs = ["%s_OPCODE" % method, "%s_RENAME" % method,
-             "Get%sOpInput" % method.capitalize()]
+             "%s_ALIASES" % method, "Get%sOpInput" % method.capitalize()]
     assert attrs == dict((opattrs[0], list(opattrs[1:]))
                          for opattrs in baserlib.OPCODE_ATTRS)[method]
     return attrs
diff --git a/test/py/ganeti.rapi.client_unittest.py b/test/py/ganeti.rapi.client_unittest.py
index ec6b386..de9d631 100755
--- a/test/py/ganeti.rapi.client_unittest.py
+++ b/test/py/ganeti.rapi.client_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the RAPI client module"""
@@ -503,8 +512,8 @@
   def testInstancesMultiAlloc(self):
     response = {
       constants.JOB_IDS_KEY: ["23423"],
-      opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: ["foobar"],
-      opcodes.OpInstanceMultiAlloc.FAILED_KEY: ["foobar2"],
+      constants.ALLOCATABLE_KEY: ["foobar"],
+      constants.FAILED_KEY: ["foobar2"],
       }
     self.rapi.AddResponse(serializer.DumpJson(response))
     insts = [self.client.InstanceAllocation("create", "foobar",
diff --git a/test/py/ganeti.rapi.resources_unittest.py b/test/py/ganeti.rapi.resources_unittest.py
index 1f0d74e..0185e25 100755
--- a/test/py/ganeti.rapi.resources_unittest.py
+++ b/test/py/ganeti.rapi.resources_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the RAPI resources module"""
diff --git a/test/py/ganeti.rapi.rlib2_unittest.py b/test/py/ganeti.rapi.rlib2_unittest.py
index 47a86df..9fd7a86 100755
--- a/test/py/ganeti.rapi.rlib2_unittest.py
+++ b/test/py/ganeti.rapi.rlib2_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the RAPI rlib2 module
@@ -261,7 +270,7 @@
     handler = _CreateHandler(rlib2.R_2_nodes_name_evacuate, ["node92"], {
       "dry-run": ["1"],
       }, {
-      "mode": constants.IALLOCATOR_NEVAC_SEC,
+      "mode": constants.NODE_EVAC_SEC,
       }, clfactory)
     job_id = handler.POST()
 
@@ -272,7 +281,7 @@
     self.assertEqual(job_id, exp_job_id)
     self.assertTrue(isinstance(op, opcodes.OpNodeEvacuate))
     self.assertEqual(op.node_name, "node92")
-    self.assertEqual(op.mode, constants.IALLOCATOR_NEVAC_SEC)
+    self.assertEqual(op.mode, constants.NODE_EVAC_SEC)
     self.assertTrue(op.dry_run)
 
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
@@ -558,6 +567,40 @@
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
 
+class TestInstanceModify(unittest.TestCase):
+  def testCustomParamRename(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    name = "instant_instance"
+    data = {
+      "custom_beparams": {},
+      "custom_hvparams": {},
+      }
+
+    handler = _CreateHandler(rlib2.R_2_instances_name_modify, [name], {}, data,
+                             clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpInstanceSetParams))
+    self.assertEqual(op.beparams, {})
+    self.assertEqual(op.hvparams, {})
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+    # Define both
+    data["beparams"] = {}
+    assert "beparams" in data and "custom_beparams" in data
+    handler = _CreateHandler(rlib2.R_2_instances_name_modify, [name], {}, data,
+                             clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
+
 class TestBackupPrepare(unittest.TestCase):
   def test(self):
     clfactory = _FakeClientFactory(_FakeClient)
@@ -630,11 +673,12 @@
   def testErrors(self):
     clfactory = _FakeClientFactory(_FakeClient)
 
+    # storage type which does not support space reporting
     queryargs = {
-      "output_fields": "name,other",
+      "storage_type": constants.ST_DISKLESS,
       }
     handler = _CreateHandler(rlib2.R_2_nodes_name_storage,
-                             ["node10538"], queryargs, {}, clfactory)
+                             ["node21273"], queryargs, {}, clfactory)
     self.assertRaises(http.HttpBadRequest, handler.GET)
 
     queryargs = {
@@ -1011,7 +1055,6 @@
       "disks": [],
       "nics": [],
       "mode": constants.INSTANCE_CREATE,
-      "disk_template": constants.DT_PLAIN,
       }
 
     for name in reqfields.keys():
@@ -1588,6 +1631,40 @@
     self.assertFalse(hasattr(op, "dry_run"))
     self.assertRaises(IndexError, cl.GetNextSubmittedJob)
 
+  def testCustomParamRename(self):
+    clfactory = _FakeClientFactory(_FakeClient)
+
+    name = "groupie"
+    data = {
+      "custom_diskparams": {},
+      "custom_ipolicy": {},
+      "custom_ndparams": {},
+      }
+
+    handler = _CreateHandler(rlib2.R_2_groups_name_modify, [name], {}, data,
+                             clfactory)
+    job_id = handler.PUT()
+
+    cl = clfactory.GetNextClient()
+    self.assertRaises(IndexError, clfactory.GetNextClient)
+
+    (exp_job_id, (op, )) = cl.GetNextSubmittedJob()
+    self.assertEqual(job_id, exp_job_id)
+
+    self.assertTrue(isinstance(op, opcodes.OpGroupSetParams))
+    self.assertEqual(op.diskparams, {})
+    self.assertEqual(op.ipolicy, {})
+    self.assertEqual(op.ndparams, {})
+
+    self.assertRaises(IndexError, cl.GetNextSubmittedJob)
+
+    # Define both
+    data["diskparams"] = {}
+    assert "diskparams" in data and "custom_diskparams" in data
+    handler = _CreateHandler(rlib2.R_2_groups_name_modify, [name], {}, data,
+                             clfactory)
+    self.assertRaises(http.HttpBadRequest, handler.PUT)
+
 
 class TestGroupAdd(unittest.TestCase):
   def test(self):
@@ -1746,7 +1823,7 @@
 
     def QueryClusterInfo(self):
       assert self.cluster_info is None
-      self.cluster_info = object()
+      self.cluster_info = {}
       return self.cluster_info
 
   def test(self):
diff --git a/test/py/ganeti.rapi.testutils_unittest.py b/test/py/ganeti.rapi.testutils_unittest.py
index 38a63d6..4d4b7d8 100755
--- a/test/py/ganeti.rapi.testutils_unittest.py
+++ b/test/py/ganeti.rapi.testutils_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.rapi.testutils"""
@@ -39,6 +48,7 @@
 
 KNOWN_UNUSED_LUXI = compat.UniqueFrozenset([
   luxi.REQ_SUBMIT_MANY_JOBS,
+  luxi.REQ_SUBMIT_JOB_TO_DRAINED_QUEUE,
   luxi.REQ_ARCHIVE_JOB,
   luxi.REQ_AUTO_ARCHIVE_JOBS,
   luxi.REQ_CHANGE_JOB_PRIORITY,
@@ -112,8 +122,6 @@
   def testNoResultCheck(self):
     vor = rapi.testutils.VerifyOpResult
 
-    assert opcodes.OpTestDummy.OP_RESULT is None
-
     vor(opcodes.OpTestDummy.OP_ID, None)
 
 
diff --git a/test/py/ganeti.rpc_unittest.py b/test/py/ganeti.rpc_unittest.py
index 0a37136..d0231fe 100755
--- a/test/py/ganeti.rpc_unittest.py
+++ b/test/py/ganeti.rpc_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.rpc"""
@@ -481,11 +490,11 @@
 class TestCompress(unittest.TestCase):
   def test(self):
     for data in ["", "Hello", "Hello World!\nnew\nlines"]:
-      self.assertEqual(rpc._Compress(data),
+      self.assertEqual(rpc._Compress(NotImplemented, data),
                        (constants.RPC_ENCODING_NONE, data))
 
     for data in [512 * " ", 5242 * "Hello World!\n"]:
-      compressed = rpc._Compress(data)
+      compressed = rpc._Compress(NotImplemented, data)
       self.assertEqual(len(compressed), 2)
       self.assertEqual(backend._Decompress(compressed), data)
 
@@ -566,8 +575,8 @@
       ]
 
     encoders = {
-      AT1: hex,
-      AT2: hash,
+      AT1: lambda _, value: hex(value),
+      AT2: lambda _, value: hash(value),
       }
 
     cdef = ("test_call", NotImplemented, None, constants.RPC_TMO_NORMAL, [
@@ -720,6 +729,9 @@
   def GetNodeInfo(self, name):
     return objects.Node(name=name)
 
+  def GetMultiNodeInfo(self, names):
+    return [(name, self.GetNodeInfo(name)) for name in names]
+
   def GetClusterInfo(self):
     return self._cluster
 
@@ -736,11 +748,15 @@
     tmpfile.flush()
     st = os.stat(tmpfile.name)
 
+    nodes = [
+      "node1.example.com",
+      ]
+
     def _VerifyRequest(req):
       (uldata, ) = serializer.LoadJson(req.post_data)
       self.assertEqual(len(uldata), 7)
       self.assertEqual(uldata[0], tmpfile.name)
-      self.assertEqual(list(uldata[1]), list(rpc._Compress(data)))
+      self.assertEqual(list(uldata[1]), list(rpc._Compress(nodes[0], data)))
       self.assertEqual(uldata[2], st.st_mode)
       self.assertEqual(uldata[3], "user%s" % os.getuid())
       self.assertEqual(uldata[4], "group%s" % os.getgid())
@@ -761,10 +777,6 @@
                                   _req_process_fn=http_proc,
                                   _getents=mocks.FakeGetentResolver)
 
-    nodes = [
-      "node1.example.com",
-      ]
-
     for runner in [std_runner, cfg_runner]:
       result = runner.call_upload_file(nodes, tmpfile.name)
       self.assertEqual(len(result), len(nodes))
@@ -829,16 +841,17 @@
                        "mymode")
 
     # Generic object serialization
-    result = runner._encoder((rpc_defs.ED_OBJECT_DICT, inst))
+    result = runner._encoder(NotImplemented, (rpc_defs.ED_OBJECT_DICT, inst))
     _CheckBasics(result)
     self.assertEqual(len(result["hvparams"]), 2)
 
-    result = runner._encoder((rpc_defs.ED_OBJECT_DICT_LIST, 5 * [inst]))
+    result = runner._encoder(NotImplemented,
+                             (rpc_defs.ED_OBJECT_DICT_LIST, 5 * [inst]))
     map(_CheckBasics, result)
     map(lambda r: self.assertEqual(len(r["hvparams"]), 2), result)
 
     # Just an instance
-    result = runner._encoder((rpc_defs.ED_INST_DICT, inst))
+    result = runner._encoder(NotImplemented, (rpc_defs.ED_INST_DICT, inst))
     _CheckBasics(result)
     self.assertEqual(result["beparams"][constants.BE_MAXMEM], 256)
     self.assertEqual(result["hvparams"][constants.HV_CDROM_IMAGE_PATH], "bar")
@@ -850,10 +863,11 @@
                      len(constants.HVC_DEFAULTS[constants.HT_KVM]))
 
     # Instance with OS parameters
-    result = runner._encoder((rpc_defs.ED_INST_DICT_OSP_DP, (inst, {
-      "role": "webserver",
-      "other": "field",
-      })))
+    result = runner._encoder(NotImplemented,
+                             (rpc_defs.ED_INST_DICT_OSP_DP, (inst, {
+                               "role": "webserver",
+                               "other": "field",
+                             })))
     _CheckBasics(result)
     self.assertEqual(result["beparams"][constants.BE_MAXMEM], 256)
     self.assertEqual(result["hvparams"][constants.HV_CDROM_IMAGE_PATH], "bar")
@@ -864,8 +878,11 @@
       })
 
     # Instance with hypervisor and backend parameters
-    result = runner._encoder((rpc_defs.ED_INST_DICT_HVP_BEP_DP, (inst, {
-      constants.HV_BOOT_ORDER: "xyz",
+    result = runner._encoder(NotImplemented,
+                             (rpc_defs.ED_INST_DICT_HVP_BEP_DP, (inst, {
+      constants.HT_KVM: {
+        constants.HV_BOOT_ORDER: "xyz",
+        },
       }, {
       constants.BE_VCPUS: 100,
       constants.BE_MAXMEM: 4096,
@@ -873,14 +890,18 @@
     _CheckBasics(result)
     self.assertEqual(result["beparams"][constants.BE_MAXMEM], 4096)
     self.assertEqual(result["beparams"][constants.BE_VCPUS], 100)
-    self.assertEqual(result["hvparams"][constants.HV_BOOT_ORDER], "xyz")
+    self.assertEqual(result["hvparams"][constants.HT_KVM], {
+      constants.HV_BOOT_ORDER: "xyz",
+      })
     self.assertEqual(result["disks"], [{
       "dev_type": constants.DT_PLAIN,
+      "dynamic_params": {},
       "size": 4096,
       "logical_id": ("vg", "disk6120"),
       "params": constants.DISK_DT_DEFAULTS[inst.disk_template],
       }, {
       "dev_type": constants.DT_PLAIN,
+      "dynamic_params": {},
       "size": 1024,
       "logical_id": ("vg", "disk8508"),
       "params": constants.DISK_DT_DEFAULTS[inst.disk_template],
@@ -892,94 +913,58 @@
 
 class TestLegacyNodeInfo(unittest.TestCase):
   KEY_BOOT = "bootid"
-  KEY_VG0 = "name"
-  KEY_VG1 = "storage_free"
-  KEY_VG2 = "storage_size"
-  KEY_HV = "cpu_count"
-  KEY_SP1 = "spindles_free"
-  KEY_SP2 = "spindles_total"
-  KEY_ST = "type" # key for storage type
+  KEY_NAME = "name"
+  KEY_STORAGE_FREE = "storage_free"
+  KEY_STORAGE_TOTAL = "storage_size"
+  KEY_CPU_COUNT = "cpu_count"
+  KEY_SPINDLES_FREE = "spindles_free"
+  KEY_SPINDLES_TOTAL = "spindles_total"
+  KEY_STORAGE_TYPE = "type" # key for storage type
   VAL_BOOT = 0
-  VAL_VG0 = "xy"
-  VAL_VG1 = 11
-  VAL_VG2 = 12
-  VAL_VG3 = "lvm-vg"
-  VAL_HV = 2
-  VAL_SP0 = "ab"
-  VAL_SP1 = 31
-  VAL_SP2 = 32
-  VAL_SP3 = "lvm-pv"
+  VAL_VG_NAME = "xy"
+  VAL_VG_FREE = 11
+  VAL_VG_TOTAL = 12
+  VAL_VG_TYPE = "lvm-vg"
+  VAL_CPU_COUNT = 2
+  VAL_PV_NAME = "ab"
+  VAL_PV_FREE = 31
+  VAL_PV_TOTAL = 32
+  VAL_PV_TYPE = "lvm-pv"
   DICT_VG = {
-    KEY_VG0: VAL_VG0,
-    KEY_VG1: VAL_VG1,
-    KEY_VG2: VAL_VG2,
-    KEY_ST: VAL_VG3,
+    KEY_NAME: VAL_VG_NAME,
+    KEY_STORAGE_FREE: VAL_VG_FREE,
+    KEY_STORAGE_TOTAL: VAL_VG_TOTAL,
+    KEY_STORAGE_TYPE: VAL_VG_TYPE,
     }
-  DICT_HV = {KEY_HV: VAL_HV}
+  DICT_HV = {KEY_CPU_COUNT: VAL_CPU_COUNT}
   DICT_SP = {
-    KEY_ST: VAL_SP3,
-    KEY_VG0: VAL_SP0,
-    KEY_VG1: VAL_SP1,
-    KEY_VG2: VAL_SP2,
+    KEY_STORAGE_TYPE: VAL_PV_TYPE,
+    KEY_NAME: VAL_PV_NAME,
+    KEY_STORAGE_FREE: VAL_PV_FREE,
+    KEY_STORAGE_TOTAL: VAL_PV_TOTAL,
     }
   STD_LST = [VAL_BOOT, [DICT_VG, DICT_SP], [DICT_HV]]
   STD_DICT = {
     KEY_BOOT: VAL_BOOT,
-    KEY_VG0: VAL_VG0,
-    KEY_VG1: VAL_VG1,
-    KEY_VG2: VAL_VG2,
-    KEY_HV: VAL_HV,
+    KEY_NAME: VAL_VG_NAME,
+    KEY_STORAGE_FREE: VAL_VG_FREE,
+    KEY_STORAGE_TOTAL: VAL_VG_TOTAL,
+    KEY_SPINDLES_FREE: VAL_PV_FREE,
+    KEY_SPINDLES_TOTAL: VAL_PV_TOTAL,
+    KEY_CPU_COUNT: VAL_CPU_COUNT,
     }
 
-  def testStandard(self):
-    result = rpc.MakeLegacyNodeInfo(self.STD_LST)
+  def testWithSpindles(self):
+    result = rpc.MakeLegacyNodeInfo(self.STD_LST, constants.DT_PLAIN)
     self.assertEqual(result, self.STD_DICT)
 
-  def testSpindlesRequired(self):
-    my_lst = [self.VAL_BOOT, [], [self.DICT_HV]]
-    self.assertRaises(errors.OpExecError, rpc.MakeLegacyNodeInfo, my_lst,
-        require_spindles=True)
-
-  def testNoSpindlesRequired(self):
-    my_lst = [self.VAL_BOOT, [], [self.DICT_HV]]
-    result = rpc.MakeLegacyNodeInfo(my_lst, require_spindles = False)
-    self.assertEqual(result, {self.KEY_BOOT: self.VAL_BOOT,
-                              self.KEY_HV: self.VAL_HV})
-    result = rpc.MakeLegacyNodeInfo(self.STD_LST, require_spindles = False)
-    self.assertEqual(result, self.STD_DICT)
-
-
-class TestAddDefaultStorageInfoToLegacyNodeInfo(unittest.TestCase):
-
-  def setUp(self):
-    self.free_storage_file = 23
-    self.total_storage_file = 42
-    self.free_storage_lvm = 69
-    self.total_storage_lvm = 666
-    self.node_info = [{"name": "myfile",
-                       "type": constants.ST_FILE,
-                       "storage_free": self.free_storage_file,
-                       "storage_size": self.total_storage_file},
-                      {"name": "myvg",
-                       "type": constants.ST_LVM_VG,
-                       "storage_free": self.free_storage_lvm,
-                       "storage_size": self.total_storage_lvm},
-                      {"name": "myspindle",
-                       "type": constants.ST_LVM_PV,
-                       "storage_free": 33,
-                       "storage_size": 44}]
-
-  def testAddDefaultStorageInfoToLegacyNodeInfo(self):
-    result = {}
-    rpc._AddDefaultStorageInfoToLegacyNodeInfo(result, self.node_info)
-    self.assertEqual(self.free_storage_file, result["storage_free"])
-    self.assertEqual(self.total_storage_file, result["storage_size"])
-
-  def testAddDefaultStorageInfoToLegacyNodeInfoNoDefaults(self):
-    result = {}
-    rpc._AddDefaultStorageInfoToLegacyNodeInfo(result, self.node_info[-1:])
-    self.assertFalse("storage_free" in result)
-    self.assertFalse("storage_size" in result)
+  def testNoSpindles(self):
+    my_lst = [self.VAL_BOOT, [self.DICT_VG], [self.DICT_HV]]
+    result = rpc.MakeLegacyNodeInfo(my_lst, constants.DT_PLAIN)
+    expected_dict = dict((k,v) for k, v in self.STD_DICT.iteritems())
+    expected_dict[self.KEY_SPINDLES_FREE] = 0
+    expected_dict[self.KEY_SPINDLES_TOTAL] = 0
+    self.assertEqual(result, expected_dict)
 
 
 if __name__ == "__main__":
diff --git a/test/py/ganeti.runtime_unittest.py b/test/py/ganeti.runtime_unittest.py
index 73f4143..74a36b7 100755
--- a/test/py/ganeti.runtime_unittest.py
+++ b/test/py/ganeti.runtime_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Script for testing ganeti.runtime"""
 
@@ -41,6 +50,7 @@
     constants.CONFD_USER: _EntStub(uid=1),
     constants.RAPI_USER: _EntStub(uid=2),
     constants.NODED_USER: _EntStub(uid=3),
+    constants.LUXID_USER: _EntStub(uid=4),
     }
   return users[user]
 
@@ -53,6 +63,7 @@
     constants.DAEMONS_GROUP: _EntStub(gid=3),
     constants.ADMIN_GROUP: _EntStub(gid=4),
     constants.NODED_GROUP: _EntStub(gid=5),
+    constants.LUXID_GROUP: _EntStub(gid=6),
     }
   return groups[group]
 
diff --git a/test/py/ganeti.serializer_unittest.py b/test/py/ganeti.serializer_unittest.py
index 45a3932..6ae9076 100755
--- a/test/py/ganeti.serializer_unittest.py
+++ b/test/py/ganeti.serializer_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the serializer module"""
diff --git a/test/py/ganeti.server.rapi_unittest.py b/test/py/ganeti.server.rapi_unittest.py
index bf73da9..ee879bd 100755
--- a/test/py/ganeti.server.rapi_unittest.py
+++ b/test/py/ganeti.server.rapi_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.server.rapi"""
diff --git a/test/py/ganeti.ssconf_unittest.py b/test/py/ganeti.ssconf_unittest.py
index c0d4154..b3a0a72 100755
--- a/test/py/ganeti.ssconf_unittest.py
+++ b/test/py/ganeti.ssconf_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.ssconf"""
diff --git a/test/py/ganeti.ssh_unittest.py b/test/py/ganeti.ssh_unittest.py
index eedc852..b520a97 100755
--- a/test/py/ganeti.ssh_unittest.py
+++ b/test/py/ganeti.ssh_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the ssh module"""
diff --git a/test/py/ganeti.storage.bdev_unittest.py b/test/py/ganeti.storage.bdev_unittest.py
index e4685fe..819c940 100755
--- a/test/py/ganeti.storage.bdev_unittest.py
+++ b/test/py/ganeti.storage.bdev_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the bdev module"""
diff --git a/test/py/ganeti.storage.container_unittest.py b/test/py/ganeti.storage.container_unittest.py
index e8811cc..07d25a5 100755
--- a/test/py/ganeti.storage.container_unittest.py
+++ b/test/py/ganeti.storage.container_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.storage.container"""
diff --git a/test/py/ganeti.storage.drbd_unittest.py b/test/py/ganeti.storage.drbd_unittest.py
index ff0dd77..9553e47 100755
--- a/test/py/ganeti.storage.drbd_unittest.py
+++ b/test/py/ganeti.storage.drbd_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the drbd module"""
@@ -428,12 +437,18 @@
         filename=testutils.TestDataFilename("proc_drbd84.txt"))
 
     self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0, "secret")
+    self.test_dyn_params = {
+      constants.DDP_LOCAL_IP: "192.0.2.1",
+      constants.DDP_LOCAL_MINOR: 0,
+      constants.DDP_REMOTE_IP: "192.0.2.2",
+      constants.DDP_REMOTE_MINOR: 0,
+    }
 
   @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
   def testConstructionWith80Data(self, mock_create_from_file):
     mock_create_from_file.return_value = self.proc80_info
 
-    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {}, self.test_dyn_params)
     self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
 
@@ -441,7 +456,7 @@
   def testConstructionWith83Data(self, mock_create_from_file):
     mock_create_from_file.return_value = self.proc83_info
 
-    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {}, self.test_dyn_params)
     self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
 
@@ -449,7 +464,7 @@
   def testConstructionWith84Data(self, mock_create_from_file):
     mock_create_from_file.return_value = self.proc84_info
 
-    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {}, self.test_dyn_params)
     self.assertEqual(inst._show_info_cls, drbd_info.DRBD84ShowInfo)
     self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD84CmdGenerator))
 
diff --git a/test/py/ganeti.storage.filestorage_unittest.py b/test/py/ganeti.storage.filestorage_unittest.py
index 43b8de3..795bb8c 100755
--- a/test/py/ganeti.storage.filestorage_unittest.py
+++ b/test/py/ganeti.storage.filestorage_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the ganeti.storage.filestorage module"""
diff --git a/test/py/ganeti.tools.burnin_unittest.py b/test/py/ganeti.tools.burnin_unittest.py
index 2f37d9e..7566068 100755
--- a/test/py/ganeti.tools.burnin_unittest.py
+++ b/test/py/ganeti.tools.burnin_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.tools.burnin"""
diff --git a/test/py/ganeti.tools.ensure_dirs_unittest.py b/test/py/ganeti.tools.ensure_dirs_unittest.py
index 31debc2..11d698e 100755
--- a/test/py/ganeti.tools.ensure_dirs_unittest.py
+++ b/test/py/ganeti.tools.ensure_dirs_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.tools.ensure_dirs"""
diff --git a/test/py/ganeti.tools.node_daemon_setup_unittest.py b/test/py/ganeti.tools.node_daemon_setup_unittest.py
index 9abeb2a..a9fd1a9 100755
--- a/test/py/ganeti.tools.node_daemon_setup_unittest.py
+++ b/test/py/ganeti.tools.node_daemon_setup_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.tools.node_daemon_setup"""
diff --git a/test/py/ganeti.tools.prepare_node_join_unittest.py b/test/py/ganeti.tools.prepare_node_join_unittest.py
index 54d1d62..e0c60a4 100755
--- a/test/py/ganeti.tools.prepare_node_join_unittest.py
+++ b/test/py/ganeti.tools.prepare_node_join_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.tools.prepare_node_join"""
diff --git a/test/py/ganeti.uidpool_unittest.py b/test/py/ganeti.uidpool_unittest.py
index deed3db..93f8276 100755
--- a/test/py/ganeti.uidpool_unittest.py
+++ b/test/py/ganeti.uidpool_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the uidpool module"""
diff --git a/test/py/ganeti.utils.algo_unittest.py b/test/py/ganeti.utils.algo_unittest.py
index 73475ec..36c93d2 100755
--- a/test/py/ganeti.utils.algo_unittest.py
+++ b/test/py/ganeti.utils.algo_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.algo"""
diff --git a/test/py/ganeti.utils.filelock_unittest.py b/test/py/ganeti.utils.filelock_unittest.py
index 29e7f64..dd5aabc 100755
--- a/test/py/ganeti.utils.filelock_unittest.py
+++ b/test/py/ganeti.utils.filelock_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.filelock"""
diff --git a/test/py/ganeti.utils.hash_unittest.py b/test/py/ganeti.utils.hash_unittest.py
index 3095935..8210658 100755
--- a/test/py/ganeti.utils.hash_unittest.py
+++ b/test/py/ganeti.utils.hash_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.hash"""
diff --git a/test/py/ganeti.utils.io_unittest-runasroot.py b/test/py/ganeti.utils.io_unittest-runasroot.py
index 7a7d8b5..2eab2f0 100644
--- a/test/py/ganeti.utils.io_unittest-runasroot.py
+++ b/test/py/ganeti.utils.io_unittest-runasroot.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.io (tests that require root access)"""
diff --git a/test/py/ganeti.utils.io_unittest.py b/test/py/ganeti.utils.io_unittest.py
index 12cc4df..967f9db 100755
--- a/test/py/ganeti.utils.io_unittest.py
+++ b/test/py/ganeti.utils.io_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.io"""
diff --git a/test/py/ganeti.utils.log_unittest.py b/test/py/ganeti.utils.log_unittest.py
index 517694c..a5d98e9 100755
--- a/test/py/ganeti.utils.log_unittest.py
+++ b/test/py/ganeti.utils.log_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.log"""
diff --git a/test/py/ganeti.utils.lvm_unittest.py b/test/py/ganeti.utils.lvm_unittest.py
index eb85b6f..d7de10a 100755
--- a/test/py/ganeti.utils.lvm_unittest.py
+++ b/test/py/ganeti.utils.lvm_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.lvm"""
diff --git a/test/py/ganeti.utils.mlock_unittest.py b/test/py/ganeti.utils.mlock_unittest.py
index 8c5c746..cdd1bee 100755
--- a/test/py/ganeti.utils.mlock_unittest.py
+++ b/test/py/ganeti.utils.mlock_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing utils.Mlockall
diff --git a/test/py/ganeti.utils.nodesetup_unittest.py b/test/py/ganeti.utils.nodesetup_unittest.py
index 2c61489..0ed2af8 100755
--- a/test/py/ganeti.utils.nodesetup_unittest.py
+++ b/test/py/ganeti.utils.nodesetup_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.nodesetup"""
diff --git a/test/py/ganeti.utils.process_unittest.py b/test/py/ganeti.utils.process_unittest.py
index 2e36cfa..7d4cbb6 100755
--- a/test/py/ganeti.utils.process_unittest.py
+++ b/test/py/ganeti.utils.process_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.process"""
diff --git a/test/py/ganeti.utils.retry_unittest.py b/test/py/ganeti.utils.retry_unittest.py
index d58c0af..f8c5daa 100755
--- a/test/py/ganeti.utils.retry_unittest.py
+++ b/test/py/ganeti.utils.retry_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.retry"""
diff --git a/test/py/ganeti.utils.storage_unittest.py b/test/py/ganeti.utils.storage_unittest.py
index cbcc719..6066e61 100755
--- a/test/py/ganeti.utils.storage_unittest.py
+++ b/test/py/ganeti.utils.storage_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the ganeti.utils.storage module"""
@@ -26,7 +35,6 @@
 import unittest
 
 from ganeti import constants
-from ganeti import objects
 from ganeti.utils import storage
 
 import testutils
@@ -72,38 +80,23 @@
     self.assertEqual(storage_type, constants.ST_DISKLESS)
     self.assertEqual(storage_key, None)
 
-  def testGetDefaultStorageUnitForSpindles(self):
-    (storage_type, storage_key) = \
-        storage._GetDefaultStorageUnitForSpindles(self._cfg)
-    self.assertEqual(storage_type, constants.ST_LVM_PV)
-    self.assertEqual(storage_key, self._default_vg_name)
 
-
-class TestGetStorageUnitsOfCluster(unittest.TestCase):
+class TestGetStorageUnits(unittest.TestCase):
 
   def setUp(self):
     storage._GetDefaultStorageUnitForDiskTemplate = \
         mock.Mock(return_value=("foo", "bar"))
-
-    self._cluster_cfg = objects.Cluster()
-    self._enabled_disk_templates = \
-        [constants.DT_DRBD8, constants.DT_PLAIN, constants.DT_FILE,
-         constants.DT_SHARED_FILE]
-    self._cluster_cfg.enabled_disk_templates = \
-        self._enabled_disk_templates
     self._cfg = mock.Mock()
-    self._cfg.GetClusterInfo = mock.Mock(return_value=self._cluster_cfg)
-    self._cfg.GetVGName = mock.Mock(return_value="some_vg_name")
 
-  def testGetStorageUnitsOfCluster(self):
-    storage_units = storage.GetStorageUnitsOfCluster(self._cfg)
-    self.assertEqual(len(storage_units), len(self._enabled_disk_templates))
+  def testGetStorageUnits(self):
+    disk_templates = [constants.DT_FILE, constants.DT_SHARED_FILE]
+    storage_units = storage.GetStorageUnits(self._cfg, disk_templates)
+    self.assertEqual(len(storage_units), len(disk_templates))
 
-  def testGetStorageUnitsOfClusterWithSpindles(self):
-    storage_units = storage.GetStorageUnitsOfCluster(
-        self._cfg, include_spindles=True)
-    self.assertEqual(len(storage_units), len(self._enabled_disk_templates) + 1)
-    self.assertTrue(constants.ST_LVM_PV in [st for (st, sk) in storage_units])
+  def testGetStorageUnitsLvm(self):
+    disk_templates = [constants.DT_PLAIN, constants.DT_DRBD8]
+    storage_units = storage.GetStorageUnits(self._cfg, disk_templates)
+    self.assertEqual(len(storage_units), len(disk_templates))
 
 
 class TestLookupSpaceInfoByStorageType(unittest.TestCase):
diff --git a/test/py/ganeti.utils.text_unittest.py b/test/py/ganeti.utils.text_unittest.py
index b99bcfd..611a25d 100755
--- a/test/py/ganeti.utils.text_unittest.py
+++ b/test/py/ganeti.utils.text_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.text"""
@@ -445,6 +454,24 @@
       b = ["a", "b" + sep + "c", "d" + sep + "e" + sep + "f", "g"]
       self.failUnlessEqual(utils.UnescapeAndSplit(sep.join(a), sep=sep), b)
 
+class TestEscapeAndJoin(unittest.TestCase):
+  def verifyParsesCorrect(self, args):
+    for sep in [",", "+", ".", ":"]:
+      self.assertEqual(utils.UnescapeAndSplit(
+          utils.EscapeAndJoin(args, sep=sep),
+          sep=sep), args)
+
+  def test(self):
+    self.verifyParsesCorrect(["a", "b", "c"])
+    self.verifyParsesCorrect(["2.10.0", "12345"])
+    self.verifyParsesCorrect(["2.10.0~alpha1", "12345"])
+    self.verifyParsesCorrect(["..:", ",,+"])
+    self.verifyParsesCorrect(["a\\", "b\\\\", "c"])
+    self.verifyParsesCorrect(["a"])
+    self.verifyParsesCorrect(["+"])
+    self.verifyParsesCorrect(["\\"])
+    self.verifyParsesCorrect(["\\\\"])
+
 
 class TestCommaJoin(unittest.TestCase):
   def test(self):
diff --git a/test/py/ganeti.utils.version_unittest.py b/test/py/ganeti.utils.version_unittest.py
new file mode 100755
index 0000000..8181411
--- /dev/null
+++ b/test/py/ganeti.utils.version_unittest.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Script for unittesting the version utility functions"""
+
+import unittest
+
+from ganeti.utils import version
+
+import testutils
+
+class ParseVersionTest(unittest.TestCase):
+    def testParseVersion(self):
+        self.assertEquals(version.ParseVersion("2.10"), (2, 10, 0))
+        self.assertEquals(version.ParseVersion("2.10.1"), (2, 10, 1))
+        self.assertEquals(version.ParseVersion("2.10.1~beta2"), (2, 10, 1))
+        self.assertEquals(version.ParseVersion("2.10.1-3"), (2, 10, 1))
+        self.assertEquals(version.ParseVersion("2"), None)
+        self.assertEquals(version.ParseVersion("pink bunny"), None)
+
+class UpgradeRangeTest(unittest.TestCase):
+    def testUpgradeRange(self):
+        self.assertEquals(version.UpgradeRange((2,11,0), current=(2,10,0)),
+                          None)
+        self.assertEquals(version.UpgradeRange((2,10,0), current=(2,10,0)),
+                          None)
+        self.assertEquals(version.UpgradeRange((2,11,3), current=(2,12,0)),
+                          None)
+        self.assertEquals(version.UpgradeRange((2,11,3), current=(2,12,99)),
+                          None)
+        self.assertEquals(version.UpgradeRange((3,0,0), current=(2,12,0)),
+                          "different major versions")
+        self.assertEquals(version.UpgradeRange((2,12,0), current=(3,0,0)),
+                          "different major versions")
+        self.assertEquals(version.UpgradeRange((2,10,0), current=(2,12,0)),
+                          "can only downgrade one minor version at a time")
+        self.assertEquals(version.UpgradeRange((2,9,0), current=(2,10,0)),
+                          "automatic upgrades only supported from 2.10 onwards")
+        self.assertEquals(version.UpgradeRange((2,10,0), current=(2,9,0)),
+                          "automatic upgrades only supported from 2.10 onwards")
+
+class ShouldCfgdowngradeTest(unittest.TestCase):
+    def testShouldCfgDowngrade(self):
+        self.assertTrue(version.ShouldCfgdowngrade((2,9,3), current=(2,10,0)))
+        self.assertTrue(version.ShouldCfgdowngrade((2,9,0), current=(2,10,4)))
+        self.assertFalse(version.ShouldCfgdowngrade((2,9,0), current=(2,11,0)))
+        self.assertFalse(version.ShouldCfgdowngrade((2,9,0), current=(3,10,0)))
+        self.assertFalse(version.ShouldCfgdowngrade((2,10,0), current=(3,10,0)))
+
+
+class IsCorrectConfigVersionTest(unittest.TestCase):
+    def testIsCorrectConfigVersion(self):
+        self.assertTrue(version.IsCorrectConfigVersion((2,10,1), (2,10,0)))
+        self.assertFalse(version.IsCorrectConfigVersion((2,11,0), (2,10,0)))
+        self.assertFalse(version.IsCorrectConfigVersion((3,10,0), (2,10,0)))
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.utils.wrapper_unittest.py b/test/py/ganeti.utils.wrapper_unittest.py
index cfdae9a..271996b 100755
--- a/test/py/ganeti.utils.wrapper_unittest.py
+++ b/test/py/ganeti.utils.wrapper_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.wrapper"""
diff --git a/test/py/ganeti.utils.x509_unittest.py b/test/py/ganeti.utils.x509_unittest.py
index a95bafc..68c8337 100755
--- a/test/py/ganeti.utils.x509_unittest.py
+++ b/test/py/ganeti.utils.x509_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.utils.x509"""
diff --git a/test/py/ganeti.utils_unittest.py b/test/py/ganeti.utils_unittest.py
index f57b713..bc6b8af 100755
--- a/test/py/ganeti.utils_unittest.py
+++ b/test/py/ganeti.utils_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the utils module"""
diff --git a/test/py/ganeti.vcluster_unittest.py b/test/py/ganeti.vcluster_unittest.py
index 0f216da..933d7f5 100755
--- a/test/py/ganeti.vcluster_unittest.py
+++ b/test/py/ganeti.vcluster_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing ganeti.vcluster"""
diff --git a/test/py/ganeti.workerpool_unittest.py b/test/py/ganeti.workerpool_unittest.py
index 8f35a69..d759526 100755
--- a/test/py/ganeti.workerpool_unittest.py
+++ b/test/py/ganeti.workerpool_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2008, 2009, 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for unittesting the workerpool module"""
diff --git a/test/py/import-export_unittest-helper b/test/py/import-export_unittest-helper
index 7d1ee96..f71eba4 100755
--- a/test/py/import-export_unittest-helper
+++ b/test/py/import-export_unittest-helper
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Helpers for testing import-export daemon"""
diff --git a/test/py/import-export_unittest.bash b/test/py/import-export_unittest.bash
index b1672b4..0124d07 100755
--- a/test/py/import-export_unittest.bash
+++ b/test/py/import-export_unittest.bash
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e
 set -o pipefail
diff --git a/test/py/lockperf.py b/test/py/lockperf.py
index 5128c33..c53e8bd 100755
--- a/test/py/lockperf.py
+++ b/test/py/lockperf.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing lock performance"""
@@ -54,7 +63,7 @@
   return (opts, args)
 
 
-class State:
+class State(object):
   def __init__(self, thread_count):
     """Initializes this class.
 
diff --git a/test/py/mocks.py b/test/py/mocks.py
index 21481de..649b032 100644
--- a/test/py/mocks.py
+++ b/test/py/mocks.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Module implementing a fake ConfigWriter"""
@@ -24,7 +33,6 @@
 
 import os
 
-from ganeti import utils
 from ganeti import netutils
 
 
@@ -37,7 +45,7 @@
                     "vYdB2nQds7/+Bf40C/OpbvnAxna1kVtgFHAo18cQ==")
 
 
-class FakeConfig:
+class FakeConfig(object):
   """Fake configuration object"""
 
   def IsCluster(self):
@@ -61,7 +69,7 @@
   def GetMasterNodeName(self):
     return netutils.Hostname.GetSysName()
 
-  def GetDefaultIAllocator(Self):
+  def GetDefaultIAllocator(self):
     return "testallocator"
 
   def GetNodeName(self, node_uuid):
@@ -74,7 +82,7 @@
     return map(self.GetNodeName, node_uuids)
 
 
-class FakeProc:
+class FakeProc(object):
   """Fake processor object"""
 
   def Log(self, msg, *args, **kwargs):
@@ -90,14 +98,14 @@
     pass
 
 
-class FakeGLM:
+class FakeGLM(object):
   """Fake global lock manager object"""
 
-  def list_owned(self, level):
+  def list_owned(self, _):
     return set()
 
 
-class FakeContext:
+class FakeContext(object):
   """Fake context object"""
 
   def __init__(self):
@@ -105,7 +113,7 @@
     self.glm = FakeGLM()
 
 
-class FakeGetentResolver:
+class FakeGetentResolver(object):
   """Fake runtime.GetentResolver"""
 
   def __init__(self):
diff --git a/test/py/pycurl_reset_unittest.py b/test/py/pycurl_reset_unittest.py
index 7e3e43b..1559c66 100755
--- a/test/py/pycurl_reset_unittest.py
+++ b/test/py/pycurl_reset_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing for an issue in PycURL"""
diff --git a/test/py/qa.qa_config_unittest.py b/test/py/qa.qa_config_unittest.py
index 4715153..71de0ba 100755
--- a/test/py/qa.qa_config_unittest.py
+++ b/test/py/qa.qa_config_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing qa.qa_config"""
diff --git a/test/py/tempfile_fork_unittest.py b/test/py/tempfile_fork_unittest.py
index 6811f76..b87bcd5 100755
--- a/test/py/tempfile_fork_unittest.py
+++ b/test/py/tempfile_fork_unittest.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Script for testing utils.ResetTempfileModule"""
diff --git a/test/py/testutils.py b/test/py/testutils.py
index 8249847..a8ac82f 100644
--- a/test/py/testutils.py
+++ b/test/py/testutils.py
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Utilities for unit testing"""
@@ -27,7 +36,6 @@
 import tempfile
 import unittest
 import logging
-import types
 
 from ganeti import utils
 
@@ -103,18 +111,6 @@
     else:
       raise Exception("Assertion not evaluated")
 
-    # The following piece of code is a backport from Python 2.6. Python 2.4/2.5
-    # only accept class instances as test runners. Being able to pass classes
-    # reduces the amount of code necessary for using a custom test runner.
-    # 2.6 and above should use their own code, however.
-    if (self.testRunner and sys.hexversion < 0x2060000 and
-        isinstance(self.testRunner, (type, types.ClassType))):
-      try:
-        self.testRunner = self.testRunner(verbosity=self.verbosity)
-      except TypeError:
-        # didn't accept the verbosity argument
-        self.testRunner = self.testRunner()
-
     return unittest.TestProgram.runTests(self)
 
 
@@ -132,7 +128,7 @@
     while self._temp_files:
       try:
         utils.RemoveFile(self._temp_files.pop())
-      except EnvironmentError, err:
+      except EnvironmentError:
         pass
 
   def assertFileContent(self, file_name, expected_content):
@@ -219,8 +215,10 @@
   """
   import mock
   try:
+    # pylint: disable=W0212
     return mock._patch_object(*args, **kwargs)
   except AttributeError:
+    # pylint: disable=E1101
     return mock.patch_object(*args, **kwargs)
 
 
diff --git a/tools/cfgshell b/tools/cfgshell
index 90b3dcf..7865501 100755
--- a/tools/cfgshell
+++ b/tools/cfgshell
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Tool to do manual changes to the config file.
diff --git a/tools/cfgupgrade b/tools/cfgupgrade
index a7be848..8f5caba 100755
--- a/tools/cfgupgrade
+++ b/tools/cfgupgrade
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Tool to upgrade the configuration file.
@@ -44,6 +53,8 @@
 from ganeti import netutils
 from ganeti import pathutils
 
+from ganeti.utils import version
+
 
 options = None
 args = None
@@ -52,11 +63,11 @@
 #: Target major version we will upgrade to
 TARGET_MAJOR = 2
 #: Target minor version we will upgrade to
-TARGET_MINOR = 9
+TARGET_MINOR = 10
 #: Target major version for downgrade
 DOWNGRADE_MAJOR = 2
 #: Target minor version for downgrade
-DOWNGRADE_MINOR = 8
+DOWNGRADE_MINOR = 9
 
 # map of legacy device types
 # (mapping differing old LD_* constants to new DT_* constants)
@@ -179,6 +190,14 @@
   return ret
 
 
+def RemovePhysicalId(disk):
+  if "children" in disk:
+    for d in disk["children"]:
+      RemovePhysicalId(d)
+  if "physical_id" in disk:
+    del disk["physical_id"]
+
+
 def ChangeDiskDevType(disk, dev_type_map):
   """Replaces disk's dev_type attributes according to the given map.
 
@@ -219,6 +238,8 @@
       raise Error("Instance '%s' doesn't have a disks entry?!" % instance)
     disks = iobj["disks"]
     for idx, dobj in enumerate(disks):
+      RemovePhysicalId(dobj)
+
       expected = "disk/%s" % idx
       current = dobj.get("iv_name", "")
       if current != expected:
@@ -370,8 +391,7 @@
 
 
 def UpgradeAll(config_data):
-  config_data["version"] = constants.BuildVersion(TARGET_MAJOR,
-                                                  TARGET_MINOR, 0)
+  config_data["version"] = version.BuildVersion(TARGET_MAJOR, TARGET_MINOR, 0)
   UpgradeRapiUsers()
   UpgradeWatcher()
   UpgradeFileStoragePaths(config_data)
@@ -383,66 +403,64 @@
   UpgradeInstanceIndices(config_data)
 
 
-def DowngradeDiskDevType(disk):
-  """Downgrades the disks' device type."""
-  ChangeDiskDevType(disk, DEV_TYPE_NEW_OLD)
+def DowngradeNDParams(ndparams):
+  for param in ["ovs", "ovs_link", "ovs_name"]:
+    if param in ndparams:
+      del ndparams[param]
 
 
-def DowngradeDisks(disks, owner):
-  for disk in disks:
-    # Remove spindles to downgrade to 2.8
-    if "spindles" in disk:
-      logging.warning("Removing spindles (value=%s) from disk %s (%s) of"
-                      " instance %s",
-                      disk["spindles"], disk["iv_name"], disk["uuid"], owner)
-      del disk["spindles"]
-    if "dev_type" in disk:
-      DowngradeDiskDevType(disk)
+def DowngradeNicParams(nicparams):
+  if "vlan" in nicparams:
+    del nicparams["vlan"]
+
+
+def DowngradeHVParams(hvparams):
+  for hv in ["xen-pvm", "xen-hvm"]:
+    if hv not in hvparams:
+      continue
+    for param in ["cpuid", "soundhw"]:
+      if param in hvparams[hv]:
+        del hvparams[hv][param]
+
+
+def DowngradeCluster(config_data):
+  cluster = config_data["cluster"]
+  if "ndparams" in cluster:
+    DowngradeNDParams(cluster["ndparams"])
+  if "nicparams" in cluster:
+    DowngradeNicParams(cluster["nicparams"][constants.PP_DEFAULT])
+  if "hvparams" in cluster:
+    DowngradeHVParams(cluster["hvparams"])
+
+
+def DowngradeNodeGroups(config_data):
+  for (_, ngobj) in config_data["nodegroups"].items():
+    if "ndparams" in ngobj:
+      DowngradeNDParams(ngobj["ndparams"])
+
+
+def DowngradeNodes(config_data):
+  for (_, nobj) in config_data["nodes"].items():
+    if "ndparams" in nobj:
+      DowngradeNDParams(nobj["ndparams"])
 
 
 def DowngradeInstances(config_data):
-  if "instances" not in config_data:
-    raise Error("Cannot find the 'instances' key in the configuration!")
-  for (iname, iobj) in config_data["instances"].items():
-    if "disks" not in iobj:
-      raise Error("Cannot find 'disks' key for instance %s" % iname)
-    DowngradeDisks(iobj["disks"], iname)
-
-
-def DowngradeNodeIndices(config_data):
-  ChangeNodeIndices(config_data, "uuid", "name")
-
-
-def DowngradeInstanceIndices(config_data):
-  ChangeInstanceIndices(config_data, "uuid", "name")
-
-
-def DowngradeHvparams(config_data):
-  """Downgrade the cluster's hypervisor parameters."""
-  cluster = config_data["cluster"]
-  if "hvparams" in cluster:
-    hvparams = cluster["hvparams"]
-    xen_params = None
-    for xen_variant in [constants.HT_XEN_PVM, constants.HT_XEN_HVM]:
-      if xen_variant in hvparams:
-        xen_params = hvparams[xen_variant]
-        # 'xen_cmd' was introduced in 2.9
-        if constants.HV_XEN_CMD in xen_params:
-          del xen_params[constants.HV_XEN_CMD]
-        # 'vif_script' was introducted in 2.9
-        if constants.HV_VIF_SCRIPT in xen_params:
-          del xen_params[constants.HV_VIF_SCRIPT]
+  for (_, iobj) in config_data["instances"].items():
+    DowngradeHVParams(iobj["hvparams"])
+    for nic in iobj["nics"]:
+      DowngradeNicParams(nic["nicparams"])
 
 
 def DowngradeAll(config_data):
   # Any code specific to a particular version should be labeled that way, so
   # it can be removed when updating to the next version.
-  config_data["version"] = constants.BuildVersion(DOWNGRADE_MAJOR,
-                                                  DOWNGRADE_MINOR, 0)
+  config_data["version"] = version.BuildVersion(DOWNGRADE_MAJOR,
+                                                DOWNGRADE_MINOR, 0)
+  DowngradeCluster(config_data)
+  DowngradeNodeGroups(config_data)
+  DowngradeNodes(config_data)
   DowngradeInstances(config_data)
-  DowngradeNodeIndices(config_data)
-  DowngradeInstanceIndices(config_data)
-  DowngradeHvparams(config_data)
 
 
 def main():
@@ -543,7 +561,7 @@
     raise Error("Unable to determine configuration version")
 
   (config_major, config_minor, config_revision) = \
-    constants.SplitVersion(config_version)
+    version.SplitVersion(config_version)
 
   logging.info("Found configuration version %s (%d.%d.%d)",
                config_version, config_major, config_minor, config_revision)
@@ -563,7 +581,7 @@
                    config_minor, config_revision))
     DowngradeAll(config_data)
 
-  # Upgrade from 2.{0..7} to 2.9
+  # Upgrade from 2.{0..9} to 2.10
   elif config_major == 2 and config_minor in range(0, 10):
     if config_revision != 0:
       logging.warning("Config revision is %s, not 0", config_revision)
diff --git a/tools/cfgupgrade12 b/tools/cfgupgrade12
index dd76ce9..f706fd5 100755
--- a/tools/cfgupgrade12
+++ b/tools/cfgupgrade12
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2007, 2008, 2009 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # pylint: disable=C0103,E1103
 
@@ -49,6 +58,8 @@
 from ganeti import cli
 from ganeti import pathutils
 
+from ganeti.utils import version
+
 
 options = None
 args = None
@@ -345,7 +356,7 @@
       raise Error("Unsupported configuration version: %s" %
                   old_config_version)
     if "version" not in config_data:
-      config_data["version"] = constants.BuildVersion(2, 0, 0)
+      config_data["version"] = version.BuildVersion(2, 0, 0)
     if F_SERIAL not in config_data:
       config_data[F_SERIAL] = 1
 
diff --git a/tools/check-cert-expired b/tools/check-cert-expired
index 32580bd..c7bb32c 100755
--- a/tools/check-cert-expired
+++ b/tools/check-cert-expired
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Tool to detect expired X509 certificates.
 
diff --git a/tools/cluster-merge b/tools/cluster-merge
index f6bd138..926b705 100755
--- a/tools/cluster-merge
+++ b/tools/cluster-merge
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Tool to merge two or more clusters together.
 
@@ -400,10 +409,6 @@
             logical_id[2] = port
             dsk.logical_id = tuple(logical_id)
 
-            physical_id = list(dsk.physical_id)
-            physical_id[1] = physical_id[3] = port
-            dsk.physical_id = tuple(physical_id)
-
         my_config.AddInstance(instance_info,
                               _CLUSTERMERGE_ECID + str(fake_ec_id))
         fake_ec_id += 1
diff --git a/tools/confd-client b/tools/confd-client
index e6755ab..67b8b2d 100755
--- a/tools/confd-client
+++ b/tools/confd-client
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # pylint: disable=C0103
 
@@ -213,9 +222,9 @@
       {"type": constants.CONFD_REQ_CLUSTER_MASTER},
       {"type": constants.CONFD_REQ_CLUSTER_MASTER,
        "query": {constants.CONFD_REQQ_FIELDS:
-                 [constants.CONFD_REQFIELD_NAME,
-                  constants.CONFD_REQFIELD_IP,
-                  constants.CONFD_REQFIELD_MNODE_PIP,
+                 [str(constants.CONFD_REQFIELD_NAME),
+                  str(constants.CONFD_REQFIELD_IP),
+                  str(constants.CONFD_REQFIELD_MNODE_PIP),
                   ]}},
       {"type": constants.CONFD_REQ_NODE_ROLE_BYNAME},
       {"type": constants.CONFD_REQ_NODE_PIP_LIST},
diff --git a/tools/fmtjson b/tools/fmtjson
index 0bfb114..0da3407 100755
--- a/tools/fmtjson
+++ b/tools/fmtjson
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Tool to format JSON data.
 
diff --git a/tools/ganeti-listrunner b/tools/ganeti-listrunner
index 061d53b..453e1bb 100755
--- a/tools/ganeti-listrunner
+++ b/tools/ganeti-listrunner
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Run an executable on a list of hosts.
 
diff --git a/tools/kvm-console-wrapper b/tools/kvm-console-wrapper
index 7e65566..193209d 100755
--- a/tools/kvm-console-wrapper
+++ b/tools/kvm-console-wrapper
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 SOCAT="$1"
 INSTANCE="$2"
diff --git a/tools/kvm-ifup.in b/tools/kvm-ifup.in
index 5a53cee..aba63dd 100644
--- a/tools/kvm-ifup.in
+++ b/tools/kvm-ifup.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 source @PKGLIBDIR@/net-common
 
diff --git a/tools/lvmstrap b/tools/lvmstrap
index 47a710f..8f08ab0 100755
--- a/tools/lvmstrap
+++ b/tools/lvmstrap
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Program which configures LVM on the Ganeti nodes.
diff --git a/tools/master-ip-setup b/tools/master-ip-setup
index 5d9c4fc..a4ee7db 100755
--- a/tools/master-ip-setup
+++ b/tools/master-ip-setup
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u
 
diff --git a/tools/move-instance b/tools/move-instance
index b0b8a3e..1a9afa4 100755
--- a/tools/move-instance
+++ b/tools/move-instance
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010, 2011, 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 """Tool to move instances from one cluster to another.
 
@@ -110,6 +119,11 @@
                  help=("Secondary node on destination cluster (only"
                        " when moving exactly one instance)"))
 
+DEST_DISK_TEMPLATE_OPT = \
+  cli.cli_option("--dest-disk-template", action="store", type="string",
+                 dest="dest_disk_template", default=None,
+                 help="Disk template to use on destination cluster")
+
 PARALLEL_OPT = \
   cli.cli_option("-p", "--parallel", action="store", type="int", default=1,
                  dest="parallel", metavar="<number>",
@@ -128,7 +142,7 @@
   """
 
 
-class RapiClientFactory:
+class RapiClientFactory(object):
   """Factory class for creating RAPI clients.
 
   @ivar src_cluster_name: Source cluster name
@@ -272,7 +286,8 @@
   """
   def __init__(self, src_instance_name, dest_instance_name,
                dest_pnode, dest_snode, dest_iallocator,
-               hvparams, beparams, osparams, nics):
+               dest_disk_template, hvparams,
+               beparams, osparams, nics):
     """Initializes this class.
 
     @type src_instance_name: string
@@ -285,6 +300,8 @@
     @param dest_snode: Name of secondary node on destination cluster
     @type dest_iallocator: string or None
     @param dest_iallocator: Name of iallocator to use
+    @type dest_disk_template: string or None
+    @param dest_disk_template: Disk template to use instead of the original one
     @type hvparams: dict or None
     @param hvparams: Hypervisor parameters to override
     @type beparams: dict or None
@@ -300,6 +317,7 @@
     self.dest_pnode = dest_pnode
     self.dest_snode = dest_snode
     self.dest_iallocator = dest_iallocator
+    self.dest_disk_template = dest_disk_template
     self.hvparams = hvparams
     self.beparams = beparams
     self.osparams = osparams
@@ -429,9 +447,10 @@
     job_id = self._CreateInstance(dest_client, mrt.move.dest_instance_name,
                                   mrt.move.dest_pnode, mrt.move.dest_snode,
                                   mrt.move.dest_iallocator,
+                                  mrt.move.dest_disk_template,
                                   mrt.src_instinfo, mrt.src_expinfo,
                                   mrt.move.hvparams, mrt.move.beparams,
-                                  mrt.move.beparams, mrt.move.nics)
+                                  mrt.move.osparams, mrt.move.nics)
     mrt.PollJob(dest_client, job_id,
                 remote_import_fn=compat.partial(self._SetImportInfo, mrt))
 
@@ -454,9 +473,9 @@
       mrt.dest_to_source.release()
 
   @staticmethod
-  def _CreateInstance(cl, name, pnode, snode, iallocator, instance, expinfo,
-                      override_hvparams, override_beparams, override_osparams,
-                      override_nics):
+  def _CreateInstance(cl, name, pnode, snode, iallocator, dest_disk_template,
+                      instance, expinfo, override_hvparams, override_beparams,
+                      override_osparams, override_nics):
     """Starts the instance creation in remote import mode.
 
     @type cl: L{rapi.client.GanetiRapiClient}
@@ -469,6 +488,8 @@
     @param snode: Name of secondary node on destination cluster
     @type iallocator: string or None
     @param iallocator: Name of iallocator to use
+    @type dest_disk_template: string or None
+    @param dest_disk_template: Disk template to use instead of the original one
     @type instance: dict
     @param instance: Instance details from source cluster
     @type expinfo: dict
@@ -484,7 +505,10 @@
     @return: Job ID
 
     """
-    disk_template = instance["disk_template"]
+    if dest_disk_template:
+      disk_template = dest_disk_template
+    else:
+      disk_template = instance["disk_template"]
 
     disks = []
     for idisk in instance["disks"]:
@@ -503,9 +527,11 @@
       constants.INIC_MAC: mac,
       constants.INIC_MODE: mode,
       constants.INIC_LINK: link,
+      constants.INIC_VLAN: vlan,
       constants.INIC_NETWORK: network,
       constants.INIC_NAME: nic_name
-      } for nic_name, _, ip, mac, mode, link, network, _ in instance["nics"]]
+      } for nic_name, _, ip, mac, mode, link, vlan, network, _
+        in instance["nics"]]
 
     if len(override_nics) > len(nics):
       raise Error("Can not create new NICs")
@@ -759,6 +785,7 @@
   parser.add_option(DEST_INSTANCE_NAME_OPT)
   parser.add_option(DEST_PRIMARY_NODE_OPT)
   parser.add_option(DEST_SECONDARY_NODE_OPT)
+  parser.add_option(DEST_DISK_TEMPLATE_OPT)
   parser.add_option(PARALLEL_OPT)
 
   (options, args) = parser.parse_args()
@@ -787,17 +814,12 @@
   if options.parallel < 1:
     parser.error("Number of simultaneous moves must be >= 1")
 
-  if not (bool(options.iallocator) ^
-          bool(options.dest_primary_node or options.dest_secondary_node)):
+  if (bool(options.iallocator) and
+      bool(options.dest_primary_node or options.dest_secondary_node)):
     parser.error("Destination node and iallocator options exclude each other")
 
   if len(instance_names) == 1:
     # Moving one instance only
-    if not (options.iallocator or
-            options.dest_primary_node or
-            options.dest_secondary_node):
-      parser.error("An iallocator or the destination node is required")
-
     if options.hvparams:
       utils.ForceDictType(options.hvparams, constants.HVS_PARAMETER_TYPES)
 
@@ -816,13 +838,26 @@
                    " --backend-parameters, --os-parameters and --net can"
                    " only be used when moving exactly one instance")
 
-    if not options.iallocator:
-      parser.error("An iallocator must be specified for moving more than one"
-                   " instance")
-
   return (src_cluster_name, dest_cluster_name, instance_names)
 
 
+def DestClusterHasDefaultIAllocator(rapi_factory):
+  """Determines if a given cluster has a default iallocator.
+
+  """
+  result = rapi_factory.GetDestClient().GetInfo()
+  ia_name = "default_iallocator"
+  return ia_name in result and result[ia_name]
+
+
+def ExitWithError(message):
+  """Exits after an error and shows a message.
+
+  """
+  sys.stderr.write("move-instance: error: " + message + "\n")
+  sys.exit(constants.EXIT_FAILURE)
+
+
 @UsesRapiClient
 def main():
   """Main routine.
@@ -843,14 +878,17 @@
 
   CheckRapiSetup(rapi_factory)
 
-  assert (len(instance_names) == 1 or
-          not (options.dest_primary_node or options.dest_secondary_node))
-  assert len(instance_names) == 1 or options.iallocator
-  assert (len(instance_names) > 1 or options.iallocator or
-          options.dest_primary_node or options.dest_secondary_node)
-  assert (len(instance_names) == 1 or
-          not (options.hvparams or options.beparams or options.osparams or
-               options.nics))
+  has_iallocator = options.iallocator or \
+                   DestClusterHasDefaultIAllocator(rapi_factory)
+
+  if len(instance_names) > 1 and not has_iallocator:
+    ExitWithError("When moving multiple nodes, an iallocator must be used. "
+                  "None was provided and the target cluster does not have "
+                  "a default iallocator.")
+  if (len(instance_names) == 1 and not (has_iallocator or
+      options.dest_primary_node or options.dest_secondary_node)):
+    ExitWithError("Target cluster does not have a default iallocator, "
+                  "please specify either destination nodes or an iallocator.")
 
   # Prepare list of instance moves
   moves = []
@@ -865,8 +903,11 @@
     moves.append(InstanceMove(src_instance_name, dest_instance_name,
                               options.dest_primary_node,
                               options.dest_secondary_node,
-                              options.iallocator, options.hvparams,
-                              options.beparams, options.osparams,
+                              options.iallocator,
+                              options.dest_disk_template,
+                              options.hvparams,
+                              options.beparams,
+                              options.osparams,
                               options.nics))
 
   assert len(moves) == len(instance_names)
diff --git a/tools/net-common.in b/tools/net-common.in
index 7305875..bc768cc 100644
--- a/tools/net-common.in
+++ b/tools/net-common.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 @SHELL_ENV_INIT@
 
@@ -60,6 +69,11 @@
 
 function setup_ovs {
   if [ "$MODE" = "openvswitch" ]; then
+    # Remove stale port
+    ovs-vsctl del-port $INTERFACE || true
+    # Bring interface up
+    ip link set $INTERFACE up
+    # Add port
     ovs-vsctl add-port ${LINK} $INTERFACE
   fi
 }
diff --git a/tools/ovfconverter b/tools/ovfconverter
index fd7aa43..ba437c7 100755
--- a/tools/ovfconverter
+++ b/tools/ovfconverter
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Tool to translate between ovf and ganeti backup format.
diff --git a/tools/post-upgrade b/tools/post-upgrade
new file mode 100644
index 0000000..55ac7a0
--- /dev/null
+++ b/tools/post-upgrade
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2014 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+# pylint: disable=C0103
+
+"""Hook to be run after upgrading to this version.
+
+"""
+
+
+def main():
+  """Main program.
+
+  """
+  return 0
+
+if __name__ == "__main__":
+  exit(main())
diff --git a/tools/sanitize-config b/tools/sanitize-config
index 5556ffb..af3cd65 100755
--- a/tools/sanitize-config
+++ b/tools/sanitize-config
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2010 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 # pylint: disable=C0103
@@ -199,13 +208,8 @@
       for child in disk["children"]:
         helper(child)
 
-    if disk["dev_type"] == constants.DT_DRBD8:
-      if "physical_id" in disk:
-        del disk["physical_id"]
-
     if disk["dev_type"] == constants.DT_PLAIN and opts.sanitize_lvs:
       disk["logical_id"][1] = _Get(disk["logical_id"][1])
-      disk["physical_id"][1] = disk["logical_id"][1]
 
   lv_map = {}
 
@@ -279,9 +283,7 @@
 
   config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
 
-  # first, do some disk cleanup: remove DRBD physical_ids, since it
-  # contains both IPs (which we want changed) and the DRBD secret, and
-  # it's not needed for normal functioning, and randomize LVM names
+  # Randomize LVM names
   SanitizeDisks(opts, config_data)
 
   SanitizeSecrets(opts, config_data)
diff --git a/tools/vcluster-setup.in b/tools/vcluster-setup.in
index 081cc65..36fce60 100644
--- a/tools/vcluster-setup.in
+++ b/tools/vcluster-setup.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2012 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 set -e -u -o pipefail
 shopt -s extglob
diff --git a/tools/vif-ganeti.in b/tools/vif-ganeti.in
index 99b2460..6af4748 100755
--- a/tools/vif-ganeti.in
+++ b/tools/vif-ganeti.in
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011, 2012, 2013 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 if [ -x "@XEN_CONFIG_DIR@/scripts/vif-custom" ]; then
diff --git a/tools/xen-console-wrapper b/tools/xen-console-wrapper
index f3d015a..fb6f986 100755
--- a/tools/xen-console-wrapper
+++ b/tools/xen-console-wrapper
@@ -2,21 +2,30 @@
 #
 
 # Copyright (C) 2011 Google Inc.
+# All rights reserved.
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 XEN_CMD="$1"
 INSTANCE="$2"