Merge branch 'stable-2.12' into stable-2.13
authorHrvoje Ribicic <riba@google.com>
Thu, 3 Dec 2015 21:13:39 +0000 (21:13 +0000)
committerHrvoje Ribicic <riba@google.com>
Thu, 3 Dec 2015 21:13:39 +0000 (21:13 +0000)
* stable-2.12
  Restrict showing of DRBD secret using types
  Calculate correct affected nodes set in InstanceChangeGroup

* stable-2.11
  (no changes)

* stable-2.10
  (no changes)

* stable-2.9
  QA: Ensure the DRBD secret is not retrievable via RAPI
  Redact the DRBD secret in instance queries
  Do not attempt to use the DRBD secret in gnt-instance info

Signed-off-by: Hrvoje Ribicic <riba@google.com>
Reviewed-by: Oleg Ponomarev <oponomarev@google.com>

267 files changed:
.gitignore
INSTALL
Makefile.am
NEWS
README
UPGRADE
autotools/build-bash-completion
configure.ac
daemons/daemon-util.in
devel/build_chroot
doc/admin.rst
doc/design-2.13.rst [copied from doc/design-2.12.rst with 68% similarity]
doc/design-configlock.rst [new file with mode: 0644]
doc/design-daemons.rst
doc/design-disk-conversion.rst [new file with mode: 0644]
doc/design-disks.rst
doc/design-draft.rst
doc/design-hotplug.rst
doc/design-ifdown.rst [new file with mode: 0644]
doc/design-location.rst [new file with mode: 0644]
doc/design-network2.rst [new file with mode: 0644]
doc/design-node-add.rst
doc/design-node-security.rst
doc/design-optables.rst
doc/design-reservations.rst [new file with mode: 0644]
doc/design-shared-storage.rst
doc/design-sync-rate-throttling.rst [new file with mode: 0644]
doc/hooks.rst
doc/iallocator.rst
doc/index.rst
doc/rapi.rst
doc/security.rst
doc/virtual-cluster.rst
lib/backend.py
lib/bootstrap.py
lib/build/sphinx_ext.py
lib/cli.py
lib/cli_opts.py [new file with mode: 0644]
lib/client/gnt_cluster.py
lib/client/gnt_debug.py
lib/client/gnt_filter.py [new file with mode: 0644]
lib/client/gnt_instance.py
lib/client/gnt_job.py
lib/client/gnt_node.py
lib/client/gnt_os.py
lib/cmdlib/base.py
lib/cmdlib/cluster.py
lib/cmdlib/common.py
lib/cmdlib/group.py
lib/cmdlib/instance.py
lib/cmdlib/instance_migration.py
lib/cmdlib/instance_query.py
lib/cmdlib/instance_storage.py
lib/cmdlib/instance_utils.py
lib/cmdlib/node.py
lib/cmdlib/test.py
lib/config.py
lib/constants.py
lib/errors.py
lib/hypervisor/hv_base.py
lib/hypervisor/hv_kvm/__init__.py
lib/hypervisor/hv_kvm/monitor.py
lib/hypervisor/hv_kvm/netdev.py
lib/hypervisor/hv_lxc.py
lib/hypervisor/hv_xen.py
lib/jqueue/__init__.py
lib/luxi.py
lib/masterd/iallocator.py
lib/masterd/instance.py
lib/mcpu.py
lib/objects.py
lib/pathutils.py
lib/qlang.py
lib/query.py
lib/rapi/client.py
lib/rapi/connector.py
lib/rapi/rlib2.py
lib/rpc_defs.py
lib/server/noded.py
lib/ssh.py
lib/storage/base.py
lib/storage/bdev.py
lib/storage/drbd.py
lib/storage/extstorage.py [new file with mode: 0644]
lib/tools/burnin.py
lib/tools/common.py
lib/tools/node_daemon_setup.py
lib/tools/prepare_node_join.py
lib/tools/ssh_update.py [new file with mode: 0644]
lib/tools/ssl_update.py
lib/utils/__init__.py
lib/utils/bitarrays.py [copied from tools/post-upgrade with 56% similarity]
lib/utils/io.py
lib/utils/process.py
lib/utils/retry.py
lib/utils/storage.py
lib/watcher/__init__.py
lib/watcher/nodemaint.py
man/ganeti-extstorage-interface.rst
man/ganeti-watcher.rst
man/ganeti.rst
man/gnt-backup.rst
man/gnt-cluster.rst
man/gnt-debug.rst
man/gnt-filter.rst [new file with mode: 0644]
man/gnt-group.rst
man/gnt-instance.rst
man/gnt-job.rst
man/gnt-network.rst
man/gnt-node.rst
man/gnt-os.rst
man/hail.rst
man/hbal.rst
man/hcheck.rst
man/hsqueeze.rst
man/htools.rst
pylintrc
qa/ganeti-qa.py
qa/qa-sample.json
qa/qa_cluster.py
qa/qa_config.py
qa/qa_env.py
qa/qa_filters.py [new file with mode: 0644]
qa/qa_instance.py
qa/qa_iptables.py
qa/qa_job.py
qa/qa_job_utils.py
qa/qa_node.py
qa/qa_performance.py
qa/qa_rapi.py
qa/qa_utils.py
qa/rapi-workload.py [deleted file]
src/AutoConf.hs.in
src/Ganeti/BasicTypes.hs
src/Ganeti/Compat.hs
src/Ganeti/Confd/Server.hs
src/Ganeti/Confd/Types.hs
src/Ganeti/Confd/Utils.hs
src/Ganeti/Config.hs
src/Ganeti/ConfigReader.hs
src/Ganeti/Constants.hs
src/Ganeti/DataCollectors.hs [new file with mode: 0644]
src/Ganeti/DataCollectors/CPUload.hs
src/Ganeti/DataCollectors/Diskstats.hs
src/Ganeti/DataCollectors/Drbd.hs
src/Ganeti/DataCollectors/InstStatus.hs
src/Ganeti/DataCollectors/Lv.hs
src/Ganeti/DataCollectors/Types.hs
src/Ganeti/HTools/AlgorithmParams.hs [new file with mode: 0644]
src/Ganeti/HTools/Backend/IAlloc.hs
src/Ganeti/HTools/CLI.hs
src/Ganeti/HTools/Cluster.hs
src/Ganeti/HTools/ExtLoader.hs
src/Ganeti/HTools/Loader.hs
src/Ganeti/HTools/Node.hs
src/Ganeti/HTools/Program/Hail.hs
src/Ganeti/HTools/Program/Harep.hs
src/Ganeti/HTools/Program/Hbal.hs
src/Ganeti/HTools/Program/Hcheck.hs
src/Ganeti/HTools/Program/Hspace.hs
src/Ganeti/HTools/Program/Hsqueeze.hs
src/Ganeti/HTools/Tags.hs [new file with mode: 0644]
src/Ganeti/HTools/Types.hs
src/Ganeti/JQScheduler.hs
src/Ganeti/JQScheduler/Filtering.hs [new file with mode: 0644]
src/Ganeti/JQScheduler/ReasonRateLimiting.hs [new file with mode: 0644]
src/Ganeti/JQScheduler/Types.hs [copied from src/Ganeti/DataCollectors/InstStatusTypes.hs with 58% similarity]
src/Ganeti/JQueue.hs
src/Ganeti/JQueue/Objects.hs
src/Ganeti/JSON.hs
src/Ganeti/Jobs.hs
src/Ganeti/Logging.hs
src/Ganeti/Logging/WriterLog.hs
src/Ganeti/Luxi.hs
src/Ganeti/Monitoring/Server.hs
src/Ganeti/Objects.hs
src/Ganeti/Objects/BitArray.hs
src/Ganeti/OpCodes.hs
src/Ganeti/OpParams.hs
src/Ganeti/Query/Exec.hs
src/Ganeti/Query/Filter.hs
src/Ganeti/Query/FilterRules.hs [new file with mode: 0644]
src/Ganeti/Query/Language.hs
src/Ganeti/Query/Query.hs
src/Ganeti/Query/Server.hs
src/Ganeti/SlotMap.hs [new file with mode: 0644]
src/Ganeti/Storage/Lvm/LVParser.hs
src/Ganeti/THH.hs
src/Ganeti/THH/HsRPC.hs
src/Ganeti/THH/Types.hs
src/Ganeti/Types.hs
src/Ganeti/Utils.hs
src/Ganeti/Utils/Monad.hs [moved from src/Ganeti/Utils/MonadPlus.hs with 64% similarity]
src/Ganeti/Utils/Validate.hs
src/Ganeti/WConfd/Client.hs
src/Ganeti/WConfd/Core.hs
src/Ganeti/WConfd/DeathDetection.hs
src/Ganeti/WConfd/Monad.hs
src/Ganeti/WConfd/Persistent.hs
src/Ganeti/WConfd/Server.hs
src/Ganeti/WConfd/TempRes.hs
src/ganeti-mond.hs
test/data/cgroup_root/cpuset/some_group/lxc/instance1/cpuset.cpus [new file with mode: 0644]
test/data/cgroup_root/devices/some_group/lxc/instance1/devices.list [new file with mode: 0644]
test/data/cgroup_root/memory/lxc/instance1/memory.limit_in_bytes [new file with mode: 0644]
test/data/cluster_config_2.12.json [copied from test/data/cluster_config_2.8.json with 80% similarity]
test/data/htools/hail-alloc-plain-tags.json [new file with mode: 0644]
test/data/htools/hail-reloc-drbd-crowded.json [copied from test/data/htools/hail-reloc-drbd.json with 99% similarity]
test/data/htools/hbal-migration-1.data [new file with mode: 0644]
test/data/htools/hbal-migration-2.data [new file with mode: 0644]
test/data/htools/hbal-migration-3.data [new file with mode: 0644]
test/data/htools/hbal-soft-errors.data [new file with mode: 0644]
test/data/proc_cgroup.txt [new file with mode: 0644]
test/hs/Test/Ganeti/HTools/Backend/Text.hs
test/hs/Test/Ganeti/HTools/Cluster.hs
test/hs/Test/Ganeti/JQScheduler.hs [new file with mode: 0644]
test/hs/Test/Ganeti/JQueue.hs
test/hs/Test/Ganeti/JQueue/Objects.hs [copied from src/hconfd.hs with 54% similarity]
test/hs/Test/Ganeti/JSON.hs
test/hs/Test/Ganeti/Luxi.hs
test/hs/Test/Ganeti/Objects.hs
test/hs/Test/Ganeti/OpCodes.hs
test/hs/Test/Ganeti/Query/Language.hs
test/hs/Test/Ganeti/SlotMap.hs [new file with mode: 0644]
test/hs/Test/Ganeti/TestCommon.hs
test/hs/Test/Ganeti/Types.hs
test/hs/Test/Ganeti/Utils.hs
test/hs/Test/Ganeti/Utils/Statistics.hs
test/hs/htest.hs
test/hs/shelltests/htools-balancing.test
test/hs/shelltests/htools-hail.test
test/hs/shelltests/htools-hbal.test [new file with mode: 0644]
test/py/cfgupgrade_unittest.py
test/py/cmdlib/cluster_unittest.py
test/py/cmdlib/instance_storage_unittest.py
test/py/cmdlib/instance_unittest.py
test/py/cmdlib/test_unittest.py
test/py/cmdlib/testsupport/config_mock.py
test/py/daemon-util_unittest.bash
test/py/docs_unittest.py
test/py/ganeti.backend_unittest.py
test/py/ganeti.cli_opts_unittest.py [new file with mode: 0644]
test/py/ganeti.cli_unittest.py
test/py/ganeti.client.gnt_cluster_unittest.py
test/py/ganeti.client.gnt_job_unittest.py
test/py/ganeti.config_unittest.py
test/py/ganeti.hypervisor.hv_lxc_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py
test/py/ganeti.mcpu_unittest.py
test/py/ganeti.qlang_unittest.py
test/py/ganeti.query_unittest.py
test/py/ganeti.rapi.client_unittest.py
test/py/ganeti.rapi.testutils_unittest.py
test/py/ganeti.ssh_unittest.py
test/py/ganeti.storage.bdev_unittest.py
test/py/ganeti.tools.common_unittest.py
test/py/ganeti.tools.node_daemon_setup_unittest.py
test/py/ganeti.tools.prepare_node_join_unittest.py
test/py/ganeti.tools.ssh_update_unittest.py [new file with mode: 0755]
test/py/ganeti.utils.bitarrays_unittest.py [copied from test/py/ganeti.utils.mlock_unittest.py with 62% similarity]
test/py/ganeti.utils.io_unittest.py
test/py/ganeti.utils.process_unittest.py
test/py/testutils.py
test/py/testutils_ssh.py [new file with mode: 0644]
tools/cfgupgrade
tools/post-upgrade
tools/query-config [new file with mode: 0755]

index d6344d6..2c03279 100644 (file)
 /tools/node-daemon-setup
 /tools/prepare-node-join
 /tools/shebang/
+/tools/ssh-update
 /tools/ssl-update
 
 # scripts
 /scripts/gnt-backup
 /scripts/gnt-cluster
 /scripts/gnt-debug
+/scripts/gnt-filter
 /scripts/gnt-group
 /scripts/gnt-instance
 /scripts/gnt-job
diff --git a/INSTALL b/INSTALL
index b2afdf5..05b24e2 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -159,7 +159,7 @@ deploy Ganeti on production machines). More specifically:
 - `lifted-base <http://hackage.haskell.org/package/lifted-base>`_,
   version 0.1.1 and above.
 - `lens <http://hackage.haskell.org/package/lens>`_,
-  version 3.0 and above.
+  version 3.10 and above.
 
 Some of these are also available as package in Debian/Ubuntu::
 
@@ -228,21 +228,23 @@ Optionally, more functionality can be enabled if your build machine has
 a few more Haskell libraries enabled: the ``ganeti-confd`` daemon
 (``--enable-confd``), the monitoring daemon (``--enable-monitoring``) and
 the meta-data daemon (``--enable-metadata``).
-The extra dependency for these is:
+The extra dependencies for these are:
 
 - `snap-server` <http://hackage.haskell.org/package/snap-server>`_, version
   0.8.1 and above.
+- `PSQueue <http://hackage.haskell.org/package/PSQueue>`_,
+  version 1.0 and above.
 
-This library is available in Debian Wheezy or later, so you can use
+These libraries are available in Debian Wheezy or later, so you can use
 either apt::
 
-  $ apt-get install libghc-snap-server-dev
+  $ apt-get install libghc-snap-server-dev libghc-psqueue-dev
 
 or ``cabal``::
 
-  $ cabal install snap-server
+  $ cabal install snap-server PSQueue
 
-to install it.
+to install them.
 
 .. _cabal-note:
 .. note::
index 265de5f..a30d82e 100644 (file)
@@ -5,6 +5,12 @@
 # - All directories must be listed DIRS.
 # - Use autogen.sh to generate Makefile.in and configure script.
 
+# For distcheck we need the haskell tests to be enabled. Note:
+# The "correct" way would be to define AM_DISTCHECK_CONFIGURE_FLAGS.
+# However, we still have to support older versions of autotools,
+# so we cannot use that yet, hence we fall back to..
+DISTCHECK_CONFIGURE_FLAGS = --enable-haskell-tests
+
 # Automake doesn't export these variables before version 1.10.
 abs_top_builddir = @abs_top_builddir@
 abs_top_srcdir = @abs_top_srcdir@
@@ -20,6 +26,8 @@ strip_hsroot = $(patsubst src/%,%,$(patsubst test/hs/%,%,$(1)))
 # Use bash in order to be able to use pipefail
 SHELL=/bin/bash
 
+EXTRA_DIST=
+
 # Enable colors in shelltest
 SHELLTESTARGS = "-c"
 
@@ -135,6 +143,7 @@ HS_DIRS = \
        src/Ganeti/HTools/Program \
        src/Ganeti/Hypervisor \
        src/Ganeti/Hypervisor/Xen \
+       src/Ganeti/JQScheduler \
        src/Ganeti/JQueue \
        src/Ganeti/Locking \
        src/Ganeti/Logging \
@@ -162,6 +171,7 @@ HS_DIRS = \
        test/hs/Test/Ganeti/HTools/Backend \
        test/hs/Test/Ganeti/Hypervisor \
        test/hs/Test/Ganeti/Hypervisor/Xen \
+       test/hs/Test/Ganeti/JQueue \
        test/hs/Test/Ganeti/Locking \
        test/hs/Test/Ganeti/Objects \
        test/hs/Test/Ganeti/Query \
@@ -216,6 +226,18 @@ DIRS = \
        test/data/bdev-rbd \
        test/data/ovfdata \
        test/data/ovfdata/other \
+       test/data/cgroup_root \
+        test/data/cgroup_root/memory \
+        test/data/cgroup_root/memory/lxc \
+        test/data/cgroup_root/memory/lxc/instance1 \
+        test/data/cgroup_root/cpuset \
+        test/data/cgroup_root/cpuset/some_group \
+        test/data/cgroup_root/cpuset/some_group/lxc \
+        test/data/cgroup_root/cpuset/some_group/lxc/instance1 \
+        test/data/cgroup_root/devices \
+        test/data/cgroup_root/devices/some_group \
+        test/data/cgroup_root/devices/some_group/lxc \
+        test/data/cgroup_root/devices/some_group/lxc/instance1 \
        test/py \
        test/py/cmdlib \
        test/py/cmdlib/testsupport \
@@ -302,6 +324,8 @@ CLEANFILES = \
        tools/users-setup \
        tools/ssl-update \
        tools/vcluster-setup \
+       tools/prepare-node-join \
+       tools/ssh-update \
        $(python_scripts_shebang) \
        stamp-directories \
        stamp-srclinks \
@@ -327,10 +351,8 @@ GENERATED_FILES = \
 clean-local:
        rm -rf tools/shebang
 
-HS_GENERATED_FILES = $(HS_PROGS) src/hluxid src/ganeti-luxid
-if ENABLE_CONFD
-HS_GENERATED_FILES += src/hconfd src/ganeti-confd
-endif
+HS_GENERATED_FILES = $(HS_PROGS) src/hluxid src/ganeti-luxid \
+       src/hconfd src/ganeti-confd
 if ENABLE_MOND
 HS_GENERATED_FILES += src/ganeti-mond
 endif
@@ -376,7 +398,7 @@ BUILT_EXAMPLES = \
        doc/examples/systemd/ganeti-rapi.service \
        doc/examples/systemd/ganeti-wconfd.service
 
-dist_ifup_SCRIPTS = \
+nodist_ifup_SCRIPTS = \
        tools/kvm-ifup-os \
        tools/xen-ifup-os
 
@@ -404,6 +426,7 @@ pkgpython_PYTHON = \
        lib/backend.py \
        lib/bootstrap.py \
        lib/cli.py \
+       lib/cli_opts.py \
        lib/compat.py \
        lib/config.py \
        lib/constants.py \
@@ -446,7 +469,8 @@ client_PYTHON = \
        lib/client/gnt_node.py \
        lib/client/gnt_network.py \
        lib/client/gnt_os.py \
-       lib/client/gnt_storage.py
+       lib/client/gnt_storage.py \
+       lib/client/gnt_filter.py
 
 cmdlib_PYTHON = \
        lib/cmdlib/__init__.py \
@@ -494,6 +518,7 @@ storage_PYTHON = \
        lib/storage/drbd.py \
        lib/storage/drbd_info.py \
        lib/storage/drbd_cmdgen.py \
+       lib/storage/extstorage.py \
        lib/storage/filestorage.py \
        lib/storage/gluster.py
 
@@ -553,6 +578,7 @@ pytools_PYTHON = \
        lib/tools/node_cleanup.py \
        lib/tools/node_daemon_setup.py \
        lib/tools/prepare_node_join.py \
+       lib/tools/ssh_update.py \
        lib/tools/ssl_update.py
 
 utils_PYTHON = \
@@ -573,7 +599,8 @@ utils_PYTHON = \
        lib/utils/text.py \
        lib/utils/version.py \
        lib/utils/wrapper.py \
-       lib/utils/x509.py
+       lib/utils/x509.py \
+       lib/utils/bitarrays.py
 
 docinput = \
        doc/admin.rst \
@@ -594,15 +621,18 @@ docinput = \
        doc/design-2.10.rst \
        doc/design-2.11.rst \
        doc/design-2.12.rst \
+       doc/design-2.13.rst \
        doc/design-autorepair.rst \
        doc/design-bulk-create.rst \
        doc/design-ceph-ganeti-support.rst \
+       doc/design-configlock.rst \
        doc/design-chained-jobs.rst \
        doc/design-cmdlib-unittests.rst \
        doc/design-cpu-pinning.rst \
        doc/design-cpu-speed.rst \
        doc/design-daemons.rst \
        doc/design-device-uuid-name.rst \
+       doc/design-disk-conversion.rst \
        doc/design-disks.rst \
        doc/design-draft.rst \
        doc/design-file-based-storage.rst \
@@ -613,9 +643,11 @@ docinput = \
        doc/design-htools-2.3.rst \
        doc/design-http-server.rst \
        doc/design-hugepages-support.rst \
+       doc/design-ifdown.rst \
        doc/design-impexp2.rst \
        doc/design-internal-shutdown.rst \
        doc/design-kvmd.rst \
+       doc/design-location.rst \
        doc/design-linuxha.rst \
        doc/design-lu-generated-jobs.rst \
        doc/design-monitoring-agent.rst \
@@ -623,6 +655,7 @@ docinput = \
        doc/design-multi-reloc.rst \
        doc/design-multi-version-tests.rst \
        doc/design-network.rst \
+       doc/design-network2.rst \
        doc/design-node-add.rst \
        doc/design-node-security.rst \
        doc/design-oob.rst \
@@ -637,11 +670,13 @@ docinput = \
        doc/design-query2.rst \
        doc/design-query-splitting.rst \
        doc/design-reason-trail.rst \
+       doc/design-reservations.rst \
        doc/design-resource-model.rst \
        doc/design-restricted-commands.rst \
        doc/design-shared-storage.rst \
        doc/design-ssh-ports.rst \
        doc/design-storagetypes.rst \
+       doc/design-sync-rate-throttling.rst \
        doc/design-systemd.rst \
        doc/design-upgrade.rst \
        doc/design-virtual-clusters.rst \
@@ -683,12 +718,10 @@ endif
 HS_COMPILE_PROGS = \
        src/ganeti-kvmd \
        src/ganeti-wconfd \
+       src/hconfd \
        src/hluxid \
        src/hs2py \
        src/rpc-test
-if ENABLE_CONFD
-HS_COMPILE_PROGS += src/hconfd
-endif
 if ENABLE_MOND
 HS_COMPILE_PROGS += src/ganeti-mond
 endif
@@ -708,9 +741,14 @@ HS_DEFAULT_PROGS = \
        $(HS_BIN_PROGS) \
        test/hs/hpc-htools \
        test/hs/hpc-mon-collector \
-       test/hs/htest \
        $(HS_COMPILE_PROGS)
 
+if HTEST
+HS_DEFAULT_PROGS += test/hs/htest
+else
+EXTRA_DIST += test/hs/htest.hs
+endif
+
 HS_ALL_PROGS = $(HS_DEFAULT_PROGS) $(HS_MYEXECLIB_PROGS)
 
 HS_TEST_PROGS = $(filter test/%,$(HS_ALL_PROGS))
@@ -806,6 +844,7 @@ HS_LIB_SRCS = \
        src/Ganeti/Curl/Multi.hs \
        src/Ganeti/Daemon.hs \
        src/Ganeti/Daemon/Utils.hs \
+       src/Ganeti/DataCollectors.hs \
        src/Ganeti/DataCollectors/CLI.hs \
        src/Ganeti/DataCollectors/CPUload.hs \
        src/Ganeti/DataCollectors/Diskstats.hs \
@@ -816,6 +855,7 @@ HS_LIB_SRCS = \
        src/Ganeti/DataCollectors/Program.hs \
        src/Ganeti/DataCollectors/Types.hs \
        src/Ganeti/Errors.hs \
+       src/Ganeti/HTools/AlgorithmParams.hs \
        src/Ganeti/HTools/Backend/IAlloc.hs \
        src/Ganeti/HTools/Backend/Luxi.hs \
        src/Ganeti/HTools/Backend/Rapi.hs \
@@ -842,6 +882,7 @@ HS_LIB_SRCS = \
        src/Ganeti/HTools/Program/Hsqueeze.hs \
        src/Ganeti/HTools/Program/Hroller.hs \
        src/Ganeti/HTools/Program/Main.hs \
+       src/Ganeti/HTools/Tags.hs \
        src/Ganeti/HTools/Types.hs \
        src/Ganeti/Hypervisor/Xen.hs \
        src/Ganeti/Hypervisor/Xen/XmParser.hs \
@@ -850,10 +891,13 @@ HS_LIB_SRCS = \
        src/Ganeti/Hs2Py/GenConstants.hs \
        src/Ganeti/Hs2Py/GenOpCodes.hs \
        src/Ganeti/Hs2Py/OpDoc.hs \
+       src/Ganeti/JQScheduler.hs \
+       src/Ganeti/JQScheduler/Filtering.hs \
+       src/Ganeti/JQScheduler/ReasonRateLimiting.hs \
+       src/Ganeti/JQScheduler/Types.hs \
        src/Ganeti/JQueue.hs \
        src/Ganeti/JQueue/Lens.hs \
        src/Ganeti/JQueue/Objects.hs \
-       src/Ganeti/JQScheduler.hs \
        src/Ganeti/JSON.hs \
        src/Ganeti/Jobs.hs \
        src/Ganeti/Kvmd.hs \
@@ -866,12 +910,6 @@ HS_LIB_SRCS = \
        src/Ganeti/Logging/Lifted.hs \
        src/Ganeti/Logging/WriterLog.hs \
        src/Ganeti/Luxi.hs \
-       src/Ganeti/Metad/Config.hs \
-       src/Ganeti/Metad/ConfigServer.hs \
-       src/Ganeti/Metad/Server.hs \
-       src/Ganeti/Metad/Types.hs \
-       src/Ganeti/Metad/WebServer.hs \
-       src/Ganeti/Monitoring/Server.hs \
        src/Ganeti/Network.hs \
        src/Ganeti/Objects.hs \
        src/Ganeti/Objects/BitArray.hs \
@@ -887,6 +925,7 @@ HS_LIB_SRCS = \
        src/Ganeti/Query/Exec.hs \
        src/Ganeti/Query/Export.hs \
        src/Ganeti/Query/Filter.hs \
+       src/Ganeti/Query/FilterRules.hs \
        src/Ganeti/Query/Group.hs \
        src/Ganeti/Query/Instance.hs \
        src/Ganeti/Query/Job.hs \
@@ -899,6 +938,7 @@ HS_LIB_SRCS = \
        src/Ganeti/Query/Types.hs \
        src/Ganeti/Rpc.hs \
        src/Ganeti/Runtime.hs \
+       src/Ganeti/SlotMap.hs \
        src/Ganeti/Ssconf.hs \
        src/Ganeti/Storage/Diskstats/Parser.hs \
        src/Ganeti/Storage/Diskstats/Types.hs \
@@ -921,7 +961,7 @@ HS_LIB_SRCS = \
        src/Ganeti/Utils/AsyncWorker.hs \
        src/Ganeti/Utils/IORef.hs \
        src/Ganeti/Utils/Livelock.hs \
-       src/Ganeti/Utils/MonadPlus.hs \
+       src/Ganeti/Utils/Monad.hs \
        src/Ganeti/Utils/MultiMap.hs \
        src/Ganeti/Utils/MVarLock.hs \
        src/Ganeti/Utils/Random.hs \
@@ -942,6 +982,28 @@ HS_LIB_SRCS = \
        src/Ganeti/WConfd/Ssconf.hs \
        src/Ganeti/WConfd/TempRes.hs
 
+if ENABLE_MOND
+HS_LIB_SRCS += src/Ganeti/Monitoring/Server.hs
+else
+EXTRA_DIST += src/Ganeti/Monitoring/Server.hs
+endif
+
+if ENABLE_METADATA
+HS_LIB_SRCS += \
+       src/Ganeti/Metad/Config.hs \
+       src/Ganeti/Metad/ConfigServer.hs \
+       src/Ganeti/Metad/Server.hs \
+       src/Ganeti/Metad/Types.hs \
+       src/Ganeti/Metad/WebServer.hs
+else
+EXTRA_DIST += \
+       src/Ganeti/Metad/Config.hs \
+       src/Ganeti/Metad/ConfigServer.hs \
+       src/Ganeti/Metad/Server.hs \
+       src/Ganeti/Metad/Types.hs \
+       src/Ganeti/Metad/WebServer.hs
+endif
+
 HS_TEST_SRCS = \
        test/hs/Test/AutoConf.hs \
        test/hs/Test/Ganeti/Attoparsec.hs \
@@ -967,7 +1029,9 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs \
        test/hs/Test/Ganeti/JSON.hs \
        test/hs/Test/Ganeti/Jobs.hs \
+       test/hs/Test/Ganeti/JQScheduler.hs \
        test/hs/Test/Ganeti/JQueue.hs \
+       test/hs/Test/Ganeti/JQueue/Objects.hs \
        test/hs/Test/Ganeti/Kvmd.hs \
        test/hs/Test/Ganeti/Luxi.hs \
        test/hs/Test/Ganeti/Locking/Allocation.hs \
@@ -985,6 +1049,7 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/Query/Query.hs \
        test/hs/Test/Ganeti/Rpc.hs \
        test/hs/Test/Ganeti/Runtime.hs \
+       test/hs/Test/Ganeti/SlotMap.hs \
        test/hs/Test/Ganeti/Ssconf.hs \
        test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs \
        test/hs/Test/Ganeti/Storage/Drbd/Parser.hs \
@@ -1146,7 +1211,8 @@ gnt_scripts = \
        scripts/gnt-network \
        scripts/gnt-node \
        scripts/gnt-os \
-       scripts/gnt-storage
+       scripts/gnt-storage \
+       scripts/gnt-filter
 
 gnt_scripts_basenames = \
        $(patsubst scripts/%,%,$(patsubst daemons/%,%,$(gnt_scripts) $(gnt_python_sbin_SCRIPTS)))
@@ -1166,8 +1232,9 @@ PYTHON_BOOTSTRAP = \
        tools/ensure-dirs \
        tools/node-cleanup \
        tools/node-daemon-setup \
-       tools/ssl-update \
-       tools/prepare-node-join
+       tools/prepare-node-join \
+       tools/ssh-update \
+       tools/ssl-update
 
 qa_scripts = \
        qa/__init__.py \
@@ -1177,19 +1244,22 @@ qa_scripts = \
        qa/qa_daemon.py \
        qa/qa_env.py \
        qa/qa_error.py \
+       qa/qa_filters.py \
        qa/qa_group.py \
        qa/qa_instance.py \
        qa/qa_instance_utils.py \
+       qa/qa_iptables.py \
        qa/qa_job.py \
        qa/qa_job_utils.py \
-       qa/qa_monitoring.py \
        qa/qa_logging.py \
+       qa/qa_monitoring.py \
+       qa/qa_network.py \
        qa/qa_node.py \
        qa/qa_os.py \
+       qa/qa_performance.py \
        qa/qa_rapi.py \
        qa/qa_tags.py \
-       qa/qa_utils.py \
-       qa/rapi-workload.py
+       qa/qa_utils.py
 
 bin_SCRIPTS = $(HS_BIN_PROGS)
 install-exec-hook:
@@ -1333,6 +1403,7 @@ nodist_sbin_SCRIPTS = \
        daemons/ganeti-cleaner \
   src/ganeti-kvmd \
   src/ganeti-luxid \
+  src/ganeti-confd \
   src/ganeti-wconfd
 
 src/ganeti-luxid: src/hluxid
@@ -1343,13 +1414,9 @@ 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 $< $@
 
-nodist_sbin_SCRIPTS += src/ganeti-confd
-endif
-
 if ENABLE_MOND
 nodist_sbin_SCRIPTS += src/ganeti-mond
 endif
@@ -1369,7 +1436,8 @@ python_scripts = \
        tools/move-instance \
        tools/ovfconverter \
        tools/post-upgrade \
-       tools/sanitize-config
+       tools/sanitize-config \
+       tools/query-config
 
 python_scripts_shebang = \
        $(patsubst tools/%,tools/shebang/%, $(python_scripts))
@@ -1411,6 +1479,7 @@ nodist_pkglib_python_scripts = \
        tools/ensure-dirs \
        tools/node-daemon-setup \
        tools/prepare-node-join \
+       tools/ssh-update \
        tools/ssl-update
 
 pkglib_python_basenames = \
@@ -1431,7 +1500,7 @@ myexeclib_SCRIPTS = \
 myexeclib_scripts_basenames = \
        $(patsubst tools/%,%,$(patsubst daemons/%,%,$(patsubst src/%,%,$(myexeclib_SCRIPTS))))
 
-EXTRA_DIST = \
+EXTRA_DIST += \
        NEWS \
        UPGRADE \
        epydoc.conf.in \
@@ -1523,6 +1592,7 @@ man_MANS = \
        man/gnt-node.8 \
        man/gnt-os.8 \
        man/gnt-storage.8 \
+       man/gnt-filter.8 \
        man/hail.1 \
        man/harep.1 \
        man/hbal.1 \
@@ -1559,16 +1629,22 @@ TEST_FILES = \
        test/data/htools/hail-alloc-invalid-network.json \
        test/data/htools/hail-alloc-invalid-twodisks.json \
        test/data/htools/hail-alloc-restricted-network.json \
+       test/data/htools/hail-alloc-plain-tags.json \
        test/data/htools/hail-alloc-spindles.json \
        test/data/htools/hail-alloc-twodisks.json \
        test/data/htools/hail-change-group.json \
        test/data/htools/hail-invalid-reloc.json \
        test/data/htools/hail-node-evac.json \
        test/data/htools/hail-reloc-drbd.json \
+       test/data/htools/hail-reloc-drbd-crowded.json \
        test/data/htools/hbal-cpu-speed.data \
        test/data/htools/hbal-dyn.data \
        test/data/htools/hbal-evac.data \
        test/data/htools/hbal-excl-tags.data \
+       test/data/htools/hbal-migration-1.data \
+       test/data/htools/hbal-migration-2.data \
+       test/data/htools/hbal-migration-3.data \
+       test/data/htools/hbal-soft-errors.data \
        test/data/htools/hbal-split-insts.data \
        test/data/htools/hspace-groups-one.data \
        test/data/htools/hspace-groups-two.data \
@@ -1603,6 +1679,7 @@ TEST_FILES = \
        test/hs/shelltests/htools-excl.test \
        test/hs/shelltests/htools-hail.test \
        test/hs/shelltests/htools-hbal-evac.test \
+       test/hs/shelltests/htools-hbal.test \
        test/hs/shelltests/htools-hroller.test \
        test/hs/shelltests/htools-hspace.test \
        test/hs/shelltests/htools-hsqueeze.test \
@@ -1634,11 +1711,15 @@ TEST_FILES = \
        test/data/bdev-rbd/output_invalid.txt \
        test/data/cert1.pem \
        test/data/cert2.pem \
+        test/data/cgroup_root/memory/lxc/instance1/memory.limit_in_bytes \
+        test/data/cgroup_root/cpuset/some_group/lxc/instance1/cpuset.cpus \
+        test/data/cgroup_root/devices/some_group/lxc/instance1/devices.list \
        test/data/cluster_config_2.7.json \
        test/data/cluster_config_2.8.json \
        test/data/cluster_config_2.9.json \
        test/data/cluster_config_2.10.json \
        test/data/cluster_config_2.11.json \
+       test/data/cluster_config_2.12.json \
        test/data/instance-minor-pairing.txt \
        test/data/instance-disks.txt \
        test/data/ip-addr-show-dummy0.txt \
@@ -1683,7 +1764,8 @@ TEST_FILES = \
        test/data/ovfdata/wrong_manifest.ovf \
        test/data/ovfdata/wrong_ova.ova \
        test/data/ovfdata/wrong_xml.ovf \
-       test/data/proc_diskstats.txt \
+       test/data/proc_cgroup.txt \
+        test/data/proc_diskstats.txt \
        test/data/proc_drbd8.txt \
        test/data/proc_drbd80-emptyline.txt \
        test/data/proc_drbd80-emptyversion.txt \
@@ -1733,6 +1815,7 @@ python_tests = \
        test/py/ganeti.backend_unittest.py \
        test/py/ganeti.bootstrap_unittest.py \
        test/py/ganeti.cli_unittest.py \
+       test/py/ganeti.cli_opts_unittest.py \
        test/py/ganeti.client.gnt_cluster_unittest.py \
        test/py/ganeti.client.gnt_instance_unittest.py \
        test/py/ganeti.client.gnt_job_unittest.py \
@@ -1805,6 +1888,7 @@ python_tests = \
        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.bitarrays_unittest.py \
        test/py/ganeti.utils_unittest.py \
        test/py/ganeti.vcluster_unittest.py \
        test/py/ganeti.workerpool_unittest.py \
@@ -1816,6 +1900,7 @@ python_test_support = \
        test/py/__init__.py \
        test/py/lockperf.py \
        test/py/testutils.py \
+       test/py/testutils_ssh.py \
        test/py/mocks.py \
        test/py/cmdlib/__init__.py \
        test/py/cmdlib/testsupport/__init__.py \
@@ -2007,9 +2092,9 @@ tools/users-setup: Makefile $(userspecs)
          echo 'read confirm'; \
          echo 'if [ "x$$confirm" != "xy" ]; then exit 0; fi'; \
          echo 'fi'; \
-         $(AWK) -- '{print "addgroup --system",$$1}' doc/users/groups; \
-         $(AWK) -- '{if (NF > 1) {print "adduser --system --ingroup",$$2,$$1} else {print "adduser --system",$$1}}' doc/users/users; \
-         $(AWK) -- '{print "adduser",$$1,$$2}' doc/users/groupmemberships; \
+         $(AWK) -- '{print "groupadd --system",$$1}' doc/users/groups; \
+         $(AWK) -- '{if (NF > 1) {print "useradd --system --gid",$$2,$$1} else {print "useradd --system",$$1}}' doc/users/users; \
+         $(AWK) -- '{print "usermod --append --groups",$$2,$$1}' doc/users/groupmemberships; \
        } > $@
        chmod +x $@
 
@@ -2165,6 +2250,8 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DKVM_KERNEL="$(KVM_KERNEL)" \
            -DSHARED_FILE_STORAGE_DIR="$(SHARED_FILE_STORAGE_DIR)" \
            -DIALLOCATOR_SEARCH_PATH="\"$(IALLOCATOR_SEARCH_PATH)\"" \
+           -DDEFAULT_BRIDGE="$(DEFAULT_BRIDGE)" \
+           -DDEFAULT_VG="$(DEFAULT_VG)" \
            -DKVM_PATH="$(KVM_PATH)" \
            -DIP_PATH="$(IP_PATH)" \
            -DSOCAT_PATH="$(SOCAT)" \
@@ -2203,7 +2290,6 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DMOND_GROUP="$(MOND_GROUP)" \
            -DDISK_SEPARATOR="$(DISK_SEPARATOR)" \
            -DQEMUIMG_PATH="$(QEMUIMG_PATH)" \
-           -DENABLE_CONFD="$(ENABLE_CONFD)" \
            -DXEN_CMD="$(XEN_CMD)" \
            -DENABLE_RESTRICTED_COMMANDS="$(ENABLE_RESTRICTED_COMMANDS)" \
            -DENABLE_METADATA="$(ENABLE_METADATA)" \
@@ -2297,7 +2383,6 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
          echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
          echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
          echo 's#@''GNTDAEMONSGROUP@#$(DAEMONS_GROUP)#g'; \
-         echo 's#@''CUSTOM_ENABLE_CONFD@#$(ENABLE_CONFD)#g'; \
          echo 's#@''CUSTOM_ENABLE_MOND@#$(ENABLE_MOND)#g'; \
          echo 's#@''MODULES@#$(strip $(lint_python_code))#g'; \
          echo 's#@''XEN_CONFIG_DIR@#$(XEN_CONFIG_DIR)#g'; \
@@ -2316,6 +2401,7 @@ tools/burnin: MODULE = ganeti.tools.burnin
 tools/ensure-dirs: MODULE = ganeti.tools.ensure_dirs
 tools/node-daemon-setup: MODULE = ganeti.tools.node_daemon_setup
 tools/prepare-node-join: MODULE = ganeti.tools.prepare_node_join
+tools/ssh-update: MODULE = ganeti.tools.ssh_update
 tools/node-cleanup: MODULE = ganeti.tools.node_cleanup
 tools/ssl-update: MODULE = ganeti.tools.ssl_update
 $(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst test/hs/%,%,$@)
diff --git a/NEWS b/NEWS
index 859afd8..d094086 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,178 @@ News
 ====
 
 
+Version 2.13.2
+--------------
+
+*(Released Mon, 13 Jul 2015)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- This release contains a fix for the problem that different encodings in
+  SSL certificates can break RPC communication (issue 1094). The fix makes
+  it necessary to rerun 'gnt-cluster renew-crypto --new-node-certificates'
+  after the cluster is fully upgraded to 2.13.2
+
+Other fixes and known issues
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Inherited from 2.12:
+
+- Fixed Issue #1115: Race between starting WConfD and updating the config
+- Fixed Issue #1114: Binding RAPI to a specific IP makes the watcher
+  restart the RAPI
+- Fixed Issue #1100: Filter-evaluation for run-time data filter
+- Better handling of the "crashed" Xen state
+- The watcher can be instructed to skip disk verification
+- Reduce amount of logging on successful requests
+- Prevent multiple communication NICs being created for instances
+- The ``htools`` now properly work also on shared-storage clusters
+- Instance moves now work properly also for the plain disk template
+- Various improvements to the documentation have been added
+
+Known issues:
+- Issue #1104: gnt-backup: dh key too small
+
+
+Version 2.13.1
+--------------
+
+*(Released Tue, 16 Jun 2015)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- The SSH security changes reduced the number of nodes which can SSH into
+  other nodes. Unfortunately enough, the Ganeti implementation of migration
+  for the xl stack of Xen required SSH to be able to migrate the instance,
+  leading to a situation where full movement of an instance around the cluster
+  was not possible. This version fixes the issue by using socat to transfer
+  instance data. While socat is less secure than SSH, it is about as secure as
+  xm migrations, and occurs over the secondary network if present. As a
+  consequence of this change, Xen instance migrations using xl cannot occur
+  between nodes running 2.13.0 and 2.13.1.
+
+Other fixes and known issues
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Inherited from 2.12:
+
+- Fixed Issue #1082: RAPI is unresponsive after master-failover
+- Fixed Issue #1083: Cluster verify reports existing instance disks on
+  non-default VGs as missing
+- Fixed Issue #1101: Modifying the storage directory for the shared-file disk
+  template doesn't work
+- Fixed a possible file descriptor leak when forking jobs
+- Fixed missing private parameters in the environment for OS scripts
+- Fixed a performance regression when handling configuration
+  (only upgrade it if it changes)
+- Adapt for compilation with GHC7.8 (compiles with warnings;
+  cherrypicked from 2.14)
+
+Known issues:
+- Issue #1094: Mismatch in SSL encodings breaks RPC communication
+- Issue #1104: Export fails: key is too small
+
+
+Version 2.13.0
+--------------
+
+*(Released Tue, 28 Apr 2015)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Ganeti now internally retries the instance creation opcode if opportunistic
+  locking did not acquire nodes with enough free resources. The internal retry
+  will not use opportunistic locking. In particular, instance creation, even
+  if opportunistic locking is set, will never fail with ECODE_TEMP_NORES.
+- The handling of SSH security had undergone a significant change. From
+  this version on, each node has an individual SSH key pair instead of
+  sharing one with all nodes of the cluster. From now on, we also
+  restrict SSH access to master candidates. This means that only master
+  candidates can ssh into other cluster nodes and all
+  non-master-candidates cannot. Refer to the UPGRADE notes
+  for further instructions on the creation and distribution of the keys.
+- Ganeti now checks hypervisor version compatibility before trying an instance
+  migration. It errors out if the versions are not compatible. Add the option
+  --ignore-hvversions to restore the old behavior of only warning.
+- Node tags starting with htools:migration: or htools:allowmigration: now have
+  a special meaning to htools(1). See hbal(1) for details.
+- The LXC hypervisor code has been repaired and improved. Instances cannot be
+  migrated and cannot have more than one disk, but should otherwise work as with
+  other hypervisors. OS script changes should not be necessary. LXC version
+  1.0.0 or higher required.
+
+New features
+~~~~~~~~~~~~
+
+- A new job filter rules system allows to define iptables-like rules for the
+  job scheduler, making it easier to (soft-)drain the job queue, perform
+  maintenance, and rate-limit selected job types. See gnt-filter(8) for
+  details.
+- Ganeti jobs can now be ad-hoc rate limited via the reason trail.
+  For a set of jobs queued with "--reason=rate-limit:n:label", the job
+  scheduler ensures that not more than n will be scheduled to run at the same
+  time. See ganeti(7), section "Options", for details.
+- The monitoring daemon has now variable sleep times for the data
+  collectors. This currently means that the granularity of cpu-avg-load
+  can be configured.
+- The 'gnt-cluster verify' command now has the option
+  '--verify-ssh-clutter', which verifies whether Ganeti (accidentally)
+  cluttered up the 'authorized_keys' file.
+- Instance disks can now be converted from one disk template to another for many
+  different template combinations. When available, more efficient conversions
+  will be used, otherwise the disks are simply copied over.
+
+New dependencies
+~~~~~~~~~~~~~~~~
+
+- The monitoring daemon uses the PSQueue library. Be sure to install it
+  if you use Mond.
+- The formerly optional regex-pcre is now an unconditional dependency because
+  the new job filter rules have regular expressions as a core feature.
+
+Since 2.13.0 rc1
+~~~~~~~~~~~~~~~~~~
+
+The following issues have been fixed:
+
+- Bugs related to ssh-key handling of master candidates (issues 1045,
+  1046, 1047)
+
+Fixes inherited from the 2.12 branch:
+
+- Upgrade from old versions (2.5 and 2.6) was failing (issues 1070, 1019).
+- gnt-network info outputs wrong external reservations (issue 1068)
+- Refuse to demote master from master capability (issue 1023)
+
+
+Version 2.13.0 rc1
+------------------
+
+*(Released Wed, 25 Mar 2015)*
+
+This was the first release candidate of the 2.13 series.
+All important changes are listed in the latest 2.13 entry.
+
+Since 2.13.0 beta1
+~~~~~~~~~~~~~~~~~~
+
+The following issues have been fixed:
+
+- Issue 1018: Cluster init (and possibly other jobs) occasionally fail to start
+
+
+Version 2.13.0 beta1
+--------------------
+
+*(Released Wed, 14 Jan 2015)*
+
+This was the first beta release of the 2.13 series. All important changes
+are listed in the latest 2.13 entry.
+
+
 Version 2.12.5
 --------------
 
diff --git a/README b/README
index 2c2208b..e2898ad 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.12
+Ganeti 2.13
 ===========
 
 For installation instructions, read the INSTALL and the doc/install.rst
diff --git a/UPGRADE b/UPGRADE
index e7cc46a..43e62b7 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -39,6 +39,34 @@ the Ganeti binaries should happen in the same way as for all other binaries on
 your system.
 
 
+2.13
+----
+
+When upgrading to 2.13, first apply the instructions of ``2.11 and
+above``. 2.13 comes with the new feature of enhanced SSH security
+through individual SSH keys. This features needs to be enabled
+after the upgrade by::
+
+   $ gnt-cluster renew-crypto --new-ssh-keys --no-ssh-key-check
+
+Note that new SSH keys are generated automatically without warning when
+upgrading with ``gnt-cluster upgrade``.
+
+If you instructed Ganeti to not touch the SSH setup (by using the
+``--no-ssh-init`` option of ``gnt-cluster init``, the changes in the
+handling of SSH keys will not affect your cluster.
+
+If you want to be prompted for each newly created SSH key, leave out
+the ``--no-ssh-key-check`` option in the command listed above.
+
+Note that after a downgrade from 2.13 to 2.12, the individual SSH keys
+will not get removed automatically. This can lead to reachability
+errors under very specific circumstances (Issue 1008). In case you plan
+on keeping 2.12 for a while and not upgrade to 2.13 again soon, we recommend
+to replace all SSH key pairs of non-master nodes' with the master node's SSH
+key pair.
+
+
 2.12
 ----
 
@@ -107,6 +135,10 @@ To run commands on all nodes, the `distributed shell (dsh)
 
     $ /usr/lib/ganeti/ensure-dirs --full-run
 
+   Note that ensure-dirs does not create the directories for file
+   and shared-file storage. This is due to security reasons. They need
+   to be created manually. For details see ``man gnt-cluster``. 
+
 #. Create the (missing) required users and make users part of the required
    groups on all nodes::
 
index 48687ea..3e35ee1 100755 (executable)
@@ -404,6 +404,8 @@ class CompletionWriter(object):
           WriteCompReply(sw, "-W \"$(_ganeti_os)\"", cur=cur)
         elif suggest == cli.OPT_COMPL_ONE_EXTSTORAGE:
           WriteCompReply(sw, "-W \"$(_ganeti_extstorage)\"", cur=cur)
+        elif suggest == cli.OPT_COMPL_ONE_FILTER:
+          WriteCompReply(sw, "-W \"$(_ganeti_filter)\"", cur=cur)
         elif suggest == cli.OPT_COMPL_ONE_IALLOCATOR:
           WriteCompReply(sw, "-W \"$(_ganeti_iallocator)\"", cur=cur)
         elif suggest == cli.OPT_COMPL_ONE_NODEGROUP:
@@ -516,6 +518,8 @@ class CompletionWriter(object):
           choices = "$(_ganeti_os)"
         elif isinstance(arg, cli.ArgExtStorage):
           choices = "$(_ganeti_extstorage)"
+        elif isinstance(arg, cli.ArgFilter):
+          choices = "$(_ganeti_filter)"
         elif isinstance(arg, cli.ArgFile):
           choices = ""
           compgenargs.append("-f")
@@ -896,9 +900,8 @@ def main():
     WriteHaskellCompletion(sw, script, htools=True, debug=debug)
 
   # ganeti-confd, if enabled
-  if _constants.ENABLE_CONFD:
-    WriteHaskellCompletion(sw, "src/ganeti-confd", htools=False,
-                           debug=debug)
+  WriteHaskellCompletion(sw, "src/ganeti-confd", htools=False,
+                         debug=debug)
 
   # mon-collector, if monitoring is enabled
   if _constants.ENABLE_MOND:
index 3dee845..c7186ad 100644 (file)
@@ -1,7 +1,7 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [12])
-m4_define([gnt_version_revision], [5])
+m4_define([gnt_version_minor], [13])
+m4_define([gnt_version_revision], [2])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
@@ -219,6 +219,24 @@ AC_ARG_WITH([iallocator-search-path],
   [iallocator_search_path="$libdir/$PACKAGE_NAME/iallocators"])
 AC_SUBST(IALLOCATOR_SEARCH_PATH, $iallocator_search_path)
 
+# --with-default-vg=...
+AC_ARG_WITH([default-vg],
+  [AS_HELP_STRING([--with-default-vg=VOLUMEGROUP],
+    [default volume group (default is xenvg)]
+  )],
+  [default_vg="$withval"],
+  [default_vg="xenvg"])
+AC_SUBST(DEFAULT_VG, $default_vg)
+
+# --with-default-bridge=...
+AC_ARG_WITH([default-bridge],
+  [AS_HELP_STRING([--with-default-bridge=BRIDGE],
+    [default bridge (default is xen-br0)]
+  )],
+  [default_bridge="$withval"],
+  [default_bridge="xen-br0"])
+AC_SUBST(DEFAULT_BRIDGE, $default_bridge)
+
 # --with-xen-bootloader=...
 AC_ARG_WITH([xen-bootloader],
   [AS_HELP_STRING([--with-xen-bootloader=PATH],
@@ -592,14 +610,6 @@ then
   AC_MSG_WARN([qemu-img not found, using ovfconverter will not be possible])
 fi
 
-# --enable-confd
-ENABLE_CONFD=
-AC_ARG_ENABLE([confd],
-  [AS_HELP_STRING([--enable-confd],
-  [enable the ganeti-confd daemon (default: check)])],
-  [],
-  [enable_confd=check])
-
 ENABLE_MOND=
 AC_ARG_ENABLE([monitoring],
   [AS_HELP_STRING([--enable-monitoring],
@@ -669,47 +679,18 @@ AC_GHC_PKG_REQUIRE(hinotify)
 AC_GHC_PKG_REQUIRE(Crypto)
 AC_GHC_PKG_REQUIRE(lifted-base)
 AC_GHC_PKG_REQUIRE(lens)
-
-# extra modules for confd functionality; also needed for tests
-HS_NODEV=
-CONFD_PKG=
-# if a new confd dependency is needed, add it here like:
-# AC_GHC_PKG_CHECK([somepkg], [], [HS_NODEV=1; CONFD_PKG="$CONFD_PKG somepkg"])
-HS_REGEX_PCRE=-DNO_REGEX_PCRE
-AC_GHC_PKG_CHECK([regex-pcre], [HS_REGEX_PCRE=],
-                 [HS_NODEV=1; CONFD_PKG="$CONFD_PKG regex-pcre"])
-
-has_confd=False
-if test "$enable_confd" != no; then
-  if test -z "$CONFD_PKG"; then
-    has_confd=True
-  elif test "$enable_confd" = check; then
-    AC_MSG_WARN(m4_normalize([The required extra libraries for confd were
-                              not found ($CONFD_PKG), confd disabled]))
-  else
-    AC_MSG_FAILURE(m4_normalize([The confd functionality was requested, but
-                                 required libraries were not found:
-                                 $CONFD_PKG]))
-  fi
-fi
-AC_SUBST(HS_REGEX_PCRE)
-if test "$has_confd" = True; then
-  AC_MSG_NOTICE([Enabling confd usage])
-fi
-AC_SUBST(ENABLE_CONFD, $has_confd)
-AM_CONDITIONAL([ENABLE_CONFD], [test x$has_confd = xTrue])
+AC_GHC_PKG_REQUIRE(regex-pcre)
 
 #extra modules for monitoring daemon functionality; also needed for tests
 MONITORING_PKG=
 AC_GHC_PKG_CHECK([snap-server], [],
                  [NS_NODEV=1; MONITORING_PKG="$MONITORING_PKG snap-server"])
+AC_GHC_PKG_CHECK([PSQueue], [],
+                 [NS_NODEV=1; MONITORING_PKG="$MONITORING_PKG PSQueue"])
 
 has_monitoring=False
 if test "$enable_monitoring" != no; then
   MONITORING_DEP=
-  if test "$has_confd" = False; then
-    MONITORING_DEP="$MONITORING_DEP confd"
-  fi
   has_monitoring_pkg=False
   if test -z "$MONITORING_PKG"; then
     has_monitoring_pkg=True
index 6a47253..7636fc9 100644 (file)
@@ -38,6 +38,7 @@ readonly defaults_file="$SYSCONFDIR/default/ganeti"
 # they're stopped in reverse order.
 DAEMONS=(
   ganeti-noded
+  ganeti-confd
   ganeti-wconfd
   ganeti-rapi
   ganeti-luxid
@@ -50,14 +51,6 @@ ON_DEMAND_DAEMONS=(
   ganeti-metad
   )
 
-_confd_enabled() {
-  [[ "@CUSTOM_ENABLE_CONFD@" == True ]]
-}
-
-if _confd_enabled; then
-  DAEMONS+=( ganeti-confd )
-fi
-
 _mond_enabled() {
   [[ "@CUSTOM_ENABLE_MOND@" == True ]]
 }
@@ -277,12 +270,6 @@ start() {
   local usergroup=$(_daemon_usergroup $plain_name)
   local daemonexec=$(_daemon_executable $name)
 
-  if [[ "$name" == ganeti-confd ]] \
-      && ! _confd_enabled; then
-    echo 'ganeti-confd disabled at build time' >&2
-    return 1
-  fi
-
   if use_systemctl; then
     systemctl start "${name}.service"
     return $?
index a62adf8..c78fbcb 100755 (executable)
@@ -1,11 +1,23 @@
 #!/bin/bash
+
+#Requirements for this script to work:
+#* Make sure that the user who uses the chroot is in group 'src', or change
+#  the ${GROUP} variable to a group that contains the user.
+#* Add any path of the host system that you want to access inside the chroot
+#  to the /etc/schroot/default/fstab file. This is important in particular if
+#  your homedir is not in /home.
+#* Add this to your /etc/fstab:
+#  tmpfs /var/lib/schroot/mount tmpfs defaults,size=3G 0 0
+#  tmpfs /var/lib/schroot/unpack tmpfs defaults,size=3G 0 0
+
 #Configuration
 : ${ARCH:=amd64}
-: ${DIST_RELEASE:=squeeze}
+: ${DIST_RELEASE:=wheezy}
 : ${CONF_DIR:=/etc/schroot/chroot.d}
 : ${CHROOT_DIR:=/srv/chroot}
 : ${ALTERNATIVE_EDITOR:=/usr/bin/vim.basic}
 : ${CHROOT_FINAL_HOOK:=/bin/true}
+: ${GROUP:=src}
 # Additional Variables taken from the environmen
 # DATA_DIR
 # CHROOT_EXTRA_DEBIAN_PACKAGES
@@ -69,7 +81,7 @@ then
   cat <<END >$ACTUAL_DATA_DIR/final.schroot.conf.in
 [${CHROOTNAME}]
 description=Debian ${DIST_RELEASE} ${ARCH}
-groups=src
+groups=${GROUP}
 source-root-groups=root
 type=file
 file=${CHROOT_DIR}/${COMP_FILENAME}
@@ -85,7 +97,7 @@ then
 [${CHNAME}]
 description=Debian ${DIST_RELEASE} ${ARCH}
 directory=${CHDIR}
-groups=src
+groups=${GROUP}
 users=root
 type=directory
 END
@@ -286,6 +298,7 @@ case $DIST_RELEASE in
         test-framework-quickcheck2-0.3.0.2 \
         \
         snap-server-0.9.4.0 \
+        PSQueue-1.1 \
         \
         cabal-file-th-0.2.3 \
         shelltestrunner
@@ -311,12 +324,12 @@ case $DIST_RELEASE in
       libghc-regex-pcre-dev libghc-attoparsec-dev \
       libghc-vector-dev libghc-temporary-dev \
       libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
-      libghc-zlib-dev \
+      libghc-zlib-dev libghc-psqueue-dev \
       cabal-install \
       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
+      shelltestrunner python-dev 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.
@@ -329,6 +342,14 @@ case $DIST_RELEASE in
     #   https://github.com/seb-m/pyinotify/commit/98c5f41a6e2e90827a63ff1b878596
 
     in_chroot -- \
+      easy_install \
+        logilab-astng==0.24.1 \
+        logilab-common==0.58.3 \
+        mock==1.0.1 \
+        pylint==0.26.0 \
+        pep8==1.3.3
+
+    in_chroot -- \
       easy_install pyinotify==0.9.4
 
      in_chroot -- \
@@ -353,7 +374,7 @@ case $DIST_RELEASE in
       libghc-regex-pcre-dev libghc-attoparsec-dev \
       libghc-vector-dev libghc-temporary-dev \
       libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
-      libghc-zlib-dev \
+      libghc-zlib-dev libghc-psqueue-dev \
       libghc-base64-bytestring-dev libghc-lens-dev libghc-lifted-base-dev \
       cabal-install \
       python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
@@ -381,7 +402,7 @@ EOF
       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-vector-dev libghc-temporary-dev libghc-psqueue-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 \
@@ -415,7 +436,7 @@ EOF
       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-vector-dev libghc-temporary-dev libghc-psqueue-dev \
       libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
       libghc-lifted-base-dev \
       libghc-base64-bytestring-dev \
@@ -492,6 +513,7 @@ rm -f $TEMP_CHROOT_CONF
 rm -rf $TEMP_DATA_DIR
 
 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 " * sudo cp $FINAL_CHROOT_CONF $CONF_DIR/$FINAL_CHROOT_CONF"
+echo " * sudo mkdir -p $CHROOT_DIR"
+echo " * sudo cp $COMP_FILEPATH $CHROOT_DIR/$COMP_FILENAME"
 echo "Then run \"schroot -c $CHROOTNAME\""
index e9c016c..efecec7 100644 (file)
@@ -164,7 +164,9 @@ There are several disk templates you can choose from:
 .. note::
   Disk templates marked with an asterisk require Ganeti to access the
   file system. Ganeti will refuse to do so unless you whitelist the
-  relevant paths in :pyeval:`pathutils.FILE_STORAGE_PATHS_FILE`.
+  relevant paths in the file storage paths configuration which,
+  with default configure-time paths is located
+  in :pyeval:`pathutils.FILE_STORAGE_PATHS_FILE`.
 
   The default paths used by Ganeti are:
 
similarity index 68%
copy from doc/design-2.12.rst
copy to doc/design-2.13.rst
index cf18c52..5083b4b 100644 (file)
@@ -1,15 +1,15 @@
 ==================
-Ganeti 2.12 design
+Ganeti 2.13 design
 ==================
 
-The following design documents have been implemented in Ganeti 2.12.
+The following design documents have been implemented in Ganeti 2.13.
 
-- :doc:`design-daemons`
-- :doc:`design-systemd`
-- :doc:`design-cpu-speed`
+- :doc:`design-disk-conversion`
+- :doc:`design-optables`
+- :doc:`design-hsqueeze`
 
-The following designs have been partially implemented in Ganeti 2.12.
+The following designs have been partially implemented in Ganeti 2.13.
 
+- :doc:`design-location`
 - :doc:`design-node-security`
-- :doc:`design-hsqueeze`
 - :doc:`design-os`
diff --git a/doc/design-configlock.rst b/doc/design-configlock.rst
new file mode 100644 (file)
index 0000000..e19f0b9
--- /dev/null
@@ -0,0 +1,118 @@
+===================================
+Removal of the Config Lock Overhead
+===================================
+
+.. contents:: :depth: 4
+
+This is a design document detailing how the adverse effect of
+the config lock can be removed in an incremental way.
+
+Current state and shortcomings
+==============================
+
+As a result of the :doc:`design-daemons`, the configuration is held
+in a proccess different from the processes carrying out the Ganeti
+jobs. Therefore, job processes have to contact WConfD in order to
+change the configuration. Of course, these modifications of the
+configuration need to be synchronised.
+
+The current form of synchronisation is via ``ConfigLock``. Exclusive
+possession of this lock guarantees that no one else modifies the
+configuration. In other words, the current procedure for a job to
+update the configuration is to
+
+- acquire the ``ConfigLock`` from WConfD,
+
+- read the configration,
+
+- write the modified configuration, and
+
+- release ``ConfigLock``.
+
+The current procedure has some drawbacks. These also affect the
+overall throughput of jobs in a Ganeti cluster.
+
+- At each configuration update, the whole configuration is
+  transferred between the job and WConfD.
+
+- More importantly, however, jobs can only release the ``ConfigLock`` after
+  the write; the write, in turn, is only confirmed once the configuration
+  is written on disk. In particular, we can only have one update per
+  configuration write. Also, having the ``ConfigLock`` is only confirmed
+  to the job, once the new lock status is written to disk.
+
+Additional overhead is caused by the fact that reads are synchronised over
+a shared config lock. This used to make sense when the configuration was
+modifiable in the same process to ensure consistent read. With the new
+structure, all access to the configuration via WConfD are consistent
+anyway, and local modifications by other jobs do not happen.
+
+
+Proposed changes for an incremental improvement
+===============================================
+
+Ideally, jobs would just send patches for the configuration to WConfD
+that are applied by means of atomically updating the respective ``IORef``.
+This, however, would require chaning all of Ganeti's logical units in
+one big change. Therefore, we propose to keep the ``ConfigLock`` and,
+step by step, reduce its impact till it eventually will be just used
+internally in the WConfD process.
+
+Unlocked Reads
+--------------
+
+In a first step, all configuration operations that are synchronised over
+a shared config lock, and therefore necessarily read-only, will instead
+use WConfD's ``readConfig`` used to obtain a snapshot of the configuration.
+This will be done without modifying the locks. It is sound, as reads to
+a Haskell ``IORef`` always yield a consistent value. From that snapshot
+the required view is computed locally. This saves two lock-configurtion
+write cycles per read and, additionally, does not block any concurrent
+modifications.
+
+In a second step, more specialised read functions will be added to ``WConfD``.
+This will reduce the traffic for reads.
+
+
+Set-and-release action
+----------------------
+
+As a typical pattern is to change the configuration and afterwards release
+the ``ConfigLock``. To avoid unncecessary delay in this operation (the next
+modification of the configuration can already happen while the last change
+is written out), WConfD will offer a combined command that will
+
+- set the configuration to the specified value,
+
+- release the config lock,
+
+- and only then wait for the configuration write to finish; it will not
+  wait for confirmation of the lock-release write.
+
+If jobs use this combined command instead of the sequential set followed
+by release, new configuration changes can come in during writeout of the
+current change; in particular, a writeout can contain more than one change.
+
+Short-lived ``ConfigLock``
+--------------------------
+
+For a lot of operations, the regular locks already ensure that only
+one job can modify a certain part of the configuration. For example,
+only jobs with an exclusive lock on an instance will modify that
+instance. Therefore, it can update that entity atomically,
+without relying on the configuration lock to achive consistency.
+``WConfD`` will provide such operations. To
+avoid interference with non-atomic operations that still take the
+config lock and write the configuration as a whole, this operation
+will only be carried out at times the config lock is not taken. To
+ensure this, the thread handling the request will take the config lock
+itself (hence no one else has it, if that succeeds) before the change
+and release afterwards; both operations will be done without
+triggering a writeout of the lock status.
+
+Note that the thread handling the request has to take the lock in its
+own name and not in that of the requesting job. A writeout of the lock
+status can still happen, triggered by other requests. Now, if
+``WConfD`` gets restarted after the lock acquisition, if that happend
+in the name of the job, it would own a lock without knowing about it,
+and hence that lock would never get released.
index 1299f39..425b6a8 100644 (file)
@@ -437,8 +437,8 @@ Step 2:
 
 Step 3:
   In a later step, the impact of the config lock will be reduced by moving
-  it more and more into an internal detail of WConfD. This process will be
-  detailed in a forthcoming design document.
+  it more and more into an internal detail of WConfD. This forthcoming process
+  of :doc:`design-configlock` is described separately.
 
 
 Locking
diff --git a/doc/design-disk-conversion.rst b/doc/design-disk-conversion.rst
new file mode 100644 (file)
index 0000000..47f94a8
--- /dev/null
@@ -0,0 +1,281 @@
+=================================
+Conversion between disk templates
+=================================
+
+.. contents:: :depth: 4
+
+This design document describes the support for generic disk template
+conversion in Ganeti. The logic used is disk template agnostic and
+targets to cover the majority of conversions among the supported disk
+templates.
+
+
+Current state and shortcomings
+==============================
+
+Currently, Ganeti supports choosing among different disk templates when
+creating an instance. However, converting the disk template of an
+existing instance is possible only between the ``plain`` and ``drbd``
+templates. This feature was added in Ganeti since its early versions
+when the number of supported disk templates was limited. Now that Ganeti
+supports plenty of choices, this feature should be extended to provide
+more flexibility to the user.
+
+The procedure for converting from the plain to the drbd disk template
+works as follows. Firstly, a completely new disk template is generated
+matching the size, mode, and the count of the current instance's disks.
+The missing volumes are created manually both in the primary (meta disk)
+and the secondary node. The original LVs running on the primary node are
+renamed to match the new names. The last step is to manually associate
+the DRBD devices with their mirror block device pairs. The conversion
+from the drbd to the plain disk template is much simpler than the
+opposite. Firstly, the DRBD mirroring is manually disabled. Then the
+unnecessary volumes including the meta disk(s) of the primary node, and
+the meta and data disk(s) from the previously secondary node are
+removed.
+
+
+Proposed changes
+================
+
+This design proposes the creation of a unified interface for handling
+the disk template conversions in Ganeti. Currently, there is no such
+interface and each one of the supported conversions uses a separate code
+path.
+
+This proposal introduces a single, disk-agnostic interface for handling
+the disk template conversions in Ganeti, keeping in mind that we want it
+to be as generic as possible. An exception case will be the currently
+supported conversions between the LVM-based disk templates. Their basic
+functionality will not be affected and will diverge from the rest disk
+template conversions. The target is to provide support for conversions
+among the majority of the available disk templates, and also creating
+a mechanism that will easily support any new templates that may be
+probably added in Ganeti, at a future point.
+
+
+Design decisions
+================
+
+Currently, the supported conversions for the LVM-based templates are
+handled by the ``LUInstanceSetParams`` LU. Our implementation will
+follow the same approach. From a high-level point-of-view this design
+can be split in two parts:
+
+* The extension of the LU's checks to cover all the supported template
+  conversions
+
+* The new functionality which will be introduced to provide the new
+  feature
+
+The instance must be stopped before starting the disk template
+conversion, as it currently is, otherwise the operation will fail. The
+new mechanism will need to copy the disk's data for the conversion to be
+possible. We propose using the Unix ``dd`` command to copy the
+instance's data. It can be used to copy data from source to destination,
+block-by-block, regardless of their filesystem types, making it a
+convenient tool for the case. Since the conversion will be done via data
+copy it will take a long time for bigger disks to copy their data and
+consequently for the instance to switch to the new template.
+
+Some template conversions can be done faster without copying explicitly
+their disks' data. A use case is the conversions between the LVM-based
+templates, i.e., ``drbd`` and ``plain`` which will be done as happens
+now and not using the ``dd`` command. Also, this implementation will
+provide partial support for the ``blockdev`` disk template which will
+act only as a source template. Since those volumes are adopted
+pre-existent block devices we will not support conversions targeting
+this template. Another exception case will be the ``diskless`` template.
+Since it is a testing template that creates instances with no disks we
+will not provide support for conversions that include this template
+type.
+
+
+We divide the design into the following parts:
+
+* Block device changes, that include the new methods which will be
+  introduced and will be responsible for building the commands for the
+  data copy from/to the requested devices
+
+* Backend changes, that include a new RPC call which will concatenate
+  the output of the above two methods and will execute the data copy
+  command
+
+* Core changes, that include the modifications in the Logical Unit
+
+* User interface changes, i.e., command line changes
+
+
+Block device changes
+--------------------
+
+The block device abstract class will be extended with two new methods,
+named ``Import`` and ``Export``. Those methods will be responsible for
+building the commands that will be used for the data copy between the
+corresponding devices. The ``Export`` method will build the command
+which will export the data from the source device, while the ``Import``
+method will do the opposite. It will import the data to the newly
+created target device. Those two methods will not perform the actual
+data copy; they will simply return the requested commands for
+transferring the data from/to the individual devices. The output of the
+two methods will be combined using a pipe ("|") by the caller method in
+the backend level.
+
+By default the data import and export will be done using the ``dd``
+command. All the inherited classes will use the base functionality
+unless there is a faster way to convert to. In that case the underlying
+block device will overwrite those methods with its specific
+functionality. A use case will be the Ceph/RADOS block devices which
+will make use of the ``rbd import`` and ``rbd export`` commands to copy
+their data instead of using the default ``dd`` command.
+
+Keeping the data copy functionality in the block device layer, provides
+us with a generic mechanism that works between almost all conversions
+and furthermore can be easily extended for new disk templates. It also
+covers the devices that support the ``access=userspace`` parameter and
+solves this problem in a generic way, by implementing the logic in the
+right level where we know what is the best to do for each device.
+
+
+Backend changes
+---------------
+
+Introduce a new RPC call:
+
+* blockdev_convert(src_disk, dest_disk)
+
+where ``src_disk`` and ``dest_disk`` are the original and the new disk
+objects respectively. First, the actual device instances will be
+computed and then they will be used to build the export and import
+commands for the data copy. The output of those methods will be
+concatenated using a pipe, following a similar approach with the impexp
+daemon. Finally, the unified data copy command will be executed, at this
+level, by the ``nodeD``.
+
+
+Core changes
+------------
+
+The main modifications will be made in the ``LUInstanceSetParams`` LU.
+The implementation of the conversion mechanism will be split into the
+following parts:
+
+* The generation of the new disk template for the instance. The new
+  disks will match the size, mode, and name of the original volumes.
+  Those parameters and any other needed, .i.e., the provider's name for
+  the ExtStorage conversions, will be computed by a new method which we
+  will introduce, named ``ComputeDisksInfo``. The output of that
+  function will be used as the ``disk_info`` argument of the
+  ``GenerateDiskTemplate`` method.
+
+* The creation of the new block devices. We will make use of the
+  ``CreateDisks`` method which creates and attaches the new block
+  devices.
+
+* The data copy for each disk of the instance from the original to the
+  newly created volume. The data copy will be made by the ``nodeD`` with
+  the rpc call we have introduced earlier in this design. In case some
+  disks fail to copy their data the operation will fail and the newly
+  created disks will be removed. The instance will remain intact.
+
+* The detachment of the original disks of the instance when the data
+  copy operation successfully completes by calling the
+  ``RemoveInstanceDisk`` method for each instance's disk.
+
+* The attachment of the new disks to the instance by calling the
+  ``AddInstanceDisk`` method for each disk we have created.
+
+* The update of the configuration file with the new values.
+
+* The removal of the original block devices from the node using the
+  ``BlockdevRemove`` method for each one of the old disks.
+
+
+User interface changes
+----------------------
+
+The ``-t`` (``--disk-template``) option from the gnt-instance modify
+command will specify the disk template to convert *to*, as it happens
+now. The rest disk options such as its size, its mode, and its name will
+be computed from the original volumes by the conversion mechanism, and
+the user will not explicitly provide them.
+
+
+ExtStorage conversions
+~~~~~~~~~~~~~~~~~~~~~~
+
+When converting to an ExtStorage disk template the
+``provider=*PROVIDER*`` option which specifies the ExtStorage provider
+will be mandatory. Also, arbitrary parameters can be passed to the
+ExtStorage provider. Those parameters will be optional and could be
+passed as additional comma separated options. Since it is not allowed to
+convert the disk template of an instance and make use of the ``--disk``
+option at the same time, we propose to introduce a new option named
+``--ext-params`` to handle the ``ext`` template conversions.
+
+::
+
+  gnt-instance modify -t ext --ext-params provider=pvdr1 test_vm
+  gnt-instance modify -t ext --ext-params provider=pvdr1,param1=val1,param2=val2 test_vm
+
+
+File-based conversions
+~~~~~~~~~~~~~~~~~~~~~~
+
+For conversions *to* a file-based template the ``--file-storage-dir``
+and the ``--file-driver`` options could be used, similarly to the
+**add** command, to manually configure the storage directory and the
+preferred driver for the file-based disks.
+
+::
+
+  gnt-instance modify -t file --file-storage-dir=mysubdir test_vm
+
+
+Supported template conversions
+==============================
+
+This is a summary of the disk template conversions that the conversion
+mechanism will support:
+
++--------------+-----------------------------------------------------------------------------------+
+| Source       |                                 Target Disk Template                              |
+| Disk         +---------+-------+------+------------+---------+------+------+----------+----------+
+| Template     |  Plain  |  DRBD | File | Sharedfile | Gluster | RBD  | Ext  | BlockDev | Diskless |
++==============+=========+=======+======+============+=========+======+======+==========+==========+
+| Plain        |    -    |  Yes. | Yes. |    Yes.    |   Yes.  | Yes. | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| DRBD         |   Yes.  |   -   | Yes. |    Yes.    |   Yes.  | Yes. | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| File         |   Yes.  |  Yes. |   -  |    Yes.    |   Yes.  | Yes. | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| Sharedfile   |   Yes.  |  Yes. | Yes. |     -      |   Yes.  | Yes. | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| Gluster      |   Yes.  |  Yes. | Yes. |    Yes.    |    -    | Yes. | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| RBD          |   Yes.  |  Yes. | Yes. |    Yes.    |   Yes.  |  -   | Yes. |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| Ext          |   Yes.  |  Yes. | Yes. |    Yes.    |   Yes.  | Yes. |  -   |    No.   |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| BlockDev     |   Yes.  |  Yes. | Yes. |    Yes.    |   Yes.  | Yes. | Yes. |     -    |   No.    |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+| Diskless     |   No.   |  No.  | No.  |    No.     |   No.   | No.  | No.  |    No.   |    -     |
++--------------+---------+-------+------+------------+---------+------+------+----------+----------+
+
+
+Future Work
+===========
+
+Expand the conversion mechanism to provide a visual indication of the
+data copy operation. We could monitor the progress of the data sent via
+a pipe, and provide to the user information such as the time elapsed,
+percentage completed (probably with a progress bar), total data
+transferred, and so on, similar to the progress tracking that is
+currently done by the impexp daemon.
+
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index ec9c63d..18fb75c 100644 (file)
@@ -23,6 +23,11 @@ This implementation imposes a number of limitations:
   config file can be made taggable. Having taggable disks will allow for
   further customizations.
 
+* All disks of an instance have to be of the same template. Dropping
+  this constraint would allow mixing different kinds of storage (e.g. an
+  instance might have a local ``plain`` storage for the OS and a
+  remotely replicated ``sharedstorage`` for the data).
+
 
 Proposed changes
 ================
@@ -40,6 +45,11 @@ The implementation is going to be split in four parts:
 * Allow creation/modification/deletion of disks that are not attached to
   any instance (requires new LUs for disks).
 
+* Allow disks of a single instance to be of different templates.
+
+* Remove all unnecessary distinction between disk templates and disk
+  types.
+
 
 Design decisions
 ================
@@ -70,8 +80,10 @@ The ``Disk`` object gets two extra slots, ``_TIMESTAMPS`` and
 ``serial_no``.
 
 The ``Instance`` object will no longer contain the list of disk objects
-that are attached to it. Instead, an Instance object will refer to its
-disks using their UUIDs. Since the order in which the disks are attached
+that are attached to it or a disk template.
+Instead, an Instance object will refer to its
+disks using their UUIDs and the disks will contain their own template.
+Since the order in which the disks are attached
 to an instance is important we are going to have a list of disk UUIDs
 under the Instance object which will denote the disks attached to the
 instance and their order at the same time. So the Instance's ``disks``
@@ -108,10 +120,14 @@ are no dangling pointers from instances to disks (i.e. an instance
 points to a disk that doesn't exist in the config).
 
 The 'upgrade' operation for the config should check if disks are top level
-citizens and if not it has to extract the disk objects from the instances and
-replace them with their uuids. In case of the 'downgrade' operation (where
+citizens and if not it has to extract the disk objects from the instances,
+replace them with their uuids, and copy the disk template. In case of the 'downgrade' operation (where
 disks will be made again part of the `Instance` object) all disks that are not
 attached to any instance at all will be ignored (removed from config).
+The disk template of the
+instance is set to the disk template of any disk attached to it. If
+there are multiple disk templates present, the downgrade fails and the
+user is requested to detach disks from the instances.
 
 
 Apply Disk modifications
@@ -119,7 +135,8 @@ Apply Disk modifications
 
 There are four operations that can be performed to a `Disk` object:
 
-* Create a new `Disk` object and save it to the config.
+* Create a new `Disk` object of a given template and save it to the
+  config.
 
 * Remove an existing `Disk` object from the config.
 
@@ -152,6 +169,34 @@ with the instance's disk objects. So in the backend we will only have to
 replace the ``disks`` slot with ``disks_info``.
 
 
+Eliminating the disk template from the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to remove the disk template from the instance model, all
+current uses of the disk template there need to be replaced. These uses
+fall into the following general categories:
+
+1. The configuration needs to reflect the new model. `cfgupgrade` and
+   `bootstrap` need to be fixed, creating and modifying instances and
+   disks for instances needs to be fixed.
+2. The query interface will no longer be able to return an instance disk
+   template.
+3. Several checks for the DISKLESS template will be replaced by checking
+   if any disks are attached.
+4. If an operation works disk by disk, the operation will dispatch for
+   the functionality by disk instead of by instance. If an operation
+   requires that all disks are of the same kind (e.g. a query if the
+   instance is DRBD backed) then the assumption is checked beforehand.
+   Since this is a user visible change, it will have to be announced in
+   the NEWS file specifying the calls changed.
+5. Operations that operate on the instance and extract the disk template
+   e.g. for creation of a new disk will require an additional parameter
+   for the disk template. Several instances already provide an optional
+   parameter to override the instance setting, those will become
+   required. This is incompatible as well and will need to be listed in
+   the NEWS file.
+
+
 .. TODO: Locks for Disk objects
 
 .. TODO: Attach/Detach disks
index 321871d..bc6735c 100644 (file)
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.12
+.. Last updated for Ganeti 2.13
 
 .. toctree::
    :maxdepth: 2
@@ -14,12 +14,16 @@ Design document drafts
    design-storagetypes.rst
    design-glusterfs-ganeti-support.rst
    design-hugepages-support.rst
-   design-optables.rst
    design-ceph-ganeti-support.rst
-   design-hsqueeze.rst
    design-os.rst
    design-move-instance-improvements.rst
    design-node-security.rst
+   design-ifdown.rst
+   design-location.rst
+   design-reservations.rst
+   design-sync-rate-throttling.rst
+   design-network2.rst
+   design-configlock.rst
    design-disks.rst
 
 .. vim: set textwidth=72 :
index 1a52229..a15381d 100644 (file)
@@ -54,7 +54,7 @@ To keep track where each device is plugged into, we add the
 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
+We propose to make use of QEMU 1.7 QMP 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.
@@ -81,7 +81,7 @@ 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
+PCI configuration (i.e. ``query-pci`` QMP 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
@@ -149,18 +149,19 @@ 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
+QEMU 1.7 QMP commands (``device_add``, ``device_del``,
+``blockdev-add``, ``netdev_add``, ``netdev_del``). Since ``drive_del``
+is not yet implemented in QMP we use the one of HMP. 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
+``query-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
+corresponding file descriptor over the QMP socket (UNIX domain
 socket). The open file is passed as a socket-level control message
 (SCM), using the ``fdsend`` python library.
 
@@ -220,8 +221,8 @@ support only disk addition/deletion.
  gnt-instance modify --net 1:remove --hotplug test
 
 
-Dealing with chroot and uid pool
---------------------------------
+Dealing with chroot and uid pool (and disks in general)
+-------------------------------------------------------
 
 The design so far covers all issues that arise without addressing the
 case where the kvm process will not run with root privileges.
@@ -232,18 +233,18 @@ Specifically:
 - 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
+For NIC hotplug we address this problem by using the ``getfd`` QMP
 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
+workarounds can be avoided, if we make use of the ``add-fd``
+QMP command, that was introduced in version 1.7. 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.
+every case. So, if the QMP does not support the ``add-fd``
+command, we will not allow disk hotplug
+and notify the user with the corresponding warning.
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-ifdown.rst b/doc/design-ifdown.rst
new file mode 100644 (file)
index 0000000..7626da9
--- /dev/null
@@ -0,0 +1,156 @@
+======================================
+Design for adding ifdown script to KVM
+======================================
+
+.. contents:: :depth: 4
+
+This is a design document about adding support for an ifdown script responsible
+for deconfiguring network devices and cleanup changes made by the ifup script. The
+first implementation will target KVM but it could be ported to Xen as well
+especially when hotplug gets implemented.
+
+
+Current state and shortcomings
+==============================
+
+Currently, KVM before instance startup, instance migration and NIC hotplug, it
+creates a tap and invokes explicitly the kvm-ifup script with the relevant
+environment (INTERFACE, MAC, IP, MODE, LINK, TAGS, and all the network info if
+any; NETWORK\_SUBNET, NETWORK\_TAGS, etc).
+
+For Xen we have the `vif-ganeti` script (associated with vif-script hypervisor
+parameter). The main difference is that Xen calls it by itself by passing it as
+an extra option in the configuration file.
+
+This ifup script can do several things; bridge a tap to a bridge, add ip rules,
+update a external DNS or DHCP server, enable proxy ARP or proxy NDP, issue
+openvswitch commands, etc.  In general we can divide those actions in two
+categories:
+
+1) Commands that change the state of the host
+2) Commands that change the state of external components.
+
+Currently those changes do not get cleaned up or modified upon instance
+shutdown, remove, migrate, or NIC hot-unplug. Thus we have stale entries in
+hosts and most important might have stale/invalid configuration on external
+components like routers that could affect connectivity.
+
+A workaround could be hooks but:
+
+1) During migrate hooks the environment is the one held in config data
+and not in runtime files. The NIC configuration might have changed on
+master but not on the running KVM process (unless hotplug is used).
+Plus the NIC order in config data might not be the same one on the KVM
+process.
+
+2) On instance modification, changes are not available on hooks. With
+other words we do not know the configuration before and after modification.
+
+Since Ganeti is the orchestrator and is the one who explicitly configures
+host devices (tap, vif) it should be the one responsible for cleanup/
+deconfiguration. Especially on a SDN approach this kind of script might
+be useful to cleanup flows in the cluster in order to ensure correct paths
+without ping pongs between hosts or connectivity loss for the instance.
+
+
+Proposed Changes
+================
+
+We add an new script, kvm-ifdown that is explicitly invoked after:
+
+1) instance shutdown on primary node
+2) successful instance migration on source node
+3) failed instance migration on target node
+4) successful NIC hot-remove on primary node
+
+If an administrator's custom ifdown script exists (e.g. `kvm-ifdown-custom`),
+the `kvm-ifdown` script executes that script, as happens with `kvm-ifup`.
+
+Along with that change we should rename custom ifup script from
+`kvm-vif-bridge` (which does not make any sense) to `kvm-ifup-custom`.
+
+In contrary to `kvm-ifup`, one cannot rely on `kvm-ifdown` script to be
+called. A node might die just after a successful migration or after an
+instance shutdown. In that case, all "undo" operations will not be invoked.
+Thus, this script should work "on a best effort basis" and the network
+should not rely on the script being called or being successful. Additionally
+it should modify *only* the node local dynamic configs (routes, arp entries,
+SDN, firewalls, etc.), whereas static ones (DNS, DHCP, etc.) should be modified
+via hooks.
+
+
+Implementation Details
+======================
+
+1) Where to get the NIC info?
+
+We cannot account on config data since it might have changed. So the only
+place we keep our valid data is inside the runtime file. During instance
+modifications (NIC hot-remove, hot-modify) we have the NIC object from
+the RPC. We take its UUID and search for the corresponding entry in the
+runtime file to get further info. After instance shutdown and migration
+we just take all NICs from the runtime file and invoke the ifdown script
+for each one
+
+2) Where to find the corresponding TAP?
+
+Currently TAP names are kept under
+/var/run/ganeti/kvm-hypervisor/nics/<instance>/<nic\_index>.
+This is not enough. As told above a NIC's index might change during instance's
+life. An example will make things clear:
+
+* The admin starts an instance with three NICs.
+* The admin removes the second without hotplug.
+* The admin removes the first with hotplug.
+
+The index that will arrive with the RPC will be 1 and if we read the relevant
+NIC file we will get the tap of the NIC that has been removed on second
+step but is still existing in the KVM process.
+
+So upon TAP creation we write another file with the same info but named
+after the NIC's UUID. The one named after its index can be left
+for compatibility (Ganeti does not use it; external tools might)
+Obviously this info will not be available for old instances in the cluster.
+The ifdown script should be aware of this corner case.
+
+3) What should we cleanup/deconfigure?
+
+Upon NIC hot-remove we obviously want to wipe everything. But on instance
+migration we don't want to reset external configuration like DNS.  So we choose
+to pass an extra positional argument to the ifdown script (it already has the
+TAP name) that will reflect the context it was invoked with. Please note that
+de-configuration of external components is not encouraged and should be
+done via hooks. Still we could easily support it via this extra argument.
+
+4) What will be the script environment?
+
+In general the same environment passed to ifup script. Except instance's
+tags. Those are the only info not kept in runtime file and it can
+change between ifup and ifdown script execution. The ifdown
+script must be aware of it and should cleanup everything that ifup script
+might setup depending on instance tags (e.g. firewalls, etc)
+
+
+Configuration Changes
+~~~~~~~~~~~~~~~~~~~~~
+
+1) The `kvm-ifdown` script will be an extra file installed under the same dir
+   `kvm-ifup` resides. We could have a single script (and symbolic links to it)
+   that shares the same code, where a second positional argument or an extra
+   environment variable would define if we are bringing the interface up or
+   down. Still this is not the best practice since it is not equivalent
+   with how KVM uses `script` and `downscript` in the `netdev` option; scripts
+   are different files that get the tap name as positional argument. Of course
+   common code will go in `net-common` so that it can be sourced from either
+   Xen or KVM specific scripts.
+
+2) An extra file written upon TAP creation named after the NIC's UUID and
+   including the TAP's name. Since this should be the correct file to keep
+   backwards compatibility we create a symbolic link named after the NIC's
+   index and pointing to this new file.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-location.rst b/doc/design-location.rst
new file mode 100644 (file)
index 0000000..5d4989a
--- /dev/null
@@ -0,0 +1,108 @@
+======================================
+Improving location awareness of Ganeti
+======================================
+
+This document describes an enhancement of Ganeti's instance
+placement by taking into account that some nodes are vulnerable
+to common failures.
+
+.. contents:: :depth: 4
+
+
+Current state and shortcomings
+==============================
+
+Currently, Ganeti considers all nodes in a single node group as
+equal. However, this is not true in some setups. Nodes might share
+common causes of failure or be even located in different places
+with spacial redundancy being a desired feature.
+
+The similar problem for instances, i.e., instances providing the
+same external service should not placed on the same nodes, is
+solved by means of exclusion tags. However, there is no mechanism
+for a good choice of node pairs for a single instance. Moreover,
+while instances providing the same service run on different nodes,
+they are not spread out location wise.
+
+
+Proposed changes
+================
+
+We propose to the cluster metric (as used, e.g., by ``hbal`` and ``hail``)
+to honor additional node tags indicating nodes that might have a common
+cause of failure.
+
+Failure tags
+------------
+
+As for exclusion tags, cluster tags will determine which tags are considered
+to denote a source of common failure. More precisely, a cluster tag of the
+form *htools:nlocation:x* will make node tags starting with *x:* indicate a
+common cause of failure, that redundant instances should avoid.
+
+Metric changes
+--------------
+
+The following components will be added cluster metric, weighed appropriately.
+
+- The number of pairs of an instance and a common-failure tag, where primary
+  and secondary node both have this tag.
+
+- The number of pairs of exclusion tags and common-failure tags where there
+  exist at least two instances with the given exclusion tag with the primary
+  node having the given common-failure tag.
+
+The weights for these components might have to be tuned as experience with these
+setups grows, but as a starting point, both components will have a weight of
+0.5 each. In this way, any common-failure violations are less important than
+any hard constraints missed (instances on offline nodes, N+1 redundancy,
+exclusion tags) so that the hard constraints will be restored first when
+balancing a cluster. Nevertheless, with weight 0.5 the new common-failure
+components will still be significantly more important than all the balancedness
+components (cpu, disk, memory), as the latter are standard deviations of
+fractions.
+
+Appart from changing the balancedness metric, common-failure tags will
+not have any other effect. In particular, as opposed to exclusion tags,
+no hard guarantees are made: ``hail`` will try allocate an instance in
+a common-failure avoiding way if possible, but still allocate the instance
+if not.
+
+Additional migration restrictions
+=================================
+
+Inequality between nodes can also restrict the set of instance migrations
+possible. Here, the most prominent example is updating the hypervisor where
+usually migrations from the new to the old hypervisor version is not possible.
+
+Migration tags
+--------------
+
+As for exclusion tags, cluster tags will determine which tags are considered
+restricting migration. More precisely, a cluster tag of the form
+*htools:migration:x* will make node tags starting with *x:* a migration relevant
+node property. Additionally, cluster tags of the form
+*htools:allowmigration:y::z* where *y* and *z* are migration tags not containing
+*::* specify a unidirectional migration possibility from *y* to *z*.
+
+Restriction
+-----------
+
+An instance migration will only be considered by ``htools``, if for all
+migration tags *y* present on the node migrated from, either the tag
+is also present on the node migrated to or there is a cluster tag
+*htools::allowmigration:y::z* and the target node is tagged *z* (or both).
+
+Example
+-------
+
+For the simple hypervisor upgrade, where migration from old to new is possible,
+but not the other way round, tagging all already upgraded nodes suffices.
+
+
+Advise only
+-----------
+
+These tags are of advisory nature only. That is, all ``htools`` will strictly
+obey the restrictions imposed by those tags, but Ganeti will not prevent users
+from manually instructing other migrations.
diff --git a/doc/design-network2.rst b/doc/design-network2.rst
new file mode 100644 (file)
index 0000000..fd14b2b
--- /dev/null
@@ -0,0 +1,502 @@
+============================
+Network Management (revised)
+============================
+
+.. contents:: :depth: 4
+
+This is a design document detailing how to extend the existing network
+management and make it more flexible and able to deal with more generic
+use cases.
+
+
+Current state and shortcomings
+------------------------------
+
+Currently in Ganeti, networks are tightly connected with IP pools,
+since creation of a network implies the existence of one subnet
+and the corresponding IP pool. This design does not allow common
+scenarios like:
+
+- L2 only networks
+- IPv6 only networks
+- Networks without an IP pool
+- Networks with an IPv6 pool
+- Networks with multiple IP pools (alternative to externally reserving
+  IPs)
+
+Additionally one cannot have multiple IP pools inside one network.
+Finally, from the instance perspective, a NIC cannot get more than one
+IPs (v4 and v6).
+
+
+Proposed changes
+----------------
+
+In order to deal with the above shortcomings, we propose to extend
+the existing networks in Ganeti and support:
+
+a) Networks with multiple subnets
+b) Subnets with multiple IP pools
+c) NICs with multiple IPs from various subnets of a single network
+
+These changes bring up some design and implementation issues that we
+discuss in the following sections.
+
+Semantics
+++++++++++
+
+Quoting the initial network management design doc "an IP pool consists
+of two bitarrays. Specifically the ``reservations`` bitarray which holds
+all IP addresses reserved by Ganeti instances and the ``external
+reservations`` bitarray with all IPs that are excluded from the IP pool
+and cannot be assigned automatically by Ganeti to instances (via
+ip=pool)".
+
+Without violating those semantics, here, we clarify the following
+definitions.
+
+**network**: A cluster level taggable configuration object with a
+user-provider name, (e.g. network1, network2), UUID and MAC prefix.
+
+**L2**: The `mode` and `link` with which we connect a network to a
+nodegroup. A NIC attached to a network will inherit this info, just like
+connecting an Ethernet cable to a physical NIC. In this sense we only
+have one L2 info per NIC.
+
+**L3**: A CIDR and a gateway related to the network. Since a NIC can
+have multiple IPs on the same cable each network can have multiple L3
+info with the restriction that they do not overlap with each other.
+The gateway is optional (just like with current implementation). No
+gateway can be used for private networks that do not have a default
+route.
+
+**subnet**: A subnet is the above L3 info plus some additional information
+(see below).
+
+**ip**: A valid IP should reside in a network's subnet, and should not
+be used by more than one instance. An IP can be either obtained dynamically
+from a pool or requested explicitly from a subnet (or a pool).
+
+**range**: Sequential IPs inside one subnet calculated either from the
+first IP and a size (e.g. start=192.0.2.10, size=10) or the first IP and
+the last IP (e.g. start=192.0.2.10, end=192.0.2.19). A single IP can
+also be thought of as an IP range with size=1 (see configuration
+changes).
+
+**reservations**: All IPs that are used by instances in the cluster at
+any time.
+
+**external reservations**: All IPs that are supposed to be reserved
+by the admin for either some external component or specific instances.
+If one instance requests an external IP explicitly (ip=192.0.2.100),
+Ganeti will allow the operation only if ``--force`` is given. Still, the
+admin can externally reserve an IP that is already in use by an
+instance, as happens now. This helps to reserve an IP for future use and
+at the same time prevent any possible race between the instance that
+releases this IP and another that tries to retrieve it.
+
+**pool**: A (range, reservations, name) tuple from which instances can
+dynamically obtain an IP. Reservations is a bitarray with
+length the size of the range, and is needed so that we know which IPs
+are available at any time without querying all instances. The use of
+name is explained below. A subnet can have multiple pools.
+
+
+Split L2 from L3
+++++++++++++++++
+
+Currently networks in Ganeti do not separate L2 from L3. This means
+that one cannot use L2 only networks. The reason is because the CIDR
+(passed currently with the ``--network`` option) and the derived IP pool
+are mandatory. This design makes L3 info optional. This way we can have
+an L2 only network just by connecting a Ganeti network to a nodegroup
+with the desired `mode` and `link`. Then one could add one or more subnets
+to the existing network.
+
+
+Multiple Subnets per Network
+++++++++++++++++++++++++++++
+
+Currently the IPv4 CIDR is mandatory for a network. Also a network can
+obtain at most one IPv4 CIDR and one IPv6 CIDR. These restrictions will
+be lifted.
+
+This design doc introduces support for multiple subnets per network. The
+L3 info will be moved inside the subnet. A subnet will have a `name` and
+a `uuid` just like NIC and Disk config objects. Additionally it will contain
+the `dhcp` flag which is explained below, and the `pools` and `external`
+fields which are mentioned in the next section. Only the `cidr` will be
+mandatory.
+
+Any subnet related actions will be done via the new ``--subnet`` option.
+Its syntax will be similar to ``--net``.
+
+The network's subnets must not overlap with each other. Logic will
+validate any operations related to reserving/releasing of IPs and check
+whether a requested IP is included inside one of the network's subnets.
+Just like currently, the L3 info will be exported to NIC configuration
+hooks and scripts as environment variables. The example below adds
+subnets to a network:
+
+::
+
+  gnt-network modify --subnet add:cidr=10.0.0.0/24,gateway=10.0.0.1,dhcp=true net1
+  gnt-network modify --subnet add:cidr=2001::/64,gateway=2001::1,dhcp=true net1
+
+To remove a subnet from a network one should use:
+
+::
+
+  gnt-network modify --subnet some-ident:remove network1
+
+where some-ident can be either a CIDR, a name or a UUID. Ganeti will
+allow this operation only if no instances use IPs from this subnet.
+
+Since DHCP is allowed only for a single CIDR on the same cable, the
+subnet must have a `dhcp` flag. Logic must not allow more that one
+subnets of the same version (4 or 6) in the same network to have DHCP enabled.
+To modify a subnet's name or the dhcp flag one could use:
+
+::
+
+  gnt-network modify --subnet some-ident:modify,dhcp=false,name=foo network1
+
+This would search for a registered subnet that matches the identifier,
+disable DHCP on it and change its name.
+The ``dhcp`` parameter is used only for validation purposes and does not
+make Ganeti starting a DHCP service. It will just be exported to
+external scripts (ifup and hooks) and handled accordingly.
+
+Changing the CIDR or the gateway of a subnet should also be supported.
+
+::
+
+  gnt-network modify --subnet some-ident:modify,cidr=192.0.2.0/22 net1
+  gnt-network modify --subnet some-ident:modify,cidr=192.0.2.32/28 net1
+  gnt-network modify --subnet some-ident:modify,gateway=192.0.2.40 net1
+
+Before expanding a subnet logic should should check for overlapping
+subnets. Shrinking the subnet should be allowed only if the ranges
+that are about to be trimmed are not included either in pool
+reservations or external ranges.
+
+
+Multiple IP pools per Subnet
+++++++++++++++++++++++++++++
+
+Currently IP pools are automatically created during network creation and
+include the whole subnet. Some IPs can be excluded from the pool by
+passing them explicitly with ``--add-reserved-ips`` option.
+
+Still for IPv6 subnets or even big IPv4 ones this might be insufficient.
+It is impossible to have two bitarrays for a /64 prefix. Even for IPv4
+networks a /20 subnet currently requires 8K long bitarrays. And the
+second 4K is practically useless since the external reservations are way
+less than the actual reservations.
+
+This design extract IP pool management from the network logic, and pools
+will become optional. Currently the pool is created based on the
+network's CIDR. With multiple subnets per network, we should be able to
+create and add IP pools to a network (and eventually to the
+corresponding subnet). Each pool will have an optional user friendly
+`name` so that the end user can refer to it (see instance related
+operations).
+
+The user will be able to obtain dynamically an IP only if we have
+already defined a pool for a network's subnet. One would use ``ip=pool``
+for the first available IP of the first available pool, or
+``ip=some-pool-name`` for the first available IP of a specific pool.
+
+Any pool related actions will be done via the new ``--pool`` option.
+
+In order to add a pool a relevant subnet should pre-exist. Overlapping
+pools won't be allowed. For example:
+
+::
+
+  gnt-network modify --pool add:192.0.2.10-192.0.2.100,name=pool1 net1
+  gnt-network modify --pool add:10.0.0.7-10.0.0.20 net1
+  gnt-network modify --pool add:10.0.0.100 net1
+
+will first parse and find the ranges. Then for each range, Ganeti will
+try to find a matching subnet meaning that a pool must be a subrange of
+the subnet. If found, the range with empty reservations will be appended
+to the list of the subnet's pools. Moreover, logic must be added to
+reserve the IPs that are currently in use by instances of this network.
+
+Adding a pool can be easier if we associate it directly with a subnet.
+For example on could use the following shortcuts:
+
+::
+
+  gnt-network modify --subnet add:cidr=10.0.0.0/27,pool net1
+  gnt-network modify --pool add:subnet=some-ident
+  gnt-network modify --pool add:10.0.0.0/27 net1
+
+During pool removal, logic should be added to split pools if ranges
+given overlap existing ones. For example:
+
+::
+
+  gnt-network modify --pool remove:192.0.2.20-192.0.2.50 net1
+
+will split the pool previously added (10-100) into two new ones;
+10-19 and 51-100. The corresponding bitarrays will be trimmed
+accordingly. The name will be preserved.
+
+The same things apply to external reservations. Just like now,
+modifications will take place via the ``--add|remove-reserved-ips``
+option. Logic must be added to support IP ranges.
+
+::
+
+  gnt-network modify --add-reserved-ips 192.0.2.20-192.0.2.50 net1
+
+
+Based on the aforementioned we propose the following changes:
+
+1) Change the IP pool representation in config data.
+
+  Existing `reservations` and `external_reservations` bitarrays will be
+  removed. Instead, for each subnet we will have:
+
+  * `pools`: List of (IP range, reservations bitarray) tuples.
+  * `external`: List of IP ranges
+
+  For external ranges the reservations bitarray is not needed
+  since this will be all 1's.
+
+  A configuration example could be::
+
+    net1 {
+      subnets [
+        uuid1 {
+            name: subnet1
+            cidr: 192.0.2.0/24
+            pools: [
+              {range:Range(192.0.2.10, 192.0.2.15), reservations: 00000, name:pool1}
+              ]
+            reserved: [192.0.2.15]
+            }
+        uuid2  {
+            name: subnet2
+            cidr: 10.0.0.0/24
+            pools: [
+              {range:10.0.0.8/29, reservations: 00000000, name:pool3}
+              {range:10.0.0.40-10.0.0.45, reservations: 000000, name:pool3}
+              ]
+            reserved: [Range(10.0.0.8, 10.0.0.15), 10.2.4.5]
+            }
+        ]
+    }
+
+  Range(start, end) will be some json representation of an IPRange().
+  We decide not to store external reservations as pools (and in the
+  same list) since we get the following advantages:
+
+ - Keep the existing semantics for pools and external reservations.
+
+ - Each list has similar entries: one has pools the other has ranges.
+   The pool must have a bitarray, and has an optional name. It is
+   meaningless to add a name and a bitarray to external ranges.
+
+ - Each list must not have overlapping ranges. Still external
+   reservations can overlap with pools.
+
+ - The --pool option supports add|remove|modify command just like
+   `--net` and `--disk` and operate on single entities (a restriction that
+   is not needed for external reservations).
+
+ - Another thing, and probably the most important, is that in order to
+   get the first available IP, only the reserved list must be checked for
+   conflicts. The ipaddr.summarize_address_range(first, last) could be very
+   helpful.
+
+
+2) Change the network module logic.
+
+  The above changes should be done in the network module and be transparent
+  to the rest of the Ganeti code. If a random IP from the networks is
+  requested, Ganeti searches for an available IP from the first pool of
+  the first subnet. If it is full it gets to the next pool. Then to the
+  next subnet and so on. Of course the `external` IP ranges will be
+  excluded. If an IP is explicitly requested, Ganeti will try to find a
+  matching subnet. Its pools and external will be checked for
+  availability. All this logic will be extracted in a separate class
+  with helper methods for easier manipulation of IP ranges and
+  bitarrays.
+
+  Bitarray processing can be optimized too. The usage of bitarrays will
+  be reduced since (a) we no longer have `external_reservations` and (b)
+  pools will have shorter bitarrays (i.e. will not have to cover the whole
+  subnet). Besides that, we could keep the bitarrays in memory, so that
+  in most cases (e.g. adding/removing reservations, querying), we don't
+  keep converting strings to bitarrays and vice versa. Also, the Haskell
+  code could as well keep this in memory as a bitarray, and validate it
+  on load.
+
+3) Changes in config module.
+
+  We should not have instances with the same IP inside the same network.
+  We introduce _AllIPs() helper config method that will hold all existing
+  (IP, network) tuples. Config logic will check this list as well
+  before passing it to TemporaryReservationManager.
+
+4) Change the query mechanism.
+
+  Since we have more that one subnets the new `subnets` field will
+  include a list of:
+
+  * cidr: IPv4 or IPv6 CIDR
+  * gateway: IPv4 or IPv6 address
+  * dhcp: True or False
+  * name: The user friendly name for the subnet
+
+  Since we want to support small pools inside big subnets, current query
+  results are not practical as far as the `map` field is concerned. It
+  should be replaced with the new `pools` field for each subnet, which will
+  contain:
+
+  * start: The first IP of the pool
+  * end: The last IP of the pool
+  * map: A string with 'X' for reserved IPs (either external or not) and
+    with '.' for all available ones inside the pool
+
+
+
+Multiple IPs per NIC
+++++++++++++++++++++
+
+Currently IP is a simple string inside the NIC object and there is a
+one-to-one mapping between the `ip` and the `network` slots. The whole
+logic behind this is that a NIC belongs to a network (cable) and
+inherits its mode and link. This rational will not change.
+
+Since this design adds support for multiple subnets per network, a NIC
+must be able to obtain multiple IPs from various subnets of the same
+network. Thus we change the `ip` slot into list.
+
+We introduce a new `ipX` attribute. For backwards compatibility `ip`
+will denote `ip0`.
+During instance related operations one could use something like:
+
+::
+
+  gnt-instance add --net 0:ip0=192.0.2.4,ip1=pool,ip2=some-pool-name,network=network1 inst1
+  gnt-instance add --net 0:ip=pool,network1 inst1
+
+
+This will be parsed, converted to a proper list (e.g. ip = [192.0.2.4,
+"pool", "some-pool-name"]) and finally passed to the corresponding opcode.
+Based on the previous example, here the first IP will match subnet1, the
+second IP will be retrieved from the first available pool of the first
+available subnet, and the third from the pool with the some-pool name.
+
+During instance modification, the `ip` option will refer to the first IP
+of the NIC, whereas the `ipX` will refer to the X'th IP. As with NICs
+we start counting from 0 so `ip1` will refer to the second IP. For example
+one should pass:
+
+::
+
+ --net 0:modify,ip1=1.2.3.10
+
+to change the second IP of the first NIC to 1.2.3.10,
+
+::
+
+  --net -1:add,ip0=pool,ip1=1.2.3.4,network=test
+
+to add a new NIC with two IPs, and
+
+::
+
+  --net 1:modify,ip1=none
+
+to remove the second IP of the second NIC.
+
+
+Configuration changes
+---------------------
+
+IPRange config object:
+  Introduce new config object that will hold ranges needed by pools, and
+  reservations. It will be either a tuple of (start, size, end) or a
+  simple string. The `end` is redundant and can derive from start and
+  size in runtime, but will appear in the representation for readability
+  and debug reasons.
+
+Pool config object:
+  Introduce new config object to represent a single subnet's pool. It
+  will have the `range`, `reservations`, `name` slots. The range slot
+  will be an IPRange config object, the reservations a bitarray and the
+  name a simple string.
+
+Subnet config object:
+  Introduce new config object with slots: `name`, `uuid`, `cidr`,
+  `gateway`, `dhcp`, `pools`, `external`. Pools is a list of Pool config
+  objects. External is a list of IPRange config objects. All ranges must
+  reside inside the subnet's CIDR. Only `cidr` will be mandatory. The
+  `dhcp` attribute will be False by default.
+
+Network config objects:
+  The L3 and the IP pool representation will change. Specifically all
+  slots besides `name`, `mac_prefix`, and `tag` will be removed. Instead
+  the slot `subnets` with a list of Subnet config objects will be added.
+
+NIC config objects:
+  NIC's network slot will be removed and the `ip` slot will be modified
+  to a list of strings.
+
+KVM runtime files:
+  Any change done in config data must be done also in KVM runtime files.
+  For this purpose the existing _UpgradeSerializedRuntime() can be used.
+
+
+Exported variables
+------------------
+
+The exported variables during instance related operations will be just
+like Linux uses aliases for interfaces. Specifically:
+
+``IP:i`` for the ith IP.
+
+``NETWORK_*:i`` for the ith subnet. * is SUBNET, GATEWAY, DHCP.
+
+In case of hooks those variables will be prefixed with ``INSTANCE_NICn``
+for the nth NIC.
+
+
+Backwards Compatibility
+-----------------------
+
+The existing networks representation will be internally modified.
+They will obtain one subnet, and one pool with range the whole subnet.
+
+During `gnt-network add` if the deprecated ``--network`` option is passed
+will still create a network with one subnet, and one IP pool with the
+size of the subnet. Otherwise ``--subnet`` and ``--pool`` options
+will be needed.
+
+The query mechanism will also include the deprecated `map` field. For the
+newly created network this will contain only the mapping of the first
+pool. The deprecated `network`, `gateway`, `network6`, `gateway6` fields
+will point to the first IPv4 and IPv6 subnet accordingly.
+
+During instance related operation the `ip` argument of the ``--net``
+option will refer to the first IP of the NIC.
+
+Hooks and scripts will still have the same environment exported in case
+of single IP per NIC.
+
+This design allows more fine-grained configurations which in turn yields
+more flexibility and a wider coverage of use cases. Still basic cases
+(the ones that are currently available) should be easy to set up.
+Documentation will be enriched with examples for both typical and
+advanced use cases of gnt-network.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 4c52cea..e1d460d 100644 (file)
@@ -4,6 +4,16 @@ Design for adding a node to a cluster
 .. contents:: :depth: 3
 
 
+Note
+----
+
+Closely related to this design is the more recent design
+:doc:`node security <design-node-security>` which extends and changes
+some of the aspects mentioned in this document. Make sure that you
+read the more recent design as well to get an up to date picture of
+Ganeti's procedure for adding new nodes.
+
+
 Current state and shortcomings
 ------------------------------
 
index 208bcb3..1215277 100644 (file)
@@ -234,7 +234,13 @@ In case of readding a node that used to be in the cluster before,
 handling of the SSH keys would basically be the same, in particular also
 a new SSH key pair is generated for the node, because we cannot be sure
 that the old key pair has not been compromised while the node was
-offlined.
+offlined. Note that for reasons of data hygiene, a node's
+``ganeti_pub_keys`` file is cleared before the node is readded.
+Also, Ganeti attempts to remove any Ganeti keys from the ``authorized_keys``
+file before the node is readded. However, since Ganeti does not keep a list
+of all keys ever used in the cluster, this applies only to keys which
+are currently used in the cluster. Note that Ganeti won't touch any keys
+that were added to the ``authorized_keys`` by other systems than Ganeti.
 
 
 Pro- and demoting a node to/from master candidate
@@ -299,7 +305,7 @@ The same behavior should be ensured for the corresponding rapi command.
 Cluster verify
 ~~~~~~~~~~~~~~
 
-So far, 'gnt-cluster verify' checks the SSH connectivity of all nodes to
+So far, ``gnt-cluster verify`` checks the SSH connectivity of all nodes to
 all other nodes. We propose to replace this by the following checks:
 
 - For all master candidates, we check if they can connect any other node
@@ -340,7 +346,7 @@ will be backed up and not simply overridden.
 Downgrades
 ~~~~~~~~~~
 
-These downgrading steps will be implemtented from 2.12 to 2.11:
+These downgrading steps will be implemtented from 2.13 to 2.12:
 
 - The master node's private/public key pair will be distributed to all
   nodes (via SSH) and the individual SSH keys will be backed up.
@@ -351,8 +357,26 @@ These downgrading steps will be implemtented from 2.12 to 2.11:
 Renew-Crypto
 ~~~~~~~~~~~~
 
-The ``gnt-cluster renew-crypto`` command is not affected by the proposed
-changes related to SSH.
+The ``gnt-cluster renew-crypto`` command will be extended by a new
+option ``--new-ssh-keys``, which will renew all SSH keys on all nodes
+and rebuild the ``authorized_keys`` files and the ``ganeti_pub_keys``
+files according to the previous sections. This operation will be
+performed considering the already stated security considerations, for
+example minimizing RPC calls, distribution of keys via SSH only etc.
+
+
+Compliance to --no-ssh-init and --no-node-setup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+With this design, Ganeti will do more manipulations of SSH keys and
+``authorized_keys`` files than before. If this is not feasible in
+a Ganeti environment, the administrator has the option to prevent
+Ganeti from performing any manipulations on the SSH setup of the nodes.
+The options for doing so, are ``--no-ssh-init`` for ``gnt-cluster
+init``, and ``--no-node-setup`` for ``gnt-node add``. Note that
+these options already existed before the implementation of this
+design, we just confirm that they will be complied to with the
+new design as well.
 
 
 Proposal regarding node daemon certificates
index db24690..6c0c1e0 100644 (file)
@@ -65,14 +65,13 @@ Filter rules are given by the following data.
   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
+  in order of increasing priority. 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
+  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.
+- A list of predicates to be matched against the job.
 
 - An action. For the time being, one of the following, but more
   actions might be added in the future (in particular, future
@@ -88,16 +87,34 @@ Filter rules are given by the following data.
     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.
+    it will be 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. 
+  - RATE_LIMIT ``n``, where ``n`` is a positive integer. The job will
+    be held in the queue while ``n`` or more jobs where this rule
+    applies are running. Jobs that are forked off from luxid are
+    considered running. Jobs already running when this rule is added
+    are not changed. Logically, this rule is applied job by job
+    sequentially, so that the number of jobs where this rule applies
+    is limited to ``n`` once the jobs running at rule addition have
+    finished.
+
+- 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.
 
+When a filter rule applies
+--------------------------
+
+A filter rule in a filter chain *applies* to a job if it is the first rule
+in the chain of which all predicates *match* the job, and if its action is not
+CONTINUE.
+
+Filter chains are processed in increasing order of priority (lowest number
+means highest priority), then watermark, then UUID.
+
 Predicates available for the filter rules
 -----------------------------------------
 
@@ -115,7 +132,7 @@ in the future.
   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``
+- ``opcode``. Only parameter is a boolean expression. 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.
@@ -132,9 +149,9 @@ Examples
 Draining the queue.
 ::
 
-   {'priority': 0,
-    'predicates': [['jobid', ['>', 'id', 'watermark']]],
-    'action': 'REJECT'}
+  {"priority": 0,
+   "predicates": [["jobid", [">", "id", "watermark"]]],
+   "action": "REJECT"}
 
 Soft draining could be achieved by replacing ``REJECT`` by ``PAUSE`` in the
 above example.
@@ -142,17 +159,27 @@ 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'}
+  {"priority": 0,
+   "predicates": [["reason", ["=~", "reason", "maintenance pink bunny"]]],
+   "action": "ACCEPT"}
+  {"priority": 1,
+   "predicates": [["jobid", [">", "id", "watermark"]]],
+   "action": "PAUSE"}
+
+Cancelling all queued instance creations and disallowing new such jobs.
+::
+
+  {"priority": 1,
+   "predicates": [["opcode", ["=", "OP_ID", "OP_INSTANCE_CREATE"]]],
+   "action": "REJECT"}
 
-Canceling all queued instance creations and disallowing new such jobs.
+Limiting the number of simultaneous instance disk replacements to 10 in order
+to throttle replication traffic.
 ::
 
-  {'priority': 1,
-   'predicates': [['opcode', ['=', 'OP_ID', 'OP_INSTANCE_CREATE']]],
-   'action': 'REJECT'}
+  {"priority": 99,
+   "predicates": [["opcode", ["=", "OP_ID", "OP_INSTANCE_REPLACE_DISKS"]]],
+   "action": ["RATE_LIMIT", 10]}
 
 
 
@@ -189,3 +216,27 @@ 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.
+
+
+Additional Ad-Hoc Rate Limiting
+===============================
+
+Besides a general policy to control the job queue, it is often very
+useful to have a lightweight way for one-off rate-limiting. One
+example would be evacuating a node but limiting the number of
+simultaneous instance moves to no overload the replication network.
+
+Therefore, an additional rate limiting is done over the
+:doc:`design-reason-trail` as follows. ``reason`` fields in a reason
+3-tuple starting with ``rate-limit:n:`` where ``n`` is a positive
+integer are considered rate-limiting buckets. A job belongs to a
+rate-limiting bucket if it contains at least one op-code with at least
+one reason-trail 3-tuple with that particular ``reason`` field. The
+scheduler will ensure that, for each rate-limiting bucket, there are
+at most ``n`` jobs belonging to that bucket that are running in
+parallel.
+
+The limiting in the initial example can then be done as follows.
+::
+
+  # gnt-node evacuate --reason='rate-limit:7:operation pink bunny' node1
diff --git a/doc/design-reservations.rst b/doc/design-reservations.rst
new file mode 100644 (file)
index 0000000..b54071b
--- /dev/null
@@ -0,0 +1,129 @@
+=====================
+Instance Reservations
+=====================
+
+.. contents:: :depth: 4
+
+This is a design document detailing the addition of a concept
+of reservations for forthcoming instances to Ganeti.
+
+
+Current state and shortcomings
+==============================
+
+Currently, for a new instance, all information about the instance,
+including a resolvable full name, needs to be present before an
+instance creation can be attempted. Moreover, the only way to find
+out if a cluster can host an instance is to try creating it. This
+can lead to problems in situations where the host name can only
+be determined, and hence DNS entries created, once the cluster for
+the instance is chosen. If lot of instances are created in parallel,
+by the time the DNS entries propagated, the cluster capacity might
+already be exceeded.
+
+
+Proposed changes
+================
+
+The proposed solution to overcome this shortcoming is to support
+*forthcoming instances*. Those forthcoming instances only exist as entries in
+in the configuration, hence creation and removal is cheap. Forthcoming instances
+can have an arbitrary subset of the attributes of a real instance with
+only the UUID being mandatory. In a similar way, their disks are also
+considered forthcoming. If a forthcoming instance specifies resources
+(memory, disk sizes, number of CPUs), these resources are accounted
+for as if they were real. In particular, a forthcoming can always be
+turned into a real one without running out of resources.
+
+RAPI extension
+--------------
+
+To accomdate the handling of forthcoming instances, the :doc:`rapi`
+will be extended as follows.
+
+The following functionality will be added to existing resources.
+
+- /2/instances/
+
+  - POST. This request will have an additional, optional, ``forthcoming``
+    flag with default ``False``. If the ``forthcoming`` flag is set, all
+    parameters are optional, including the instance name. Even if
+    ``forthcoming`` is set, the result of this request will still be the job id
+    to be used later for polling. A job to create a forthcoming instance,
+    however, will return the UUID of the instance instead of the hosts
+    allocated for it.
+
+- /2/instances/[instance_name]/modify
+
+  - PUT. This request will be able to handle forthcoming instances
+    in the same way as existing ones.
+
+The following resources will be added.
+
+- /2/instances/[instance_uuid]/modify
+
+  - PUT. This will behave the same as the ``modify`` indexed by instance
+    name and is added to allow modification of an instance that does
+    not yet have a name.
+
+- /2/instances/[instance_uuid]/rename
+
+  - PUT. This will behave the same as the ``rename`` indexed by
+    instance name. This will allow to assign a name to a forthcoming
+    instance.
+
+- /2/instances/[instance_name]/create
+
+  - POST. Create the forthcoming instance. It is a prerequisite that
+    all mandatory parameters of the instance are specified by now.
+    It will return the id of the creation job, for later polling.
+
+
+Representation in the Configuration
+-----------------------------------
+
+As for most part of the system, forthcoming instances and their disks are to
+be treated as if they were real. Therefore, the wire representation will
+be by adding an additional, optional, ``fortcoming`` flag to the ``instances``
+and ``disks`` objects. Additionally, the internal consistency condition will
+be relaxed to have all non-uuid fields optional if an instance or disk is
+forthcoming.
+
+Rationale
+~~~~~~~~~
+
+The alternative to the chosen approach would be to add a new top-level object
+``forthcoming_instances`` to the configuration. Both approaches bear the risk
+of introducing subtle bugs. Adding a new top-level object bears the risk of
+missing in some places to take into account the resources consumed by the
+forthcoming instances. Adding a new attribute and relaxing the consistency
+conditions bears the risk that some parts of the Python code cannot handle the
+fact that some fields are now optional if the instance is forthcoming.
+The design choice is to prefer the latter kind of errors, as they will always
+show up immediately when a faulty part of the code is touched and will always
+precisely indicate the part of the code base that needs to be changed.
+
+Haskell Representation
+~~~~~~~~~~~~~~~~~~~~~~
+
+The semantical condition on the instance fields renders the type into
+a Pascal-style variant record (one element of an enumeration type,
+and the remaining fields depend on the value of that field). Of course, in
+the Haskell part of our code base, this will be represented in the standard way
+having two constructors for the type; additionally there will be accessors
+for all the fields of the JSON representation (yielding ``Maybe`` values,
+as they can be optional if we're in the ``Forthcoming`` constuctor).
+
+
+Adaptions of htools
+-------------------
+
+Forthcoming instances are handled by htools essentially the same way as
+real instances with more possible moves, as a forthcoming instance may
+change primary and secondary node simultaneously. The restriction of not
+changing node group without explicit user request to do so remains.
+Wherever possible, moves of forthcoming instances are preferred over
+moving real instances, as forthcoming instances can be moved for
+free. Implementation wise, the existing ``Instance`` data structure is
+used, and a new bit ``forthcoming`` is added; for forthcoming
+instances, the ``name`` field will carry the UUID.
index 5c9a9a5..793c522 100644 (file)
@@ -185,6 +185,7 @@ An “ExtStorage provider” will have to provide the following methods:
 - Detach a disk from a given node
 - SetInfo to a disk (add metadata)
 - Verify its supported parameters
+- Snapshot a disk (currently used during gnt-backup export)
 
 The proposed ExtStorage interface borrows heavily from the OS
 interface and follows a one-script-per-function approach. An ExtStorage
@@ -197,6 +198,7 @@ provider is expected to provide the following scripts:
 - ``detach``
 - ``setinfo``
 - ``verify``
+- ``snapshot`` (optional)
 
 All scripts will be called with no arguments and get their input via
 environment variables. A common set of variables will be exported for
@@ -216,6 +218,14 @@ all commands, and some of them might have extra ones.
 ``VOL_METADATA``
   A string containing metadata to be set for the volume.
   This is exported only to the ``setinfo`` script.
+``VOL_CNAME``
+  The human readable name of the disk (if any).
+``VOL_SNAPSHOT_NAME``
+  The name of the volume's snapshot to be taken.
+  Available only to the `snapshot` script.
+``VOL_SNAPSHOT_SIZE``
+  The size of the volume's snapshot to be taken.
+  Available only to the `snapshot` script.
 
 All scripts except `attach` should return 0 on success and non-zero on
 error, accompanied by an appropriate error message on stderr. The
@@ -223,6 +233,10 @@ error, accompanied by an appropriate error message on stderr. The
 the block device's full path, after it has been successfully attached to
 the host node. On error it should return non-zero.
 
+To keep backwards compatibility we let the ``snapshot`` script be
+optional. If present then the provider will support instance backup
+export as well.
+
 Implementation
 --------------
 
@@ -266,6 +280,48 @@ will be used to diagnose ExtStorage providers and show information about
 them, similarly to the way  `gnt-os diagose` and `gnt-os info` handle OS
 definitions.
 
+ExtStorage Interface support for userspace access
+=================================================
+
+Overview
+--------
+
+The ExtStorage Interface gets extended to cater for ExtStorage providers
+that support userspace access. This will allow the instances to access
+their external storage devices directly without going through a block
+device, avoiding expensive context switches with kernel space and the
+potential for deadlocks in low memory scenarios. The implementation
+should be backwards compatible and allow existing ExtStorage
+providers to work as is.
+
+Implementation
+--------------
+
+Since the implementation should be backwards compatible we are not going
+to add a new script in the set of scripts an ExtStorage provider should
+ship with. Instead, the 'attach' script, which is currently responsible
+to map the block device and return a valid device path, should also be
+responsible for providing the URIs that will be used by each
+hypervisor. Even though Ganeti currently allows userspace access only
+for the KVM hypervisor, we want the implementation to enable the
+extstorage providers to support more than one hypervisors for future
+compliance.
+
+More specifically, the 'attach' script will be allowed to return more
+than one line. The first line will contain as always the block device
+path. Each one of the extra lines will contain a URI to be used for the
+userspace access by a specific hypervisor. Each URI should be prefixed
+with the hypervisor it corresponds to (e.g. kvm:<uri>). The prefix will
+be case insensitive. If the 'attach' script doesn't return any extra
+lines, we assume that the ExtStorage provider doesn't support userspace
+access (this way we maintain backward compatibility with the existing
+'attach' scripts).
+
+The 'GetUserspaceAccessUri' method of the 'ExtStorageDevice' class will
+parse the output of the 'attach' script and if the provider supports
+userspace access for the requested hypervisor, it will use the
+corresponding URI instead of the block device itself.
+
 Long-term shared storage goals
 ==============================
 
diff --git a/doc/design-sync-rate-throttling.rst b/doc/design-sync-rate-throttling.rst
new file mode 100644 (file)
index 0000000..47549e6
--- /dev/null
@@ -0,0 +1,64 @@
+DRBD Sync Rate Throttling
+=========================
+
+Objective
+---------
+
+This document outlines the functionality to conveniently set rate limits for
+synchronization tasks. A use-case of this is that moving instances might
+otherwise clog the network for the nodes. If the replication network differs
+from the network used by the instances, there would be no benefits.
+
+Namely there should be two limits that can be set:
+
+* `resync-rate`: which should not be exceeded for each device. This exists
+  already.
+* `total-resync-rate`: which should not be exceeded collectively for each
+  node.
+
+Configuration
+-------------
+
+Suggested command line parameters for controlling throttling are as
+follows::
+
+  gnt-cluster modify -D resync-rate=<bytes-per-second>
+  gnt-cluster modify -D total-resync-rate=<bytes-per-second>
+
+Where ``bytes-per-second`` can be in the format ``<N>{b,k,M,G}`` to set the
+limit to N bytes, kilobytes, megabytes, and gigabytes respectively.
+
+Semantics
+---------
+
+The rate limit that is set for the drbdsetup is at least
+
+  rate = min(resync-rate,
+             total-resync-rate/number-of-syncing-devices)
+
+
+where number-of-syncing-devices is checked on beginning and end of syncs. This
+is set on each node with
+
+  drbdsetup <minor> disk-options --resync-rate <rate> --c-max-rate <rate>
+
+Later versions might free additional bandwidth on the source/target if the
+target/source is more throttled.
+
+Architecture
+------------
+
+The code to adjust the sync rates is collected in a separate tool ``hrates``
+that
+
+#. is run when a new sync is started or finished.
+#. can be run manually if necessary.
+
+Since the rates don't depend on the job, an unparameterized RPC
+``perspective_node_run_hrates`` to NodeD will trigger the execution of the
+tool.
+
+A first version will query ConfD for the other nodes of the group and request
+the sync state for all of them.
+
+.. TODO: second version that avoids overhead.
index dbdd173..45d375a 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.12
+Documents Ganeti version 2.13
 
 .. contents::
 
@@ -290,10 +290,13 @@ INSTANCE_NICn_NETWORK,
 INSTANCE_NICn_NETWORK_UUID, INSTANCE_NICn_NETWORK_SUBNET,
 INSTANCE_NICn_NETWORK_GATEWAY, INSTANCE_NICn_NETWORK_SUBNET6,
 INSTANCE_NICn_NETWORK_GATEWAY6, INSTANCE_NICn_NETWORK_MAC_PREFIX,
-INSTANCE_DISK_COUNT, INSTANCE_DISKn_SIZE, INSTANCE_DISKn_MODE.
+INSTANCE_DISK_COUNT, INSTANCE_DISKn_SIZE, INSTANCE_DISKn_MODE,
+INSTANCE_DISKn_NAME, INSTANCE_DISKn_UUID, INSTANCE_DISKn_DEV_TYPE.
 
 The INSTANCE_NICn_* and INSTANCE_DISKn_* variables represent the
 properties of the *n* -th NIC and disk, and are zero-indexed.
+Depending on the disk template, Ganeti exports some info related to
+the logical id of the disk, that is basically its driver and id.
 
 The INSTANCE_NICn_NETWORK_* variables are only passed if a NIC's network
 parameter is set (that is if the NIC is associated to a network defined
index b8d7e44..d5896ea 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.12
+Documents Ganeti version 2.13
 
 .. contents::
 
index 4dd62f3..a95025d 100644 (file)
@@ -78,6 +78,7 @@ and draft versions (which are either incomplete or not implemented).
    design-2.10.rst
    design-2.11.rst
    design-2.12.rst
+   design-2.13.rst
 
 Draft designs
 -------------
@@ -100,8 +101,11 @@ Draft designs
    design-cpu-pinning.rst
    design-device-uuid-name.rst
    design-daemons.rst
+   design-disk-conversion.rst
+   design-disks.rst
    design-file-based-storage.rst
    design-hroller.rst
+   design-hsqueeze.rst
    design-hotplug.rst
    design-internal-shutdown.rst
    design-kvmd.rst
@@ -117,6 +121,7 @@ Draft designs
    design-oob.rst
    design-openvswitch.rst
    design-opportunistic-locking.rst
+   design-optables.rst
    design-os.rst
    design-ovf-support.rst
    design-partitioned
index c206a63..dc7784a 100644 (file)
@@ -474,6 +474,161 @@ features:
   a new-style result (see resource description)
 
 
+.. _rapi-res-filters:
+
+``/2/filters``
++++++++++++++++
+
+The filters resource.
+
+.. rapi_resource_details:: /2/filters
+
+
+.. _rapi-res-filters+get:
+
+``GET``
+~~~~~~~
+
+Returns a list of all existing filters.
+
+Example::
+
+    [
+      {
+        "id": "8b53f7de-f8e2-4470-99bd-1efe746e434f",
+        "uri": "/2/filters/8b53f7de-f8e2-4470-99bd-1efe746e434f"
+      },
+      {
+        "id": "b296f0c9-4809-46a8-b928-5ccf7720fa8c",
+        "uri": "/2/filters/b296f0c9-4809-46a8-b928-5ccf7720fa8c"
+      }
+    ]
+
+If the optional bool *bulk* argument is provided and set to a true value
+(i.e ``?bulk=1``), the output contains detailed information about filters
+as a list.
+
+Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.FILTER_RULE_FIELDS))`.
+
+Example::
+
+    [
+      {
+        "uuid": "8b53f7de-f8e2-4470-99bd-1efe746e434f",
+        "watermark": 12534,
+        "reason_trail": [
+          ["luxid", "someFilterReason", 1409249801259897000]
+        ],
+        "priority": 0,
+        "action": "REJECT",
+        "predicates": [
+          ["jobid", [">", "id", "watermark"]]
+        ]
+      },
+      {
+        "uuid": "b296f0c9-4809-46a8-b928-5ccf7720fa8c",
+        "watermark": 12534,
+        "reason_trail": [
+          ["luxid", "someFilterReason", 1409249917268978000]
+        ],
+        "priority": 1,
+        "action": "REJECT",
+        "predicates": [
+          ["opcode", ["=", "OP_ID", "OP_INSTANCE_CREATE"]]
+        ]
+      }
+    ]
+
+
+.. _rapi-res-filters+post:
+
+``POST``
+~~~~~~~~
+
+Creates a filter.
+
+Body parameters:
+
+``priority`` (int, defaults to ``0``)
+  Must be non-negative. Lower numbers mean higher filter priority.
+
+``predicates`` (list, defaults to ``[]``)
+  The first element is the name (``str``) of the predicate and the
+  rest are parameters suitable for that predicate.
+  Most predicates take a single parameter: A boolean expression
+  in the Ganeti query language.
+
+``action`` (defaults to ``"CONTINUE"``)
+  The effect of the filter. Can be one of ``"ACCEPT"``, ``"PAUSE"``,
+  ``"REJECT"``, ``"CONTINUE"`` and ``["RATE_LIMIT", n]``, where ``n``
+  is a positive integer.
+
+``reason`` (list, defaults to ``[]``)
+  An initial reason trail for this filter. Each element in this list
+  is a list with 3 elements: ``[source, reason, timestamp]``, where
+  ``source`` and ``reason`` are strings and ``timestamp`` is a time
+  since the UNIX epoch in nanoseconds as an integer.
+
+Returns:
+
+A filter UUID (``str``) that can be used for accessing the filter later.
+
+
+.. _rapi-res-filters-filter_uuid:
+
+``/2/filters/[filter_uuid]``
+++++++++++++++++++++++++++++++
+
+Returns information about a filter.
+
+.. rapi_resource_details:: /2/filters/[filter_uuid]
+
+
+.. _rapi-res-filters-filter_uuid+get:
+
+``GET``
+~~~~~~~
+
+Returns information about a filter, similar to the bulk output from
+the filter list.
+
+Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.FILTER_RULE_FIELDS))`.
+
+
+.. _rapi-res-filters-filter_uuid+put:
+
+``PUT``
+~~~~~~~
+
+Replaces a filter with given UUID, or creates it with the given UUID
+if it doesn't already exist.
+
+Body parameters:
+
+All parameters for adding a new filter via ``POST``, plus the following:
+
+``uuid``: (string)
+  The UUID of the filter to replace or create.
+
+Returns:
+
+The filter UUID (``str``) of the replaced or created filter.
+This will be the ``uuid`` body parameter if given, and a freshly generated
+UUID otherwise.
+
+
+.. _rapi-res-filters-filter_uuid+delete:
+
+``DELETE``
+~~~~~~~~~~
+
+Deletes a filter.
+
+Returns:
+
+``None``
+
+
 .. _rapi-res-modify:
 
 ``/2/modify``
index 99e13ab..31178dc 100644 (file)
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.12
+Documents Ganeti version 2.13
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
index 1f84afb..9fd37d9 100644 (file)
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.12
+Documents Ganeti version 2.13
 
 .. contents::
 
index 5a04c7f..646fc70 100644 (file)
@@ -61,6 +61,7 @@ import stat
 import tempfile
 import time
 import zlib
+import copy
 
 from ganeti import errors
 from ganeti import http
@@ -71,6 +72,7 @@ from ganeti.hypervisor import hv_base
 from ganeti import constants
 from ganeti.storage import bdev
 from ganeti.storage import drbd
+from ganeti.storage import extstorage
 from ganeti.storage import filestorage
 from ganeti import objects
 from ganeti import ssconf
@@ -558,7 +560,7 @@ def LeaveCluster(modify_ssh_setup):
     try:
       priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.SSH_LOGIN_USER)
 
-      utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
+      ssh.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
 
       utils.RemoveFile(priv_key)
       utils.RemoveFile(pub_key)
@@ -966,6 +968,139 @@ def _VerifyClientCertificate(cert_file=pathutils.NODED_CLIENT_CERT_FILE):
   return (None, utils.GetCertificateDigest(cert_filename=cert_file))
 
 
+def _VerifySshSetup(node_status_list, my_name,
+                    pub_key_file=pathutils.SSH_PUB_KEYS):
+  """Verifies the state of the SSH key files.
+
+  @type node_status_list: list of tuples
+  @param node_status_list: list of nodes of the cluster associated with a
+    couple of flags: (uuid, name, is_master_candidate,
+    is_potential_master_candidate, online)
+  @type my_name: str
+  @param my_name: name of this node
+  @type pub_key_file: str
+  @param pub_key_file: filename of the public key file
+
+  """
+  if node_status_list is None:
+    return ["No node list to check against the pub_key_file received."]
+
+  my_status_list = [(my_uuid, name, mc, pot_mc, online) for
+                    (my_uuid, name, mc, pot_mc, online)
+                    in node_status_list if name == my_name]
+  if len(my_status_list) == 0:
+    return ["Cannot find node information for node '%s'." % my_name]
+  (my_uuid, _, _, potential_master_candidate, online) = \
+     my_status_list[0]
+
+  result = []
+
+  if not os.path.exists(pub_key_file):
+    result.append("The public key file '%s' does not exist. Consider running"
+                  " 'gnt-cluster renew-crypto --new-ssh-keys"
+                  " [--no-ssh-key-check]' to fix this." % pub_key_file)
+    return result
+
+  pot_mc_uuids = [uuid for (uuid, _, _, _, _) in node_status_list]
+  offline_nodes = [uuid for (uuid, _, _, _, online) in node_status_list
+                   if not online]
+  pub_keys = ssh.QueryPubKeyFile(None)
+
+  if potential_master_candidate:
+    # Check that the set of potential master candidates matches the
+    # public key file
+    pub_uuids_set = set(pub_keys.keys()) - set(offline_nodes)
+    pot_mc_uuids_set = set(pot_mc_uuids) - set(offline_nodes)
+    missing_uuids = set([])
+    if pub_uuids_set != pot_mc_uuids_set:
+      unknown_uuids = pub_uuids_set - pot_mc_uuids_set
+      if unknown_uuids:
+        result.append("The following node UUIDs are listed in the public key"
+                      " file on node '%s', but are not potential master"
+                      " candidates: %s."
+                      % (my_name, ", ".join(list(unknown_uuids))))
+      missing_uuids = pot_mc_uuids_set - pub_uuids_set
+      if missing_uuids:
+        result.append("The following node UUIDs of potential master candidates"
+                      " are missing in the public key file on node %s: %s."
+                      % (my_name, ", ".join(list(missing_uuids))))
+
+    (_, key_files) = \
+      ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+    (_, dsa_pub_key_filename) = key_files[constants.SSHK_DSA]
+
+    my_keys = pub_keys[my_uuid]
+
+    dsa_pub_key = utils.ReadFile(dsa_pub_key_filename)
+    if dsa_pub_key.strip() not in my_keys:
+      result.append("The dsa key of node %s does not match this node's key"
+                    " in the pub key file." % (my_name))
+    if len(my_keys) != 1:
+      result.append("There is more than one key for node %s in the public key"
+                    " file." % my_name)
+  else:
+    if len(pub_keys.keys()) > 0:
+      result.append("The public key file of node '%s' is not empty, although"
+                    " the node is not a potential master candidate."
+                    % my_name)
+
+  # Check that all master candidate keys are in the authorized_keys file
+  (auth_key_file, _) = \
+    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+  for (uuid, name, mc, _, online) in node_status_list:
+    if not online:
+      continue
+    if uuid in missing_uuids:
+      continue
+    if mc:
+      for key in pub_keys[uuid]:
+        if not ssh.HasAuthorizedKey(auth_key_file, key):
+          result.append("A SSH key of master candidate '%s' (UUID: '%s') is"
+                        " not in the 'authorized_keys' file of node '%s'."
+                        % (name, uuid, my_name))
+    else:
+      for key in pub_keys[uuid]:
+        if name != my_name and ssh.HasAuthorizedKey(auth_key_file, key):
+          result.append("A SSH key of normal node '%s' (UUID: '%s') is in the"
+                        " 'authorized_keys' file of node '%s'."
+                        % (name, uuid, my_name))
+        if name == my_name and not ssh.HasAuthorizedKey(auth_key_file, key):
+          result.append("A SSH key of normal node '%s' (UUID: '%s') is not"
+                        " in the 'authorized_keys' file of itself."
+                        % (my_name, uuid))
+
+  return result
+
+
+def _VerifySshClutter(node_status_list, my_name):
+  """Verifies that the 'authorized_keys' files are not cluttered up.
+
+  @type node_status_list: list of tuples
+  @param node_status_list: list of nodes of the cluster associated with a
+    couple of flags: (uuid, name, is_master_candidate,
+    is_potential_master_candidate, online)
+  @type my_name: str
+  @param my_name: name of this node
+
+  """
+  result = []
+  (auth_key_file, _) = \
+    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+  node_names = [name for (_, name, _, _) in node_status_list]
+  multiple_occurrences = ssh.CheckForMultipleKeys(auth_key_file, node_names)
+  if multiple_occurrences:
+    msg = "There are hosts which have more than one SSH key stored for the" \
+          " same user in the 'authorized_keys' file of node %s. This can be" \
+          " due to an unsuccessful operation which cluttered up the" \
+          " 'authorized_keys' file. We recommend to clean this up manually. " \
+          % my_name
+    for host, occ in multiple_occurrences.items():
+      msg += "Entry for '%s' in lines %s. " % (host, utils.CommaJoin(occ))
+    result.append(msg)
+
+  return result
+
+
 def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
   """Verify the status of the local node.
 
@@ -1022,8 +1157,15 @@ def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
   if constants.NV_CLIENT_CERT in what:
     result[constants.NV_CLIENT_CERT] = _VerifyClientCertificate()
 
+  if constants.NV_SSH_SETUP in what:
+    result[constants.NV_SSH_SETUP] = \
+      _VerifySshSetup(what[constants.NV_SSH_SETUP], my_name)
+    if constants.NV_SSH_CLUTTER in what:
+      result[constants.NV_SSH_CLUTTER] = \
+        _VerifySshClutter(what[constants.NV_SSH_SETUP], my_name)
+
   if constants.NV_NODELIST in what:
-    (nodes, bynode) = what[constants.NV_NODELIST]
+    (nodes, bynode, mcs) = what[constants.NV_NODELIST]
 
     # Add nodes from other groups (different for each node)
     try:
@@ -1041,10 +1183,16 @@ def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
       ssh_port = params["ndparams"].get(constants.ND_SSH_PORT)
       logging.debug("Ssh port %s (None = default) for node %s",
                     str(ssh_port), node)
-      success, message = _GetSshRunner(cluster_name). \
-                            VerifyNodeHostname(node, ssh_port)
-      if not success:
-        val[node] = message
+
+      # We only test if master candidates can communicate to other nodes.
+      # We cannot test if normal nodes cannot communicate with other nodes,
+      # because the administrator might have installed additional SSH keys,
+      # over which Ganeti has no power.
+      if my_name in mcs:
+        success, message = _GetSshRunner(cluster_name). \
+                              VerifyNodeHostname(node, ssh_port)
+        if not success:
+          val[node] = message
 
     result[constants.NV_NODELIST] = val
 
@@ -1261,6 +1409,663 @@ def EnsureDaemon(daemon_name, run):
   return fn(daemon_name)
 
 
+def _InitSshUpdateData(data, noded_cert_file, ssconf_store):
+  (_, noded_cert) = \
+    utils.ExtractX509Certificate(utils.ReadFile(noded_cert_file))
+  data[constants.SSHS_NODE_DAEMON_CERTIFICATE] = noded_cert
+
+  cluster_name = ssconf_store.GetClusterName()
+  data[constants.SSHS_CLUSTER_NAME] = cluster_name
+
+
+def AddNodeSshKey(node_uuid, node_name,
+                  potential_master_candidates,
+                  to_authorized_keys=False,
+                  to_public_keys=False,
+                  get_public_keys=False,
+                  pub_key_file=pathutils.SSH_PUB_KEYS,
+                  ssconf_store=None,
+                  noded_cert_file=pathutils.NODED_CERT_FILE,
+                  run_cmd_fn=ssh.RunSshCmdWithStdin):
+  """Distributes a node's public SSH key across the cluster.
+
+  Note that this function should only be executed on the master node, which
+  then will copy the new node's key to all nodes in the cluster via SSH.
+
+  Also note: at least one of the flags C{to_authorized_keys},
+  C{to_public_keys}, and C{get_public_keys} has to be set to C{True} for
+  the function to actually perform any actions.
+
+  @type node_uuid: str
+  @param node_uuid: the UUID of the node whose key is added
+  @type node_name: str
+  @param node_name: the name of the node whose key is added
+  @type potential_master_candidates: list of str
+  @param potential_master_candidates: list of node names of potential master
+    candidates; this should match the list of uuids in the public key file
+  @type to_authorized_keys: boolean
+  @param to_authorized_keys: whether the key should be added to the
+    C{authorized_keys} file of all nodes
+  @type to_public_keys: boolean
+  @param to_public_keys: whether the keys should be added to the public key file
+  @type get_public_keys: boolean
+  @param get_public_keys: whether the node should add the clusters' public keys
+    to its {ganeti_pub_keys} file
+
+  """
+  # assure that at least one of those flags is true, as the function would
+  # not do anything otherwise
+  assert (to_authorized_keys or to_public_keys or get_public_keys)
+
+  if not ssconf_store:
+    ssconf_store = ssconf.SimpleStore()
+
+  # Check and fix sanity of key file
+  keys_by_name = ssh.QueryPubKeyFile([node_name], key_file=pub_key_file)
+  keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
+
+  if (not keys_by_name or node_name not in keys_by_name) \
+      and (not keys_by_uuid or node_uuid not in keys_by_uuid):
+    raise errors.SshUpdateError(
+      "No keys found for the new node '%s' (UUID %s) in the list of public"
+      " SSH keys, neither for the name or the UUID" % (node_name, node_uuid))
+  else:
+    if node_name in keys_by_name:
+      keys_by_uuid = {}
+      # Replace the name by UUID in the file as the name should only be used
+      # temporarily
+      ssh.ReplaceNameByUuid(node_uuid, node_name,
+                            error_fn=errors.SshUpdateError,
+                            key_file=pub_key_file)
+      keys_by_uuid[node_uuid] = keys_by_name[node_name]
+
+  # Update the master node's key files
+  if to_authorized_keys:
+    (auth_key_file, _) = \
+      ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+    ssh.AddAuthorizedKeys(auth_key_file, keys_by_uuid[node_uuid])
+
+  base_data = {}
+  _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
+  cluster_name = base_data[constants.SSHS_CLUSTER_NAME]
+
+  ssh_port_map = ssconf_store.GetSshPortMap()
+
+  # Update the target node itself
+  logging.debug("Updating SSH key files of target node '%s'.", node_name)
+  if get_public_keys:
+    node_data = {}
+    _InitSshUpdateData(node_data, noded_cert_file, ssconf_store)
+    all_keys = ssh.QueryPubKeyFile(None, key_file=pub_key_file)
+    node_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+      (constants.SSHS_OVERRIDE, all_keys)
+
+    try:
+      utils.RetryByNumberOfTimes(
+          constants.SSHS_MAX_RETRIES,
+          errors.SshUpdateError,
+          run_cmd_fn, cluster_name, node_name, pathutils.SSH_UPDATE,
+          ssh_port_map.get(node_name), node_data,
+          debug=False, verbose=False, use_cluster_key=False,
+          ask_key=False, strict_host_check=False)
+    except errors.SshUpdateError as e:
+      # Clean up the master's public key file if adding key fails
+      if to_public_keys:
+        ssh.RemovePublicKey(node_uuid)
+      raise e
+
+  # Update all nodes except master and the target node
+  if to_authorized_keys:
+    base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
+      (constants.SSHS_ADD, keys_by_uuid)
+
+  pot_mc_data = copy.deepcopy(base_data)
+  if to_public_keys:
+    pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+      (constants.SSHS_REPLACE_OR_ADD, keys_by_uuid)
+
+  all_nodes = ssconf_store.GetNodeList()
+  master_node = ssconf_store.GetMasterNode()
+  online_nodes = ssconf_store.GetOnlineNodeList()
+
+  node_errors = []
+  for node in all_nodes:
+    if node == master_node:
+      logging.debug("Skipping master node '%s'.", master_node)
+      continue
+    if node not in online_nodes:
+      logging.debug("Skipping offline node '%s'.", node)
+      continue
+    if node in potential_master_candidates:
+      logging.debug("Updating SSH key files of node '%s'.", node)
+      try:
+        utils.RetryByNumberOfTimes(
+            constants.SSHS_MAX_RETRIES,
+            errors.SshUpdateError,
+            run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
+            ssh_port_map.get(node), pot_mc_data,
+            debug=False, verbose=False, use_cluster_key=False,
+            ask_key=False, strict_host_check=False)
+      except errors.SshUpdateError as last_exception:
+        error_msg = ("When adding the key of node '%s', updating SSH key"
+                     " files of node '%s' failed after %s retries."
+                     " Not trying again. Last error was: %s." %
+                     (node, node_name, constants.SSHS_MAX_RETRIES,
+                      last_exception))
+        node_errors.append((node, error_msg))
+        # We only log the error and don't throw an exception, because
+        # one unreachable node shall not abort the entire procedure.
+        logging.error(error_msg)
+
+    else:
+      if to_authorized_keys:
+        run_cmd_fn(cluster_name, node, pathutils.SSH_UPDATE,
+                   ssh_port_map.get(node), base_data,
+                   debug=False, verbose=False, use_cluster_key=False,
+                   ask_key=False, strict_host_check=False)
+
+  return node_errors
+
+
+def RemoveNodeSshKey(node_uuid, node_name,
+                     master_candidate_uuids,
+                     potential_master_candidates,
+                     master_uuid=None,
+                     keys_to_remove=None,
+                     from_authorized_keys=False,
+                     from_public_keys=False,
+                     clear_authorized_keys=False,
+                     clear_public_keys=False,
+                     pub_key_file=pathutils.SSH_PUB_KEYS,
+                     ssconf_store=None,
+                     noded_cert_file=pathutils.NODED_CERT_FILE,
+                     readd=False,
+                     run_cmd_fn=ssh.RunSshCmdWithStdin):
+  """Removes the node's SSH keys from the key files and distributes those.
+
+  Note that at least one of the flags C{from_authorized_keys},
+  C{from_public_keys}, C{clear_authorized_keys}, and C{clear_public_keys}
+  has to be set to C{True} for the function to perform any action at all.
+  Not doing so will trigger an assertion in the function.
+
+  @type node_uuid: str
+  @param node_uuid: UUID of the node whose key is removed
+  @type node_name: str
+  @param node_name: name of the node whose key is remove
+  @type master_candidate_uuids: list of str
+  @param master_candidate_uuids: list of UUIDs of the current master candidates
+  @type potential_master_candidates: list of str
+  @param potential_master_candidates: list of names of potential master
+    candidates
+  @type keys_to_remove: dict of str to list of str
+  @param keys_to_remove: a dictionary mapping node UUIDS to lists of SSH keys
+    to be removed. This list is supposed to be used only if the keys are not
+    in the public keys file. This is for example the case when removing a
+    master node's key.
+  @type from_authorized_keys: boolean
+  @param from_authorized_keys: whether or not the key should be removed
+    from the C{authorized_keys} file
+  @type from_public_keys: boolean
+  @param from_public_keys: whether or not the key should be remove from
+    the C{ganeti_pub_keys} file
+  @type clear_authorized_keys: boolean
+  @param clear_authorized_keys: whether or not the C{authorized_keys} file
+    should be cleared on the node whose keys are removed
+  @type clear_public_keys: boolean
+  @param clear_public_keys: whether to clear the node's C{ganeti_pub_key} file
+  @type readd: boolean
+  @param readd: whether this is called during a readd operation.
+  @rtype: list of string
+  @returns: list of feedback messages
+
+  """
+  # Non-disruptive error messages, list of (node, msg) pairs
+  result_msgs = []
+
+  # Make sure at least one of these flags is true.
+  if not (from_authorized_keys or from_public_keys or clear_authorized_keys
+          or clear_public_keys):
+    raise errors.SshUpdateError("No removal from any key file was requested.")
+
+  if not ssconf_store:
+    ssconf_store = ssconf.SimpleStore()
+
+  master_node = ssconf_store.GetMasterNode()
+  ssh_port_map = ssconf_store.GetSshPortMap()
+
+  if from_authorized_keys or from_public_keys:
+    if keys_to_remove:
+      keys = keys_to_remove
+    else:
+      keys = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
+      if (not keys or node_uuid not in keys) and not readd:
+        raise errors.SshUpdateError("Node '%s' not found in the list of public"
+                                    " SSH keys. It seems someone tries to"
+                                    " remove a key from outside the cluster!"
+                                    % node_uuid)
+      # During an upgrade all nodes have the master key. In this case we
+      # should not remove it to avoid accidentally shutting down cluster
+      # SSH communication
+      master_keys = None
+      if master_uuid:
+        master_keys = ssh.QueryPubKeyFile([master_uuid], key_file=pub_key_file)
+        for master_key in master_keys:
+          if master_key in keys[node_uuid]:
+            keys[node_uuid].remove(master_key)
+
+    if node_name == master_node and not keys_to_remove:
+      raise errors.SshUpdateError("Cannot remove the master node's keys.")
+
+    if node_uuid in keys:
+      base_data = {}
+      _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
+      cluster_name = base_data[constants.SSHS_CLUSTER_NAME]
+
+      if from_authorized_keys:
+        base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
+          (constants.SSHS_REMOVE, keys)
+        (auth_key_file, _) = \
+          ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False,
+                              dircheck=False)
+        ssh.RemoveAuthorizedKeys(auth_key_file, keys[node_uuid])
+
+      pot_mc_data = copy.deepcopy(base_data)
+
+      if from_public_keys:
+        pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+          (constants.SSHS_REMOVE, keys)
+        ssh.RemovePublicKey(node_uuid, key_file=pub_key_file)
+
+      all_nodes = ssconf_store.GetNodeList()
+      online_nodes = ssconf_store.GetOnlineNodeList()
+      logging.debug("Removing key of node '%s' from all nodes but itself and"
+                    " master.", node_name)
+      for node in all_nodes:
+        if node == master_node:
+          logging.debug("Skipping master node '%s'.", master_node)
+          continue
+        if node not in online_nodes:
+          logging.debug("Skipping offline node '%s'.", node)
+          continue
+        ssh_port = ssh_port_map.get(node)
+        if not ssh_port:
+          raise errors.OpExecError("No SSH port information available for"
+                                   " node '%s', map: %s." %
+                                   (node, ssh_port_map))
+        error_msg_final = ("When removing the key of node '%s', updating the"
+                           " SSH key files of node '%s' failed. Last error"
+                           " was: %s.")
+        if node in potential_master_candidates:
+          logging.debug("Updating key setup of potential master candidate node"
+                        " %s.", node)
+          try:
+            utils.RetryByNumberOfTimes(
+                constants.SSHS_MAX_RETRIES,
+                errors.SshUpdateError,
+                run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
+                ssh_port, pot_mc_data,
+                debug=False, verbose=False, use_cluster_key=False,
+                ask_key=False, strict_host_check=False)
+          except errors.SshUpdateError as last_exception:
+            error_msg = error_msg_final % (
+                node_name, node, last_exception)
+            result_msgs.append((node, error_msg))
+            logging.error(error_msg)
+
+        else:
+          if from_authorized_keys:
+            logging.debug("Updating key setup of normal node %s.", node)
+            try:
+              utils.RetryByNumberOfTimes(
+                  constants.SSHS_MAX_RETRIES,
+                  errors.SshUpdateError,
+                  run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
+                  ssh_port, base_data,
+                  debug=False, verbose=False, use_cluster_key=False,
+                  ask_key=False, strict_host_check=False)
+            except errors.SshUpdateError as last_exception:
+              error_msg = error_msg_final % (
+                  node_name, node, last_exception)
+              result_msgs.append((node, error_msg))
+              logging.error(error_msg)
+
+  if clear_authorized_keys or from_public_keys or clear_public_keys:
+    data = {}
+    _InitSshUpdateData(data, noded_cert_file, ssconf_store)
+    cluster_name = data[constants.SSHS_CLUSTER_NAME]
+    ssh_port = ssh_port_map.get(node_name)
+    if not ssh_port:
+      raise errors.OpExecError("No SSH port information available for"
+                               " node '%s', which is leaving the cluster.")
+
+    if clear_authorized_keys:
+      # The 'authorized_keys' file is not solely managed by Ganeti. Therefore,
+      # we have to specify exactly which keys to clear to leave keys untouched
+      # that were not added by Ganeti.
+      other_master_candidate_uuids = [uuid for uuid in master_candidate_uuids
+                                      if uuid != node_uuid]
+      candidate_keys = ssh.QueryPubKeyFile(other_master_candidate_uuids,
+                                           key_file=pub_key_file)
+      data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
+        (constants.SSHS_REMOVE, candidate_keys)
+
+    if clear_public_keys:
+      data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+        (constants.SSHS_CLEAR, {})
+    elif from_public_keys:
+      # Since clearing the public keys subsumes removing just a single key,
+      # we only do it of clear_public_keys is 'False'.
+
+      if keys[node_uuid]:
+        data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+          (constants.SSHS_REMOVE, keys)
+
+    # If we have no changes to any keyfile, just return
+    if not (constants.SSHS_SSH_PUBLIC_KEYS in data or
+            constants.SSHS_SSH_AUTHORIZED_KEYS in data):
+      return
+
+    logging.debug("Updating SSH key setup of target node '%s'.", node_name)
+    try:
+      utils.RetryByNumberOfTimes(
+          constants.SSHS_MAX_RETRIES,
+          errors.SshUpdateError,
+          run_cmd_fn, cluster_name, node_name, pathutils.SSH_UPDATE,
+          ssh_port, data,
+          debug=False, verbose=False, use_cluster_key=False,
+          ask_key=False, strict_host_check=False)
+    except errors.SshUpdateError as last_exception:
+      result_msgs.append(
+          (node_name,
+           ("Removing SSH keys from node '%s' failed."
+            " This can happen when the node is already unreachable."
+            " Error: %s" % (node_name, last_exception))))
+
+  return result_msgs
+
+
+def _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map,
+                        pub_key_file=pathutils.SSH_PUB_KEYS,
+                        ssconf_store=None,
+                        noded_cert_file=pathutils.NODED_CERT_FILE,
+                        run_cmd_fn=ssh.RunSshCmdWithStdin,
+                        suffix=""):
+  """Generates the root SSH key pair on the node.
+
+  @type node_uuid: str
+  @param node_uuid: UUID of the node whose key is removed
+  @type node_name: str
+  @param node_name: name of the node whose key is remove
+  @type ssh_port_map: dict of str to int
+  @param ssh_port_map: mapping of node names to their SSH port
+
+  """
+  if not ssconf_store:
+    ssconf_store = ssconf.SimpleStore()
+
+  keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
+  if not keys_by_uuid or node_uuid not in keys_by_uuid:
+    raise errors.SshUpdateError("Node %s (UUID: %s) whose key is requested to"
+                                " be regenerated is not registered in the"
+                                " public keys file." % (node_name, node_uuid))
+
+  data = {}
+  _InitSshUpdateData(data, noded_cert_file, ssconf_store)
+  cluster_name = data[constants.SSHS_CLUSTER_NAME]
+  data[constants.SSHS_GENERATE] = {constants.SSHS_SUFFIX: suffix}
+
+  run_cmd_fn(cluster_name, node_name, pathutils.SSH_UPDATE,
+             ssh_port_map.get(node_name), data,
+             debug=False, verbose=False, use_cluster_key=False,
+             ask_key=False, strict_host_check=False)
+
+
+def _GetMasterNodeUUID(node_uuid_name_map, master_node_name):
+  master_node_uuids = [node_uuid for (node_uuid, node_name)
+                       in node_uuid_name_map
+                       if node_name == master_node_name]
+  if len(master_node_uuids) != 1:
+    raise errors.SshUpdateError("No (unique) master UUID found. Master node"
+                                " name: '%s', Master UUID: '%s'" %
+                                (master_node_name, master_node_uuids))
+  return master_node_uuids[0]
+
+
+def _GetOldMasterKeys(master_node_uuid, pub_key_file):
+  old_master_keys_by_uuid = ssh.QueryPubKeyFile([master_node_uuid],
+                                                key_file=pub_key_file)
+  if not old_master_keys_by_uuid:
+    raise errors.SshUpdateError("No public key of the master node (UUID '%s')"
+                                " found, not generating a new key."
+                                % master_node_uuid)
+  return old_master_keys_by_uuid
+
+
+def _GetNewMasterKey(root_keyfiles, master_node_uuid):
+  new_master_keys = []
+  for (_, (_, public_key_file)) in root_keyfiles.items():
+    public_key_dir = os.path.dirname(public_key_file)
+    public_key_file_tmp_filename = \
+        os.path.splitext(os.path.basename(public_key_file))[0] \
+        + constants.SSHS_MASTER_SUFFIX + ".pub"
+    public_key_path_tmp = os.path.join(public_key_dir,
+                                       public_key_file_tmp_filename)
+    if os.path.exists(public_key_path_tmp):
+      # for some key types, there might not be any keys
+      key = utils.ReadFile(public_key_path_tmp)
+      new_master_keys.append(key)
+  if not new_master_keys:
+    raise errors.SshUpdateError("Cannot find any type of temporary SSH key.")
+  return {master_node_uuid: new_master_keys}
+
+
+def _ReplaceMasterKeyOnMaster(root_keyfiles):
+  number_of_moves = 0
+  for (_, (private_key_file, public_key_file)) in root_keyfiles.items():
+    key_dir = os.path.dirname(public_key_file)
+    private_key_file_tmp = \
+      os.path.basename(private_key_file) + constants.SSHS_MASTER_SUFFIX
+    public_key_file_tmp = private_key_file_tmp + ".pub"
+    private_key_path_tmp = os.path.join(key_dir,
+                                        private_key_file_tmp)
+    public_key_path_tmp = os.path.join(key_dir,
+                                       public_key_file_tmp)
+    if os.path.exists(public_key_file):
+      utils.CreateBackup(public_key_file)
+      utils.RemoveFile(public_key_file)
+    if os.path.exists(private_key_file):
+      utils.CreateBackup(private_key_file)
+      utils.RemoveFile(private_key_file)
+    if os.path.exists(public_key_path_tmp) and \
+        os.path.exists(private_key_path_tmp):
+      # for some key types, there might not be any keys
+      shutil.move(public_key_path_tmp, public_key_file)
+      shutil.move(private_key_path_tmp, private_key_file)
+      number_of_moves += 1
+  if not number_of_moves:
+    raise errors.SshUpdateError("Could not move at least one master SSH key.")
+
+
+def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
+                 potential_master_candidates,
+                 pub_key_file=pathutils.SSH_PUB_KEYS,
+                 ssconf_store=None,
+                 noded_cert_file=pathutils.NODED_CERT_FILE,
+                 run_cmd_fn=ssh.RunSshCmdWithStdin):
+  """Renews all SSH keys and updates authorized_keys and ganeti_pub_keys.
+
+  @type node_uuids: list of str
+  @param node_uuids: list of node UUIDs whose keys should be renewed
+  @type node_names: list of str
+  @param node_names: list of node names whose keys should be removed. This list
+    should match the C{node_uuids} parameter
+  @type master_candidate_uuids: list of str
+  @param master_candidate_uuids: list of UUIDs of master candidates or
+    master node
+  @type pub_key_file: str
+  @param pub_key_file: file path of the the public key file
+  @type noded_cert_file: str
+  @param noded_cert_file: path of the noded SSL certificate file
+  @type run_cmd_fn: function
+  @param run_cmd_fn: function to run commands on remote nodes via SSH
+  @raises ProgrammerError: if node_uuids and node_names don't match;
+    SshUpdateError if a node's key is missing from the public key file,
+    if a node's new SSH key could not be fetched from it, if there is
+    none or more than one entry in the public key list for the master
+    node.
+
+  """
+  if not ssconf_store:
+    ssconf_store = ssconf.SimpleStore()
+  cluster_name = ssconf_store.GetClusterName()
+
+  if not len(node_uuids) == len(node_names):
+    raise errors.ProgrammerError("List of nodes UUIDs and node names"
+                                 " does not match in length.")
+
+  (_, root_keyfiles) = \
+    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+  (_, dsa_pub_keyfile) = root_keyfiles[constants.SSHK_DSA]
+  old_master_key = utils.ReadFile(dsa_pub_keyfile)
+
+  node_uuid_name_map = zip(node_uuids, node_names)
+
+  master_node_name = ssconf_store.GetMasterNode()
+  master_node_uuid = _GetMasterNodeUUID(node_uuid_name_map, master_node_name)
+  ssh_port_map = ssconf_store.GetSshPortMap()
+  # List of all node errors that happened, but which did not abort the
+  # procedure as a whole. It is important that this is a list to have a
+  # somewhat chronological history of events.
+  all_node_errors = []
+
+  # process non-master nodes
+  for node_uuid, node_name in node_uuid_name_map:
+    if node_name == master_node_name:
+      continue
+    master_candidate = node_uuid in master_candidate_uuids
+    potential_master_candidate = node_name in potential_master_candidates
+
+    keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
+    if not keys_by_uuid:
+      raise errors.SshUpdateError("No public key of node %s (UUID %s) found,"
+                                  " not generating a new key."
+                                  % (node_name, node_uuid))
+
+    if master_candidate:
+      logging.debug("Fetching old SSH key from node '%s'.", node_name)
+      old_pub_key = ssh.ReadRemoteSshPubKeys(dsa_pub_keyfile,
+                                             node_name, cluster_name,
+                                             ssh_port_map[node_name],
+                                             False, # ask_key
+                                             False) # key_check
+      if old_pub_key != old_master_key:
+        # If we are already in a multi-key setup (that is past Ganeti 2.12),
+        # we can safely remove the old key of the node. Otherwise, we cannot
+        # remove that node's key, because it is also the master node's key
+        # and that would terminate all communication from the master to the
+        # node.
+        logging.debug("Removing SSH key of node '%s'.", node_name)
+        node_errors = RemoveNodeSshKey(
+           node_uuid, node_name, master_candidate_uuids,
+           potential_master_candidates,
+           master_uuid=master_node_uuid, from_authorized_keys=master_candidate,
+           from_public_keys=False, clear_authorized_keys=False,
+           clear_public_keys=False)
+        if node_errors:
+          all_node_errors = all_node_errors + node_errors
+      else:
+        logging.debug("Old key of node '%s' is the same as the current master"
+                      " key. Not deleting that key on the node.", node_name)
+
+    logging.debug("Generating new SSH key for node '%s'.", node_name)
+    _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map,
+                        pub_key_file=pub_key_file,
+                        ssconf_store=ssconf_store,
+                        noded_cert_file=noded_cert_file,
+                        run_cmd_fn=run_cmd_fn)
+
+    try:
+      logging.debug("Fetching newly created SSH key from node '%s'.", node_name)
+      pub_key = ssh.ReadRemoteSshPubKeys(dsa_pub_keyfile,
+                                         node_name, cluster_name,
+                                         ssh_port_map[node_name],
+                                         False, # ask_key
+                                         False) # key_check
+    except:
+      raise errors.SshUpdateError("Could not fetch key of node %s"
+                                  " (UUID %s)" % (node_name, node_uuid))
+
+    if potential_master_candidate:
+      ssh.RemovePublicKey(node_uuid, key_file=pub_key_file)
+      ssh.AddPublicKey(node_uuid, pub_key, key_file=pub_key_file)
+
+    logging.debug("Add ssh key of node '%s'.", node_name)
+    node_errors = AddNodeSshKey(
+        node_uuid, node_name, potential_master_candidates,
+        to_authorized_keys=master_candidate,
+        to_public_keys=potential_master_candidate,
+        get_public_keys=True,
+        pub_key_file=pub_key_file, ssconf_store=ssconf_store,
+        noded_cert_file=noded_cert_file,
+        run_cmd_fn=run_cmd_fn)
+    if node_errors:
+      all_node_errors = all_node_errors + node_errors
+
+  # Renewing the master node's key
+
+  # Preserve the old keys for now
+  old_master_keys_by_uuid = _GetOldMasterKeys(master_node_uuid, pub_key_file)
+
+  # Generate a new master key with a suffix, don't touch the old one for now
+  logging.debug("Generate new ssh key of master.")
+  _GenerateNodeSshKey(master_node_uuid, master_node_name, ssh_port_map,
+                      pub_key_file=pub_key_file,
+                      ssconf_store=ssconf_store,
+                      noded_cert_file=noded_cert_file,
+                      run_cmd_fn=run_cmd_fn,
+                      suffix=constants.SSHS_MASTER_SUFFIX)
+  # Read newly created master key
+  new_master_key_dict = _GetNewMasterKey(root_keyfiles, master_node_uuid)
+
+  # Replace master key in the master nodes' public key file
+  ssh.RemovePublicKey(master_node_uuid, key_file=pub_key_file)
+  for pub_key in new_master_key_dict[master_node_uuid]:
+    ssh.AddPublicKey(master_node_uuid, pub_key, key_file=pub_key_file)
+
+  # Add new master key to all node's public and authorized keys
+  logging.debug("Add new master key to all nodes.")
+  node_errors = AddNodeSshKey(
+      master_node_uuid, master_node_name, potential_master_candidates,
+      to_authorized_keys=True, to_public_keys=True,
+      get_public_keys=False, pub_key_file=pub_key_file,
+      ssconf_store=ssconf_store, noded_cert_file=noded_cert_file,
+      run_cmd_fn=run_cmd_fn)
+  if node_errors:
+    all_node_errors = all_node_errors + node_errors
+
+  # Remove the old key file and rename the new key to the non-temporary filename
+  _ReplaceMasterKeyOnMaster(root_keyfiles)
+
+  # Remove old key from authorized keys
+  (auth_key_file, _) = \
+      ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+  ssh.RemoveAuthorizedKeys(auth_key_file,
+                           old_master_keys_by_uuid[master_node_uuid])
+
+  # Remove the old key from all node's authorized keys file
+  logging.debug("Remove the old master key from all nodes.")
+  node_errors = RemoveNodeSshKey(
+      master_node_uuid, master_node_name, master_candidate_uuids,
+      potential_master_candidates,
+      keys_to_remove=old_master_keys_by_uuid, from_authorized_keys=True,
+      from_public_keys=False, clear_authorized_keys=False,
+      clear_public_keys=False)
+  if node_errors:
+    all_node_errors = all_node_errors + node_errors
+
+  return all_node_errors
+
+
 def GetBlockDevSizes(devices):
   """Return the size of the given block devices
 
@@ -2320,7 +3125,7 @@ def _DumpDevice(source_path, target_path, offset, size, truncate):
   # Internal sizes are always in Mebibytes; if the following "dd" command
   # should use a different block size the offset and size given to this
   # function must be adjusted accordingly before being passed to "dd".
-  block_size = 1024 * 1024
+  block_size = constants.DD_BLOCK_SIZE
 
   cmd = [constants.DD_CMD, "if=%s" % source_path, "seek=%d" % offset,
          "bs=%s" % block_size, "oflag=direct", "of=%s" % target_path,
@@ -2388,6 +3193,44 @@ def _DownloadAndDumpDevice(source_url, target_path, size):
   target_file.close()
 
 
+def BlockdevConvert(src_disk, target_disk):
+  """Copies data from source block device to target.
+
+  This function gets the export and import commands from the source and
+  target devices respectively, and then concatenates them to a single
+  command using a pipe ("|"). Finally, executes the unified command that
+  will transfer the data between the devices during the disk template
+  conversion operation.
+
+  @type src_disk: L{objects.Disk}
+  @param src_disk: the disk object we want to copy from
+  @type target_disk: L{objects.Disk}
+  @param target_disk: the disk object we want to copy to
+
+  @rtype: NoneType
+  @return: None
+  @raise RPCFail: in case of failure
+
+  """
+  src_dev = _RecursiveFindBD(src_disk)
+  if src_dev is None:
+    _Fail("Cannot copy from device '%s': device not found", src_disk.uuid)
+
+  dest_dev = _RecursiveFindBD(target_disk)
+  if dest_dev is None:
+    _Fail("Cannot copy to device '%s': device not found", target_disk.uuid)
+
+  src_cmd = src_dev.Export()
+  dest_cmd = dest_dev.Import()
+  command = "%s | %s" % (utils.ShellQuoteArgs(src_cmd),
+                         utils.ShellQuoteArgs(dest_cmd))
+
+  result = utils.RunCmd(command)
+  if result.failed:
+    _Fail("Disk conversion command '%s' exited with error: %s; output: %s",
+          result.cmd, result.fail_reason, result.output)
+
+
 def BlockdevWipe(disk, offset, size):
   """Wipes a block device.
 
@@ -3295,7 +4138,7 @@ def DiagnoseExtStorage(top_dirs=None):
         break
       for name in f_names:
         es_path = utils.PathJoin(dir_name, name)
-        status, es_inst = bdev.ExtStorageFromDisk(name, base_dir=dir_name)
+        status, es_inst = extstorage.ExtStorageFromDisk(name, base_dir=dir_name)
         if status:
           diagnose = ""
           parameters = es_inst.supported_parameters
@@ -3340,7 +4183,7 @@ def BlockdevGrow(disk, amount, dryrun, backingstore, excl_stor):
     _Fail("Failed to grow block device: %s", err, exc=True)
 
 
-def BlockdevSnapshot(disk):
+def BlockdevSnapshot(disk, snap_name, snap_size):
   """Create a snapshot copy of a block device.
 
   This function is called recursively, and the snapshot is actually created
@@ -3348,25 +4191,32 @@ def BlockdevSnapshot(disk):
 
   @type disk: L{objects.Disk}
   @param disk: the disk to be snapshotted
+  @type snap_name: string
+  @param snap_name: the name of the snapshot
+  @type snap_size: int
+  @param snap_size: the size of the snapshot
   @rtype: string
   @return: snapshot disk ID as (vg, lv)
 
   """
+  def _DiskSnapshot(disk, snap_name=None, snap_size=None):
+    r_dev = _RecursiveFindBD(disk)
+    if r_dev is not None:
+      return r_dev.Snapshot(snap_name=snap_name, snap_size=snap_size)
+    else:
+      _Fail("Cannot find block device %s", disk)
+
   if disk.dev_type == constants.DT_DRBD8:
     if not disk.children:
       _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
             disk.unique_id)
-    return BlockdevSnapshot(disk.children[0])
+    return BlockdevSnapshot(disk.children[0], snap_name, snap_size)
   elif disk.dev_type == constants.DT_PLAIN:
-    r_dev = _RecursiveFindBD(disk)
-    if r_dev is not None:
-      # FIXME: choose a saner value for the snapshot size
-      # let's stay on the safe side and ask for the full size, for now
-      return r_dev.Snapshot(disk.size)
-    else:
-      _Fail("Cannot find block device %s", disk)
+    return _DiskSnapshot(disk, snap_name, snap_size)
+  elif disk.dev_type == constants.DT_EXT:
+    return _DiskSnapshot(disk, snap_name, snap_size)
   else:
-    _Fail("Cannot snapshot non-lvm block device '%s' of type '%s'",
+    _Fail("Cannot snapshot block device '%s' of type '%s'",
           disk.logical_id, disk.dev_type)
 
 
@@ -3415,7 +4265,7 @@ def FinalizeExport(instance, snap_disks):
   config = objects.SerializableConfigParser()
 
   config.add_section(constants.INISECT_EXP)
-  config.set(constants.INISECT_EXP, "version", "0")
+  config.set(constants.INISECT_EXP, "version", str(constants.EXPORT_VERSION))
   config.set(constants.INISECT_EXP, "timestamp", "%d" % int(time.time()))
   config.set(constants.INISECT_EXP, "source", instance.primary_node)
   config.set(constants.INISECT_EXP, "os", instance.os)
@@ -4092,13 +4942,13 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
       # wrong path; we use notrunc to no attempt truncate on an LV device
       suffix = utils.BuildShellCmd("| dd of=%s conv=nocreat,notrunc bs=%s",
                                    real_disk.dev_path,
-                                   str(1024 * 1024)) # 1 MB
+                                   str(constants.DD_BLOCK_SIZE)) # 1 MB
 
     elif mode == constants.IEM_EXPORT:
       # the block size on the read dd is 1MiB to match our units
       prefix = utils.BuildShellCmd("dd if=%s bs=%s count=%s |",
                                    real_disk.dev_path,
-                                   str(1024 * 1024), # 1 MB
+                                   str(constants.DD_BLOCK_SIZE), # 1 MB
                                    str(disk.size))
       exp_size = disk.size
 
index 466c53c..4b6564c 100644 (file)
@@ -37,7 +37,6 @@ import os.path
 import re
 import logging
 import time
-import tempfile
 
 from ganeti.cmdlib import cluster
 import ganeti.rpc.node as rpc
@@ -67,30 +66,6 @@ _INITCONF_ECID = "initconfig-ecid"
 _DAEMON_READY_TIMEOUT = 10.0
 
 
-def _InitSSHSetup():
-  """Setup the SSH configuration for the cluster.
-
-  This generates a dsa keypair for root, adds the pub key to the
-  permitted hosts and adds the hostkey to its own known hosts.
-
-  """
-  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.SSH_LOGIN_USER)
-
-  for name in priv_key, pub_key:
-    if os.path.exists(name):
-      utils.CreateBackup(name)
-    utils.RemoveFile(name)
-
-  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
-                         "-f", priv_key,
-                         "-q", "-N", ""])
-  if result.failed:
-    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
-                             result.output)
-
-  utils.AddAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
-
-
 def GenerateHmacKey(file_name):
   """Writes a new HMAC key.
 
@@ -320,93 +295,6 @@ def _WaitForSshDaemon(hostname, port):
                              (hostname, port, hostip, _DAEMON_READY_TIMEOUT))
 
 
-def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
-                    use_cluster_key, ask_key, strict_host_check,
-                    port, data):
-  """Runs a command to configure something on a remote machine.
-
-  @type cluster_name: string
-  @param cluster_name: Cluster name
-  @type node: string
-  @param node: Node name
-  @type basecmd: string
-  @param basecmd: Base command (path on the remote machine)
-  @type debug: bool
-  @param debug: Enable debug output
-  @type verbose: bool
-  @param verbose: Enable verbose output
-  @type use_cluster_key: bool
-  @param use_cluster_key: See L{ssh.SshRunner.BuildCmd}
-  @type ask_key: bool
-  @param ask_key: See L{ssh.SshRunner.BuildCmd}
-  @type strict_host_check: bool
-  @param strict_host_check: See L{ssh.SshRunner.BuildCmd}
-  @type port: int
-  @param port: The SSH port of the remote machine or None for the default
-  @param data: JSON-serializable input data for script (passed to stdin)
-
-  """
-  cmd = [basecmd]
-
-  # Pass --debug/--verbose to the external script if set on our invocation
-  if debug:
-    cmd.append("--debug")
-
-  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)
-
-  if port is None:
-    port = netutils.GetDaemonPort(constants.SSH)
-
-  srun = ssh.SshRunner(cluster_name)
-  scmd = srun.BuildCmd(node, constants.SSH_LOGIN_USER,
-                       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,
-                       port=port)
-
-  tempfh = tempfile.TemporaryFile()
-  try:
-    tempfh.write(serializer.DumpJson(data))
-    tempfh.seek(0)
-
-    result = utils.RunCmd(scmd, interactive=True, input_fd=tempfh)
-  finally:
-    tempfh.close()
-
-  if result.failed:
-    raise errors.OpExecError("Command '%s' failed: %s" %
-                             (result.cmd, result.fail_reason))
-
-  _WaitForSshDaemon(node, port)
-
-
 def _InitFileStorageDir(file_storage_dir):
   """Initialize if needed the file storage.
 
@@ -825,7 +713,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     utils.AddHostToEtcHosts(hostname.name, hostname.ip)
 
   if modify_ssh_setup:
-    _InitSSHSetup()
+    ssh.InitSSHSetup()
 
   if default_iallocator is not None:
     alloc_script = utils.FindFile(default_iallocator,
@@ -856,6 +744,12 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   if compression_tools is not None:
     cluster.CheckCompressionTools(compression_tools)
 
+  initial_dc_config = dict(active=True,
+                           interval=int(constants.MOND_TIME_INTERVAL * 1e6))
+  data_collectors = dict(
+      (name, initial_dc_config.copy())
+      for name in constants.DATA_COLLECTOR_NAMES)
+
   # init of cluster config file
   cluster_config = objects.Cluster(
     serial_no=1,
@@ -885,6 +779,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     ctime=now,
     mtime=now,
     maintain_node_health=maintain_node_health,
+    data_collectors=data_collectors,
     drbd_usermode_helper=drbd_helper,
     default_iallocator=default_iallocator,
     default_iallocator_params=default_iallocator_params,
@@ -917,6 +812,9 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   cfg.Update(cfg.GetClusterInfo(), logging.error)
   ssconf.WriteSsconfFiles(cfg.GetSsconfValues())
 
+  master_uuid = cfg.GetMasterNode()
+  if modify_ssh_setup:
+    ssh.InitPubKeyFile(master_uuid)
   # set up the inter-node password and certificate
   _InitGanetiServerSetup(hostname.name, cfg)
 
@@ -973,6 +871,7 @@ def InitConfig(version, cluster_config, master_node_config,
                                    instances={},
                                    networks={},
                                    disks={},
+                                   filters={},
                                    serial_no=1,
                                    ctime=now, mtime=now)
   utils.WriteFile(cfg_file,
@@ -1037,11 +936,14 @@ def SetupNodeDaemon(opts, cluster_name, node, ssh_port):
     constants.NDS_NODE_NAME: node,
     }
 
-  RunNodeSetupCmd(cluster_name, node, pathutils.NODE_DAEMON_SETUP,
-                  opts.debug, opts.verbose,
-                  True, opts.ssh_key_check, opts.ssh_key_check,
-                  ssh_port, data)
+  ssh.RunSshCmdWithStdin(cluster_name, node, pathutils.NODE_DAEMON_SETUP,
+                         ssh_port, data,
+                         debug=opts.debug, verbose=opts.verbose,
+                         use_cluster_key=True, ask_key=opts.ssh_key_check,
+                         strict_host_check=opts.ssh_key_check,
+                         ensure_version=True)
 
+  _WaitForSshDaemon(node, ssh_port)
   _WaitForNodeDaemon(node)
 
 
index 2380ff6..2150288 100644 (file)
@@ -518,7 +518,8 @@ class _RapiHandlersForDocsHelper(object):
     resources = \
       rapi.connector.GetHandlers("[node_name]", "[instance_name]",
                                  "[group_name]", "[network_name]", "[job_id]",
-                                 "[disk_index]", "[resource]",
+                                 "[disk_index]", "[filter_uuid]",
+                                 "[resource]",
                                  translate=cls._TranslateResourceUri)
 
     return resources
index c9ab93d..3fcfd98 100644 (file)
@@ -54,206 +54,16 @@ from ganeti import qlang
 from ganeti import objects
 from ganeti import pathutils
 from ganeti import serializer
+import ganeti.cli_opts
+# Import constants
+from ganeti.cli_opts import *  # pylint: disable=W0401
 
 from ganeti.runtime import (GetClient)
 
-from optparse import (OptionParser, TitledHelpFormatter,
-                      Option, OptionValueError)
+from optparse import (OptionParser, TitledHelpFormatter)
 
 
 __all__ = [
-  # Command line options
-  "ABSOLUTE_OPT",
-  "ADD_UIDS_OPT",
-  "ADD_RESERVED_IPS_OPT",
-  "ALLOCATABLE_OPT",
-  "ALLOC_POLICY_OPT",
-  "ALL_OPT",
-  "ALLOW_FAILOVER_OPT",
-  "AUTO_PROMOTE_OPT",
-  "AUTO_REPLACE_OPT",
-  "BACKEND_OPT",
-  "BLK_OS_OPT",
-  "CAPAB_MASTER_OPT",
-  "CAPAB_VM_OPT",
-  "CLEANUP_OPT",
-  "CLUSTER_DOMAIN_SECRET_OPT",
-  "CONFIRM_OPT",
-  "CP_SIZE_OPT",
-  "COMPRESSION_TOOLS_OPT",
-  "DEBUG_OPT",
-  "DEBUG_SIMERR_OPT",
-  "DISKIDX_OPT",
-  "DISK_OPT",
-  "DISK_PARAMS_OPT",
-  "DISK_TEMPLATE_OPT",
-  "DRAINED_OPT",
-  "DRY_RUN_OPT",
-  "DRBD_HELPER_OPT",
-  "DST_NODE_OPT",
-  "EARLY_RELEASE_OPT",
-  "ENABLED_HV_OPT",
-  "ENABLED_DISK_TEMPLATES_OPT",
-  "ENABLED_USER_SHUTDOWN_OPT",
-  "ERROR_CODES_OPT",
-  "FAILURE_ONLY_OPT",
-  "FIELDS_OPT",
-  "FILESTORE_DIR_OPT",
-  "FILESTORE_DRIVER_OPT",
-  "FORCE_FAILOVER_OPT",
-  "FORCE_FILTER_OPT",
-  "FORCE_OPT",
-  "FORCE_VARIANT_OPT",
-  "GATEWAY_OPT",
-  "GATEWAY6_OPT",
-  "GLOBAL_FILEDIR_OPT",
-  "HID_OS_OPT",
-  "GLOBAL_GLUSTER_FILEDIR_OPT",
-  "GLOBAL_SHARED_FILEDIR_OPT",
-  "HOTPLUG_OPT",
-  "HOTPLUG_IF_POSSIBLE_OPT",
-  "HVLIST_OPT",
-  "HVOPTS_OPT",
-  "HYPERVISOR_OPT",
-  "IALLOCATOR_OPT",
-  "DEFAULT_IALLOCATOR_OPT",
-  "DEFAULT_IALLOCATOR_PARAMS_OPT",
-  "IDENTIFY_DEFAULTS_OPT",
-  "IGNORE_CONSIST_OPT",
-  "IGNORE_ERRORS_OPT",
-  "IGNORE_FAILURES_OPT",
-  "IGNORE_OFFLINE_OPT",
-  "IGNORE_REMOVE_FAILURES_OPT",
-  "IGNORE_SECONDARIES_OPT",
-  "IGNORE_SIZE_OPT",
-  "INCLUDEDEFAULTS_OPT",
-  "INTERVAL_OPT",
-  "INSTALL_IMAGE_OPT",
-  "INSTANCE_COMMUNICATION_OPT",
-  "INSTANCE_COMMUNICATION_NETWORK_OPT",
-  "MAC_PREFIX_OPT",
-  "MAINTAIN_NODE_HEALTH_OPT",
-  "MASTER_NETDEV_OPT",
-  "MASTER_NETMASK_OPT",
-  "MAX_TRACK_OPT",
-  "MC_OPT",
-  "MIGRATION_MODE_OPT",
-  "MODIFY_ETCHOSTS_OPT",
-  "NET_OPT",
-  "NETWORK_OPT",
-  "NETWORK6_OPT",
-  "NEW_CLUSTER_CERT_OPT",
-  "NEW_NODE_CERT_OPT",
-  "NEW_CLUSTER_DOMAIN_SECRET_OPT",
-  "NEW_CONFD_HMAC_KEY_OPT",
-  "NEW_RAPI_CERT_OPT",
-  "NEW_PRIMARY_OPT",
-  "NEW_SECONDARY_OPT",
-  "NEW_SPICE_CERT_OPT",
-  "NIC_PARAMS_OPT",
-  "NOCONFLICTSCHECK_OPT",
-  "NODE_FORCE_JOIN_OPT",
-  "NODE_LIST_OPT",
-  "NODE_PLACEMENT_OPT",
-  "NODEGROUP_OPT",
-  "NODE_PARAMS_OPT",
-  "NODE_POWERED_OPT",
-  "NOHDR_OPT",
-  "NOIPCHECK_OPT",
-  "NO_INSTALL_OPT",
-  "NONAMECHECK_OPT",
-  "NOMODIFY_ETCHOSTS_OPT",
-  "NOMODIFY_SSH_SETUP_OPT",
-  "NONICS_OPT",
-  "NONLIVE_OPT",
-  "NONPLUS1_OPT",
-  "NORUNTIME_CHGS_OPT",
-  "NOSHUTDOWN_OPT",
-  "NOSTART_OPT",
-  "NOSSH_KEYCHECK_OPT",
-  "NOVOTING_OPT",
-  "NO_REMEMBER_OPT",
-  "NWSYNC_OPT",
-  "OFFLINE_INST_OPT",
-  "ONLINE_INST_OPT",
-  "ON_PRIMARY_OPT",
-  "ON_SECONDARY_OPT",
-  "OFFLINE_OPT",
-  "OS_OPT",
-  "OSPARAMS_OPT",
-  "OSPARAMS_PRIVATE_OPT",
-  "OSPARAMS_SECRET_OPT",
-  "OS_SIZE_OPT",
-  "OOB_TIMEOUT_OPT",
-  "POWER_DELAY_OPT",
-  "PREALLOC_WIPE_DISKS_OPT",
-  "PRIMARY_IP_VERSION_OPT",
-  "PRIMARY_ONLY_OPT",
-  "PRINT_JOBID_OPT",
-  "PRIORITY_OPT",
-  "RAPI_CERT_OPT",
-  "READD_OPT",
-  "REASON_OPT",
-  "REBOOT_TYPE_OPT",
-  "REMOVE_INSTANCE_OPT",
-  "REMOVE_RESERVED_IPS_OPT",
-  "REMOVE_UIDS_OPT",
-  "RESERVED_LVS_OPT",
-  "RQL_OPT",
-  "RUNTIME_MEM_OPT",
-  "ROMAN_OPT",
-  "SECONDARY_IP_OPT",
-  "SECONDARY_ONLY_OPT",
-  "SELECT_OS_OPT",
-  "SEP_OPT",
-  "SHOWCMD_OPT",
-  "SHOW_MACHINE_OPT",
-  "COMPRESS_OPT",
-  "TRANSPORT_COMPRESSION_OPT",
-  "SHUTDOWN_TIMEOUT_OPT",
-  "SINGLE_NODE_OPT",
-  "SPECS_CPU_COUNT_OPT",
-  "SPECS_DISK_COUNT_OPT",
-  "SPECS_DISK_SIZE_OPT",
-  "SPECS_MEM_SIZE_OPT",
-  "SPECS_NIC_COUNT_OPT",
-  "SPLIT_ISPECS_OPTS",
-  "IPOLICY_STD_SPECS_OPT",
-  "IPOLICY_DISK_TEMPLATES",
-  "IPOLICY_VCPU_RATIO",
-  "IPOLICY_SPINDLE_RATIO",
-  "SEQUENTIAL_OPT",
-  "SPICE_CACERT_OPT",
-  "SPICE_CERT_OPT",
-  "SRC_DIR_OPT",
-  "SRC_NODE_OPT",
-  "SUBMIT_OPT",
-  "SUBMIT_OPTS",
-  "STARTUP_PAUSED_OPT",
-  "STATIC_OPT",
-  "SYNC_OPT",
-  "TAG_ADD_OPT",
-  "TAG_SRC_OPT",
-  "TIMEOUT_OPT",
-  "TO_GROUP_OPT",
-  "UIDPOOL_OPT",
-  "USEUNITS_OPT",
-  "USE_EXTERNAL_MIP_SCRIPT",
-  "USE_REPL_NET_OPT",
-  "VERBOSE_OPT",
-  "VG_NAME_OPT",
-  "WFSYNC_OPT",
-  "YES_DOIT_OPT",
-  "ZEROING_IMAGE_OPT",
-  "ZERO_FREE_SPACE_OPT",
-  "HELPER_STARTUP_TIMEOUT_OPT",
-  "HELPER_SHUTDOWN_TIMEOUT_OPT",
-  "ZEROING_TIMEOUT_FIXED_OPT",
-  "ZEROING_TIMEOUT_PER_MIB_OPT",
-  "DISK_STATE_OPT",
-  "HV_STATE_OPT",
-  "IGNORE_IPOLICY_OPT",
-  "INSTANCE_POLICY_OPTS",
   # Generic functions for CLI programs
   "ConfirmOperation",
   "CreateIPolicyFromOpts",
@@ -264,6 +74,7 @@ __all__ = [
   "GetClient",
   "GetOnlineNodes",
   "GetNodesSshPorts",
+  "GetNodeUUIDs",
   "JobExecutor",
   "JobSubmittedException",
   "ParseTimespec",
@@ -294,12 +105,14 @@ __all__ = [
   "ARGS_MANY_NODES",
   "ARGS_MANY_GROUPS",
   "ARGS_MANY_NETWORKS",
+  "ARGS_MANY_FILTERS",
   "ARGS_NONE",
   "ARGS_ONE_INSTANCE",
   "ARGS_ONE_NODE",
   "ARGS_ONE_GROUP",
   "ARGS_ONE_OS",
   "ARGS_ONE_NETWORK",
+  "ARGS_ONE_FILTER",
   "ArgChoice",
   "ArgCommand",
   "ArgFile",
@@ -311,39 +124,14 @@ __all__ = [
   "ArgNode",
   "ArgOs",
   "ArgExtStorage",
+  "ArgFilter",
   "ArgSuggest",
   "ArgUnknown",
-  "OPT_COMPL_INST_ADD_NODES",
-  "OPT_COMPL_MANY_NODES",
-  "OPT_COMPL_ONE_IALLOCATOR",
-  "OPT_COMPL_ONE_INSTANCE",
-  "OPT_COMPL_ONE_NODE",
-  "OPT_COMPL_ONE_NODEGROUP",
-  "OPT_COMPL_ONE_NETWORK",
-  "OPT_COMPL_ONE_OS",
-  "OPT_COMPL_ONE_EXTSTORAGE",
-  "cli_option",
   "FixHvParams",
   "SplitNodeOption",
   "CalculateOSNames",
   "ParseFields",
-  "COMMON_CREATE_OPTS",
-  ]
-
-NO_PREFIX = "no_"
-UN_PREFIX = "-"
-
-#: Priorities (sorted)
-_PRIORITY_NAMES = [
-  ("low", constants.OP_PRIO_LOW),
-  ("normal", constants.OP_PRIO_NORMAL),
-  ("high", constants.OP_PRIO_HIGH),
-  ]
-
-#: Priority dictionary for easier lookup
-# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
-# we migrate to Python 2.6
-_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
+  ] + ganeti.cli_opts.__all__ # Command line options
 
 # Query result status for clients
 (QR_NORMAL,
@@ -480,17 +268,24 @@ class ArgExtStorage(_Argument):
   """
 
 
+class ArgFilter(_Argument):
+  """Filter UUID argument.
+
+  """
+
+
 ARGS_NONE = []
 ARGS_MANY_INSTANCES = [ArgInstance()]
 ARGS_MANY_NETWORKS = [ArgNetwork()]
 ARGS_MANY_NODES = [ArgNode()]
 ARGS_MANY_GROUPS = [ArgGroup()]
+ARGS_MANY_FILTERS = [ArgFilter()]
 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
 ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)]
 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
-# TODO
 ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
+ARGS_ONE_FILTER = [ArgFilter(min=1, max=1)]
 
 
 def _ExtractTagsObject(opts, args):
@@ -599,1295 +394,6 @@ def RemoveTags(opts, args):
   SubmitOrSend(op, opts)
 
 
-def check_unit(option, opt, value): # pylint: disable=W0613
-  """OptParsers custom converter for units.
-
-  """
-  try:
-    return utils.ParseUnit(value)
-  except errors.UnitParseError, err:
-    raise OptionValueError("option %s: %s" % (opt, err))
-
-
-def _SplitKeyVal(opt, data, parse_prefixes):
-  """Convert a KeyVal string into a dict.
-
-  This function will convert a key=val[,...] string into a dict. Empty
-  values will be converted specially: keys which have the prefix 'no_'
-  will have the value=False and the prefix stripped, keys with the prefix
-  "-" will have value=None and the prefix stripped, and the others will
-  have value=True.
-
-  @type opt: string
-  @param opt: a string holding the option name for which we process the
-      data, used in building error messages
-  @type data: string
-  @param data: a string of the format key=val,key=val,...
-  @type parse_prefixes: bool
-  @param parse_prefixes: whether to handle prefixes specially
-  @rtype: dict
-  @return: {key=val, key=val}
-  @raises errors.ParameterError: if there are duplicate keys
-
-  """
-  kv_dict = {}
-  if data:
-    for elem in utils.UnescapeAndSplit(data, sep=","):
-      if "=" in elem:
-        key, val = elem.split("=", 1)
-      elif parse_prefixes:
-        if elem.startswith(NO_PREFIX):
-          key, val = elem[len(NO_PREFIX):], False
-        elif elem.startswith(UN_PREFIX):
-          key, val = elem[len(UN_PREFIX):], None
-        else:
-          key, val = elem, True
-      else:
-        raise errors.ParameterError("Missing value for key '%s' in option %s" %
-                                    (elem, opt))
-      if key in kv_dict:
-        raise errors.ParameterError("Duplicate key '%s' in option %s" %
-                                    (key, opt))
-      kv_dict[key] = val
-  return kv_dict
-
-
-def _SplitIdentKeyVal(opt, value, parse_prefixes):
-  """Helper function to parse "ident:key=val,key=val" options.
-
-  @type opt: string
-  @param opt: option name, used in error messages
-  @type value: string
-  @param value: expected to be in the format "ident:key=val,key=val,..."
-  @type parse_prefixes: bool
-  @param parse_prefixes: whether to handle prefixes specially (see
-      L{_SplitKeyVal})
-  @rtype: tuple
-  @return: (ident, {key=val, key=val})
-  @raises errors.ParameterError: in case of duplicates or other parsing errors
-
-  """
-  if ":" not in value:
-    ident, rest = value, ""
-  else:
-    ident, rest = value.split(":", 1)
-
-  if parse_prefixes and ident.startswith(NO_PREFIX):
-    if rest:
-      msg = "Cannot pass options when removing parameter groups: %s" % value
-      raise errors.ParameterError(msg)
-    retval = (ident[len(NO_PREFIX):], False)
-  elif (parse_prefixes and ident.startswith(UN_PREFIX) and
-        (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
-    if rest:
-      msg = "Cannot pass options when removing parameter groups: %s" % value
-      raise errors.ParameterError(msg)
-    retval = (ident[len(UN_PREFIX):], None)
-  else:
-    kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
-    retval = (ident, kv_dict)
-  return retval
-
-
-def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
-  """Custom parser for ident:key=val,key=val options.
-
-  This will store the parsed values as a tuple (ident, {key: val}). As such,
-  multiple uses of this option via action=append is possible.
-
-  """
-  return _SplitIdentKeyVal(opt, value, True)
-
-
-def check_key_val(option, opt, value):  # pylint: disable=W0613
-  """Custom parser class for key=val,key=val options.
-
-  This will store the parsed values as a dict {key: val}.
-
-  """
-  return _SplitKeyVal(opt, value, True)
-
-
-def check_key_private_val(option, opt, value):  # pylint: disable=W0613
-  """Custom parser class for private and secret key=val,key=val options.
-
-  This will store the parsed values as a dict {key: val}.
-
-  """
-  return serializer.PrivateDict(_SplitKeyVal(opt, value, True))
-
-
-def _SplitListKeyVal(opt, value):
-  retval = {}
-  for elem in value.split("/"):
-    if not elem:
-      raise errors.ParameterError("Empty section in option '%s'" % opt)
-    (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
-    if ident in retval:
-      msg = ("Duplicated parameter '%s' in parsing %s: %s" %
-             (ident, opt, elem))
-      raise errors.ParameterError(msg)
-    retval[ident] = valdict
-  return retval
-
-
-def check_multilist_ident_key_val(_, opt, value):
-  """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options.
-
-  @rtype: list of dictionary
-  @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}]
-
-  """
-  retval = []
-  for line in value.split("//"):
-    retval.append(_SplitListKeyVal(opt, line))
-  return retval
-
-
-def check_bool(option, opt, value): # pylint: disable=W0613
-  """Custom parser for yes/no options.
-
-  This will store the parsed value as either True or False.
-
-  """
-  value = value.lower()
-  if value == constants.VALUE_FALSE or value == "no":
-    return False
-  elif value == constants.VALUE_TRUE or value == "yes":
-    return True
-  else:
-    raise errors.ParameterError("Invalid boolean value '%s'" % value)
-
-
-def check_list(option, opt, value): # pylint: disable=W0613
-  """Custom parser for comma-separated lists.
-
-  """
-  # we have to make this explicit check since "".split(",") is [""],
-  # not an empty list :(
-  if not value:
-    return []
-  else:
-    return utils.UnescapeAndSplit(value)
-
-
-def check_maybefloat(option, opt, value): # pylint: disable=W0613
-  """Custom parser for float numbers which might be also defaults.
-
-  """
-  value = value.lower()
-
-  if value == constants.VALUE_DEFAULT:
-    return value
-  else:
-    return float(value)
-
-
-# completion_suggestion is normally a list. Using numeric values not evaluating
-# to False for dynamic completion.
-(OPT_COMPL_MANY_NODES,
- OPT_COMPL_ONE_NODE,
- OPT_COMPL_ONE_INSTANCE,
- OPT_COMPL_ONE_OS,
- OPT_COMPL_ONE_EXTSTORAGE,
- OPT_COMPL_ONE_IALLOCATOR,
- OPT_COMPL_ONE_NETWORK,
- OPT_COMPL_INST_ADD_NODES,
- OPT_COMPL_ONE_NODEGROUP) = range(100, 109)
-
-OPT_COMPL_ALL = compat.UniqueFrozenset([
-  OPT_COMPL_MANY_NODES,
-  OPT_COMPL_ONE_NODE,
-  OPT_COMPL_ONE_INSTANCE,
-  OPT_COMPL_ONE_OS,
-  OPT_COMPL_ONE_EXTSTORAGE,
-  OPT_COMPL_ONE_IALLOCATOR,
-  OPT_COMPL_ONE_NETWORK,
-  OPT_COMPL_INST_ADD_NODES,
-  OPT_COMPL_ONE_NODEGROUP,
-  ])
-
-
-class CliOption(Option):
-  """Custom option class for optparse.
-
-  """
-  ATTRS = Option.ATTRS + [
-    "completion_suggest",
-    ]
-  TYPES = Option.TYPES + (
-    "multilistidentkeyval",
-    "identkeyval",
-    "keyval",
-    "keyprivateval",
-    "unit",
-    "bool",
-    "list",
-    "maybefloat",
-    )
-  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
-  TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
-  TYPE_CHECKER["identkeyval"] = check_ident_key_val
-  TYPE_CHECKER["keyval"] = check_key_val
-  TYPE_CHECKER["keyprivateval"] = check_key_private_val
-  TYPE_CHECKER["unit"] = check_unit
-  TYPE_CHECKER["bool"] = check_bool
-  TYPE_CHECKER["list"] = check_list
-  TYPE_CHECKER["maybefloat"] = check_maybefloat
-
-
-# optparse.py sets make_option, so we do it for our own option class, too
-cli_option = CliOption
-
-
-_YORNO = "yes|no"
-
-DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
-                       help="Increase debugging level")
-
-NOHDR_OPT = cli_option("--no-headers", default=False,
-                       action="store_true", dest="no_headers",
-                       help="Don't display column headers")
-
-SEP_OPT = cli_option("--separator", default=None,
-                     action="store", dest="separator",
-                     help=("Separator between output fields"
-                           " (defaults to one space)"))
-
-USEUNITS_OPT = cli_option("--units", default=None,
-                          dest="units", choices=("h", "m", "g", "t"),
-                          help="Specify units for output (one of h/m/g/t)")
-
-FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
-                        type="string", metavar="FIELDS",
-                        help="Comma separated list of output fields")
-
-FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
-                       default=False, help="Force the operation")
-
-CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
-                         default=False, help="Do not require confirmation")
-
-IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
-                                  action="store_true", default=False,
-                                  help=("Ignore offline nodes and do as much"
-                                        " as possible"))
-
-TAG_ADD_OPT = cli_option("--tags", dest="tags",
-                         default=None, help="Comma-separated list of instance"
-                                            " tags")
-
-TAG_SRC_OPT = cli_option("--from", dest="tags_source",
-                         default=None, help="File with tag names")
-
-SUBMIT_OPT = cli_option("--submit", dest="submit_only",
-                        default=False, action="store_true",
-                        help=("Submit the job and return the job ID, but"
-                              " don't wait for the job to finish"))
-
-PRINT_JOBID_OPT = cli_option("--print-jobid", dest="print_jobid",
-                             default=False, action="store_true",
-                             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"
-                            " in order to ensure more consistent results"))
-
-DRY_RUN_OPT = cli_option("--dry-run", default=False,
-                         action="store_true",
-                         help=("Do not execute the operation, just run the"
-                               " check steps and verify if it could be"
-                               " executed"))
-
-VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
-                         action="store_true",
-                         help="Increase the verbosity of the operation")
-
-DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
-                              action="store_true", dest="simulate_errors",
-                              help="Debugging option that makes the operation"
-                              " treat most runtime checks as failed")
-
-NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
-                        default=True, action="store_false",
-                        help="Don't wait for sync (DANGEROUS!)")
-
-WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
-                        default=False, action="store_true",
-                        help="Wait for disks to sync")
-
-ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
-                             action="store_true", default=False,
-                             help="Enable offline instance")
-
-OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
-                              action="store_true", default=False,
-                              help="Disable down instance")
-
-DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
-                               help=("Custom disk setup (%s)" %
-                                     utils.CommaJoin(constants.DISK_TEMPLATES)),
-                               default=None, metavar="TEMPL",
-                               choices=list(constants.DISK_TEMPLATES))
-
-NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
-                        help="Do not create any network cards for"
-                        " the instance")
-
-FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
-                               help="Relative path under default cluster-wide"
-                               " file storage dir to store file-based disks",
-                               default=None, metavar="<DIR>")
-
-FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
-                                  help="Driver to use for image files",
-                                  default=None, metavar="<DRIVER>",
-                                  choices=list(constants.FILE_DRIVER))
-
-IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
-                            help="Select nodes for the instance automatically"
-                            " using the <NAME> iallocator plugin",
-                            default=None, type="string",
-                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
-
-DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
-                                    metavar="<NAME>",
-                                    help="Set the default instance"
-                                    " allocator plugin",
-                                    default=None, type="string",
-                                    completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
-
-DEFAULT_IALLOCATOR_PARAMS_OPT = cli_option("--default-iallocator-params",
-                                           dest="default_iallocator_params",
-                                           help="iallocator template"
-                                           " parameters, in the format"
-                                           " template:option=value,"
-                                           " option=value,...",
-                                           type="keyval",
-                                           default=None)
-
-OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
-                    metavar="<os>",
-                    completion_suggest=OPT_COMPL_ONE_OS)
-
-OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
-                          type="keyval", default={},
-                          help="OS parameters")
-
-OSPARAMS_PRIVATE_OPT = cli_option("--os-parameters-private",
-                                  dest="osparams_private",
-                                  type="keyprivateval",
-                                  default=serializer.PrivateDict(),
-                                  help="Private OS parameters"
-                                       " (won't be logged)")
-
-OSPARAMS_SECRET_OPT = cli_option("--os-parameters-secret",
-                                 dest="osparams_secret",
-                                 type="keyprivateval",
-                                 default=serializer.PrivateDict(),
-                                 help="Secret OS parameters (won't be logged or"
-                                      " saved; you must supply these for every"
-                                      " operation.)")
-
-FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
-                               action="store_true", default=False,
-                               help="Force an unknown variant")
-
-NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
-                            action="store_true", default=False,
-                            help="Do not install the OS (will"
-                            " enable no-start)")
-
-NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
-                                dest="allow_runtime_chgs",
-                                default=True, action="store_false",
-                                help="Don't allow runtime changes")
-
-BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
-                         type="keyval", default={},
-                         help="Backend parameters")
-
-HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
-                        default={}, dest="hvparams",
-                        help="Hypervisor parameters")
-
-DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
-                             help="Disk template parameters, in the format"
-                             " template:option=value,option=value,...",
-                             type="identkeyval", action="append", default=[])
-
-SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
-                                 type="keyval", default={},
-                                 help="Memory size specs: list of key=value,"
-                                " where key is one of min, max, std"
-                                 " (in MB or using a unit)")
-
-SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
-                                 type="keyval", default={},
-                                 help="CPU count specs: list of key=value,"
-                                 " where key is one of min, max, std")
-
-SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
-                                  dest="ispecs_disk_count",
-                                  type="keyval", default={},
-                                  help="Disk count specs: list of key=value,"
-                                  " where key is one of min, max, std")
-
-SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
-                                 type="keyval", default={},
-                                 help="Disk size specs: list of key=value,"
-                                 " where key is one of min, max, std"
-                                 " (in MB or using a unit)")
-
-SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
-                                 type="keyval", default={},
-                                 help="NIC count specs: list of key=value,"
-                                 " where key is one of min, max, std")
-
-IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs"
-IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR,
-                                      dest="ipolicy_bounds_specs",
-                                      type="multilistidentkeyval", default=None,
-                                      help="Complete instance specs limits")
-
-IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
-IPOLICY_STD_SPECS_OPT = cli_option(IPOLICY_STD_SPECS_STR,
-                                   dest="ipolicy_std_specs",
-                                   type="keyval", default=None,
-                                   help="Complete standard instance specs")
-
-IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
-                                    dest="ipolicy_disk_templates",
-                                    type="list", default=None,
-                                    help="Comma-separated list of"
-                                    " enabled disk templates")
-
-IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
-                                 dest="ipolicy_vcpu_ratio",
-                                 type="maybefloat", default=None,
-                                 help="The maximum allowed vcpu-to-cpu ratio")
-
-IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
-                                   dest="ipolicy_spindle_ratio",
-                                   type="maybefloat", default=None,
-                                   help=("The maximum allowed instances to"
-                                         " spindle ratio"))
-
-HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
-                            help="Hypervisor and hypervisor options, in the"
-                            " format hypervisor:option=value,option=value,...",
-                            default=None, type="identkeyval")
-
-HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
-                        help="Hypervisor and hypervisor options, in the"
-                        " format hypervisor:option=value,option=value,...",
-                        default=[], action="append", type="identkeyval")
-
-NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
-                           action="store_false",
-                           help="Don't check that the instance's IP"
-                           " is alive")
-
-NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
-                             default=True, action="store_false",
-                             help="Don't check that the instance's name"
-                             " is resolvable")
-
-NET_OPT = cli_option("--net",
-                     help="NIC parameters", default=[],
-                     dest="nics", action="append", type="identkeyval")
-
-DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
-                      dest="disks", action="append", type="identkeyval")
-
-DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
-                         help="Comma-separated list of disks"
-                         " indices to act on (e.g. 0,2) (optional,"
-                         " defaults to all disks)")
-
-OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
-                         help="Enforces a single-disk configuration using the"
-                         " given disk size, in MiB unless a suffix is used",
-                         default=None, type="unit", metavar="<size>")
-
-IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
-                                dest="ignore_consistency",
-                                action="store_true", default=False,
-                                help="Ignore the consistency of the disks on"
-                                " the secondary")
-
-ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
-                                dest="allow_failover",
-                                action="store_true", default=False,
-                                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"
-                         " freeze the instance, save the state, transfer and"
-                         " only then resume running on the secondary node)")
-
-MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
-                                default=None,
-                                choices=list(constants.HT_MIGRATION_MODES),
-                                help="Override default migration mode (choose"
-                                " either live or non-live")
-
-NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
-                                help="Target node and optional secondary node",
-                                metavar="<pnode>[:<snode>]",
-                                completion_suggest=OPT_COMPL_INST_ADD_NODES)
-
-NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
-                           action="append", metavar="<node>",
-                           help="Use only this node (can be used multiple"
-                           " times, if not given defaults to all nodes)",
-                           completion_suggest=OPT_COMPL_ONE_NODE)
-
-NODEGROUP_OPT_NAME = "--node-group"
-NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
-                           dest="nodegroup",
-                           help="Node group (name or uuid)",
-                           metavar="<nodegroup>",
-                           default=None, type="string",
-                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
-
-SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
-                             metavar="<node>",
-                             completion_suggest=OPT_COMPL_ONE_NODE)
-
-NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
-                         action="store_false",
-                         help="Don't start the instance after creation")
-
-SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
-                         action="store_true", default=False,
-                         help="Show command instead of executing it")
-
-CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
-                         default=False, action="store_true",
-                         help="Instead of performing the migration/failover,"
-                         " try to recover from a failed cleanup. This is safe"
-                         " to run even if the instance is healthy, but it"
-                         " will create extra replication traffic and "
-                         " disrupt briefly the replication (like during the"
-                         " migration/failover")
-
-STATIC_OPT = cli_option("-s", "--static", dest="static",
-                        action="store_true", default=False,
-                        help="Only show configuration data, not runtime data")
-
-ALL_OPT = cli_option("--all", dest="show_all",
-                     default=False, action="store_true",
-                     help="Show info on all instances on the cluster."
-                     " This can take a long time to run, use wisely")
-
-SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
-                           action="store_true", default=False,
-                           help="Interactive OS reinstall, lists available"
-                           " OS templates for selection")
-
-IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
-                                 action="store_true", default=False,
-                                 help="Remove the instance from the cluster"
-                                 " configuration even if there are failures"
-                                 " during the removal process")
-
-IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
-                                        dest="ignore_remove_failures",
-                                        action="store_true", default=False,
-                                        help="Remove the instance from the"
-                                        " cluster configuration even if there"
-                                        " are failures during the removal"
-                                        " process")
-
-REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
-                                 action="store_true", default=False,
-                                 help="Remove the instance from the cluster")
-
-DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
-                               help="Specifies the new node for the instance",
-                               metavar="NODE", default=None,
-                               completion_suggest=OPT_COMPL_ONE_NODE)
-
-NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
-                               help="Specifies the new secondary node",
-                               metavar="NODE", default=None,
-                               completion_suggest=OPT_COMPL_ONE_NODE)
-
-NEW_PRIMARY_OPT = cli_option("--new-primary", dest="new_primary_node",
-                             help="Specifies the new primary node",
-                             metavar="<node>", default=None,
-                             completion_suggest=OPT_COMPL_ONE_NODE)
-
-ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
-                            default=False, action="store_true",
-                            help="Replace the disk(s) on the primary"
-                                 " node (applies only to internally mirrored"
-                                 " disk templates, e.g. %s)" %
-                                 utils.CommaJoin(constants.DTS_INT_MIRROR))
-
-ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
-                              default=False, action="store_true",
-                              help="Replace the disk(s) on the secondary"
-                                   " node (applies only to internally mirrored"
-                                   " disk templates, e.g. %s)" %
-                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
-
-AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
-                              default=False, action="store_true",
-                              help="Lock all nodes and auto-promote as needed"
-                              " to MC status")
-
-AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
-                              default=False, action="store_true",
-                              help="Automatically replace faulty disks"
-                                   " (applies only to internally mirrored"
-                                   " disk templates, e.g. %s)" %
-                                   utils.CommaJoin(constants.DTS_INT_MIRROR))
-
-IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
-                             default=False, action="store_true",
-                             help="Ignore current recorded size"
-                             " (useful for forcing activation when"
-                             " the recorded size is wrong)")
-
-SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
-                          metavar="<node>",
-                          completion_suggest=OPT_COMPL_ONE_NODE)
-
-SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
-                         metavar="<dir>")
-
-SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
-                              help="Specify the secondary ip for the node",
-                              metavar="ADDRESS", default=None)
-
-READD_OPT = cli_option("--readd", dest="readd",
-                       default=False, action="store_true",
-                       help="Readd old node after replacing it")
-
-NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
-                                default=True, action="store_false",
-                                help="Disable SSH key fingerprint checking")
-
-NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
-                                 default=False, action="store_true",
-                                 help="Force the joining of a node")
-
-MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
-                    type="bool", default=None, metavar=_YORNO,
-                    help="Set the master_candidate flag on the node")
-
-OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
-                         type="bool", default=None,
-                         help=("Set the offline flag on the node"
-                               " (cluster does not communicate with offline"
-                               " nodes)"))
-
-DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
-                         type="bool", default=None,
-                         help=("Set the drained flag on the node"
-                               " (excluded from allocation operations)"))
-
-CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
-                              type="bool", default=None, metavar=_YORNO,
-                              help="Set the master_capable flag on the node")
-
-CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
-                          type="bool", default=None, metavar=_YORNO,
-                          help="Set the vm_capable flag on the node")
-
-ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
-                             type="bool", default=None, metavar=_YORNO,
-                             help="Set the allocatable flag on a volume")
-
-ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
-                            dest="enabled_hypervisors",
-                            help="Comma-separated list of hypervisors",
-                            type="string", default=None)
-
-ENABLED_DISK_TEMPLATES_OPT = cli_option("--enabled-disk-templates",
-                                        dest="enabled_disk_templates",
-                                        help="Comma-separated list of "
-                                             "disk templates",
-                                        type="string", default=None)
-
-ENABLED_USER_SHUTDOWN_OPT = cli_option("--user-shutdown",
-                                       default=None,
-                                       dest="enabled_user_shutdown",
-                                       help="Whether user shutdown is enabled",
-                                       type="bool")
-
-NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
-                            type="keyval", default={},
-                            help="NIC parameters")
-
-CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
-                         dest="candidate_pool_size", type="int",
-                         help="Set the candidate pool size")
-
-RQL_OPT = cli_option("--max-running-jobs", dest="max_running_jobs",
-                     type="int", help="Set the maximal number of jobs to "
-                                      "run simultaneously")
-
-MAX_TRACK_OPT = cli_option("--max-tracked-jobs", dest="max_tracked_jobs",
-                           type="int", help="Set the maximal number of jobs to "
-                                            "be tracked simultaneously for "
-                                            "scheduling")
-
-COMPRESSION_TOOLS_OPT = \
-    cli_option("--compression-tools",
-               dest="compression_tools", type="string", default=None,
-               help="Comma-separated list of compression tools which are"
-                    " allowed to be used by Ganeti in various operations")
-
-VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
-                         help=("Enables LVM and specifies the volume group"
-                               " name (cluster-wide) for disk allocation"
-                               " [%s]" % constants.DEFAULT_VG),
-                         metavar="VG", default=None)
-
-YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
-                          help="Destroy cluster", action="store_true")
-
-NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
-                          help="Skip node agreement check (dangerous)",
-                          action="store_true", default=False)
-
-MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
-                            help="Specify the mac prefix for the instance IP"
-                            " addresses, in the format XX:XX:XX",
-                            metavar="PREFIX",
-                            default=None)
-
-MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
-                               help="Specify the node interface (cluster-wide)"
-                               " on which the master IP address will be added"
-                               " (cluster init default: %s)" %
-                               constants.DEFAULT_BRIDGE,
-                               metavar="NETDEV",
-                               default=None)
-
-MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
-                                help="Specify the netmask of the master IP",
-                                metavar="NETMASK",
-                                default=None)
-
-USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
-                                     dest="use_external_mip_script",
-                                     help="Specify whether to run a"
-                                     " user-provided script for the master"
-                                     " IP address turnup and"
-                                     " turndown operations",
-                                     type="bool", metavar=_YORNO, default=None)
-
-GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
-                                help="Specify the default directory (cluster-"
-                                "wide) for storing the file-based disks [%s]" %
-                                pathutils.DEFAULT_FILE_STORAGE_DIR,
-                                metavar="DIR",
-                                default=None)
-
-GLOBAL_SHARED_FILEDIR_OPT = cli_option(
-  "--shared-file-storage-dir",
-  dest="shared_file_storage_dir",
-  help="Specify the default directory (cluster-wide) for storing the"
-  " shared file-based disks [%s]" %
-  pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
-  metavar="SHAREDDIR", default=None)
-
-GLOBAL_GLUSTER_FILEDIR_OPT = cli_option(
-  "--gluster-storage-dir",
-  dest="gluster_storage_dir",
-  help="Specify the default directory (cluster-wide) for mounting Gluster"
-  " file systems [%s]" %
-  pathutils.DEFAULT_GLUSTER_STORAGE_DIR,
-  metavar="GLUSTERDIR",
-  default=pathutils.DEFAULT_GLUSTER_STORAGE_DIR)
-
-NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
-                                   help="Don't modify %s" % pathutils.ETC_HOSTS,
-                                   action="store_false", default=True)
-
-MODIFY_ETCHOSTS_OPT = \
- cli_option("--modify-etc-hosts", dest="modify_etc_hosts", metavar=_YORNO,
-            default=None, type="bool",
-            help="Defines whether the cluster should autonomously modify"
-            " and keep in sync the /etc/hosts file of the nodes")
-
-NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
-                                    help="Don't initialize SSH keys",
-                                    action="store_false", default=True)
-
-ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
-                             help="Enable parseable error messages",
-                             action="store_true", default=False)
-
-NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
-                          help="Skip N+1 memory redundancy tests",
-                          action="store_true", default=False)
-
-REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
-                             help="Type of reboot: soft/hard/full",
-                             default=constants.INSTANCE_REBOOT_HARD,
-                             metavar="<REBOOT>",
-                             choices=list(constants.REBOOT_TYPES))
-
-IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
-                                    dest="ignore_secondaries",
-                                    default=False, action="store_true",
-                                    help="Ignore errors from secondaries")
-
-NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
-                            action="store_false", default=True,
-                            help="Don't shutdown the instance (unsafe)")
-
-TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
-                         default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
-                         help="Maximum time to wait")
-
-COMPRESS_OPT = cli_option("--compress", dest="compress",
-                          type="string", default=constants.IEC_NONE,
-                          help="The compression mode to use")
-
-TRANSPORT_COMPRESSION_OPT = \
-    cli_option("--transport-compression", dest="transport_compression",
-               type="string", default=constants.IEC_NONE,
-               help="The compression mode to use during transport")
-
-SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
-                                  dest="shutdown_timeout", type="int",
-                                  default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
-                                  help="Maximum time to wait for instance"
-                                  " shutdown")
-
-INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
-                          default=None,
-                          help=("Number of seconds between repetions of the"
-                                " command"))
-
-EARLY_RELEASE_OPT = cli_option("--early-release",
-                               dest="early_release", default=False,
-                               action="store_true",
-                               help="Release the locks on the secondary"
-                               " node(s) early")
-
-NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
-                                  dest="new_cluster_cert",
-                                  default=False, action="store_true",
-                                  help="Generate a new cluster certificate")
-
-NEW_NODE_CERT_OPT = cli_option(
-  "--new-node-certificates", dest="new_node_cert", default=False,
-  action="store_true", help="Generate new node certificates (for all nodes)")
-
-RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
-                           default=None,
-                           help="File containing new RAPI certificate")
-
-NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
-                               default=None, action="store_true",
-                               help=("Generate a new self-signed RAPI"
-                                     " certificate"))
-
-SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
-                            default=None,
-                            help="File containing new SPICE certificate")
-
-SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
-                              default=None,
-                              help="File containing the certificate of the CA"
-                              " which signed the SPICE certificate")
-
-NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
-                                dest="new_spice_cert", default=None,
-                                action="store_true",
-                                help=("Generate a new self-signed SPICE"
-                                      " certificate"))
-
-NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
-                                    dest="new_confd_hmac_key",
-                                    default=False, action="store_true",
-                                    help=("Create a new HMAC key for %s" %
-                                          constants.CONFD))
-
-CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
-                                       dest="cluster_domain_secret",
-                                       default=None,
-                                       help=("Load new new cluster domain"
-                                             " secret from file"))
-
-NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
-                                           dest="new_cluster_domain_secret",
-                                           default=False, action="store_true",
-                                           help=("Create a new cluster domain"
-                                                 " secret"))
-
-USE_REPL_NET_OPT = cli_option("--use-replication-network",
-                              dest="use_replication_network",
-                              help="Whether to use the replication network"
-                              " for talking to the nodes",
-                              action="store_true", default=False)
-
-MAINTAIN_NODE_HEALTH_OPT = \
-    cli_option("--maintain-node-health", dest="maintain_node_health",
-               metavar=_YORNO, default=None, type="bool",
-               help="Configure the cluster to automatically maintain node"
-               " health, by shutting down unknown instances, shutting down"
-               " unknown DRBD devices, etc.")
-
-IDENTIFY_DEFAULTS_OPT = \
-    cli_option("--identify-defaults", dest="identify_defaults",
-               default=False, action="store_true",
-               help="Identify which saved instance parameters are equal to"
-               " the current cluster defaults and set them as such, instead"
-               " of marking them as overridden")
-
-UIDPOOL_OPT = cli_option("--uid-pool", default=None,
-                         action="store", dest="uid_pool",
-                         help=("A list of user-ids or user-id"
-                               " ranges separated by commas"))
-
-ADD_UIDS_OPT = cli_option("--add-uids", default=None,
-                          action="store", dest="add_uids",
-                          help=("A list of user-ids or user-id"
-                                " ranges separated by commas, to be"
-                                " added to the user-id pool"))
-
-REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
-                             action="store", dest="remove_uids",
-                             help=("A list of user-ids or user-id"
-                                   " ranges separated by commas, to be"
-                                   " removed from the user-id pool"))
-
-RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
-                              action="store", dest="reserved_lvs",
-                              help=("A comma-separated list of reserved"
-                                    " logical volumes names, that will be"
-                                    " ignored by cluster verify"))
-
-ROMAN_OPT = cli_option("--roman",
-                       dest="roman_integers", default=False,
-                       action="store_true",
-                       help="Use roman numbers for positive integers")
-
-DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
-                             action="store", default=None,
-                             help="Specifies usermode helper for DRBD")
-
-PRIMARY_IP_VERSION_OPT = \
-    cli_option("--primary-ip-version", default=constants.IP4_VERSION,
-               action="store", dest="primary_ip_version",
-               metavar="%d|%d" % (constants.IP4_VERSION,
-                                  constants.IP6_VERSION),
-               help="Cluster-wide IP version for primary IP")
-
-SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
-                              action="store_true",
-                              help="Show machine name for every line in output")
-
-FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
-                              action="store_true",
-                              help=("Hide successful results and show failures"
-                                    " only (determined by the exit code)"))
-
-REASON_OPT = cli_option("--reason", default=None,
-                        help="The reason for executing the command")
-
-
-def _PriorityOptionCb(option, _, value, parser):
-  """Callback for processing C{--priority} option.
-
-  """
-  value = _PRIONAME_TO_VALUE[value]
-
-  setattr(parser.values, option.dest, value)
-
-
-PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
-                          metavar="|".join(name for name, _ in _PRIORITY_NAMES),
-                          choices=_PRIONAME_TO_VALUE.keys(),
-                          action="callback", type="choice",
-                          callback=_PriorityOptionCb,
-                          help="Priority for opcode processing")
-
-OPPORTUNISTIC_OPT = cli_option("--opportunistic-locking",
-                               dest="opportunistic_locking",
-                               action="store_true", default=False,
-                               help="Opportunistically acquire locks")
-
-HID_OS_OPT = cli_option("--hidden", dest="hidden",
-                        type="bool", default=None, metavar=_YORNO,
-                        help="Sets the hidden flag on the OS")
-
-BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
-                        type="bool", default=None, metavar=_YORNO,
-                        help="Sets the blacklisted flag on the OS")
-
-PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
-                                     type="bool", metavar=_YORNO,
-                                     dest="prealloc_wipe_disks",
-                                     help=("Wipe disks prior to instance"
-                                           " creation"))
-
-NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
-                             type="keyval", default=None,
-                             help="Node parameters")
-
-ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
-                              action="store", metavar="POLICY", default=None,
-                              help="Allocation policy for the node group")
-
-NODE_POWERED_OPT = cli_option("--node-powered", default=None,
-                              type="bool", metavar=_YORNO,
-                              dest="node_powered",
-                              help="Specify if the SoR for node is powered")
-
-OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
-                             default=constants.OOB_TIMEOUT,
-                             help="Maximum time to wait for out-of-band helper")
-
-POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
-                             default=constants.OOB_POWER_DELAY,
-                             help="Time in seconds to wait between power-ons")
-
-FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
-                              action="store_true", default=False,
-                              help=("Whether command argument should be treated"
-                                    " as filter"))
-
-NO_REMEMBER_OPT = cli_option("--no-remember",
-                             dest="no_remember",
-                             action="store_true", default=False,
-                             help="Perform but do not record the change"
-                             " in the configuration")
-
-PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
-                              default=False, action="store_true",
-                              help="Evacuate primary instances only")
-
-SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
-                                default=False, action="store_true",
-                                help="Evacuate secondary instances only"
-                                     " (applies only to internally mirrored"
-                                     " disk templates, e.g. %s)" %
-                                     utils.CommaJoin(constants.DTS_INT_MIRROR))
-
-STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
-                                action="store_true", default=False,
-                                help="Pause instance at startup")
-
-TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
-                          help="Destination node group (name or uuid)",
-                          default=None, action="append",
-                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
-
-IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
-                               action="append", dest="ignore_errors",
-                               choices=list(constants.CV_ALL_ECODES_STRINGS),
-                               help="Error code to be ignored")
-
-DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
-                            action="append",
-                            help=("Specify disk state information in the"
-                                  " format"
-                                  " storage_type/identifier:option=value,...;"
-                                  " note this is unused for now"),
-                            type="identkeyval")
-
-HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
-                          action="append",
-                          help=("Specify hypervisor state information in the"
-                                " format hypervisor:option=value,...;"
-                                " note this is unused for now"),
-                          type="identkeyval")
-
-IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
-                                action="store_true", default=False,
-                                help="Ignore instance policy violations")
-
-RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
-                             help="Sets the instance's runtime memory,"
-                             " ballooning it up or down to the new value",
-                             default=None, type="unit", metavar="<size>")
-
-ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
-                          action="store_true", default=False,
-                          help="Marks the grow as absolute instead of the"
-                          " (default) relative mode")
-
-NETWORK_OPT = cli_option("--network",
-                         action="store", default=None, dest="network",
-                         help="IP network in CIDR notation")
-
-GATEWAY_OPT = cli_option("--gateway",
-                         action="store", default=None, dest="gateway",
-                         help="IP address of the router (gateway)")
-
-ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
-                                  action="store", default=None,
-                                  dest="add_reserved_ips",
-                                  help="Comma-separated list of"
-                                  " reserved IPs to add")
-
-REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
-                                     action="store", default=None,
-                                     dest="remove_reserved_ips",
-                                     help="Comma-delimited list of"
-                                     " reserved IPs to remove")
-
-NETWORK6_OPT = cli_option("--network6",
-                          action="store", default=None, dest="network6",
-                          help="IP network in CIDR notation")
-
-GATEWAY6_OPT = cli_option("--gateway6",
-                          action="store", default=None, dest="gateway6",
-                          help="IP6 address of the router (gateway)")
-
-NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
-                                  dest="conflicts_check",
-                                  default=True,
-                                  action="store_false",
-                                  help="Don't check for conflicting IPs")
-
-INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
-                                 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")
-
-INSTALL_IMAGE_OPT = \
-    cli_option("--install-image",
-               dest="install_image",
-               action="store",
-               type="string",
-               default=None,
-               help="The OS image to use for running the OS scripts safely")
-
-INSTANCE_COMMUNICATION_OPT = \
-    cli_option("-c", "--communication",
-               dest="instance_communication",
-               help=constants.INSTANCE_COMMUNICATION_DOC,
-               type="bool")
-
-INSTANCE_COMMUNICATION_NETWORK_OPT = \
-    cli_option("--instance-communication-network",
-               dest="instance_communication_network",
-               type="string",
-               help="Set the network name for instance communication")
-
-ZEROING_IMAGE_OPT = \
-    cli_option("--zeroing-image",
-               dest="zeroing_image", action="store", default=None,
-               help="The OS image to use to zero instance disks")
-
-ZERO_FREE_SPACE_OPT = \
-    cli_option("--zero-free-space",
-               dest="zero_free_space", action="store_true", default=False,
-               help="Whether to zero the free space on the disks of the "
-                    "instance prior to the export")
-
-HELPER_STARTUP_TIMEOUT_OPT = \
-    cli_option("--helper-startup-timeout",
-               dest="helper_startup_timeout", action="store", type="int",
-               help="Startup timeout for the helper VM")
-
-HELPER_SHUTDOWN_TIMEOUT_OPT = \
-    cli_option("--helper-shutdown-timeout",
-               dest="helper_shutdown_timeout", action="store", type="int",
-               help="Shutdown timeout for the helper VM")
-
-ZEROING_TIMEOUT_FIXED_OPT = \
-    cli_option("--zeroing-timeout-fixed",
-               dest="zeroing_timeout_fixed", action="store", type="int",
-               help="The fixed amount of time to wait before assuming that the "
-                    "zeroing failed")
-
-ZEROING_TIMEOUT_PER_MIB_OPT = \
-    cli_option("--zeroing-timeout-per-mib",
-               dest="zeroing_timeout_per_mib", action="store", type="float",
-               help="The amount of time to wait per MiB of data to zero, in "
-                    "addition to the fixed timeout")
-
-#: Options provided by all commands
-COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
-
-# options related to asynchronous job handling
-
-SUBMIT_OPTS = [
-  SUBMIT_OPT,
-  PRINT_JOBID_OPT,
-  ]
-
-# common options for creating instances. add and import then add their own
-# specific ones.
-COMMON_CREATE_OPTS = [
-  BACKEND_OPT,
-  DISK_OPT,
-  DISK_TEMPLATE_OPT,
-  FILESTORE_DIR_OPT,
-  FILESTORE_DRIVER_OPT,
-  HYPERVISOR_OPT,
-  IALLOCATOR_OPT,
-  NET_OPT,
-  NODE_PLACEMENT_OPT,
-  NOIPCHECK_OPT,
-  NOCONFLICTSCHECK_OPT,
-  NONAMECHECK_OPT,
-  NONICS_OPT,
-  NWSYNC_OPT,
-  OSPARAMS_OPT,
-  OSPARAMS_PRIVATE_OPT,
-  OSPARAMS_SECRET_OPT,
-  OS_SIZE_OPT,
-  OPPORTUNISTIC_OPT,
-  SUBMIT_OPT,
-  PRINT_JOBID_OPT,
-  TAG_ADD_OPT,
-  DRY_RUN_OPT,
-  PRIORITY_OPT,
-  ]
-
-# common instance policy options
-INSTANCE_POLICY_OPTS = [
-  IPOLICY_BOUNDS_SPECS_OPT,
-  IPOLICY_DISK_TEMPLATES,
-  IPOLICY_VCPU_RATIO,
-  IPOLICY_SPINDLE_RATIO,
-  ]
-
-# instance policy split specs options
-SPLIT_ISPECS_OPTS = [
-  SPECS_CPU_COUNT_OPT,
-  SPECS_DISK_COUNT_OPT,
-  SPECS_DISK_SIZE_OPT,
-  SPECS_MEM_SIZE_OPT,
-  SPECS_NIC_COUNT_OPT,
-  ]
-
-
 class _ShowUsage(Exception):
   """Exception class for L{_ParseArgs}.
 
@@ -2902,6 +1408,7 @@ def GenericInstanceCreate(mode, opts, args):
     instance_name=instance,
     disks=disks,
     disk_template=opts.disk_template,
+    group_name=opts.nodegroup,
     nics=nics,
     conflicts_check=opts.conflicts_check,
     pnode=pnode, snode=snode,
@@ -3028,7 +1535,9 @@ class _RunWhileDaemonsStoppedHelper(object):
       self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
       try:
         # Stop daemons on all nodes
-        for node_name in self.online_nodes:
+        online_nodes = [self.master_node] + [n for n in self.online_nodes
+                                             if n != self.master_node]
+        for node_name in online_nodes:
           self.feedback_fn("Stopping daemons on %s" % node_name)
           self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
           # Starting any daemons listed as exception
@@ -3782,6 +2291,7 @@ def GetNodesSshPorts(nodes, cl):
   @type cl: L{ganeti.luxi.Client}
   @return: the list of SSH ports corresponding to the nodes
   @rtype: a list of tuples
+
   """
   return map(lambda t: t[0],
              cl.QueryNodes(names=nodes,
@@ -3789,6 +2299,23 @@ def GetNodesSshPorts(nodes, cl):
                            use_locking=False))
 
 
+def GetNodeUUIDs(nodes, cl):
+  """Retrieves the UUIDs of given nodes.
+
+  @param nodes: the names of nodes
+  @type nodes: a list of string
+  @param cl: a client to use for the query
+  @type cl: L{ganeti.luxi.Client}
+  @return: the list of UUIDs corresponding to the nodes
+  @rtype: a list of tuples
+
+  """
+  return map(lambda t: t[0],
+             cl.QueryNodes(names=nodes,
+                           fields=["uuid"],
+                           use_locking=False))
+
+
 def _ToStream(stream, txt, *args):
   """Write a message to a stream, bypassing the logging system
 
diff --git a/lib/cli_opts.py b/lib/cli_opts.py
new file mode 100644 (file)
index 0000000..79de008
--- /dev/null
@@ -0,0 +1,1629 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 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.
+
+
+"""Module containing Ganeti's command line parsing options"""
+
+import re
+import simplejson
+
+from ganeti import utils
+from ganeti import errors
+from ganeti import constants
+from ganeti import compat
+from ganeti import pathutils
+from ganeti import serializer
+
+from optparse import (Option, OptionValueError)
+
+
+__all__ = [
+  "ABSOLUTE_OPT",
+  "ADD_RESERVED_IPS_OPT",
+  "ADD_UIDS_OPT",
+  "ALL_OPT",
+  "ALLOC_POLICY_OPT",
+  "ALLOCATABLE_OPT",
+  "ALLOW_FAILOVER_OPT",
+  "AUTO_PROMOTE_OPT",
+  "AUTO_REPLACE_OPT",
+  "BACKEND_OPT",
+  "BLK_OS_OPT",
+  "CAPAB_MASTER_OPT",
+  "CAPAB_VM_OPT",
+  "CLEANUP_OPT",
+  "cli_option",
+  "CLUSTER_DOMAIN_SECRET_OPT",
+  "COMMON_CREATE_OPTS",
+  "COMMON_OPTS",
+  "COMPRESS_OPT",
+  "COMPRESSION_TOOLS_OPT",
+  "CONFIRM_OPT",
+  "CP_SIZE_OPT",
+  "DEBUG_OPT",
+  "DEBUG_SIMERR_OPT",
+  "DEFAULT_IALLOCATOR_OPT",
+  "DEFAULT_IALLOCATOR_PARAMS_OPT",
+  "DISK_OPT",
+  "DISK_PARAMS_OPT",
+  "DISK_STATE_OPT",
+  "DISK_TEMPLATE_OPT",
+  "DISKIDX_OPT",
+  "DRAINED_OPT",
+  "DRBD_HELPER_OPT",
+  "DRY_RUN_OPT",
+  "DST_NODE_OPT",
+  "EARLY_RELEASE_OPT",
+  "ENABLED_DATA_COLLECTORS_OPT",
+  "ENABLED_DISK_TEMPLATES_OPT",
+  "ENABLED_HV_OPT",
+  "ENABLED_USER_SHUTDOWN_OPT",
+  "ERROR_CODES_OPT",
+  "EXT_PARAMS_OPT",
+  "FAILURE_ONLY_OPT",
+  "FIELDS_OPT",
+  "FILESTORE_DIR_OPT",
+  "FILESTORE_DRIVER_OPT",
+  "FORCE_FAILOVER_OPT",
+  "FORCE_FILTER_OPT",
+  "FORCE_OPT",
+  "FORCE_VARIANT_OPT",
+  "GATEWAY6_OPT",
+  "GATEWAY_OPT",
+  "GLOBAL_FILEDIR_OPT",
+  "GLOBAL_GLUSTER_FILEDIR_OPT",
+  "GLOBAL_SHARED_FILEDIR_OPT",
+  "HELPER_SHUTDOWN_TIMEOUT_OPT",
+  "HELPER_STARTUP_TIMEOUT_OPT",
+  "HID_OS_OPT",
+  "HOTPLUG_IF_POSSIBLE_OPT",
+  "HOTPLUG_OPT",
+  "HV_STATE_OPT",
+  "HVLIST_OPT",
+  "HVOPTS_OPT",
+  "HYPERVISOR_OPT",
+  "IALLOCATOR_OPT",
+  "IDENTIFY_DEFAULTS_OPT",
+  "IGNORE_CONSIST_OPT",
+  "IGNORE_ERRORS_OPT",
+  "IGNORE_FAILURES_OPT",
+  "IGNORE_HVVERSIONS_OPT",
+  "IGNORE_IPOLICY_OPT",
+  "IGNORE_OFFLINE_OPT",
+  "IGNORE_REMOVE_FAILURES_OPT",
+  "IGNORE_SECONDARIES_OPT",
+  "IGNORE_SIZE_OPT",
+  "INCLUDEDEFAULTS_OPT",
+  "INSTALL_IMAGE_OPT",
+  "INSTANCE_COMMUNICATION_NETWORK_OPT",
+  "INSTANCE_COMMUNICATION_OPT",
+  "INSTANCE_POLICY_OPTS",
+  "INTERVAL_OPT",
+  "IPOLICY_BOUNDS_SPECS_STR",
+  "IPOLICY_DISK_TEMPLATES",
+  "IPOLICY_SPINDLE_RATIO",
+  "IPOLICY_STD_SPECS_OPT",
+  "IPOLICY_STD_SPECS_STR",
+  "IPOLICY_VCPU_RATIO",
+  "MAC_PREFIX_OPT",
+  "MAINTAIN_NODE_HEALTH_OPT",
+  "MASTER_NETDEV_OPT",
+  "MASTER_NETMASK_OPT",
+  "MAX_TRACK_OPT",
+  "MC_OPT",
+  "MIGRATION_MODE_OPT",
+  "MODIFY_ETCHOSTS_OPT",
+  "NET_OPT",
+  "NETWORK6_OPT",
+  "NETWORK_OPT",
+  "NEW_CLUSTER_CERT_OPT",
+  "NEW_CLUSTER_DOMAIN_SECRET_OPT",
+  "NEW_CONFD_HMAC_KEY_OPT",
+  "NEW_NODE_CERT_OPT",
+  "NEW_PRIMARY_OPT",
+  "NEW_RAPI_CERT_OPT",
+  "NEW_SECONDARY_OPT",
+  "NEW_SPICE_CERT_OPT",
+  "NEW_SSH_KEY_OPT",
+  "NIC_PARAMS_OPT",
+  "NO_INSTALL_OPT",
+  "NO_REMEMBER_OPT",
+  "NOCONFLICTSCHECK_OPT",
+  "NODE_FORCE_JOIN_OPT",
+  "NODE_LIST_OPT",
+  "NODE_PARAMS_OPT",
+  "NODE_PLACEMENT_OPT",
+  "NODE_POWERED_OPT",
+  "NODEGROUP_OPT",
+  "NODEGROUP_OPT_NAME",
+  "NOHDR_OPT",
+  "NOIPCHECK_OPT",
+  "NOMODIFY_ETCHOSTS_OPT",
+  "NOMODIFY_SSH_SETUP_OPT",
+  "NONAMECHECK_OPT",
+  "NONICS_OPT",
+  "NONLIVE_OPT",
+  "NONPLUS1_OPT",
+  "NORUNTIME_CHGS_OPT",
+  "NOSHUTDOWN_OPT",
+  "NOSSH_KEYCHECK_OPT",
+  "NOSTART_OPT",
+  "NOVOTING_OPT",
+  "NWSYNC_OPT",
+  "OFFLINE_INST_OPT",
+  "OFFLINE_OPT",
+  "ON_PRIMARY_OPT",
+  "ON_SECONDARY_OPT",
+  "ONLINE_INST_OPT",
+  "OOB_TIMEOUT_OPT",
+  "OPT_COMPL_ALL",
+  "OPT_COMPL_INST_ADD_NODES",
+  "OPT_COMPL_MANY_NODES",
+  "OPT_COMPL_ONE_EXTSTORAGE",
+  "OPT_COMPL_ONE_FILTER",
+  "OPT_COMPL_ONE_IALLOCATOR",
+  "OPT_COMPL_ONE_INSTANCE",
+  "OPT_COMPL_ONE_NETWORK",
+  "OPT_COMPL_ONE_NODE",
+  "OPT_COMPL_ONE_NODEGROUP",
+  "OPT_COMPL_ONE_OS",
+  "OS_OPT",
+  "OS_SIZE_OPT",
+  "OSPARAMS_OPT",
+  "OSPARAMS_PRIVATE_OPT",
+  "OSPARAMS_SECRET_OPT",
+  "POWER_DELAY_OPT",
+  "PREALLOC_WIPE_DISKS_OPT",
+  "PRIMARY_IP_VERSION_OPT",
+  "PRIMARY_ONLY_OPT",
+  "PRINT_JOBID_OPT",
+  "PRIORITY_OPT",
+  "RAPI_CERT_OPT",
+  "READD_OPT",
+  "REASON_OPT",
+  "REBOOT_TYPE_OPT",
+  "REMOVE_INSTANCE_OPT",
+  "REMOVE_RESERVED_IPS_OPT",
+  "REMOVE_UIDS_OPT",
+  "RESERVED_LVS_OPT",
+  "ROMAN_OPT",
+  "RQL_OPT",
+  "RUNTIME_MEM_OPT",
+  "SECONDARY_IP_OPT",
+  "SECONDARY_ONLY_OPT",
+  "SELECT_OS_OPT",
+  "SEP_OPT",
+  "SEQUENTIAL_OPT",
+  "SHOW_MACHINE_OPT",
+  "SHOWCMD_OPT",
+  "SHUTDOWN_TIMEOUT_OPT",
+  "SINGLE_NODE_OPT",
+  "SPECS_CPU_COUNT_OPT",
+  "SPECS_DISK_COUNT_OPT",
+  "SPECS_DISK_SIZE_OPT",
+  "SPECS_MEM_SIZE_OPT",
+  "SPECS_NIC_COUNT_OPT",
+  "SPICE_CACERT_OPT",
+  "SPICE_CERT_OPT",
+  "SPLIT_ISPECS_OPTS",
+  "SRC_DIR_OPT",
+  "SRC_NODE_OPT",
+  "STARTUP_PAUSED_OPT",
+  "STATIC_OPT",
+  "SUBMIT_OPT",
+  "SUBMIT_OPTS",
+  "SYNC_OPT",
+  "TAG_ADD_OPT",
+  "TAG_SRC_OPT",
+  "TIMEOUT_OPT",
+  "TO_GROUP_OPT",
+  "TRANSPORT_COMPRESSION_OPT",
+  "UIDPOOL_OPT",
+  "USE_EXTERNAL_MIP_SCRIPT",
+  "USE_REPL_NET_OPT",
+  "USEUNITS_OPT",
+  "VERBOSE_OPT",
+  "VERIFY_CLUTTER_OPT",
+  "VG_NAME_OPT",
+  "WFSYNC_OPT",
+  "YES_DOIT_OPT",
+  "ZERO_FREE_SPACE_OPT",
+  "ZEROING_IMAGE_OPT",
+  "ZEROING_TIMEOUT_FIXED_OPT",
+  "ZEROING_TIMEOUT_PER_MIB_OPT",
+  ]
+
+
+NO_PREFIX = "no_"
+UN_PREFIX = "-"
+
+
+#: Priorities (sorted)
+_PRIORITY_NAMES = [
+  ("low", constants.OP_PRIO_LOW),
+  ("normal", constants.OP_PRIO_NORMAL),
+  ("high", constants.OP_PRIO_HIGH),
+  ]
+
+#: Priority dictionary for easier lookup
+# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
+# we migrate to Python 2.6
+_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
+
+
+def check_unit(option, opt, value): # pylint: disable=W0613
+  """OptParsers custom converter for units.
+
+  """
+  try:
+    return utils.ParseUnit(value)
+  except errors.UnitParseError, err:
+    raise OptionValueError("option %s: %s" % (opt, err))
+
+
+def _SplitKeyVal(opt, data, parse_prefixes):
+  """Convert a KeyVal string into a dict.
+
+  This function will convert a key=val[,...] string into a dict. Empty
+  values will be converted specially: keys which have the prefix 'no_'
+  will have the value=False and the prefix stripped, keys with the prefix
+  "-" will have value=None and the prefix stripped, and the others will
+  have value=True.
+
+  @type opt: string
+  @param opt: a string holding the option name for which we process the
+      data, used in building error messages
+  @type data: string
+  @param data: a string of the format key=val,key=val,...
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially
+  @rtype: dict
+  @return: {key=val, key=val}
+  @raises errors.ParameterError: if there are duplicate keys
+
+  """
+  kv_dict = {}
+  if data:
+    for elem in utils.UnescapeAndSplit(data, sep=","):
+      if "=" in elem:
+        key, val = elem.split("=", 1)
+      elif parse_prefixes:
+        if elem.startswith(NO_PREFIX):
+          key, val = elem[len(NO_PREFIX):], False
+        elif elem.startswith(UN_PREFIX):
+          key, val = elem[len(UN_PREFIX):], None
+        else:
+          key, val = elem, True
+      else:
+        raise errors.ParameterError("Missing value for key '%s' in option %s" %
+                                    (elem, opt))
+      if key in kv_dict:
+        raise errors.ParameterError("Duplicate key '%s' in option %s" %
+                                    (key, opt))
+      kv_dict[key] = val
+  return kv_dict
+
+
+def _SplitIdentKeyVal(opt, value, parse_prefixes):
+  """Helper function to parse "ident:key=val,key=val" options.
+
+  @type opt: string
+  @param opt: option name, used in error messages
+  @type value: string
+  @param value: expected to be in the format "ident:key=val,key=val,..."
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially (see
+      L{_SplitKeyVal})
+  @rtype: tuple
+  @return: (ident, {key=val, key=val})
+  @raises errors.ParameterError: in case of duplicates or other parsing errors
+
+  """
+  if ":" not in value:
+    ident, rest = value, ""
+  else:
+    ident, rest = value.split(":", 1)
+
+  if parse_prefixes and ident.startswith(NO_PREFIX):
+    if rest:
+      msg = "Cannot pass options when removing parameter groups: %s" % value
+      raise errors.ParameterError(msg)
+    retval = (ident[len(NO_PREFIX):], False)
+  elif (parse_prefixes and ident.startswith(UN_PREFIX) and
+        (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
+    if rest:
+      msg = "Cannot pass options when removing parameter groups: %s" % value
+      raise errors.ParameterError(msg)
+    retval = (ident[len(UN_PREFIX):], None)
+  else:
+    kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
+    retval = (ident, kv_dict)
+  return retval
+
+
+def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
+  """Custom parser for ident:key=val,key=val options.
+
+  This will store the parsed values as a tuple (ident, {key: val}). As such,
+  multiple uses of this option via action=append is possible.
+
+  """
+  return _SplitIdentKeyVal(opt, value, True)
+
+
+def check_key_val(option, opt, value):  # pylint: disable=W0613
+  """Custom parser class for key=val,key=val options.
+
+  This will store the parsed values as a dict {key: val}.
+
+  """
+  return _SplitKeyVal(opt, value, True)
+
+
+def check_key_private_val(option, opt, value):  # pylint: disable=W0613
+  """Custom parser class for private and secret key=val,key=val options.
+
+  This will store the parsed values as a dict {key: val}.
+
+  """
+  return serializer.PrivateDict(_SplitKeyVal(opt, value, True))
+
+
+def _SplitListKeyVal(opt, value):
+  retval = {}
+  for elem in value.split("/"):
+    if not elem:
+      raise errors.ParameterError("Empty section in option '%s'" % opt)
+    (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
+    if ident in retval:
+      msg = ("Duplicated parameter '%s' in parsing %s: %s" %
+             (ident, opt, elem))
+      raise errors.ParameterError(msg)
+    retval[ident] = valdict
+  return retval
+
+
+def check_multilist_ident_key_val(_, opt, value):
+  """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options.
+
+  @rtype: list of dictionary
+  @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}]
+
+  """
+  retval = []
+  for line in value.split("//"):
+    retval.append(_SplitListKeyVal(opt, line))
+  return retval
+
+
+def check_bool(option, opt, value): # pylint: disable=W0613
+  """Custom parser for yes/no options.
+
+  This will store the parsed value as either True or False.
+
+  """
+  value = value.lower()
+  if value == constants.VALUE_FALSE or value == "no":
+    return False
+  elif value == constants.VALUE_TRUE or value == "yes":
+    return True
+  else:
+    raise errors.ParameterError("Invalid boolean value '%s'" % value)
+
+
+def check_list(option, opt, value): # pylint: disable=W0613
+  """Custom parser for comma-separated lists.
+
+  """
+  # we have to make this explicit check since "".split(",") is [""],
+  # not an empty list :(
+  if not value:
+    return []
+  else:
+    return utils.UnescapeAndSplit(value)
+
+
+def check_maybefloat(option, opt, value): # pylint: disable=W0613
+  """Custom parser for float numbers which might be also defaults.
+
+  """
+  value = value.lower()
+
+  if value == constants.VALUE_DEFAULT:
+    return value
+  else:
+    return float(value)
+
+
+def check_json(option, opt, value): # pylint: disable=W0613
+  """Custom parser for JSON arguments.
+
+  Takes a string containing JSON, returns a Python object.
+
+  """
+  return simplejson.loads(value)
+
+
+def check_filteraction(option, opt, value): # pylint: disable=W0613
+  """Custom parser for filter