Merge branch 'stable-2.11' into stable-2.12
authorKlaus Aehlig <aehlig@google.com>
Tue, 12 Jan 2016 15:46:43 +0000 (16:46 +0100)
committerKlaus Aehlig <aehlig@google.com>
Tue, 12 Jan 2016 16:53:39 +0000 (17:53 +0100)
* stable-2.11
  (no changes)

* stable-2.10
  KVM: explicitly configure routed NICs late

Signed-off-by: Klaus Aehlig <aehlig@google.com>
Reviewed-by: Hrvoje Ribicic <riba@google.com>

365 files changed:
.gitignore
COPYING
INSTALL
Makefile.am
NEWS
README
UPGRADE
autotools/build-rpc
configure.ac
daemons/daemon-util.in
daemons/import-export
devel/build_chroot
devel/check_copyright [new file with mode: 0755]
doc/cluster-keys-replacement.rst
doc/conf.py
doc/design-2.12.rst [copied from doc/design-2.11.rst with 64% similarity]
doc/design-cpu-pinning.rst
doc/design-cpu-speed.rst [new file with mode: 0644]
doc/design-daemons.rst
doc/design-disks.rst [new file with mode: 0644]
doc/design-draft.rst
doc/design-move-instance-improvements.rst [new file with mode: 0644]
doc/design-node-security.rst
doc/design-performance-tests.rst
doc/design-systemd.rst [new file with mode: 0644]
doc/dev-codestyle.rst
doc/examples/ganeti.default
doc/examples/ganeti.default-debug
doc/examples/systemd/ganeti-common.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-confd.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-kvmd.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-luxid.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-master.target [new file with mode: 0644]
doc/examples/systemd/ganeti-metad.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-mond.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-node.target [new file with mode: 0644]
doc/examples/systemd/ganeti-noded.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-rapi.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti-wconfd.service.in [new file with mode: 0644]
doc/examples/systemd/ganeti.service [new file with mode: 0644]
doc/examples/systemd/ganeti.target [new file with mode: 0644]
doc/hooks.rst
doc/iallocator.rst
doc/index.rst
doc/install.rst
doc/security.rst
doc/users/groupmemberships.in
doc/users/groups.in
doc/users/users.in
doc/virtual-cluster.rst
lib/backend.py
lib/bootstrap.py
lib/build/sphinx_ext.py
lib/cli.py
lib/client/base.py [new file with mode: 0644]
lib/client/gnt_backup.py
lib/client/gnt_cluster.py
lib/client/gnt_debug.py
lib/client/gnt_group.py
lib/client/gnt_instance.py
lib/client/gnt_job.py
lib/client/gnt_network.py
lib/client/gnt_node.py
lib/client/gnt_os.py
lib/cmdlib/backup.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_operation.py
lib/cmdlib/instance_query.py
lib/cmdlib/instance_storage.py
lib/cmdlib/instance_utils.py
lib/cmdlib/misc.py
lib/cmdlib/network.py
lib/cmdlib/node.py
lib/cmdlib/operating_system.py
lib/cmdlib/test.py
lib/compat.py
lib/config.py
lib/daemon.py
lib/errors.py
lib/ht.py
lib/http/__init__.py
lib/hypervisor/hv_base.py
lib/hypervisor/hv_kvm/__init__.py [moved from lib/hypervisor/hv_kvm.py with 83% similarity]
lib/hypervisor/hv_kvm/monitor.py [new file with mode: 0644]
lib/hypervisor/hv_kvm/netdev.py [new file with mode: 0644]
lib/hypervisor/hv_xen.py
lib/impexpd/__init__.py
lib/jqueue/__init__.py [moved from lib/jqueue.py with 79% similarity]
lib/jqueue/exec.py [new file with mode: 0644]
lib/locking.py
lib/luxi.py
lib/masterd/iallocator.py
lib/masterd/instance.py
lib/mcpu.py
lib/objects.py
lib/pathutils.py
lib/query.py
lib/rapi/baserlib.py
lib/rapi/client.py
lib/rapi/rlib2.py
lib/rapi/testutils.py
lib/rpc/client.py
lib/rpc/node.py
lib/rpc/stub/__init__.py [copied from lib/rpc/__init__.py with 94% similarity]
lib/rpc/transport.py
lib/rpc_defs.py
lib/runtime.py
lib/serializer.py
lib/server/masterd.py
lib/server/noded.py
lib/ssconf.py
lib/ssh.py
lib/storage/bdev.py
lib/storage/container.py
lib/storage/drbd.py
lib/tools/burnin.py
lib/tools/common.py [new file with mode: 0644]
lib/tools/ensure_dirs.py
lib/tools/node_daemon_setup.py
lib/tools/prepare_node_join.py
lib/tools/ssl_update.py [new file with mode: 0644]
lib/utils/__init__.py
lib/utils/algo.py
lib/utils/io.py
lib/utils/livelock.py [new file with mode: 0644]
lib/utils/security.py
lib/utils/storage.py
lib/utils/text.py
lib/utils/x509.py
lib/vcluster.py
lib/watcher/__init__.py
lib/wconfd.py [moved from test/py/cmdlib/testsupport/lock_manager_mock.py with 51% similarity]
lib/workerpool.py
man/footer.rst
man/ganeti-confd.rst
man/ganeti-luxid.rst
man/ganeti-masterd.rst [deleted file]
man/ganeti-mond.rst
man/ganeti-rapi.rst
man/ganeti-watcher.rst
man/ganeti-wconfd.rst [new file with mode: 0644]
man/gnt-backup.rst
man/gnt-cluster.rst
man/gnt-debug.rst
man/gnt-instance.rst
man/gnt-node.rst
man/gnt-os.rst
man/htools.rst
qa/ganeti-qa.py
qa/qa-sample.json
qa/qa_cluster.py
qa/qa_config.py
qa/qa_env.py
qa/qa_instance.py
qa/qa_job.py
qa/qa_job_utils.py
qa/qa_performance.py
qa/qa_rapi.py
qa/qa_utils.py
qa/rapi-workload.py
src/AutoConf.hs.in
src/Ganeti/BasicTypes.hs
src/Ganeti/Codec.hs [copied from src/Ganeti/VCluster.hs with 58% similarity]
src/Ganeti/Confd/ClientFunctions.hs
src/Ganeti/Confd/Server.hs
src/Ganeti/Confd/Types.hs
src/Ganeti/Config.hs
src/Ganeti/Constants.hs
src/Ganeti/Daemon.hs
src/Ganeti/Daemon/Utils.hs [new file with mode: 0644]
src/Ganeti/DataCollectors/InstStatus.hs
src/Ganeti/DataCollectors/InstStatusTypes.hs
src/Ganeti/DataCollectors/Lv.hs
src/Ganeti/Errors.hs
src/Ganeti/HTools/Backend/Luxi.hs
src/Ganeti/HTools/Backend/Text.hs
src/Ganeti/HTools/CLI.hs
src/Ganeti/HTools/Cluster.hs
src/Ganeti/HTools/Node.hs
src/Ganeti/HTools/Program/Harep.hs
src/Ganeti/HTools/Program/Hscan.hs
src/Ganeti/JQScheduler.hs
src/Ganeti/JQueue.hs
src/Ganeti/JQueue/Lens.hs [copied from test/hs/Test/Ganeti/Jobs.hs with 73% similarity]
src/Ganeti/JQueue/Objects.hs [new file with mode: 0644]
src/Ganeti/JSON.hs
src/Ganeti/Lens.hs [new file with mode: 0644]
src/Ganeti/Locking/Allocation.hs [new file with mode: 0644]
src/Ganeti/Locking/Locks.hs [new file with mode: 0644]
src/Ganeti/Locking/Types.hs [copied from src/Ganeti/Compat.hs with 59% similarity]
src/Ganeti/Locking/Waiting.hs [new file with mode: 0644]
src/Ganeti/Logging.hs
src/Ganeti/Logging/Lifted.hs [new file with mode: 0644]
src/Ganeti/Logging/WriterLog.hs [new file with mode: 0644]
src/Ganeti/Luxi.hs
src/Ganeti/Metad/Config.hs [new file with mode: 0644]
src/Ganeti/Metad/ConfigServer.hs [new file with mode: 0644]
src/Ganeti/Metad/Server.hs [copied from src/mon-collector.hs with 70% similarity]
src/Ganeti/Metad/Types.hs [copied from src/htools.hs with 88% similarity]
src/Ganeti/Metad/WebServer.hs [new file with mode: 0644]
src/Ganeti/Network.hs
src/Ganeti/Objects.hs
src/Ganeti/Objects/BitArray.hs [new file with mode: 0644]
src/Ganeti/Objects/Lens.hs [new file with mode: 0644]
src/Ganeti/OpCodes.hs
src/Ganeti/OpCodes/Lens.hs [copied from test/hs/Test/Ganeti/Jobs.hs with 83% similarity]
src/Ganeti/OpParams.hs
src/Ganeti/Path.hs
src/Ganeti/Query/Common.hs
src/Ganeti/Query/Exec.hs [new file with mode: 0644]
src/Ganeti/Query/Export.hs
src/Ganeti/Query/Group.hs
src/Ganeti/Query/Instance.hs
src/Ganeti/Query/Job.hs
src/Ganeti/Query/Language.hs
src/Ganeti/Query/Locks.hs
src/Ganeti/Query/Network.hs
src/Ganeti/Query/Node.hs
src/Ganeti/Query/Query.hs
src/Ganeti/Query/Server.hs
src/Ganeti/Query/Types.hs
src/Ganeti/Rpc.hs
src/Ganeti/Runtime.hs
src/Ganeti/Ssconf.hs
src/Ganeti/THH.hs
src/Ganeti/THH/Field.hs [new file with mode: 0644]
src/Ganeti/THH/HsRPC.hs [new file with mode: 0644]
src/Ganeti/THH/PyRPC.hs [new file with mode: 0644]
src/Ganeti/THH/RPC.hs [new file with mode: 0644]
src/Ganeti/THH/Types.hs [new file with mode: 0644]
src/Ganeti/Types.hs
src/Ganeti/UDSServer.hs
src/Ganeti/Utils.hs
src/Ganeti/Utils/AsyncWorker.hs [new file with mode: 0644]
src/Ganeti/Utils/Atomic.hs [new file with mode: 0644]
src/Ganeti/Utils/IORef.hs [new file with mode: 0644]
src/Ganeti/Utils/Livelock.hs [new file with mode: 0644]
src/Ganeti/Utils/MVarLock.hs [copied from test/hs/Test/Ganeti/Jobs.hs with 67% similarity]
src/Ganeti/Utils/MonadPlus.hs [copied from src/Ganeti/VCluster.hs with 57% similarity]
src/Ganeti/Utils/MultiMap.hs [new file with mode: 0644]
src/Ganeti/Utils/Random.hs [copied from src/Ganeti/Query/Locks.hs with 53% similarity]
src/Ganeti/Utils/Statistics.hs [new file with mode: 0644]
src/Ganeti/Utils/UniStd.hs [copied from src/Ganeti/Compat.hs with 59% similarity]
src/Ganeti/Utils/Validate.hs [new file with mode: 0644]
src/Ganeti/WConfd/Client.hs [copied from src/Ganeti/Hs2Py/ListConstants.hs.in with 60% similarity]
src/Ganeti/WConfd/ConfigState.hs [new file with mode: 0644]
src/Ganeti/WConfd/ConfigVerify.hs [new file with mode: 0644]
src/Ganeti/WConfd/ConfigWriter.hs [new file with mode: 0644]
src/Ganeti/WConfd/Core.hs [new file with mode: 0644]
src/Ganeti/WConfd/DeathDetection.hs [new file with mode: 0644]
src/Ganeti/WConfd/Language.hs [new file with mode: 0644]
src/Ganeti/WConfd/Monad.hs [new file with mode: 0644]
src/Ganeti/WConfd/Persistent.hs [new file with mode: 0644]
src/Ganeti/WConfd/Server.hs [new file with mode: 0644]
src/Ganeti/WConfd/Ssconf.hs [new file with mode: 0644]
src/Ganeti/WConfd/TempRes.hs [new file with mode: 0644]
src/ganeti-metad.hs [copied from src/ganeti-kvmd.hs with 79% similarity]
src/ganeti-wconfd.hs [copied from src/mon-collector.hs with 79% similarity]
src/hluxid.hs
src/hs2py.hs
src/rpc-test.hs
test/data/cluster_config_2.11.json [copied from test/data/cluster_config_2.10.json with 97% similarity]
test/data/htools/hbal-cpu-speed.data [new file with mode: 0644]
test/data/instance-disks.txt [new file with mode: 0644]
test/data/instance-prim-sec.txt [deleted file]
test/data/xen-xl-list-4.4-crashed-instances.txt [copied from test/data/xen-xm-list-4.0.1-four-instances.txt with 60% similarity]
test/hs/Test/Ganeti/BasicTypes.hs
test/hs/Test/Ganeti/Confd/Utils.hs
test/hs/Test/Ganeti/HTools/Backend/Text.hs
test/hs/Test/Ganeti/HTools/Cluster.hs
test/hs/Test/Ganeti/HTools/Container.hs
test/hs/Test/Ganeti/HTools/Instance.hs
test/hs/Test/Ganeti/HTools/Node.hs
test/hs/Test/Ganeti/HTools/Types.hs
test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs
test/hs/Test/Ganeti/JQueue.hs
test/hs/Test/Ganeti/JSON.hs
test/hs/Test/Ganeti/Locking/Allocation.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Locking/Locks.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Locking/Waiting.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Luxi.hs
test/hs/Test/Ganeti/Network.hs
test/hs/Test/Ganeti/Objects.hs
test/hs/Test/Ganeti/Objects/BitArray.hs [new file with mode: 0644]
test/hs/Test/Ganeti/OpCodes.hs
test/hs/Test/Ganeti/Query/Filter.hs
test/hs/Test/Ganeti/Query/Instance.hs
test/hs/Test/Ganeti/Query/Language.hs
test/hs/Test/Ganeti/Query/Query.hs
test/hs/Test/Ganeti/Rpc.hs
test/hs/Test/Ganeti/Runtime.hs
test/hs/Test/Ganeti/Ssconf.hs
test/hs/Test/Ganeti/Storage/Drbd/Types.hs
test/hs/Test/Ganeti/THH.hs
test/hs/Test/Ganeti/THH/Types.hs [copied from test/hs/Test/Ganeti/Errors.hs with 59% similarity]
test/hs/Test/Ganeti/TestCommon.hs
test/hs/Test/Ganeti/Types.hs
test/hs/Test/Ganeti/Utils.hs
test/hs/Test/Ganeti/Utils/MultiMap.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Utils/Statistics.hs [copied from test/hs/Test/Ganeti/Errors.hs with 56% similarity]
test/hs/Test/Ganeti/WConfd/TempRes.hs [copied from test/hs/Test/Ganeti/Errors.hs with 52% similarity]
test/hs/htest.hs
test/hs/shelltests/htools-balancing.test
test/hs/shelltests/htools-mon-collector.test
test/py/cfgupgrade_unittest.py
test/py/cmdlib/backup_unittest.py
test/py/cmdlib/cluster_unittest.py
test/py/cmdlib/cmdlib_unittest.py
test/py/cmdlib/group_unittest.py
test/py/cmdlib/instance_unittest.py
test/py/cmdlib/node_unittest.py
test/py/cmdlib/test_unittest.py
test/py/cmdlib/testsupport/__init__.py
test/py/cmdlib/testsupport/cmdlib_testcase.py
test/py/cmdlib/testsupport/config_mock.py
test/py/cmdlib/testsupport/livelock_mock.py [copied from lib/build/__init__.py with 72% similarity]
test/py/cmdlib/testsupport/processor_mock.py
test/py/cmdlib/testsupport/wconfd_mock.py [new file with mode: 0644]
test/py/daemon-util_unittest.bash
test/py/ganeti-cli.test
test/py/ganeti.backend_unittest.py
test/py/ganeti.config_unittest.py
test/py/ganeti.hooks_unittest.py
test/py/ganeti.hypervisor.hv_kvm_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py
test/py/ganeti.impexpd_unittest.py
test/py/ganeti.jqueue_unittest.py
test/py/ganeti.locking_unittest.py
test/py/ganeti.mcpu_unittest.py
test/py/ganeti.objects_unittest.py
test/py/ganeti.ovf_unittest.py
test/py/ganeti.query_unittest.py
test/py/ganeti.rapi.baserlib_unittest.py
test/py/ganeti.rapi.client_unittest.py
test/py/ganeti.rapi.rlib2_unittest.py
test/py/ganeti.rpc_unittest.py
test/py/ganeti.runtime_unittest.py
test/py/ganeti.serializer_unittest.py
test/py/ganeti.ssh_unittest.py
test/py/ganeti.storage.bdev_unittest.py
test/py/ganeti.storage.drbd_unittest.py
test/py/ganeti.tools.common_unittest.py [new file with mode: 0755]
test/py/ganeti.tools.prepare_node_join_unittest.py
test/py/ganeti.utils.security_unittest.py
test/py/ganeti.utils.storage_unittest.py
test/py/ganeti.utils.x509_unittest.py
test/py/ganeti.workerpool_unittest.py
test/py/systemd_unittest.bash [copied from tools/post-upgrade with 61% similarity, mode: 0755]
tools/cfgupgrade
tools/cfgupgrade12
tools/confd-client
tools/ganeti-listrunner
tools/ifup-os.in [new file with mode: 0644]
tools/kvm-ifup.in
tools/lvmstrap
tools/move-instance
tools/net-common.in
tools/post-upgrade
tools/vcluster-setup.in
tools/vif-ganeti-metad.in [copied from tools/post-upgrade with 60% similarity]

index 588af9b..d6344d6 100644 (file)
 *~
 *.o
 *.hpc_o
+*.prof_o
+*.dyn_o
 *.hi
 *.hpc_hi
+*.prof_hi
+*.dyn_hi
 *.hp
 *.tix
 *.prof
@@ -21,6 +25,7 @@
 # /
 /.hsenv
 /Makefile
+/hs-pkg-versions
 /Makefile.ghc
 /Makefile.ghc.bak
 /Makefile.in
@@ -39,6 +44,8 @@
 /configure
 /devel/squeeze-amd64.tar.gz
 /devel/squeeze-amd64.conf
+/devel/wheezy-amd64.tar.gz
+/devel/wheezy-amd64.conf
 /epydoc.conf
 /ganeti
 /stamp-srclinks
 /doc/examples/ganeti-node-role.ocf
 /doc/examples/gnt-config-backup
 /doc/examples/hooks/ipsec
+/doc/examples/systemd/ganeti-*.service
 
 # lib
 /lib/_constants.py
 /lib/_vcsversion.py
 /lib/_generated_rpc.py
 /lib/opcodes.py
+/lib/rpc/stub/
 
 # man
 /man/*.[0-9]
 
 # tools
 /tools/kvm-ifup
+/tools/kvm-ifup-os
+/tools/xen-ifup-os
 /tools/burnin
 /tools/ensure-dirs
 /tools/users-setup
 /tools/vcluster-setup
 /tools/vif-ganeti
+/tools/vif-ganeti-metad
 /tools/net-common
 /tools/node-cleanup
 /tools/node-daemon-setup
 /tools/prepare-node-join
 /tools/shebang/
+/tools/ssl-update
 
 # scripts
 /scripts/gnt-backup
 /src/hs2py
 /src/hs2py-constants
 /src/ganeti-confd
+/src/ganeti-wconfd
 /src/ganeti-kvmd
 /src/ganeti-luxid
+/src/ganeti-metad
 /src/ganeti-mond
 /src/rpc-test
 
diff --git a/COPYING b/COPYING
index 82fd7f8..36d149f 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright (C) 2006-2014 Google Inc.
+Copyright (C) 2006-2015 Google Inc.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/INSTALL b/INSTALL
index b43d77c..b2afdf5 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -41,7 +41,7 @@ Before installing, please verify that you have the following programs:
   <socat-note>` below
 - `Paramiko <http://www.lag.net/paramiko/>`_, if you want to use
   ``ganeti-listrunner``
-- `affinity Python module <http://pypi.python.org/pypi/affinity/0.1.0>`_,
+- `psutil Python module <https://github.com/giampaolo/psutil>`_,
   optional python package for supporting CPU pinning under KVM
 - `fdsend Python module <https://gitorious.org/python-fdsend>`_,
   optional Python package for supporting NIC hotplugging under KVM
@@ -64,22 +64,10 @@ packages, except for RBD, DRBD and Xen::
                     python-pyparsing python-simplejson python-bitarray \
                     python-pyinotify python-pycurl python-ipaddr socat fping
 
-For older distributions (eg. Debian  Squeeze) the package names are
-different.::
-
-  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping make \
-                    ndisc6 python python-pyopenssl openssl \
-                    python-pyparsing python-simplejson python-bitarray \
-                    python-pyinotify python-pycurl python-ipaddr socat fping
-
-If bitarray is missing it can be installed from easy-install::
-
-  $ easy_install bitarray
-
 Note that the previous instructions don't install optional packages.
 To install the optional package, run the following line.::
 
-  $ apt-get install python-paramiko python-affinity qemu-utils
+  $ apt-get install python-paramiko python-psutil qemu-utils
 
 If some of the python packages are not available in your system,
 you can try installing them using ``easy_install`` command.
@@ -87,7 +75,7 @@ For example::
 
   $ apt-get install python-setuptools python-dev
   $ cd / && easy_install \
-            affinity \
+            psutil \
             bitarray \
             ipaddr
 
@@ -100,7 +88,7 @@ On Fedora to install all required packages except RBD, DRBD and Xen::
 
 For optional packages use the command::
 
-  $ yum install python-paramiko python-affinity qemu-img
+  $ yum install python-paramiko python-psutil qemu-img
 
 If you want to build from source, please see doc/devnotes.rst for more
 dependencies.
@@ -135,7 +123,7 @@ Starting with Ganeti 2.7, the Haskell GHC compiler and a few base
 libraries are required in order to build Ganeti (but not to run and
 deploy Ganeti on production machines). More specifically:
 
-- `GHC <http://www.haskell.org/ghc/>`_ version 6.12 or higher
+- `GHC <http://www.haskell.org/ghc/>`_ version 7 or higher
 - or even better, `The Haskell Platform
   <http://hackage.haskell.org/platform/>`_ which gives you a simple way
   to bootstrap Haskell
@@ -148,39 +136,48 @@ deploy Ganeti on production machines). More specifically:
   `utf8-string <http://hackage.haskell.org/package/utf8-string>`_
   libraries; these usually come with the GHC compiler
 - `text <http://hackage.haskell.org/package/text>`_
-- `deepseq <http://hackage.haskell.org/package/deepseq>`_
+- `deepseq <http://hackage.haskell.org/package/deepseq>`_,
+  usually comes with the GHC compiler
 - `curl <http://hackage.haskell.org/package/curl>`_, tested with
   versions 1.3.4 and above
 - `hslogger <http://software.complete.org/hslogger>`_, version 1.1 and
-  above (note that Debian Squeeze only has version 1.0.9)
+  above.
 - `hinotify <http://hackage.haskell.org/package/hinotify>`_, tested with
   version 0.3.2
 - `Crypto <http://hackage.haskell.org/package/Crypto>`_, tested with
   version 4.2.4
 - `regex-pcre <http://hackage.haskell.org/package/regex-pcre>`_,
   bindings for the ``pcre`` library
-- `attoparsec <http://hackage.haskell.org/package/attoparsec>`_
+- `attoparsec <http://hackage.haskell.org/package/attoparsec>`_,
+  version 0.10 and above
 - `vector <http://hackage.haskell.org/package/vector>`_
 - `process <http://hackage.haskell.org/package/process>`_, version 1.0.1.1 and
-  above
+  above; usually comes with the GHC compiler
+- `base64-bytestring
+  <http://hackage.haskell.org/package/base64-bytestring>`_,
+  version 1.0.0.0 and above
+- `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.
 
 Some of these are also available as package in Debian/Ubuntu::
 
   $ apt-get install ghc libghc-json-dev libghc-network-dev \
-                    libghc-parallel-dev libghc-deepseq-dev \
+                    libghc-parallel-dev \
                     libghc-utf8-string-dev libghc-curl-dev \
                     libghc-hslogger-dev \
                     libghc-crypto-dev libghc-text-dev \
                     libghc-hinotify-dev libghc-regex-pcre-dev \
                     libpcre3-dev \
                     libghc-attoparsec-dev libghc-vector-dev \
-                    libghc6-zlib-dev
+                    libghc-zlib-dev
 
-Or in older versions of these distributions (using GHC 6.x)::
+Debian Jessie also includes recent enough versions of these libraries::
 
-  $ apt-get install ghc6 libghc6-json-dev libghc6-network-dev \
-                    libghc6-parallel-dev libghc6-deepseq-dev \
-                    libghc6-curl-dev
+  $ apt-get install libghc-base64-bytestring-dev \
+                    libghc-lens-dev \
+                    libghc-lifted-base-dev
 
 In Fedora, some of them are available via packages as well::
 
@@ -207,21 +204,37 @@ ones not available in your distribution packages) via ``cabal``::
 
   $ cabal install json network parallel utf8-string curl hslogger \
                   Crypto text hinotify==0.3.2 regex-pcre \
-                  attoparsec vector base64-bytestring
+                  attoparsec vector base64-bytestring \
+                  lifted-base==0.2.0.3 lens==3.10
+
+(The specified versions are suitable for Debian Wheezy, for other
+distributions different versions might be needed.)
+
+.. _cabal-order-note:
+.. note::
+  When installing additional libraries using ``cabal``, be sure to first
+  install all the required libraries available in your distribution and
+  only then install the rest using ``cabal``.
+  Otherwise cabal might install different versions of libraries that are
+  available in your distribution, causing conflicts during the
+  compilation.
+  This applies in particular when installing libraries for the optional
+  features.
 
 Haskell optional features
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Optionally, more functionality can be enabled if your build machine has
 a few more Haskell libraries enabled: the ``ganeti-confd`` daemon
-(``--enable-confd``) and the monitoring daemon (``--enable-mond``).
+(``--enable-confd``), the monitoring daemon (``--enable-monitoring``) and
+the meta-data daemon (``--enable-metadata``).
 The extra dependency for these is:
 
 - `snap-server` <http://hackage.haskell.org/package/snap-server>`_, version
   0.8.1 and above.
 
-This library is available in Debian Wheezy (but not in Squeeze), so you
-can use either apt::
+This library is available in Debian Wheezy or later, so you can use
+either apt::
 
   $ apt-get install libghc-snap-server-dev
 
@@ -231,15 +244,6 @@ or ``cabal``::
 
 to install it.
 
-In case you still use ghc-6.12, note that ``cabal`` would automatically try to
-install newer versions of some of the libraries snap-server depends on, that
-cannot be compiled with ghc-6.12, so you have to install snap-server on its
-own, explicitly forcing the installation of compatible versions::
-
-  $ cabal install MonadCatchIO-transformers==0.2.2.0 mtl==2.0.1.0 \
-                  hashable==1.1.2.0 case-insensitive==0.3 parsec==3.0.1 \
-                  network==2.3 snap-server==0.8.1
-
 .. _cabal-note:
 .. note::
   If one of the cabal packages fails to install due to unfulfilled
index a384351..265de5f 100644 (file)
@@ -73,6 +73,7 @@ bindir = $(versiondir)/$(BINDIR)
 sbindir = $(versiondir)$(SBINDIR)
 mandir = $(versionedsharedir)/root$(MANDIR)
 pkgpythondir = $(versionedsharedir)/ganeti
+pkgpython_rpc_stubdir = $(versionedsharedir)/ganeti/rpc/stub
 gntpythondir = $(versionedsharedir)
 pkgpython_bindir = $(versionedsharedir)
 gnt_python_sbindir = $(versionedsharedir)
@@ -81,12 +82,15 @@ tools_pythondir = $(versionedsharedir)
 clientdir = $(pkgpythondir)/client
 cmdlibdir = $(pkgpythondir)/cmdlib
 hypervisordir = $(pkgpythondir)/hypervisor
+hypervisor_hv_kvmdir = $(pkgpythondir)/hypervisor/hv_kvm
+jqueuedir = $(pkgpythondir)/jqueue
 storagedir = $(pkgpythondir)/storage
 httpdir = $(pkgpythondir)/http
 masterddir = $(pkgpythondir)/masterd
 confddir = $(pkgpythondir)/confd
 rapidir = $(pkgpythondir)/rapi
 rpcdir = $(pkgpythondir)/rpc
+rpc_stubdir = $(pkgpythondir)/rpc/stub
 serverdir = $(pkgpythondir)/server
 watcherdir = $(pkgpythondir)/watcher
 impexpddir = $(pkgpythondir)/impexpd
@@ -95,6 +99,7 @@ toolsdir = $(pkglibdir)/tools
 iallocatorsdir = $(pkglibdir)/iallocators
 pytoolsdir = $(pkgpythondir)/tools
 docdir = $(versiondir)$(datadir)/doc/$(PACKAGE)
+ifupdir = $(sysconfdir)/ganeti
 
 if USE_BACKUP_DIR
 backup_dir = $(BACKUP_DIR)
@@ -123,19 +128,28 @@ HS_DIRS = \
        src/Ganeti/Curl \
        src/Ganeti/Cpu \
        src/Ganeti/DataCollectors \
+       src/Ganeti/Daemon \
        src/Ganeti/Hs2Py \
        src/Ganeti/HTools \
        src/Ganeti/HTools/Backend \
        src/Ganeti/HTools/Program \
        src/Ganeti/Hypervisor \
        src/Ganeti/Hypervisor/Xen \
+       src/Ganeti/JQueue \
+       src/Ganeti/Locking \
+       src/Ganeti/Logging \
        src/Ganeti/Monitoring \
+       src/Ganeti/Metad \
+       src/Ganeti/Objects \
+       src/Ganeti/OpCodes \
        src/Ganeti/Query \
        src/Ganeti/Storage \
        src/Ganeti/Storage/Diskstats \
        src/Ganeti/Storage/Drbd \
        src/Ganeti/Storage/Lvm \
        src/Ganeti/THH \
+       src/Ganeti/Utils \
+       src/Ganeti/WConfd \
        test/hs \
        test/hs/Test \
        test/hs/Test/Ganeti \
@@ -148,7 +162,12 @@ HS_DIRS = \
        test/hs/Test/Ganeti/HTools/Backend \
        test/hs/Test/Ganeti/Hypervisor \
        test/hs/Test/Ganeti/Hypervisor/Xen \
-       test/hs/Test/Ganeti/Query
+       test/hs/Test/Ganeti/Locking \
+       test/hs/Test/Ganeti/Objects \
+       test/hs/Test/Ganeti/Query \
+       test/hs/Test/Ganeti/THH \
+       test/hs/Test/Ganeti/Utils \
+       test/hs/Test/Ganeti/WConfd
 
 # Haskell directories without the roots (src, test/hs)
 HS_DIRS_NOROOT = $(filter-out src,$(filter-out test/hs,$(HS_DIRS)))
@@ -164,6 +183,7 @@ DIRS = \
        doc/examples \
        doc/examples/gnt-debug \
        doc/examples/hooks \
+       doc/examples/systemd \
        doc/users \
        test/data/htools \
        test/data/htools/rapi \
@@ -174,12 +194,15 @@ DIRS = \
        lib/client \
        lib/cmdlib \
        lib/confd \
+       lib/jqueue \
        lib/http \
        lib/hypervisor \
+       lib/hypervisor/hv_kvm \
        lib/impexpd \
        lib/masterd \
        lib/rapi \
        lib/rpc \
+       lib/rpc/stub \
        lib/server \
        lib/storage \
        lib/tools \
@@ -249,6 +272,7 @@ CLEANFILES = \
        $(addsuffix /*.o,$(HS_DIRS)) \
        $(addsuffix /*.$(HTEST_SUFFIX)_hi,$(HS_DIRS)) \
        $(addsuffix /*.$(HTEST_SUFFIX)_o,$(HS_DIRS)) \
+       hs-pkg-versions \
        Makefile.ghc \
        Makefile.ghc.bak \
        $(PYTHON_BOOTSTRAP) \
@@ -270,26 +294,33 @@ CLEANFILES = \
        $(man_MANS) \
        $(manhtml) \
        tools/kvm-ifup \
+       tools/kvm-ifup-os \
+       tools/xen-ifup-os \
        tools/vif-ganeti \
+       tools/vif-ganeti-metad \
        tools/net-common \
        tools/users-setup \
+       tools/ssl-update \
        tools/vcluster-setup \
        $(python_scripts_shebang) \
        stamp-directories \
        stamp-srclinks \
        $(nodist_pkgpython_PYTHON) \
+       $(nodist_pkgpython_rpc_stub_PYTHON) \
        $(gnt_scripts) \
        $(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
        $(HS_BUILT_TEST_HELPERS) \
        src/ganeti-confd \
+       src/ganeti-wconfd \
        src/ganeti-luxid \
+       src/ganeti-metad \
        src/ganeti-mond \
        .hpc/*.mix src/*.tix test/hs/*.tix *.tix \
        doc/hs-lint.html
 
 GENERATED_FILES = \
        $(built_base_sources) \
-       $(BUILT_PYTHON_SOURCES) \
+       $(built_python_sources) \
        $(PYTHON_BOOTSTRAP) \
        $(gnt_python_sbin_SCRIPTS)
 
@@ -303,6 +334,9 @@ endif
 if ENABLE_MOND
 HS_GENERATED_FILES += src/ganeti-mond
 endif
+if ENABLE_METADATA
+HS_GENERATED_FILES += src/ganeti-metad
+endif
 
 built_base_sources = \
        stamp-directories \
@@ -311,11 +345,12 @@ built_base_sources = \
 built_python_base_sources = \
        lib/_constants.py \
        lib/_vcsversion.py \
-       lib/opcodes.py
+       lib/opcodes.py \
+       lib/rpc/stub/wconfd.py
 
-BUILT_PYTHON_SOURCES = \
-       $(built_python_base_sources) \
-       lib/_generated_rpc.py
+built_python_sources = \
+       $(nodist_pkgpython_PYTHON) \
+       $(nodist_pkgpython_rpc_stub_PYTHON)
 
 # Generating the RPC wrappers depends on many things, so make sure
 # it's built at the end of the built sources
@@ -330,10 +365,27 @@ BUILT_EXAMPLES = \
        doc/examples/ganeti-master-role.ocf \
        doc/examples/ganeti-node-role.ocf \
        doc/examples/gnt-config-backup \
-       doc/examples/hooks/ipsec
+       doc/examples/hooks/ipsec \
+       doc/examples/systemd/ganeti-common.service \
+       doc/examples/systemd/ganeti-confd.service \
+       doc/examples/systemd/ganeti-kvmd.service \
+       doc/examples/systemd/ganeti-luxid.service \
+       doc/examples/systemd/ganeti-metad.service \
+       doc/examples/systemd/ganeti-mond.service \
+       doc/examples/systemd/ganeti-noded.service \
+       doc/examples/systemd/ganeti-rapi.service \
+       doc/examples/systemd/ganeti-wconfd.service
+
+dist_ifup_SCRIPTS = \
+       tools/kvm-ifup-os \
+       tools/xen-ifup-os
 
 nodist_pkgpython_PYTHON = \
-       $(BUILT_PYTHON_SOURCES)
+       $(built_python_base_sources) \
+       lib/_generated_rpc.py
+
+nodist_pkgpython_rpc_stub_PYTHON = \
+       lib/rpc/stub/wconfd.py
 
 nodist_pkgpython_bin_SCRIPTS = \
        $(nodist_pkglib_python_scripts)
@@ -359,7 +411,6 @@ pkgpython_PYTHON = \
        lib/errors.py \
        lib/hooksmaster.py \
        lib/ht.py \
-       lib/jqueue.py \
        lib/jstore.py \
        lib/locking.py \
        lib/luxi.py \
@@ -380,10 +431,12 @@ pkgpython_PYTHON = \
        lib/uidpool.py \
        lib/vcluster.py \
        lib/network.py \
+       lib/wconfd.py \
        lib/workerpool.py
 
 client_PYTHON = \
        lib/client/__init__.py \
+       lib/client/base.py \
        lib/client/gnt_backup.py \
        lib/client/gnt_cluster.py \
        lib/client/gnt_debug.py \
@@ -421,10 +474,18 @@ hypervisor_PYTHON = \
        lib/hypervisor/hv_base.py \
        lib/hypervisor/hv_chroot.py \
        lib/hypervisor/hv_fake.py \
-       lib/hypervisor/hv_kvm.py \
        lib/hypervisor/hv_lxc.py \
        lib/hypervisor/hv_xen.py
 
+hypervisor_hv_kvm_PYTHON = \
+  lib/hypervisor/hv_kvm/__init__.py \
+  lib/hypervisor/hv_kvm/monitor.py \
+  lib/hypervisor/hv_kvm/netdev.py
+
+jqueue_PYTHON = \
+       lib/jqueue/__init__.py \
+       lib/jqueue/exec.py
+
 storage_PYTHON = \
        lib/storage/__init__.py \
        lib/storage/bdev.py \
@@ -481,13 +542,18 @@ rpc_PYTHON = \
        lib/rpc/node.py \
        lib/rpc/transport.py
 
+rpc_stub_PYTHON = \
+       lib/rpc/stub/__init__.py
+
 pytools_PYTHON = \
        lib/tools/__init__.py \
        lib/tools/burnin.py \
+       lib/tools/common.py \
        lib/tools/ensure_dirs.py \
        lib/tools/node_cleanup.py \
        lib/tools/node_daemon_setup.py \
-       lib/tools/prepare_node_join.py
+       lib/tools/prepare_node_join.py \
+       lib/tools/ssl_update.py
 
 utils_PYTHON = \
        lib/utils/__init__.py \
@@ -495,6 +561,7 @@ utils_PYTHON = \
        lib/utils/filelock.py \
        lib/utils/hash.py \
        lib/utils/io.py \
+       lib/utils/livelock.py \
        lib/utils/log.py \
        lib/utils/lvm.py \
        lib/utils/mlock.py \
@@ -526,14 +593,17 @@ docinput = \
        doc/design-2.9.rst \
        doc/design-2.10.rst \
        doc/design-2.11.rst \
+       doc/design-2.12.rst \
        doc/design-autorepair.rst \
        doc/design-bulk-create.rst \
        doc/design-ceph-ganeti-support.rst \
        doc/design-chained-jobs.rst \
        doc/design-cmdlib-unittests.rst \
        doc/design-cpu-pinning.rst \
+       doc/design-cpu-speed.rst \
        doc/design-daemons.rst \
        doc/design-device-uuid-name.rst \
+       doc/design-disks.rst \
        doc/design-draft.rst \
        doc/design-file-based-storage.rst \
        doc/design-glusterfs-ganeti-support.rst \
@@ -549,6 +619,7 @@ docinput = \
        doc/design-linuxha.rst \
        doc/design-lu-generated-jobs.rst \
        doc/design-monitoring-agent.rst \
+       doc/design-move-instance-improvements.rst \
        doc/design-multi-reloc.rst \
        doc/design-multi-version-tests.rst \
        doc/design-network.rst \
@@ -571,6 +642,7 @@ docinput = \
        doc/design-shared-storage.rst \
        doc/design-ssh-ports.rst \
        doc/design-storagetypes.rst \
+       doc/design-systemd.rst \
        doc/design-upgrade.rst \
        doc/design-virtual-clusters.rst \
        doc/design-x509-ca.rst \
@@ -610,6 +682,7 @@ endif
 # Haskell programs to be compiled by "make really-all"
 HS_COMPILE_PROGS = \
        src/ganeti-kvmd \
+       src/ganeti-wconfd \
        src/hluxid \
        src/hs2py \
        src/rpc-test
@@ -619,6 +692,9 @@ endif
 if ENABLE_MOND
 HS_COMPILE_PROGS += src/ganeti-mond
 endif
+if ENABLE_METADATA
+HS_COMPILE_PROGS += src/ganeti-metad
+endif
 
 # All Haskell non-test programs to be compiled but not automatically installed
 HS_PROGS = $(HS_BIN_PROGS) $(HS_MYEXECLIB_PROGS)
@@ -652,8 +728,34 @@ if DEVELOPER_MODE
 HFLAGS += -Werror
 endif
 
+HTEST_SUFFIX = hpc
+HPROF_SUFFIX = prof
+
+DEP_SUFFIXES =
+if GHC_LE_76
+DEP_SUFFIXES += -dep-suffix $(HPROF_SUFFIX) -dep-suffix $(HTEST_SUFFIX)
+else
+# GHC >= 7.8 stopped putting underscores into -dep-suffix by itself
+# (https://ghc.haskell.org/trac/ghc/ticket/9749) so we have to put them.
+# It also needs -dep-suffix "" for the .o file.
+DEP_SUFFIXES += -dep-suffix $(HPROF_SUFFIX)_ -dep-suffix $(HTEST_SUFFIX)_ \
+       -dep-suffix ""
+endif
+
+# GHC > 7.6 needs -dynamic-too when using Template Haskell since its
+# ghci is switched to loading dynamic libraries by default.
+# It must only be used in non-profiling GHC invocations.
+# We also don't use it in compilations that use HTEST_SUFFIX (which are
+# compiled with -fhpc) because HPC coverage doesn't interact well with
+# GHCI shared lib loading (https://ghc.haskell.org/trac/ghc/ticket/9762).
+HFLAGS_DYNAMIC =
+if !GHC_LE_76
+HFLAGS_DYNAMIC += -dynamic-too
+endif
+
 if HPROFILE
-HFLAGS += -prof -auto-all
+HPROFFLAGS = -prof -fprof-auto-top -osuf $(HPROF_SUFFIX)_o \
+       -hisuf $(HPROF_SUFFIX)_hi -rtsopts
 endif
 if HCOVERAGE
 HFLAGS += -fhpc
@@ -662,11 +764,9 @@ if HTEST
 HFLAGS += -DTEST
 endif
 
-HTEST_SUFFIX = hpc
-
 HTEST_FLAGS = $(HFLAGS) -fhpc -itest/hs \
-       -osuf .$(HTEST_SUFFIX)_o \
-       -hisuf .$(HTEST_SUFFIX)_hi
+       -osuf $(HTEST_SUFFIX)_o \
+       -hisuf $(HTEST_SUFFIX)_hi
 
 # extra flags that can be overriden on the command line (e.g. -Wwarn, etc.)
 HEXTRA =
@@ -689,6 +789,7 @@ HPCEXCL = --exclude Main \
 
 HS_LIB_SRCS = \
        src/Ganeti/BasicTypes.hs \
+       src/Ganeti/Codec.hs \
        src/Ganeti/Common.hs \
        src/Ganeti/Compat.hs \
        src/Ganeti/Confd/Client.hs \
@@ -704,6 +805,7 @@ HS_LIB_SRCS = \
        src/Ganeti/Cpu/Types.hs \
        src/Ganeti/Curl/Multi.hs \
        src/Ganeti/Daemon.hs \
+       src/Ganeti/Daemon/Utils.hs \
        src/Ganeti/DataCollectors/CLI.hs \
        src/Ganeti/DataCollectors/CPUload.hs \
        src/Ganeti/DataCollectors/Diskstats.hs \
@@ -749,22 +851,40 @@ HS_LIB_SRCS = \
        src/Ganeti/Hs2Py/GenOpCodes.hs \
        src/Ganeti/Hs2Py/OpDoc.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 \
+       src/Ganeti/Lens.hs \
+       src/Ganeti/Locking/Allocation.hs \
+       src/Ganeti/Locking/Types.hs \
+       src/Ganeti/Locking/Locks.hs \
+       src/Ganeti/Locking/Waiting.hs \
        src/Ganeti/Logging.hs \
+       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 \
+       src/Ganeti/Objects/Lens.hs \
        src/Ganeti/OpCodes.hs \
+       src/Ganeti/OpCodes/Lens.hs \
        src/Ganeti/OpParams.hs \
        src/Ganeti/Path.hs \
        src/Ganeti/Parsers.hs \
        src/Ganeti/PyValue.hs \
        src/Ganeti/Query/Cluster.hs \
        src/Ganeti/Query/Common.hs \
+       src/Ganeti/Query/Exec.hs \
        src/Ganeti/Query/Export.hs \
        src/Ganeti/Query/Filter.hs \
        src/Ganeti/Query/Group.hs \
@@ -788,11 +908,39 @@ HS_LIB_SRCS = \
        src/Ganeti/Storage/Lvm/Types.hs \
        src/Ganeti/Storage/Utils.hs \
        src/Ganeti/THH.hs \
+       src/Ganeti/THH/Field.hs \
+       src/Ganeti/THH/HsRPC.hs \
+       src/Ganeti/THH/PyRPC.hs \
        src/Ganeti/THH/PyType.hs \
+       src/Ganeti/THH/Types.hs \
+       src/Ganeti/THH/RPC.hs \
        src/Ganeti/Types.hs \
        src/Ganeti/UDSServer.hs \
-       src/Ganeti/Utils.hs\
-       src/Ganeti/VCluster.hs
+       src/Ganeti/Utils.hs \
+       src/Ganeti/Utils/Atomic.hs \
+       src/Ganeti/Utils/AsyncWorker.hs \
+       src/Ganeti/Utils/IORef.hs \
+       src/Ganeti/Utils/Livelock.hs \
+       src/Ganeti/Utils/MonadPlus.hs \
+       src/Ganeti/Utils/MultiMap.hs \
+       src/Ganeti/Utils/MVarLock.hs \
+       src/Ganeti/Utils/Random.hs \
+       src/Ganeti/Utils/Statistics.hs \
+       src/Ganeti/Utils/UniStd.hs \
+       src/Ganeti/Utils/Validate.hs \
+       src/Ganeti/VCluster.hs \
+       src/Ganeti/WConfd/ConfigState.hs \
+       src/Ganeti/WConfd/ConfigVerify.hs \
+       src/Ganeti/WConfd/ConfigWriter.hs \
+       src/Ganeti/WConfd/Client.hs \
+       src/Ganeti/WConfd/Core.hs \
+       src/Ganeti/WConfd/DeathDetection.hs \
+       src/Ganeti/WConfd/Language.hs \
+       src/Ganeti/WConfd/Monad.hs \
+       src/Ganeti/WConfd/Persistent.hs \
+       src/Ganeti/WConfd/Server.hs \
+       src/Ganeti/WConfd/Ssconf.hs \
+       src/Ganeti/WConfd/TempRes.hs
 
 HS_TEST_SRCS = \
        test/hs/Test/AutoConf.hs \
@@ -822,8 +970,12 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/JQueue.hs \
        test/hs/Test/Ganeti/Kvmd.hs \
        test/hs/Test/Ganeti/Luxi.hs \
+       test/hs/Test/Ganeti/Locking/Allocation.hs \
+       test/hs/Test/Ganeti/Locking/Locks.hs \
+       test/hs/Test/Ganeti/Locking/Waiting.hs \
        test/hs/Test/Ganeti/Network.hs \
        test/hs/Test/Ganeti/Objects.hs \
+       test/hs/Test/Ganeti/Objects/BitArray.hs \
        test/hs/Test/Ganeti/OpCodes.hs \
        test/hs/Test/Ganeti/Query/Aliases.hs \
        test/hs/Test/Ganeti/Query/Filter.hs \
@@ -839,11 +991,16 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/Storage/Drbd/Types.hs \
        test/hs/Test/Ganeti/Storage/Lvm/LVParser.hs \
        test/hs/Test/Ganeti/THH.hs \
+       test/hs/Test/Ganeti/THH/Types.hs \
        test/hs/Test/Ganeti/TestCommon.hs \
        test/hs/Test/Ganeti/TestHTools.hs \
        test/hs/Test/Ganeti/TestHelper.hs \
        test/hs/Test/Ganeti/Types.hs \
-       test/hs/Test/Ganeti/Utils.hs
+       test/hs/Test/Ganeti/Utils.hs \
+       test/hs/Test/Ganeti/Utils/MultiMap.hs \
+       test/hs/Test/Ganeti/Utils/Statistics.hs \
+       test/hs/Test/Ganeti/WConfd/TempRes.hs
+
 
 HS_LIBTEST_SRCS = $(HS_LIB_SRCS) $(HS_TEST_SRCS)
 
@@ -868,6 +1025,11 @@ doc/html/index.html: ENABLE_MANPAGES =
 doc/man-html/index.html: ENABLE_MANPAGES = 1
 doc/man-html/index.html: doc/manpages-enabled.rst $(mandocrst)
 
+if HAS_SPHINX_PRE13
+        SPHINX_HTML_THEME=default
+else
+        SPHINX_HTML_THEME=classic
+endif
 # Note: we use here an order-only prerequisite, as the contents of
 # _constants.py are not actually influencing the html build output: it
 # has to exist in order for the sphinx module to be loaded
@@ -877,7 +1039,7 @@ doc/html/index.html doc/man-html/index.html: $(docinput) doc/conf.py \
        configure.ac $(RUN_IN_TEMPDIR) lib/build/sphinx_ext.py \
        lib/build/shell_example_lexer.py lib/ht.py \
        doc/css/style.css lib/rapi/connector.py lib/rapi/rlib2.py \
-       autotools/sphinx-wrapper | $(BUILT_PYTHON_SOURCES)
+       autotools/sphinx-wrapper | $(built_python_sources)
        @test -n "$(SPHINX)" || \
            { echo 'sphinx-build' not found during configure; exit 1; }
 if !MANPAGES_IN_DOC
@@ -895,12 +1057,12 @@ endif
        dir=$(dir $@) && \
        @mkdir_p@ $$dir && \
        PYTHONPATH=. ENABLE_MANPAGES=$(ENABLE_MANPAGES) COPY_DOC=1 \
+        HTML_THEME=$(SPHINX_HTML_THEME) \
        $(RUN_IN_TEMPDIR) autotools/sphinx-wrapper $(SPHINX) -q -W -b html \
            -d . \
            -D version="$(VERSION_MAJOR).$(VERSION_MINOR)" \
            -D release="$(PACKAGE_VERSION)" \
            -D graphviz_dot="$(DOT)" \
-           -D enable_manpages="$(ENABLE_MANPAGES)" \
            doc $(CURDIR)/$$dir && \
        rm -f $$dir/.buildinfo $$dir/objects.inv
        touch $@
@@ -995,7 +1157,6 @@ gnt_python_sbin_SCRIPTS = \
 gntpython_SCRIPTS = $(gnt_scripts)
 
 PYTHON_BOOTSTRAP_SBIN = \
-       daemons/ganeti-masterd \
        daemons/ganeti-noded \
        daemons/ganeti-rapi \
        daemons/ganeti-watcher
@@ -1005,6 +1166,7 @@ PYTHON_BOOTSTRAP = \
        tools/ensure-dirs \
        tools/node-cleanup \
        tools/node-daemon-setup \
+       tools/ssl-update \
        tools/prepare-node-join
 
 qa_scripts = \
@@ -1027,7 +1189,7 @@ qa_scripts = \
        qa/qa_rapi.py \
        qa/qa_tags.py \
        qa/qa_utils.py \
-        qa/rapi-workload.py
+       qa/rapi-workload.py
 
 bin_SCRIPTS = $(HS_BIN_PROGS)
 install-exec-hook:
@@ -1041,13 +1203,40 @@ install-exec-hook:
 
 HS_SRCS = $(HS_LIBTESTBUILT_SRCS)
 
+# select the last line of output and extract the version number,
+# padding with 0s if needed
+hs-pkg-versions:
+       ghc-pkg list --simple-output lens \
+       | sed -r -e '$$!d' \
+         -e 's/^lens-([0-9]+(\.[0-9]+)*)/\1 0 0 0/' \
+         -e 's/\./ /g' -e 's/([0-9]+) *([0-9]+) *([0-9]+) .*/\
+             -DLENS_MAJOR=\1 -DLENS_MINOR=\2 -DLENS_REV=\3/' \
+         -e 's/^\s*//' \
+       > $@
+       ghc-pkg list --simple-output monad-control \
+       | sed -r -e '$$!d' \
+         -e 's/^monad-control-([0-9]+(\.[0-9]+)*)/\1 0 0 0/' \
+         -e 's/\./ /g' -e 's/([0-9]+) *([0-9]+) *([0-9]+) .*/\
+          -DMONAD_CONTROL_MAJOR=\1 -DMONAD_CONTROL_MINOR=\2 -DMONAD_CONTROL_REV=\3/'\
+         -e 's/^\s*//' \
+       >> $@
+       ghc-pkg list --simple-output QuickCheck \
+       | sed -r -e '$$!d' \
+         -e 's/^QuickCheck-([0-9]+(\.[0-9]+)*)/\1 0 0 0/' \
+         -e 's/\./ /g' -e 's/([0-9]+) *([0-9]+) *([0-9]+) .*/\
+          -DQUICKCHECK_MAJOR=\1 -DQUICKCHECK_MINOR=\2 -DQUICKCHECK_REV=\3/'\
+         -e 's/^\s*//' \
+       >> $@
+
 HS_MAKEFILE_GHC_SRCS = $(HS_SRC_PROGS:%=%.hs)
 if WANT_HSTESTS
 HS_MAKEFILE_GHC_SRCS += $(HS_TEST_PROGS:%=%.hs)
 endif
-Makefile.ghc: $(HS_MAKEFILE_GHC_SRCS) Makefile \
+Makefile.ghc: $(HS_MAKEFILE_GHC_SRCS) Makefile hs-pkg-versions \
               | $(built_base_sources) $(HS_BUILT_SRCS)
-       $(GHC) -M -dep-makefile $@ -dep-suffix $(HTEST_SUFFIX) $(HFLAGS) -itest/hs \
+       $(GHC) -M -dep-makefile $@ $(DEP_SUFFIXES) $(HFLAGS) $(HFLAGS_DYNAMIC) \
+               -itest/hs \
+         $(shell cat hs-pkg-versions) \
                $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(HS_MAKEFILE_GHC_SRCS)
 # Since ghc -M does not generate dependency line for object files, dependencies
 # from a target executable seed object (e.g. src/hluxid.o) to objects which
@@ -1059,37 +1248,80 @@ Makefile.ghc: $(HS_MAKEFILE_GHC_SRCS) Makefile \
 # object listed in Makefile.ghc.
 # e.g. src/hluxid.o : src/Ganeti/Daemon.hi
 #        => src/hluxid.o : src/Ganeti/Daemon.hi src/Ganeti/Daemon.o
-       sed -i -re 's/([^ ]+)\.hi$$/\1.hi \1.o/' $@
+       sed -i -r -e 's/([^ ]+)\.hi$$/\1.hi \1.o/' -e 's/([^ ]+)_hi$$/\1_hi \1_o/' $@
 
 @include_makefile_ghc@
 
-%.o:
-       @echo '[GHC]: $@ <- $^'
-       @$(GHC) -c $(HFLAGS) \
+# Like the %.o rule, but allows access to the test/hs directory.
+# This uses HFLAGS instead of HTEST_FLAGS because it's only for generating
+# object files (.o for GHC <= 7.6, .o/.so for newer GHCs) that are loaded
+# in GHCI when evaluating TH. The actual test-with-coverage .hpc_o files
+# are created in the `%.$(HTEST_SUFFIX)_o` rule.
+test/hs/%.o: hs-pkg-versions
+       @echo '[GHC|test]: $@ <- $^'
+       @$(GHC) -c $(HFLAGS) $(HFLAGS_DYNAMIC) -itest/hs \
+         $(shell cat hs-pkg-versions) \
                $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(@:%.o=%.hs)
 
-%.$(HTEST_SUFFIX)_o:
+%.o: hs-pkg-versions
        @echo '[GHC]: $@ <- $^'
+       @$(GHC) -c $(HFLAGS) $(HFLAGS_DYNAMIC) \
+         $(shell cat hs-pkg-versions) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(@:%.o=%.hs)
+
+# For TH+profiling we need to compile twice: Once without profiling,
+# and then once with profiling. See
+# http://www.haskell.org/ghc/docs/7.0.4/html/users_guide/template-haskell.html#id636646
+if HPROFILE
+%.$(HPROF_SUFFIX)_o: %.o hs-pkg-versions
+       @echo '[GHC|prof]: $@ <- $^'
+       @$(GHC) -c $(HFLAGS) \
+         $(shell cat hs-pkg-versions) \
+         $(HPROFFLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) \
+               $(@:%.$(HPROF_SUFFIX)_o=%.hs)
+endif
+
+# We depend on the non-test .o file here because we need the corresponding .so
+# file for GHC > 7.6 ghci dynamic loading for TH, and creating the .o file
+# will create the .so file since we use -dynamic-too (using the `test/hs/%.o`
+# rule).
+%.$(HTEST_SUFFIX)_o: %.o hs-pkg-versions
+       @echo '[GHC|test]: $@ <- $^'
        @$(GHC) -c $(HTEST_FLAGS) \
+         $(shell cat hs-pkg-versions) \
                $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(@:%.$(HTEST_SUFFIX)_o=%.hs)
 
 %.hi: %.o ;
 %.$(HTEST_SUFFIX)_hi: %.$(HTEST_SUFFIX)_o ;
+%.$(HPROF_SUFFIX)_hi: %.$(HPROF_SUFFIX)_o ;
 
-$(HS_SRC_PROGS): %: %.o | stamp-directories
-       $(GHC) $(HFLAGS) \
+if HPROFILE
+$(HS_SRC_PROGS): %: %.$(HPROF_SUFFIX)_o | stamp-directories
+       @echo '[GHC-link]: $@'
+       $(GHC) $(HFLAGS) $(HPROFFLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) --make $(@:%=%.hs)
+else
+$(HS_SRC_PROGS): %: %.o hs-pkg-versions | stamp-directories
+endif
+       @echo '[GHC-link]: $@'
+       $(GHC) $(HFLAGS) $(HFLAGS_DYNAMIC) \
+         $(shell cat hs-pkg-versions) \
+               $(HPROFFLAGS) \
                $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) --make $(@:%=%.hs)
        @rm -f $(notdir $@).tix
        @touch "$@"
 
-$(HS_TEST_PROGS): %: %.$(HTEST_SUFFIX)_o \
-                          | stamp-directories $(BUILT_PYTHON_SOURCES)
+$(HS_TEST_PROGS): %: %.$(HTEST_SUFFIX)_o hs-pkg-versions \
+                          | stamp-directories $(built_python_sources)
        @if [ "$(HS_NODEV)" ]; then \
          echo "Error: cannot run unittests without the development" \
               " libraries (see devnotes.rst)" 1>&2; \
          exit 1; \
        fi
+       @echo '[GHC-link|test]: $@'
        $(GHC) $(HTEST_FLAGS) \
+         $(shell cat hs-pkg-versions) \
                $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) --make $(@:%=%.hs)
        @rm -f $(notdir $@).tix
        @touch "$@"
@@ -1098,7 +1330,13 @@ dist_sbin_SCRIPTS = \
        tools/ganeti-listrunner
 
 nodist_sbin_SCRIPTS = \
-       daemons/ganeti-cleaner
+       daemons/ganeti-cleaner \
+  src/ganeti-kvmd \
+  src/ganeti-luxid \
+  src/ganeti-wconfd
+
+src/ganeti-luxid: src/hluxid
+       cp -f $< $@
 
 # strip path prefixes off the sbin scripts
 all_sbin_scripts = \
@@ -1109,18 +1347,17 @@ if ENABLE_CONFD
 src/ganeti-confd: src/hconfd
        cp -f $< $@
 
-src/ganeti-luxid: src/hluxid
-       cp -f $< $@
-
 nodist_sbin_SCRIPTS += src/ganeti-confd
-nodist_sbin_SCRIPTS += src/ganeti-luxid
-nodist_sbin_SCRIPTS += src/ganeti-kvmd
 endif
 
 if ENABLE_MOND
 nodist_sbin_SCRIPTS += src/ganeti-mond
 endif
 
+if ENABLE_METADATA
+nodist_sbin_SCRIPTS += src/ganeti-metad
+endif
+
 python_scripts = \
        tools/cfgshell \
        tools/cfgupgrade \
@@ -1173,7 +1410,8 @@ pkglib_python_scripts = \
 nodist_pkglib_python_scripts = \
        tools/ensure-dirs \
        tools/node-daemon-setup \
-       tools/prepare-node-join
+       tools/prepare-node-join \
+       tools/ssl-update
 
 pkglib_python_basenames = \
        $(patsubst daemons/%,%,$(patsubst tools/%,%,\
@@ -1182,7 +1420,10 @@ pkglib_python_basenames = \
 myexeclib_SCRIPTS = \
        daemons/daemon-util \
        tools/kvm-ifup \
+       tools/kvm-ifup-os \
+       tools/xen-ifup-os \
        tools/vif-ganeti \
+       tools/vif-ganeti-metad \
        tools/net-common \
        $(HS_MYEXECLIB_PROGS)
 
@@ -1221,7 +1462,9 @@ EXTRA_DIST = \
        devel/upload \
        devel/webserver \
        tools/kvm-ifup.in \
+       tools/ifup-os.in \
        tools/vif-ganeti.in \
+       tools/vif-ganeti-metad.in \
        tools/net-common.in \
        tools/vcluster-setup.in \
         $(python_scripts) \
@@ -1234,6 +1477,10 @@ EXTRA_DIST = \
        doc/examples/gnt-debug/README \
        doc/examples/gnt-debug/delay0.json \
        doc/examples/gnt-debug/delay50.json \
+       doc/examples/systemd/ganeti-master.target \
+       doc/examples/systemd/ganeti-node.target \
+       doc/examples/systemd/ganeti.service \
+       doc/examples/systemd/ganeti.target \
        doc/users/groupmemberships.in \
        doc/users/groups.in \
        doc/users/users.in \
@@ -1258,13 +1505,13 @@ man_MANS = \
        man/ganeti-luxid.8 \
        man/ganeti-listrunner.8 \
        man/ganeti-kvmd.8 \
-       man/ganeti-masterd.8 \
        man/ganeti-mond.8 \
        man/ganeti-noded.8 \
        man/ganeti-os-interface.7 \
        man/ganeti-extstorage-interface.7 \
        man/ganeti-rapi.8 \
        man/ganeti-watcher.8 \
+       man/ganeti-wconfd.8 \
        man/ganeti.7 \
        man/gnt-backup.8 \
        man/gnt-cluster.8 \
@@ -1318,6 +1565,7 @@ TEST_FILES = \
        test/data/htools/hail-invalid-reloc.json \
        test/data/htools/hail-node-evac.json \
        test/data/htools/hail-reloc-drbd.json \
+       test/data/htools/hbal-cpu-speed.data \
        test/data/htools/hbal-dyn.data \
        test/data/htools/hbal-evac.data \
        test/data/htools/hbal-excl-tags.data \
@@ -1390,8 +1638,9 @@ TEST_FILES = \
        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/instance-minor-pairing.txt \
-       test/data/instance-prim-sec.txt \
+       test/data/instance-disks.txt \
        test/data/ip-addr-show-dummy0.txt \
        test/data/ip-addr-show-lo-ipv4.txt \
        test/data/ip-addr-show-lo-ipv6.txt \
@@ -1454,6 +1703,7 @@ TEST_FILES = \
        test/data/vgreduce-removemissing-2.02.66-ok.txt \
        test/data/vgs-missing-pvs-2.02.02.txt \
        test/data/vgs-missing-pvs-2.02.66.txt \
+       test/data/xen-xl-list-4.4-crashed-instances.txt \
        test/data/xen-xm-info-4.0.1.txt \
        test/data/xen-xm-list-4.0.1-dom0-only.txt \
        test/data/xen-xm-list-4.0.1-four-instances.txt \
@@ -1572,20 +1822,22 @@ python_test_support = \
        test/py/cmdlib/testsupport/cmdlib_testcase.py \
        test/py/cmdlib/testsupport/config_mock.py \
        test/py/cmdlib/testsupport/iallocator_mock.py \
-       test/py/cmdlib/testsupport/lock_manager_mock.py \
+       test/py/cmdlib/testsupport/livelock_mock.py \
        test/py/cmdlib/testsupport/netutils_mock.py \
        test/py/cmdlib/testsupport/pathutils_mock.py \
        test/py/cmdlib/testsupport/processor_mock.py \
        test/py/cmdlib/testsupport/rpc_runner_mock.py \
        test/py/cmdlib/testsupport/ssh_mock.py \
        test/py/cmdlib/testsupport/utils_mock.py \
-       test/py/cmdlib/testsupport/util.py
+       test/py/cmdlib/testsupport/util.py \
+       test/py/cmdlib/testsupport/wconfd_mock.py
 
 haskell_tests = test/hs/htest
 
 dist_TESTS = \
        test/py/check-cert-expired_unittest.bash \
        test/py/daemon-util_unittest.bash \
+       test/py/systemd_unittest.bash \
        test/py/ganeti-cleaner_unittest.bash \
        test/py/import-export_unittest.bash \
        test/py/cli-test.bash \
@@ -1632,10 +1884,13 @@ all_python_code = \
        $(client_PYTHON) \
        $(cmdlib_PYTHON) \
        $(hypervisor_PYTHON) \
+       $(hypervisor_hv_kvm_PYTHON) \
+       $(jqueue_PYTHON) \
        $(storage_PYTHON) \
        $(rapi_PYTHON) \
        $(server_PYTHON) \
        $(rpc_PYTHON) \
+       $(rpc_stub_PYTHON) \
        $(pytools_PYTHON) \
        $(http_PYTHON) \
        $(confd_PYTHON) \
@@ -1655,6 +1910,7 @@ srclink_files = \
        man/footer.rst \
        test/py/check-cert-expired_unittest.bash \
        test/py/daemon-util_unittest.bash \
+       test/py/systemd_unittest.bash \
        test/py/ganeti-cleaner_unittest.bash \
        test/py/import-export_unittest.bash \
        test/py/cli-test.bash \
@@ -1705,6 +1961,8 @@ pep8_python_code = \
 
 test/py/daemon-util_unittest.bash: daemons/daemon-util
 
+test/py/systemd_unittest.bash: daemons/daemon-util $(BUILT_EXAMPLES)
+
 test/py/ganeti-cleaner_unittest.bash: daemons/ganeti-cleaner
 
 test/py/bash_completion.bash: doc/examples/bash_completion-debug
@@ -1713,10 +1971,22 @@ tools/kvm-ifup: tools/kvm-ifup.in $(REPLACE_VARS_SED)
        sed -f $(REPLACE_VARS_SED) < $< > $@
        chmod +x $@
 
+tools/kvm-ifup-os: tools/ifup-os.in $(REPLACE_VARS_SED)
+       sed -f $(REPLACE_VARS_SED) -e "s/ifup-os:/kvm-ifup-os:/" < $< > $@
+       chmod +x $@
+
+tools/xen-ifup-os: tools/ifup-os.in $(REPLACE_VARS_SED)
+       sed -f $(REPLACE_VARS_SED) -e "s/ifup-os:/xen-ifup-os:/" < $< > $@
+       chmod +x $@
+
 tools/vif-ganeti: tools/vif-ganeti.in $(REPLACE_VARS_SED)
        sed -f $(REPLACE_VARS_SED) < $< > $@
        chmod +x $@
 
+tools/vif-ganeti-metad: tools/vif-ganeti-metad.in $(REPLACE_VARS_SED)
+       sed -f $(REPLACE_VARS_SED) < $< > $@
+       chmod +x $@
+
 tools/net-common: tools/net-common.in $(REPLACE_VARS_SED)
        sed -f $(REPLACE_VARS_SED) < $< > $@
        chmod +x $@
@@ -1767,7 +2037,7 @@ doc/examples/bash_completion doc/examples/bash_completion-debug: \
 
 man/%.gen: man/%.rst lib/query.py lib/build/sphinx_ext.py \
        lib/build/shell_example_lexer.py \
-       | $(RUN_IN_TEMPDIR) $(BUILT_PYTHON_SOURCES)
+       | $(RUN_IN_TEMPDIR) $(built_python_sources)
        @echo "Checking $< for hardcoded paths..."
        @if grep -nEf autotools/wrong-hardcoded-paths $<; then \
          echo "Man page $< has hardcoded paths (see above)!" 1>&2 ; \
@@ -1898,6 +2168,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DKVM_PATH="$(KVM_PATH)" \
            -DIP_PATH="$(IP_PATH)" \
            -DSOCAT_PATH="$(SOCAT)" \
+           -DPYTHON_PATH="$(PYTHON)" \
            -DSOCAT_USE_ESCAPE="$(SOCAT_USE_ESCAPE)" \
            -DSOCAT_USE_COMPRESS="$(SOCAT_USE_COMPRESS)" \
            -DLVM_STRIPECOUNT="$(LVM_STRIPECOUNT)" \
@@ -1914,10 +2185,14 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DADMIN_GROUP="$(ADMIN_GROUP)" \
            -DMASTERD_USER="$(MASTERD_USER)" \
            -DMASTERD_GROUP="$(MASTERD_GROUP)" \
+           -DMETAD_USER="$(METAD_USER)" \
+           -DMETAD_GROUP="$(METAD_GROUP)" \
            -DRAPI_USER="$(RAPI_USER)" \
            -DRAPI_GROUP="$(RAPI_GROUP)" \
            -DCONFD_USER="$(CONFD_USER)" \
            -DCONFD_GROUP="$(CONFD_GROUP)" \
+           -DWCONFD_USER="$(WCONFD_USER)" \
+           -DWCONFD_GROUP="$(WCONFD_GROUP)" \
            -DKVMD_USER="$(KVMD_USER)" \
            -DKVMD_GROUP="$(KVMD_GROUP)" \
            -DLUXID_USER="$(LUXID_USER)" \
@@ -1931,6 +2206,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DENABLE_CONFD="$(ENABLE_CONFD)" \
            -DXEN_CMD="$(XEN_CMD)" \
            -DENABLE_RESTRICTED_COMMANDS="$(ENABLE_RESTRICTED_COMMANDS)" \
+           -DENABLE_METADATA="$(ENABLE_METADATA)" \
            -DENABLE_MOND="$(ENABLE_MOND)" \
            -DHAS_GNU_LN="$(HAS_GNU_LN)" \
            -DMAN_PAGES="$$(for i in $(notdir $(man_MANS)); do \
@@ -1970,6 +2246,9 @@ lib/opcodes.py: Makefile src/hs2py lib/opcodes.py.in_before \
 lib/_generated_rpc.py: lib/rpc_defs.py $(BUILD_RPC)
        PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(BUILD_RPC) lib/rpc_defs.py > $@
 
+lib/rpc/stub/wconfd.py: Makefile src/hs2py | stamp-directories
+       src/hs2py --wconfd-rpc > $@
+
 $(SHELL_ENV_INIT): Makefile stamp-directories
        set -e; \
        { echo '# Allow overriding for tests'; \
@@ -2005,6 +2284,7 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
          echo 's#@''GNTMASTERUSER@#$(MASTERD_USER)#g'; \
          echo 's#@''GNTRAPIUSER@#$(RAPI_USER)#g'; \
          echo 's#@''GNTCONFDUSER@#$(CONFD_USER)#g'; \
+         echo 's#@''GNTWCONFDUSER@#$(WCONFD_USER)#g'; \
          echo 's#@''GNTLUXIDUSER@#$(LUXID_USER)#g'; \
          echo 's#@''GNTNODEDUSER@#$(NODED_USER)#g'; \
          echo 's#@''GNTMONDUSER@#$(MOND_USER)#g'; \
@@ -2012,6 +2292,7 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
          echo 's#@''GNTADMINGROUP@#$(ADMIN_GROUP)#g'; \
          echo 's#@''GNTCONFDGROUP@#$(CONFD_GROUP)#g'; \
          echo 's#@''GNTNODEDGROUP@#$(NODED_GROUP)#g'; \
+         echo 's#@''GNTWCONFDGROUP@#$(CONFD_GROUP)#g'; \
          echo 's#@''GNTLUXIDGROUP@#$(LUXID_GROUP)#g'; \
          echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
          echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
@@ -2036,6 +2317,7 @@ 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/node-cleanup: MODULE = ganeti.tools.node_cleanup
+tools/ssl-update: MODULE = ganeti.tools.ssl_update
 $(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst test/hs/%,%,$@)
 
 $(PYTHON_BOOTSTRAP) $(gnt_scripts) $(gnt_python_sbin_SCRIPTS): Makefile | stamp-directories
@@ -2170,6 +2452,14 @@ hs-tests: test/hs/htest
        @rm -f htest.tix
        ./test/hs/htest
 
+.PHONY: py-tests
+py-tests: $(python_tests) ganeti $(built_python_sources)
+       error=; \
+       for file in $(python_tests); \
+         do if ! $(TESTS_ENVIRONMENT) $$file; then error=1; fi; \
+       done; \
+       test -z "$$error"
+
 .PHONY: hs-shell-%
 hs-shell-%: test/hs/hpc-htools test/hs/hpc-mon-collector \
             $(HS_BUILT_TEST_HELPERS)
@@ -2205,7 +2495,16 @@ hs-check: hs-tests hs-shell
 PEP8_IGNORE = E111,E121,E123,E125,E127,E261,E501
 
 # For excluding pep8 expects filenames only, not whole paths
-PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES))))
+PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(built_python_sources))))
+
+# A space-separated list of pylint warnings to completely ignore:
+# I0013 = disable warnings for ignoring whole files
+LINT_DISABLE = I0013
+# Additional pylint options
+LINT_OPTS =
+# The combined set of pylint options
+LINT_OPTS_ALL = $(LINT_OPTS) \
+  $(addprefix --disable=,$(LINT_DISABLE))
 
 LINT_TARGETS = pylint pylint-qa pylint-test
 if HAS_PEP8
@@ -2221,19 +2520,19 @@ lint: $(LINT_TARGETS)
 .PHONY: pylint
 pylint: $(GENERATED_FILES)
        @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
-       $(PYLINT) $(LINT_OPTS) $(lint_python_code)
+       $(PYLINT) $(LINT_OPTS_ALL) $(lint_python_code)
 
 .PHONY: pylint-qa
 pylint-qa: $(GENERATED_FILES)
        @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
        cd $(top_srcdir)/qa && \
-         PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \
-         --rcfile  ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts))
+         PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS_ALL) \
+               --rcfile  ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts))
 # FIXME: lint all test code, not just the newly added test support
 pylint-test: $(GENERATED_FILES)
        @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
        cd $(top_srcdir) && \
-               PYTHONPATH=.:./test/py $(PYLINT) $(LINT_OPTS) \
+               PYTHONPATH=.:./test/py $(PYLINT) $(LINT_OPTS_ALL) \
                --rcfile=pylintrc-test  $(python_test_support)
 
 .PHONY: pep8
@@ -2252,6 +2551,7 @@ hlint: $(HS_BUILT_SRCS) src/lint-hints.hs
        $(HLINT) --utf8 --report=doc/hs-lint.html --cross $$C \
          --ignore "Use first" \
          --ignore "Use &&&" \
+         --ignore "Use &&" \
          --ignore "Use void" \
          --ignore "Reduce duplication" \
          --hint src/lint-hints \
@@ -2402,7 +2702,7 @@ $(APIDOC_HS_DIR)/index.html: $(HS_LIBTESTBUILT_SRCS) Makefile
          f_html=$${f_notst%%.hs}.html; \
          $(HSCOLOUR) -css -anchor $$file > $(APIDOC_HS_DIR)/$$f_html ; \
        done ; \
-       $(HADDOCK) --odir $(APIDOC_HS_DIR) --html --ignore-all-exports -w \
+       $(HADDOCK) --odir $(APIDOC_HS_DIR) --html --hoogle --ignore-all-exports -w \
          -t ganeti -p src/haddock-prologue \
          --source-module="%{MODULE/.//}.html" \
          --source-entity="%{MODULE/.//}.html#%{NAME}" \
@@ -2410,10 +2710,11 @@ $(APIDOC_HS_DIR)/index.html: $(HS_LIBTESTBUILT_SRCS) Makefile
          $(HS_LIBTESTBUILT_SRCS)
 
 .PHONY: TAGS
-TAGS: $(GENERATED_FILES)
+TAGS: $(GENERATED_FILES) hs-pkg-versions
        rm -f TAGS
        $(GHC) -e ":etags TAGS_hs" -v0 \
          $(filter-out -O -Werror,$(HFLAGS)) \
+         $(shell cat hs-pkg-versions) \
                -osuf tags.o \
                -hisuf tags.hi \
     -lcurl \
diff --git a/NEWS b/NEWS
index efd7703..4d21626 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,434 @@ News
 ====
 
 
+Version 2.12.6
+--------------
+
+*(Released Mon, 14 Dec 2015)*
+
+Important changes and security notes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Security release.
+
+CVE-2015-7944
+
+Ganeti provides a RESTful control interface called the RAPI. Its HTTPS
+implementation is vulnerable to DoS attacks via client-initiated SSL
+parameter renegotiation. While the interface is not meant to be exposed
+publicly, due to the fact that it binds to all interfaces, we believe
+some users might be exposing it unintentionally and are vulnerable. A
+DoS attack can consume resources meant for Ganeti daemons and instances
+running on the master node, making both perform badly.
+
+Fixes are not feasible due to the OpenSSL Python library not exposing
+functionality needed to disable client-side renegotiation. Instead, we
+offer instructions on how to control RAPI's exposure, along with info
+on how RAPI can be setup alongside an HTTPS proxy in case users still
+want or need to expose the RAPI interface. The instructions are
+outlined in Ganeti's security document: doc/html/security.html
+
+CVE-2015-7945
+
+Ganeti leaks the DRBD secret through the RAPI interface. Examining job
+results after an instance information job reveals the secret. With the
+DRBD secret, access to the local cluster network, and ARP poisoning,
+an attacker can impersonate a Ganeti node and clone the disks of a
+DRBD-based instance. While an attacker with access to the cluster
+network is already capable of accessing any data written as DRBD
+traffic is unencrypted, having the secret expedites the process and
+allows access to the entire disk.
+
+Fixes contained in this release prevent the secret from being exposed
+via the RAPI. The DRBD secret can be changed by converting an instance
+to plain and back to DRBD, generating a new secret, but redundancy will
+be lost until the process completes.
+Since attackers with node access are capable of accessing some and
+potentially all data even without the secret, we do not recommend that
+the secret be changed for existing instances.
+
+Minor changes
+~~~~~~~~~~~~~
+
+- Calculate correct affected nodes set in InstanceChangeGroup
+  (Issue 1144)
+- Do not retry all requests after connection timeouts to prevent
+  repeated job submission
+- Fix reason trails of expanding opcodes
+- Make lockConfig call retryable
+- Return the correct error code in the post-upgrade script
+- Make OpenSSL refrain from DH altogether
+- Fix upgrades of instances with missing creation time
+- Make htools tolerate missing "dtotal" and "dfree" on luxi
+- Fix default for --default-iallocator-params
+- At IAlloc backend guess state from admin state
+- Only search for Python-2 interpreters
+- Handle Xen 4.3 states better
+- replace-disks: fix --ignore-ipolicy
+- Fix disabling of user shutdown reporting
+- Fix operations on empty nodes by accepting allocation of 0 jobs
+- Fix instance multi allocation for non-DRBD disks
+- Allow more failover options when using the --no-disk-moves flag
+
+
+Version 2.12.5
+--------------
+
+*(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.12.5.
+
+Fixed and improvements
+~~~~~~~~~~~~~~~~~~~~~~
+
+- Fixed Issue #1030: GlusterFS support breaks at upgrade to 2.12 -
+  switches back to shared-file
+- Fixed Issue #1094 (see the notice in Incompatible/important changes):
+  Differences in encodings of SSL certificates can render a cluster
+  uncommunicative after a master-failover
+- Fixed Issue #1098: Support for ECDSA SSH keys
+- Fixed Issue #1100: Filter-evaluation for run-time data filter
+- Fixed Issue #1101: Modifying the storage directory for the shared-file
+  disk template doesn't work
+- Fixed Issue #1108: Spurious "NIC name already used" errors during
+  instance creation
+- Fixed Issue #1114: Binding RAPI to a specific IP makes the watcher
+  restart the RAPI
+- Fixed Issue #1115: Race between starting WConfD and updating the config
+- Better handling of the "crashed" Xen state
+- The ``htools`` now properly work also on shared-storage clusters
+- Various improvements to the documentation have been added
+
+Inherited from the 2.11 branch:
+
+- Fixed Issue #1113: Reduce amount of logging on successful requests
+
+Known issues
+~~~~~~~~~~~~
+
+- Issue #1104: gnt-backup: dh key too small
+
+
+Version 2.12.4
+--------------
+
+*(Released Tue, 12 May 2015)*
+
+- 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 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
+~~~~~~~~~~~~
+
+Pending since 2.12.2:
+
+- Under certain conditions instance doesn't get unpaused after live
+  migration (issue #1050)
+- GlusterFS support breaks at upgrade to 2.12 - switches back to
+  shared-file (issue #1030)
+
+
+Version 2.12.3
+--------------
+
+*(Released Wed, 29 Apr 2015)*
+
+- Fixed Issue #1019: upgrade from 2.6.2 to 2.12 fails. cfgupgrade
+  doesn't migrate the config.data file properly
+- Fixed Issue 1023: Master master-capable option bug
+- Fixed Issue 1068: gnt-network info outputs wrong external reservations
+- Fixed Issue 1070: Upgrade of Ganeti 2.5.2 to 2.12.0 fails due to
+  missing UUIDs for disks
+- Fixed Issue 1073: ssconf_hvparams_* not distributed with ssconf
+
+Inherited from the 2.11 branch:
+
+- Fixed Issue 1032: Renew-crypto --new-node-certificates sometimes does not
+  complete.
+  The operation 'gnt-cluster renew-crypto --new-node-certificates' is
+  now more robust against intermitten reachability errors. Nodes that
+  are temporarily not reachable, are contacted with several retries.
+  Nodes which are marked as offline are omitted right away.
+
+Inherited from the 2.10 branch:
+
+- Fixed Issue 1057: master-failover succeeds, but IP remains assigned to
+  old master
+- Fixed Issue 1058: Python's os.minor() does not support devices with
+  high minor numbers
+- Fixed Issue 1059: Luxid fails if DNS returns an IPv6 address that does
+  not reverse resolve
+
+Known issues
+~~~~~~~~~~~~
+
+Pending since 2.12.2:
+
+- GHC 7.8 introduced some incompatible changes, so currently Ganeti
+  2.12. doesn't compile on GHC 7.8
+- Under certain conditions instance doesn't get unpaused after live
+  migration (issue #1050)
+- GlusterFS support breaks at upgrade to 2.12 - switches back to
+  shared-file (issue #1030)
+
+
+Version 2.12.2
+--------------
+
+*(Released Wed, 25 Mar 2015)*
+
+- Support for the lens Haskell library up to version 4.7 (issue #1028)
+- SSH keys are now distributed only to master and master candidates
+  (issue #377)
+- Improved performance for operations that frequently read the
+  cluster configuration
+- Improved robustness of spawning job processes that occasionally caused
+  newly-started jobs to timeout
+- Fixed race condition during cluster verify which occasionally caused
+  it to fail
+
+Inherited from the 2.11 branch:
+
+- Fix failing automatic glusterfs mounts (issue #984)
+- Fix watcher failing to read its status file after an upgrade
+  (issue #1022)
+- Improve Xen instance state handling, in particular of somewhat exotic
+  transitional states
+
+Inherited from the 2.10 branch:
+
+- Fix failing to change a diskless drbd instance to plain
+  (issue #1036)
+- Fixed issues with auto-upgrades from pre-2.6
+  (hv_state_static and disk_state_static)
+- Fix memory leak in the monitoring daemon
+
+Inherited from the 2.9 branch:
+
+- Fix file descriptor leak in Confd client
+
+Known issues
+~~~~~~~~~~~~
+
+- GHC 7.8 introduced some incompatible changes, so currently Ganeti
+  2.12. doesn't compile on GHC 7.8
+- Under certain conditions instance doesn't get unpaused after live
+  migration (issue #1050)
+- GlusterFS support breaks at upgrade to 2.12 - switches back to
+  shared-file (issue #1030)
+
+
+Version 2.12.1
+--------------
+
+*(Released Wed, 14 Jan 2015)*
+
+- Fix users under which the wconfd and metad daemons run (issue #976)
+- Clean up stale livelock files (issue #865)
+- Fix setting up the metadata daemon's network interface for Xen
+- Make watcher identify itself on disk activation
+- Add "ignore-ipolicy" option to gnt-instance grow-disk
+- Check disk size ipolicy during "gnt-instance grow-disk" (issue #995)
+
+Inherited from the 2.11 branch:
+
+- Fix counting votes when doing master failover (issue #962)
+- Fix broken haskell dependencies (issues #758 and #912)
+- Check if IPv6 is used directly when running SSH (issue #892)
+
+Inherited from the 2.10 branch:
+
+- Fix typo in gnt_cluster output (issue #1015)
+- Use the Python path detected at configure time in the top-level Python
+  scripts.
+- Fix check for sphinx-build from python2-sphinx
+- Properly check if an instance exists in 'gnt-instance console'
+
+
+Version 2.12.0
+--------------
+
+*(Released Fri, 10 Oct 2014)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Ganeti is now distributed under the 2-clause BSD license.
+  See the COPYING file.
+- Do not use debug mode in production. Certain daemons will issue warnings
+  when launched in debug mode. Some debug logging violates some of the new
+  invariants in the system (see "New features"). The logging has been kept as
+  it aids diagnostics and development.
+
+New features
+~~~~~~~~~~~~
+
+- OS install script parameters now come in public, private and secret
+  varieties:
+
+  - Public parameters are like all other parameters in Ganeti.
+  - Ganeti will not log private and secret parameters, *unless* it is running
+    in debug mode.
+  - Ganeti will not save secret parameters to configuration. Secret parameters
+    must be supplied every time you install, or reinstall, an instance.
+  - Attempting to override public parameters with private or secret parameters
+    results in an error. Similarly, you may not use secret parameters to
+    override private parameters.
+
+- The move-instance tool can now attempt to allocate an instance by using
+  opportunistic locking when an iallocator is used.
+- The build system creates sample systemd unit files, available under
+  doc/examples/systemd. These unit files allow systemd to natively
+  manage and supervise all Ganeti processes.
+- Different types of compression can be applied during instance moves, including
+  user-specified ones.
+- Ganeti jobs now run as separate processes. The jobs are coordinated by
+  a new daemon "WConfd" that manages cluster's configuration and locks
+  for individual jobs. A consequence is that more jobs can run in parallel;
+  the number is run-time configurable, see "New features" entry
+  of 2.11.0. To avoid luxid being overloaded with tracking running jobs, it
+  backs of and only occasionally, in a sequential way, checks if jobs have
+  finished and schedules new ones. In this way, luxid keeps responsive under
+  high cluster load. The limit as when to start backing of is also run-time
+  configurable.
+- The metadata daemon is now optionally available, as part of the
+  partial implementation of the OS-installs design. It allows pass
+  information to OS install scripts or to instances.
+  It is also possible to run Ganeti without the daemon, if desired.
+- Detection of user shutdown of instances has been implemented for Xen
+  as well.
+
+New dependencies
+~~~~~~~~~~~~~~~~
+
+- The KVM CPU pinning no longer uses the affinity python package, but psutil
+  instead. The package is still optional and needed only if the feature is to
+  be used.
+
+Incomplete features
+~~~~~~~~~~~~~~~~~~~
+
+The following issues are related to features which are not completely
+implemented in 2.12:
+
+- Issue 885: Network hotplugging on KVM sometimes makes an instance
+  unresponsive
+- Issues 708 and 602: The secret parameters are currently still written
+  to disk in the job queue.
+- Setting up the metadata network interface under Xen isn't fully
+  implemented yet.
+
+Known issues
+~~~~~~~~~~~~
+
+- *Wrong UDP checksums in DHCP network packets:*
+  If an instance communicates with the metadata daemon and uses DHCP to
+  obtain its IP address on the provided virtual network interface,
+  it can happen that UDP packets have a wrong checksum, due to
+  a bug in virtio. See for example https://bugs.launchpad.net/bugs/930962
+
+  Ganeti works around this bug by disabling the UDP checksums on the way
+  from a host to instances (only on the special metadata communication
+  network interface) using the ethtool command. Therefore if using
+  the metadata daemon the host nodes should have this tool available.
+- The metadata daemon is run as root in the split-user mode, to be able
+  to bind to port 80.
+  This should be improved in future versions, see issue #949.
+
+Since 2.12.0 rc2
+~~~~~~~~~~~~~~~~
+
+The following issues have been fixed:
+
+- Fixed passing additional parameters to RecreateInstanceDisks over
+  RAPI.
+- Fixed the permissions of WConfd when running in the split-user mode.
+  As WConfd takes over the previous master daemon to manage the
+  configuration, it currently runs under the masterd user.
+- Fixed the permissions of the metadata daemon  wn running in the
+  split-user mode (see Known issues).
+- Watcher now properly adds a reason trail entry when initiating disk
+  checks.
+- Fixed removing KVM parameters introduced in 2.12 when downgrading a
+  cluster to 2.11: "migration_caps", "disk_aio" and "virtio_net_queues".
+- Improved retrying of RPC calls that fail due to network errors.
+
+
+Version 2.12.0 rc2
+------------------
+
+*(Released Mon, 22 Sep 2014)*
+
+This was the second release candidate of the 2.12 series.
+All important changes are listed in the latest 2.12 entry.
+
+Since 2.12.0 rc1
+~~~~~~~~~~~~~~~~
+
+The following issues have been fixed:
+
+- Watcher now checks if WConfd is running and functional.
+- Watcher now properly adds reason trail entries.
+- Fixed NIC options in Xen's config files.
+
+Inherited from the 2.10 branch:
+
+- Fixed handling of the --online option
+- Add warning against hvparam changes with live migrations, which might
+  lead to dangerous situations for instances.
+- Only the LVs in the configured VG are checked during cluster verify.
+
+
+Version 2.12.0 rc1
+------------------
+
+*(Released Wed, 20 Aug 2014)*
+
+This was the first release candidate of the 2.12 series.
+All important changes are listed in the latest 2.12 entry.
+
+Since 2.12.0 beta1
+~~~~~~~~~~~~~~~~~~
+
+The following issues have been fixed:
+
+- Issue 881: Handle communication errors in mcpu
+- Issue 883: WConfd leaks memory for some long operations
+- Issue 884: Under heavy load the IAllocator fails with a "missing
+  instance" error
+
+Inherited from the 2.10 branch:
+
+- Improve the recognition of Xen domU states
+- Automatic upgrades:
+  - Create the config backup archive in a safe way
+  - On upgrades, check for upgrades to resume first
+  - Pause watcher during upgrade
+- Allow instance disks to be added with --no-wait-for-sync
+
+
+Version 2.12.0 beta1
+--------------------
+
+*(Released Mon, 21 Jul 2014)*
+
+This was the first beta release of the 2.12 series. All important changes
+are listed in the latest 2.12 entry.
+
+
 Version 2.11.8
 --------------
 
@@ -317,6 +745,10 @@ For Haskell:
 - ``base64-bytestring`` library (http://hackage.haskell.org/package/zlib),
   at least version 1.0.0.0
 
+- ``lifted-base`` library (http://hackage.haskell.org/package/lifted-base)
+
+- ``lens`` library (http://hackage.haskell.org/package/lens)
+
 Since 2.11.0 rc1
 ~~~~~~~~~~~~~~~~
 
diff --git a/README b/README
index bfd1cb0..2c2208b 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.11
+Ganeti 2.12
 ===========
 
 For installation instructions, read the INSTALL and the doc/install.rst
diff --git a/UPGRADE b/UPGRADE
index 62624ff..e7cc46a 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -39,6 +39,14 @@ the Ganeti binaries should happen in the same way as for all other binaries on
 your system.
 
 
+2.12
+----
+
+Due to issue #1094 in Ganeti 2.11 and 2.12 up to version 2.12.4, we
+advise to rerun 'gnt-cluster renew-crypto --new-node-certificates'
+after an upgrade to 2.12.5 or higher.
+
+
 2.11
 ----
 
index ea0f3b6..d3ccad6 100755 (executable)
@@ -204,8 +204,7 @@ def main():
     assert module.SINGLE == _SINGLE
     assert module.MULTI == _MULTI
 
-    dups = utils.FindDuplicates(itertools.chain(*map(lambda value: value.keys(),
-                                                     module.CALLS.values())))
+    dups = utils.GetRepeatedKeys(*module.CALLS.values())
     if dups:
       raise Exception("Found duplicate RPC definitions for '%s'" %
                       utils.CommaJoin(sorted(dups)))
index fe2fce3..ddce136 100644 (file)
@@ -1,7 +1,7 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [11])
-m4_define([gnt_version_revision], [8])
+m4_define([gnt_version_minor], [12])
+m4_define([gnt_version_revision], [6])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
@@ -319,22 +319,28 @@ AC_ARG_WITH([user-prefix],
     [ to change the default)]
   )],
   [user_masterd="${withval}masterd";
+   user_metad="$user_default";
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
+   user_wconfd="${withval}masterd";
    user_kvmd="$user_default";
    user_luxid="${withval}masterd";
    user_noded="$user_default";
    user_mond="$user_default"],
   [user_masterd="$user_default";
+   user_metad="$user_default";
    user_rapi="$user_default";
    user_confd="$user_default";
+   user_wconfd="$user_default";
    user_kvmd="$user_default";
    user_luxid="$user_default";
    user_noded="$user_default";
    user_mond="$user_default"])
 AC_SUBST(MASTERD_USER, $user_masterd)
+AC_SUBST(METAD_USER, $user_metad)
 AC_SUBST(RAPI_USER, $user_rapi)
 AC_SUBST(CONFD_USER, $user_confd)
+AC_SUBST(WCONFD_USER, $user_wconfd)
 AC_SUBST(KVMD_USER, $user_kvmd)
 AC_SUBST(LUXID_USER, $user_luxid)
 AC_SUBST(NODED_USER, $user_noded)
@@ -350,35 +356,43 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="${withval}rapi";
    group_admin="${withval}admin";
    group_confd="${withval}confd";
+   group_wconfd="${withval}masterd";
    group_kvmd="$group_default";
    group_luxid="${withval}luxid";
    group_masterd="${withval}masterd";
+   group_metad="$group_default";
    group_noded="$group_default";
    group_daemons="${withval}daemons";
    group_mond="$group_default"],
   [group_rapi="$group_default";
    group_admin="$group_default";
    group_confd="$group_default";
+   group_wconfd="$group_default";
    group_kvmd="$group_default";
    group_luxid="$group_default";
    group_masterd="$group_default";
+   group_metad="$group_default";
    group_noded="$group_default";
    group_daemons="$group_default";
    group_mond="$group_default"])
 AC_SUBST(RAPI_GROUP, $group_rapi)
 AC_SUBST(ADMIN_GROUP, $group_admin)
 AC_SUBST(CONFD_GROUP, $group_confd)
+AC_SUBST(WCONFD_GROUP, $group_wconfd)
 AC_SUBST(KVMD_GROUP, $group_kvmd)
 AC_SUBST(LUXID_GROUP, $group_luxid)
 AC_SUBST(MASTERD_GROUP, $group_masterd)
+AC_SUBST(METAD_GROUP, $group_metad)
 AC_SUBST(NODED_GROUP, $group_noded)
 AC_SUBST(DAEMONS_GROUP, $group_daemons)
 AC_SUBST(MOND_GROUP, $group_mond)
 
 # Print the config to the user
 AC_MSG_NOTICE([Running ganeti-masterd as $group_masterd:$group_masterd])
+AC_MSG_NOTICE([Running ganeti-metad as $group_metad:$group_metad])
 AC_MSG_NOTICE([Running ganeti-rapi as $user_rapi:$group_rapi])
 AC_MSG_NOTICE([Running ganeti-confd as $user_confd:$group_confd])
+AC_MSG_NOTICE([Running ganeti-wconfd as $user_wconfd:$group_wconfd])
 AC_MSG_NOTICE([Running ganeti-luxid as $user_luxid:$group_luxid])
 AC_MSG_NOTICE([Group for daemons is $group_daemons])
 AC_MSG_NOTICE([Group for clients is $group_admin])
@@ -503,6 +517,9 @@ else
   fi
 fi
 AM_CONDITIONAL([HAS_SPHINX], [test -n "$SPHINX"])
+AM_CONDITIONAL([HAS_SPHINX_PRE13],
+ [test -n "$SPHINX" && echo "$sphinxver" | grep -q -E \
+  '^Sphinx([[[:space:]]]+|\(sphinx-build[[1-9]]?\)|v)*[[1-9]]\.[[0-2]]\.'])
 
 AC_ARG_ENABLE([manpages-in-doc],
   [AS_HELP_STRING([--enable-manpages-in-doc],
@@ -590,6 +607,14 @@ AC_ARG_ENABLE([monitoring],
   [],
   [enable_monitoring=check])
 
+# --enable-metadata
+ENABLE_METADATA=
+AC_ARG_ENABLE([metadata],
+  [AS_HELP_STRING([--enable-metadata],
+  [enable the ganeti metadata daemon (default: check)])],
+  [],
+  [enable_metadata=check])
+
 # Check for ghc
 AC_ARG_VAR(GHC, [ghc path])
 AC_PATH_PROG(GHC, [ghc], [])
@@ -597,6 +622,10 @@ if test -z "$GHC"; then
   AC_MSG_FAILURE([ghc not found, compilation will not possible])
 fi
 
+# Note: Character classes ([...]) need to be double quoted due to autoconf
+# using m4
+AM_CONDITIONAL([GHC_LE_76], [$GHC --numeric-version | grep -q '^7\.[[0-6]]\.'])
+
 AC_MSG_CHECKING([checking for extra GHC flags])
 GHC_BYVERSION_FLAGS=
 # check for GHC supported flags that vary accross versions
@@ -638,6 +667,8 @@ AC_GHC_PKG_REQUIRE(vector)
 AC_GHC_PKG_REQUIRE(text)
 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=
@@ -712,9 +743,38 @@ fi
 AC_SUBST(ENABLE_MOND, $has_monitoring)
 AM_CONDITIONAL([ENABLE_MOND], [test "$has_monitoring" = True])
 
+# extra modules for metad functionality; also needed for tests
+METAD_PKG=
+AC_GHC_PKG_CHECK([snap-server], [],
+                 [NS_NODEV=1; METAD_PKG="$METAD_PKG snap-server"])
+has_metad=False
+if test "$enable_metadata" != no; then
+  if test -z "$METAD_PKG"; then
+    has_metad=True
+  elif test "$enable_metadata" = check; then
+    AC_MSG_WARN(m4_normalize([The required extra libraries for metad were
+                              not found ($METAD_PKG), metad disabled]))
+  else
+    AC_MSG_FAILURE(m4_normalize([The metadata functionality was requested, but
+                                 required libraries were not found:
+                                 $METAD_PKG]))
+  fi
+fi
+AC_SUBST(HS_REGEX_PCRE)
+if test "$has_metad" = True; then
+  AC_MSG_NOTICE([Enabling metadata usage])
+fi
+AC_SUBST(ENABLE_METADATA, $has_metad)
+AM_CONDITIONAL([ENABLE_METADATA], [test x$has_metad = xTrue])
+
+
 # development modules
 AC_GHC_PKG_CHECK([QuickCheck-2.*], [], [HS_NODEV=1], t)
-AC_GHC_PKG_CHECK([test-framework-0.6*], [], [HS_NODEV=1], t)
+AC_GHC_PKG_CHECK([test-framework-0.6*], [], [
+  AC_GHC_PKG_CHECK([test-framework-0.7*], [], [
+    AC_GHC_PKG_CHECK([test-framework-0.8*], [], [HS_NODEV=1], t)
+  ], t)
+], t)
 AC_GHC_PKG_CHECK([test-framework-hunit], [], [HS_NODEV=1])
 AC_GHC_PKG_CHECK([test-framework-quickcheck2], [], [HS_NODEV=1])
 AC_GHC_PKG_CHECK([temporary], [], [HS_NODEV=1])
@@ -826,7 +886,21 @@ fi
 AC_SUBST(MAN_HAS_WARNINGS)
 
 # Check for Python
+# We need a Python-2 interpreter, version at least 2.6. As AM_PATH_PYTHON
+# only supports a "minimal version" constraint, we check <3.0 afterwards.
+# We  tune _AM_PYTHON_INTERPRETER_LIST to first check interpreters that are
+# likely interpreters for Python 2.
+m4_define_default([_AM_PYTHON_INTERPRETER_LIST],
+                  [python2 python2.7 python2.6 python])
 AM_PATH_PYTHON(2.6)
+if $PYTHON -c "import sys
+if (sys.hexversion >> 24) < 3:
+  sys.exit(1)
+else:
+  sys.exit(0)
+"; then
+  AC_MSG_FAILURE([Can only work with an interpreter for Python 2])
+fi
 
 AC_PYTHON_MODULE(OpenSSL, t)
 AC_PYTHON_MODULE(simplejson, t)
@@ -836,7 +910,7 @@ AC_PYTHON_MODULE(pycurl, t)
 AC_PYTHON_MODULE(bitarray, t)
 AC_PYTHON_MODULE(ipaddr, t)
 AC_PYTHON_MODULE(mock)
-AC_PYTHON_MODULE(affinity)
+AC_PYTHON_MODULE(psutil)
 AC_PYTHON_MODULE(paramiko)
 
 # Development-only Python modules
index 73fb21b..6a47253 100644 (file)
@@ -38,12 +38,18 @@ readonly defaults_file="$SYSCONFDIR/default/ganeti"
 # they're stopped in reverse order.
 DAEMONS=(
   ganeti-noded
-  ganeti-masterd
+  ganeti-wconfd
   ganeti-rapi
   ganeti-luxid
   ganeti-kvmd
   )
 
+# This is the list of daemons that are loaded on demand; they should only be
+# stopped, not started.
+ON_DEMAND_DAEMONS=(
+  ganeti-metad
+  )
+
 _confd_enabled() {
   [[ "@CUSTOM_ENABLE_CONFD@" == True ]]
 }
@@ -60,9 +66,12 @@ if _mond_enabled; then
   DAEMONS+=( ganeti-mond )
 fi
 
+# The full list of all daemons we know about
+ALL_DAEMONS=( ${DAEMONS[@]} ${ON_DEMAND_DAEMONS[@]} )
+
 NODED_ARGS=
-MASTERD_ARGS=
 CONFD_ARGS=
+WCONFD_ARGS=
 LUXID_ARGS=
 RAPI_ARGS=
 MOND_ARGS=
@@ -88,12 +97,12 @@ _daemon_executable() {
 
 _daemon_usergroup() {
   case "$1" in
-    masterd)
-      echo "@GNTMASTERUSER@:@GNTMASTERDGROUP@"
-      ;;
     confd)
       echo "@GNTCONFDUSER@:@GNTCONFDGROUP@"
       ;;
+    wconfd)
+      echo "@GNTWCONFDUSER@:@GNTWCONFDGROUP@"
+      ;;
     luxid)
       echo "@GNTLUXIDUSER@:@GNTLUXIDGROUP@"
       ;;
@@ -150,6 +159,22 @@ check_exitcode() {
   return 0
 }
 
+# Checks if we should use systemctl to start/stop daemons
+use_systemctl() {
+  # Is systemd running as PID 1?
+  [ -d /run/systemd/system ] || return 1
+
+  type -p systemctl >/dev/null || return 1
+
+  # Does systemd know about Ganeti at all?
+  loadstate="$(systemctl show -pLoadState ganeti.target)"
+  if [ "$loadstate" = "LoadState=loaded" ]; then
+    return 0
+  fi
+
+  return 1
+}
+
 # Prints path to PID file for a daemon.
 daemon_pidfile() {
   if [[ "$#" -lt 1 ]]; then
@@ -185,7 +210,9 @@ list_start_daemons() {
 
 # Prints a list of all daemons in the order in which they should be stopped
 list_stop_daemons() {
-  list_start_daemons | tac
+  for name in "${ALL_DAEMONS[@]}"; do
+    echo "$name"
+  done | tac
 }
 
 # Checks whether a daemon name is known
@@ -197,7 +224,7 @@ is_daemon_name() {
 
   local name="$1"; shift
 
-  for i in "${DAEMONS[@]}"; do
+  for i in "${ALL_DAEMONS[@]}"; do
     if [[ "$i" == "$name" ]]; then
       return 0
     fi
@@ -218,7 +245,14 @@ check() {
   local pidfile=$(_daemon_pidfile $name)
   local daemonexec=$(_daemon_executable $name)
 
-  if type -p start-stop-daemon >/dev/null; then
+  if use_systemctl; then
+    activestate="$(systemctl show -pActiveState "${name}.service")"
+    if [ "$activestate" = "ActiveState=active" ]; then
+      return 0
+    else
+      return 1
+    fi
+  elif type -p start-stop-daemon >/dev/null; then
     start-stop-daemon --stop --signal 0 --quiet \
       --pidfile $pidfile
   else
@@ -249,6 +283,11 @@ start() {
     return 1
   fi
 
+  if use_systemctl; then
+    systemctl start "${name}.service"
+    return $?
+  fi
+
   # Read $<daemon>_ARGS and $EXTRA_<daemon>_ARGS
   eval local args="\"\$${ucname}_ARGS \$EXTRA_${ucname}_ARGS\""
 
@@ -281,7 +320,9 @@ stop() {
   local name="$1"; shift
   local pidfile=$(_daemon_pidfile $name)
 
-  if type -p start-stop-daemon >/dev/null; then
+  if use_systemctl; then
+    systemctl stop "${name}.service"
+  elif type -p start-stop-daemon >/dev/null; then
     start-stop-daemon --stop --quiet --oknodo --retry 30 \
       --pidfile $pidfile
   else
@@ -294,26 +335,42 @@ check_and_start() {
   local name="$1"
 
   if ! check $name; then
+    if use_systemctl; then
+      echo "${name} supervised by systemd but not running, will not restart."
+      return 1
+    fi
+
     start $name
   fi
 }
 
 # Starts the master role
 start_master() {
-  start ganeti-masterd
-  start ganeti-rapi
-  start ganeti-luxid
+  if use_systemctl; then
+    systemctl start ganeti-master.target
+  else
+    start ganeti-wconfd
+    start ganeti-rapi
+    start ganeti-luxid
+  fi
 }
 
 # Stops the master role
 stop_master() {
-  stop ganeti-luxid
-  stop ganeti-rapi
-  stop ganeti-masterd
+  if use_systemctl; then
+    systemctl stop ganeti-master.target
+  else
+    stop ganeti-luxid
+    stop ganeti-rapi
+    stop ganeti-wconfd
+  fi
 }
 
 # Start all daemons
 start_all() {
+  use_systemctl && systemctl start ganeti.target
+  # Fall through so that we detect any errors.
+
   for i in $(list_start_daemons); do
     local rc=0
 
@@ -331,9 +388,13 @@ start_all() {
 
 # Stop all daemons
 stop_all() {
-  for i in $(list_stop_daemons); do
-    stop $i
-  done
+  if use_systemctl; then
+    systemctl stop ganeti.target
+  else
+    for i in $(list_stop_daemons); do
+      stop $i
+    done
+  fi
 }
 
 # SIGHUP a process to force re-opening its logfiles
index c17de93..6c794f6 100755 (executable)
@@ -393,9 +393,8 @@ def ParseOptions():
                     type="int", default=DEFAULT_CONNECT_TIMEOUT,
                     help="Timeout for connection to be established (seconds)")
   parser.add_option("--compress", dest="compress", action="store",
-                    type="choice", help="Compression method",
-                    metavar="[%s]" % "|".join(constants.IEC_ALL),
-                    choices=list(constants.IEC_ALL), default=constants.IEC_GZIP)
+                    type="string", help="Compression method",
+                    default=constants.IEC_GZIP)
   parser.add_option("--expected-size", dest="exp_size", action="store",
                     type="string", default=None,
                     help="Expected import/export size (MiB)")
@@ -448,6 +447,69 @@ def ParseOptions():
   return (status_file_path, mode)
 
 
+# Return code signifying that no program was found
+PROGRAM_NOT_FOUND_RCODE = 127
+
+
+def _RunWithTimeout(cmd, timeout, silent=False):
+  """Runs a command, killing it if a timeout was reached.
+
+  Uses the alarm signal, not thread-safe. Waits regardless of whether the
+  command exited early.
+
+  @type timeout: number
+  @param timeout: Timeout, in seconds
+  @type silent: Boolean
+  @param silent: Whether command output should be suppressed
+  @rtype: tuple of (bool, int)
+  @return: Whether the command timed out, and the return code
+
+  """
+  try:
+    if silent:
+      with open(os.devnull, 'wb') as null_fd:
+        p = subprocess.Popen(cmd, stdout=null_fd, stderr=null_fd)
+    else:
+      p = subprocess.Popen(cmd)
+
+  except OSError:
+    return False, PROGRAM_NOT_FOUND_RCODE
+
+  time.sleep(timeout)
+
+  timed_out = False
+  status = p.poll()
+  if status is None:
+    timed_out = True
+    p.kill()
+
+  return timed_out, p.wait()
+
+
+CHECK_SWITCH = "-h"
+
+
+def VerifyOptions():
+  """Performs various runtime checks to make sure the options are valid.
+
+  """
+  if options.compress != constants.IEC_NONE:
+    utility_name = constants.IEC_COMPRESSION_UTILITIES.get(options.compress,
+                                                           options.compress)
+    timed_out, rcode = \
+      _RunWithTimeout([utility_name, CHECK_SWITCH], 2, silent=True)
+
+    if timed_out:
+      raise Exception("The invoked utility has timed out - the %s switch to"
+                      " check for presence must be supported" % CHECK_SWITCH)
+
+    if rcode != 0:
+      raise Exception("Verification attempt of selected compression method %s"
+                      " failed - check that %s is present and can be invoked"
+                      " safely with the %s switch" %
+                      (options.compress, utility_name, CHECK_SWITCH))
+
+
 class ChildProcess(subprocess.Popen):
   def __init__(self, env, cmd, noclose_fds):
     """Initializes this class.
@@ -520,6 +582,9 @@ def main():
   status_file = StatusFile(status_file_path)
   try:
     try:
+      # Option verification
+      VerifyOptions()
+
       # Pipe to receive socat's stderr output
       (socat_stderr_read_fd, socat_stderr_write_fd) = os.pipe()
 
index 39bec38..a62adf8 100755 (executable)
@@ -23,6 +23,21 @@ COMP_FILEPATH=$ROOT/$COMP_FILENAME
 TEMP_DATA_DIR=`mktemp -d`
 ACTUAL_DATA_DIR=$DATA_DIR
 ACTUAL_DATA_DIR=${ACTUAL_DATA_DIR:-$TEMP_DATA_DIR}
+GHC_VERSION="7.6.3"
+CABAL_INSTALL_VERSION="1.18.0.2"
+SHA1_LIST='
+cabal-install-1.18.0.2.tar.gz 2d1f7a48d17b1e02a1e67584a889b2ff4176a773
+ghc-7.6.3-i386-unknown-linux.tar.bz2 f042b4171a2d4745137f2e425e6949c185f8ea14
+ghc-7.6.3-x86_64-unknown-linux.tar.bz2 46ec3f3352ff57fba0dcbc8d9c20f7bcb6924b77
+'
+
+# export all variables needed in the schroot
+export ARCH GHC_VERSION CABAL_INSTALL_VERSION SHA1_LIST
+
+# Use gzip --rsyncable if available, to speed up transfers of generated files
+# The environment variable GZIP is read automatically by 'gzip',
+# see ENVIRONMENT in gzip(1).
+gzip --rsyncable </dev/null >/dev/null 2>&1 && export GZIP="--rsyncable"
 
 #Runnability checks
 if [ $USER != 'root' ]
@@ -122,6 +137,72 @@ fi
 in_chroot -- \
   apt-get update
 
+
+# Functions for downloading and checking Haskell core components.
+# The functions run commands within the schroot.
+
+# arguments : file_name expected_sha1
+function verify_sha1 {
+  local SUM="$( in_chroot -- sha1sum "$1" | awk '{print $1;exit}' )"
+  if [ "$SUM" != "$2" ] ; then
+    echo "ERROR: The SHA1 sum $SUM of $1 doesn't match $2." >&2
+    return 1
+  else
+    echo "SHA1 of $1 verified correct."
+  fi
+}
+
+# arguments: URL
+function lookup_sha1 {
+  grep -o "${1##*/}"'\s\+[0-9a-fA-F]*' <<<"$SHA1_LIST" | awk '{print $2;exit}'
+}
+
+# arguments : file_name URL
+function download {
+  local FNAME="$1"
+  local URL="$2"
+  in_chroot -- wget --output-document="$FNAME" "$URL"
+  verify_sha1 "$FNAME" "$( lookup_sha1 "$URL" )"
+}
+
+function install_ghc {
+  local GHC_ARCH=$ARCH
+  local TDIR=$( schroot -c $CHNAME -d / -- mktemp -d )
+  [ -n "$TDIR" ]
+  if [ "$ARCH" == "amd64" ] ; then
+    download "$TDIR"/ghc.tar.bz2 \
+      http://www.haskell.org/ghc/dist/${GHC_VERSION}/ghc-${GHC_VERSION}-x86_64-unknown-linux.tar.bz2
+  elif [ "$ARCH" == "i386" ] ; then
+    download "$TDIR"/ghc.tar.bz2 \
+      http://www.haskell.org/ghc/dist/${GHC_VERSION}/ghc-${GHC_VERSION}-i386-unknown-linux.tar.bz2
+  else
+    echo "Don't know what GHC to download for architecture $ARCH" >&2
+    return 1
+  fi
+  schroot -c $CHNAME -d "$TDIR" -- \
+    tar xjf ghc.tar.bz2
+  schroot -c $CHNAME -d "$TDIR/ghc-${GHC_VERSION}" -- \
+    ./configure --prefix=/usr/local
+  schroot -c $CHNAME -d "$TDIR/ghc-${GHC_VERSION}" -- \
+     make install
+  schroot -c $CHNAME -d "/" -- \
+    rm -rf "$TDIR"
+}
+
+function install_cabal {
+  local TDIR=$( schroot -c $CHNAME -d / -- mktemp -d )
+  [ -n "$TDIR" ]
+  download "$TDIR"/cabal-install.tar.gz \
+    http://www.haskell.org/cabal/release/cabal-install-${CABAL_INSTALL_VERSION}/cabal-install-${CABAL_INSTALL_VERSION}.tar.gz
+  schroot -c $CHNAME -d "$TDIR" -- \
+    tar xzf cabal-install.tar.gz
+  schroot -c $CHNAME -d "$TDIR/cabal-install-${CABAL_INSTALL_VERSION}" -- \
+    bash -c 'EXTRA_CONFIGURE_OPTS="--enable-library-profiling" ./bootstrap.sh --global'
+  schroot -c $CHNAME -d "/" -- \
+    rm -rf "$TDIR"
+}
+
+
 case $DIST_RELEASE in
 
   squeeze)
@@ -131,13 +212,11 @@ case $DIST_RELEASE in
     in_chroot -- \
       $APT_INSTALL \
         autoconf automake \
-        ghc cabal-install \
-        libghc6-curl-dev \
-        libghc6-parallel-dev \
-        libghc6-text-dev \
-        libghc6-vector-dev \
+        zlib1g-dev \
+        libgmp3-dev \
+        libcurl4-gnutls-dev \
         libpcre3-dev \
-        libghc6-zlib-dev \
+        happy \
         hlint hscolour pandoc \
         graphviz qemu-utils \
         python-docutils \
@@ -166,46 +245,50 @@ case $DIST_RELEASE in
         coverage==3.4 \
         bitarray==0.8.0
 
-    in_chroot -- \
-      cabal update
+    install_ghc
 
-    in_chroot -- \
-      cabal install --global \
-        blaze-builder==0.3.1.1 \
-        network==2.3 \
-        regex-pcre==0.94.4 \
-        hinotify==0.3.2 \
-        hslogger==1.1.4 \
-        quickcheck==2.5.1.1 \
-        attoparsec==0.10.1.1 \
-        crypto==4.2.4 \
-        MonadCatchIO-transformers==0.2.2.0 \
-        mtl==2.0.1.0 \
-        hashable==1.1.2.0 \
-        case-insensitive==0.3 \
-        parsec==3.0.1 \
-        snap-server==0.8.1 \
-        json==0.4.4
-
-    in_chroot -- \
-      cabal install --global \
-        hunit==1.2.5.2 \
-        happy==1.18.10 \
-        hlint==1.8.43 \
-        hscolour==1.20.3 \
-        temporary==1.1.2.3 \
-        test-framework==0.6.1 \
-        test-framework-hunit==0.2.7 \
-        test-framework-quickcheck2==0.2.12.3
+    install_cabal
 
     in_chroot -- \
-      cabal install --global cabal-file-th
-
-    in_chroot -- \
-      cabal install --global shelltestrunner
+      cabal update
 
+    # sinec we're using Cabal >=1.16, we can use the parallel install option
     in_chroot -- \
-      cabal install --global base64-bytestring
+      cabal install --global -j --enable-library-profiling \
+        attoparsec-0.11.1.0 \
+        base64-bytestring-1.0.0.1 \
+        blaze-builder-0.3.3.2 \
+        case-insensitive-1.1.0.3 \
+        Crypto-4.2.5.1 \
+        curl-1.3.8 \
+        happy \
+        hashable-1.2.1.0 \
+        hinotify-0.3.6 \
+        hscolour-1.20.3 \
+        hslogger-1.2.3 \
+        json-0.7 \
+        lifted-base-0.2.2.0 \
+        lens-4.0.4 \
+        MonadCatchIO-transformers-0.3.0.0 \
+        network-2.4.1.2 \
+        parallel-3.2.0.4 \
+        parsec-3.1.3 \
+        regex-pcre-0.94.4 \
+        temporary-1.2.0.1 \
+        vector-0.10.9.1 \
+        zlib-0.5.4.1 \
+        \
+        hlint-1.8.57 \
+        HUnit-1.2.5.2 \
+        QuickCheck-2.6 \
+        test-framework-0.8.0.3 \
+        test-framework-hunit-0.3.0.1 \
+        test-framework-quickcheck2-0.3.0.2 \
+        \
+        snap-server-0.9.4.0 \
+        \
+        cabal-file-th-0.2.3 \
+        shelltestrunner
 
     #Install selected packages from backports
     in_chroot -- \
@@ -228,8 +311,8 @@ 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 \
-      libghc6-zlib-dev \
-      cabal-install\
+      libghc-zlib-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 \
@@ -252,10 +335,13 @@ case $DIST_RELEASE in
        cabal update
 
      in_chroot -- \
-       cabal install --global base64-bytestring
+       cabal install --global \
+        'base64-bytestring>=1' \
+        lens-3.10.2 \
+        'lifted-base>=0.1.2'
 ;;
 
-  *)
+  testing)
 
     in_chroot -- \
       $APT_INSTALL \
@@ -267,21 +353,101 @@ 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-base64-bytestring-dev libghc-lens-dev libghc-lifted-base-dev \
+      cabal-install \
       python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
-      python-simplejson python-pyinotify python-pycurl python-paramiko \
+      python-simplejson python-pycurl python-pyinotify python-paramiko \
       python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
       shelltestrunner python-dev pylint openssh-client vim git git-email
+;;
+
+  precise)
+    # ghc, git-email and other dependencies are hosted in the universe
+    # repository, which is not enabled by default.
+    echo "Adding universe repository..."
+    cat > $CHDIR/etc/apt/sources.list.d/universe.list <<EOF
+deb http://archive.ubuntu.com/ubuntu precise universe
+EOF
+    in_chroot -- \
+      apt-get update
+
+    echo "Installing packages"
+    in_chroot -- \
+      $APT_INSTALL \
+      autoconf automake ghc ghc-haddock libghc-network-dev \
+      libghc-test-framework{,-hunit,-quickcheck2}-dev \
+      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
+      libghc-parallel-dev libghc-utf8-string-dev \
+      libghc-hslogger-dev libghc-crypto-dev \
+      libghc-regex-pcre-dev libghc-attoparsec-dev \
+      libghc-vector-dev libghc-temporary-dev \
+      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
+      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
+      python-simplejson python-pyinotify python-pycurl python-paramiko \
+      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
+      python-dev pylint openssh-client vim git git-email \
+      build-essential
+
+    echo "Installing cabal packages"
+    in_chroot -- \
+      $APT_INSTALL cabal-install
+
+    in_chroot -- \
+      cabal update
+
+     in_chroot -- \
+       cabal install --global \
+        'base64-bytestring>=1' \
+        lens-3.10.2 \
+        'lifted-base>=0.1.2'
+
+    in_chroot -- \
+      cabal install --global shelltestrunner
+    ;;
+
+  *)
+    in_chroot -- \
+      $APT_INSTALL \
+      autoconf automake ghc ghc-haddock libghc-network-dev \
+      libghc-test-framework{,-hunit,-quickcheck2}-dev \
+      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
+      libghc-parallel-dev libghc-utf8-string-dev \
+      libghc-hslogger-dev libghc-crypto-dev \
+      libghc-regex-pcre-dev libghc-attoparsec-dev \
+      libghc-vector-dev libghc-temporary-dev \
+      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
+      libghc-lifted-base-dev \
+      libghc-base64-bytestring-dev \
+      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
+      python-simplejson python-pyinotify python-pycurl python-paramiko \
+      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
+      shelltestrunner python-dev pylint openssh-client vim git git-email \
+      build-essential
 
 ;;
 esac
 
-echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
+# print what packages and versions are installed:
+in_chroot -- \
+  cabal list --installed --simple-output
 
 in_chroot -- \
   $APT_INSTALL sudo fakeroot rsync locales less socat
 
-in_chroot -- \
-  locale-gen
+# Configure the locale
+case $DIST_RELEASE in
+  precise)
+    in_chroot -- \
+      $APT_INSTALL language-pack-en
+    ;;
+  *)
+    echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
+
+    in_chroot -- \
+      locale-gen
+    ;;
+esac
 
 in_chroot -- \
   $APT_INSTALL lvm2 ssh bridge-utils iproute iputils-arping \
@@ -289,7 +455,7 @@ in_chroot -- \
                python-mock fping qemu-utils
 
 in_chroot -- \
-  easy_install affinity
+  easy_install psutil
 
 in_chroot -- \
   easy_install jsonpointer \
diff --git a/devel/check_copyright b/devel/check_copyright
new file mode 100755 (executable)
index 0000000..ea1fcea
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+# Copyright (C) 2014 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Script to check whether the local dirty commits are changing files
+# which do not have an updated copyright.
+#
+# The script will determine your current remote branch and local
+# branch, from which it will extract the commits to analyze.
+# Afterwards, for each commit, it will see which files are being
+# modified and, for each file, it will check the copyright.
+
+function join {
+    local IFS="$1"
+    shift
+    echo "$*"
+}
+
+# Determine the tracking branch for the current branch
+readonly REMOTE=$(git branch -vv | grep -e "^\*" | sed -e "s/ \+/ /g" | awk '{ print $4 }' | grep "\[" | tr -d ":[]")
+
+if [ -z "$REMOTE" ]
+then
+    echo check_copyright: failed to get remote branch
+    exit 1
+fi
+
+# Determine which commits have no been pushed (i.e, diff between the
+# remote branch and the current branch)
+COMMITS=$(git log --pretty=format:'%h' ${REMOTE}..HEAD)
+
+if [ -z "$COMMITS" ]
+then
+    echo check_copyright: there are no commits to check
+    exit 0
+fi
+
+# for each commit, check its files
+for commit in $(echo $COMMITS | tac -s " ")
+do
+    FILES=$(git diff-tree --no-commit-id --name-only -r $commit)
+
+    if [ -z "$FILES" ]
+    then
+       echo check_copyright: commit \"$commit\" has no files to check
+    else
+       # for each file, check if it is in the 'lib' or 'src' dirs
+       # and, if so, check the copyright
+       for file in $FILES
+       do
+           DIR=$(echo $file | cut -d "/" -f 1)
+
+           if [ "$DIR" = lib -o "$DIR" = src ]
+           then
+               COPYRIGHT=$(grep "Copyright (C)" $file)
+               YEAR=$(date +%G)
+
+               if [ -z "$COPYRIGHT" ]
+               then
+                   echo check_copyright: commit \"$commit\" misses \
+                       copyright for \"$file\"
+               elif ! echo $COPYRIGHT | grep -o $YEAR > /dev/null
+               then
+                   echo check_copyright: commit \"$commit\" misses \
+                       \"$YEAR\" copyright for \"$file\"
+               fi
+           fi
+       done
+    fi
+done
index 5b193e4..eb0b72b 100644 (file)
@@ -24,13 +24,30 @@ don't forget to use "shred" to remove files securely afterwards).
 Replacing SSL keys
 ==================
 
-The cluster SSL key is stored in ``/var/lib/ganeti/server.pem``.
+The cluster-wide SSL key is stored in ``/var/lib/ganeti/server.pem``.
+Besides that, since Ganeti 2.11, each node has an individual node
+SSL key, which is stored in ``/var/lib/ganeti/client.pem``. This
+client certificate is signed by the cluster-wide SSL certficate.
 
-Run the following command to generate a new key::
+To renew the individual node certificates, run this command::
+
+  gnt-cluster renew-crypto --new-node-certificates
+
+Run the following command to generate a new cluster-wide certificate::
 
   gnt-cluster renew-crypto --new-cluster-certificate
 
-  # Older version, which don't have this command, can instead use:
+Note that this triggers both, the renewal of the cluster certificate
+as well as the renewal of the individual node certificate. The reason
+for this is that the node certificates are signed by the cluster
+certificate and thus they need to be renewed and signed as soon as
+the changes certificate changes. Therefore, the command above is
+equivalent to::
+
+  gnt-cluster renew-crypto --new-cluster-certificate --new-node-certificates
+
+On older versions, which don't have this command, use this instead::
+
   chmod 0600 /var/lib/ganeti/server.pem &&
   openssl req -new -newkey rsa:1024 -days 1825 -nodes \
    -x509 -keyout /var/lib/ganeti/server.pem \
@@ -42,6 +59,10 @@ Run the following command to generate a new key::
 
   gnt-cluster command /etc/init.d/ganeti restart
 
+Note that older versions don't have individual node certificates and thus
+one does not have to handle the creation and distribution of them.
+
+
 Replacing SSH keys
 ==================
 
index e73b753..15b848a 100644 (file)
@@ -114,7 +114,7 @@ pygments_style = "sphinx"
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = "default"
+html_theme = os.getenv("HTML_THEME")
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
similarity index 64%
copy from doc/design-2.11.rst
copy to doc/design-2.12.rst
index 2b2ca22..cf18c52 100644 (file)
@@ -1,13 +1,15 @@
 ==================
-Ganeti 2.11 design
+Ganeti 2.12 design
 ==================
 
-The following design documents have been implemented in Ganeti 2.11.
+The following design documents have been implemented in Ganeti 2.12.
 
-- :doc:`design-internal-shutdown`
-- :doc:`design-kvmd`
+- :doc:`design-daemons`
+- :doc:`design-systemd`
+- :doc:`design-cpu-speed`
 
-The following designs have been partially implemented in Ganeti 2.11.
+The following designs have been partially implemented in Ganeti 2.12.
 
 - :doc:`design-node-security`
 - :doc:`design-hsqueeze`
+- :doc:`design-os`
index a4c2eb9..b0ada07 100644 (file)
@@ -202,10 +202,13 @@ details.
 For example, to run a specific thread-id on CPUs 1 or 3 the mask is
 0x0000000A.
 
-We will control process and thread affinity using the python affinity
-package (http://pypi.python.org/pypi/affinity). This package is a Python
-wrapper around the two affinity system calls, and has no other
-requirements.
+As of 2.12, the psutil python package
+(https://github.com/giampaolo/psutil) will be used to control process
+and thread affinity. The affinity python package
+(http://pypi.python.org/pypi/affinity) was used before, but it was not
+invoking the two underlying system calls appropriately, using a cast
+instead of the CPU_SET macro, causing failures for masks referencing
+more than 63 CPUs.
 
 Alternative Design Options
 --------------------------
diff --git a/doc/design-cpu-speed.rst b/doc/design-cpu-speed.rst
new file mode 100644 (file)
index 0000000..7787b79
--- /dev/null
@@ -0,0 +1,43 @@
+======================================
+Taking relative CPU speed into account
+======================================
+
+.. contents:: :depth: 4
+
+This document describes the suggested addition of a new
+node-parameter, describing the CPU speed of a node,
+relative to that of a normal node in the node group.
+
+
+Current state and shortcomings
+==============================
+
+Currently, for balancing a cluster, for most resources (disk, memory),
+the ratio between the amount used and the amount available is taken as
+a measure of load for that resources. As ``hbal`` tries to even out the
+load in terms of these measures, larger nodes get a larger share of the
+instances, even for a cluster not running at full capacity.
+
+For for one resources, however, hardware differences are not taken into
+account: CPU speed. For CPU, the load is measured by the ratio of used virtual
+to physical CPUs on the node. Balancing this measure implictly assumes
+equal speed of all CPUs.
+
+
+Proposed changes
+================
+
+It is proposed to add a new node parameter, ``cpu_speed``, that is a
+floating-point number, with default value ``1.0``. It can be modified in the
+same ways, as all other node parameters.
+
+The cluster metric used by ``htools`` will be changed to use the ratio
+of virtual to physical cpus weighted by speed, rather than the plain
+virtual-to-physical ratio. So, when balancing, nodes will be
+considered as if they had physical cpus equal to ``cpu_speed`` times
+the actual number.
+
+Finally, it should be noted that for IO load, in non-dedicated Ganeti, the
+``spindle_count`` already serves the same purpose as the newly proposed
+``cpu_speed``. It is a parameter to measure the amount of IO a node can handle
+in arbitrary units.
index db72d72..1299f39 100644 (file)
@@ -266,6 +266,109 @@ leaving the codebase in a consistent and usable state.
    intelligent one. Also, the implementation of :doc:`design-optables` can be
    started.
 
+Job death detection
+-------------------
+
+**Requirements:**
+
+- It must be possible to reliably detect a death of a process even under
+  uncommon conditions such as very heavy system load.
+- A daemon must be able to detect a death of a process even if the
+  daemon is restarted while the process is running.
+- The solution must not rely on being able to communicate with
+  a process.
+- The solution must work for the current situation where multiple jobs
+  run in a single process.
+- It must be POSIX compliant.
+
+These conditions rule out simple solutions like checking a process ID
+(because the process might be eventually replaced by another process
+with the same ID) or keeping an open connection to a process.
+
+**Solution:** As a job process is spawned, before attempting to
+communicate with any other process, it will create a designated empty
+lock file, open it, acquire an *exclusive* lock on it, and keep it open.
+When connecting to a daemon, the job process will provide it with the
+path of the file. If the process dies unexpectedly, the operating system
+kernel automatically cleans up the lock.
+
+Therefore, daemons can check if a process is dead by trying to acquire
+a *shared* lock on the lock file in a non-blocking mode:
+
+- If the locking operation succeeds, it means that the exclusive lock is
+  missing, therefore the process has died, but the lock
+  file hasn't been cleaned up yet. The daemon should release the lock
+  immediately. Optionally, the daemon may delete the lock file.
+- If the file is missing, the process has died and the lock file has
+  been cleaned up.
+- If the locking operation fails due to a lock conflict, it means
+  the process is alive.
+
+Using shared locks for querying lock files ensures that the detection
+works correctly even if multiple daemons query a file at the same time.
+
+A job should close and remove its lock file when completely finishes.
+The WConfD daemon will be responsible for removing stale lock files of
+jobs that didn't remove its lock files themselves.
+
+**Statelessness of the protocol:** To keep our protocols stateless,
+the job id and the path the to lock file are sent as part of every
+request that deals with resources, in particular the Ganeti Locks.
+All resources are owned by the pair (job id, lock file). In this way,
+several jobs can live in the same process (as it will be in the
+transition period), but owner death detection still only depends on the
+owner of the resource. In particular, no additional lookup table is
+needed to obtain the lock file for a given owner.
+
+**Considered alternatives:** An alternative to creating a separate lock
+file would be to lock the job status file. However, file locks are kept
+only as long as the file is open. Therefore any operation followed by
+closing the file would cause the process to release the lock. In
+particular, with jobs as threads, the master daemon wouldn't be able to
+keep locks and operate on job files at the same time.
+
+Job execution
+-------------
+
+As the Luxi daemon will be responsible for executing jobs, it needs to
+start jobs in such a way that it can properly detect if the job dies
+under any circumstances (such as Luxi daemon being restarted in the
+process).
+
+The name of the lock file will be stored in the corresponding job file
+so that anybody is able to check the status of the process corresponding
+to a job.
+
+The proposed procedure:
+
+#. The Luxi daemon saves the name of its own lock file into the job file.
+#. The Luxi daemon forks, creating a bi-directional pipe with the child
+   process.
+#. The child process creates and locks its own, proper lock file and
+   handles its name to the Luxi daemon through the pipe.
+#. The Luxi daemon saves the name of the lock file into the job file and
+   confirms it to the child process.
+#. Only then the child process can replace itself by the actual job
+   process.
+
+If the child process detects that the pipe is broken before receiving the
+confirmation, it must terminate, not starting the actual job.
+This way, the actual job is only started if it is ensured that its lock
+file name is written to the job file.
+
+If the Luxi daemon detects that the pipe is broken before successfully
+sending the confirmation in step 4., it assumes that the job has failed.
+If the pipe gets broken after sending the confirmation, no further
+action is necessary. If the child doesn't receive the confirmation,
+it will die and its death will be detected by Luxid eventually.
+
+If the Luxi daemon dies before completing the procedure, the job will
+not be started. If the job file contained the daemon's lock file name,
+it will be detected as dead (because the daemon process died). If the
+job file already contained its proper lock file, it will also be
+detected as dead (because the child process won't start the actual job
+and die).
+
 WConfD details
 --------------
 
@@ -276,10 +379,6 @@ document with a remote function name and data for its arguments. The server
 replies with a JSON response document containing either the result of
 signalling a failure.
 
-There will be a special RPC call for identifying a client when connecting to
-WConfD. The client will tell WConfD it's job number and process ID. WConfD will
-fail any other RPC calls before a client identifies this way.
-
 Any state associated with client processes will be mirrored on persistent
 storage and linked to the identity of processes so that the WConfD daemon will
 be able to resume its operation at any point after a restart or a crash. WConfD
@@ -294,22 +393,53 @@ Configuration management
 The new configuration management protocol will be implemented in the following
 steps:
 
-#. Reimplement all current methods of ``ConfigWriter`` for reading and writing
-   the configuration of a cluster in WConfD.
-#. Expose each of those functions in WConfD as a RPC function. This will allow
-   easy future extensions or modifications.
-#. Replace ``ConfigWriter`` with a stub (preferably automatically generated
-   from the Haskell code) that will contain the same methods as the current
-   ``ConfigWriter`` and delegate all calls to its methods to WConfD.
-
-After this step it'll be possible access the configuration from separate
-processes.
-
-Future aims:
+Step 1:
+  #. Implement the following functions in WConfD and export them through
+     RPC:
+
+     - Obtain a single internal lock, either in shared or
+       exclusive mode. This lock will substitute the current lock
+       ``_config_lock`` in config.py.
+     - Release the lock.
+     - Return the whole configuration data to a client.
+     - Receive the whole configuration data from a client and replace the
+       current configuration with it. Distribute it to master candidates
+       and distribute the corresponding *ssconf*.
+
+     WConfD must detect deaths of its clients (see `Job death
+     detection`_) and release locks automatically.
+
+  #. In config.py modify public methods that access configuration:
+
+     - Instead of acquiring a local lock, obtain a lock from WConfD
+       using the above functions
+     - Fetch the current configuration from WConfD.
+     - Use it to perform the method's task.
+     - If the configuration was modified, send it to WConfD at the end.
+     - Release the lock to WConfD.
+
+  This will decouple the configuration management from the master daemon,
+  even though the specific configuration tasks will still performed by
+  individual jobs.
+
+  After this step it'll be possible access the configuration from separate
+  processes.
+
+Step 2:
+  #. Reimplement all current methods of ``ConfigWriter`` for reading and
+     writing the configuration of a cluster in WConfD.
+  #. Expose each of those functions in WConfD as a separate RPC function.
+     This will allow easy future extensions or modifications.
+  #. Replace ``ConfigWriter`` with a stub (preferably automatically
+     generated from the Haskell code) that will contain the same methods
+     as the current ``ConfigWriter`` and delegate all calls to its
+     methods to WConfD.
+
+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.
 
--  Optionally refactor the RPC calls to reduce their number or improve their
-   efficiency (for example by obtaining a larger set of data instead of
-   querying items one by one).
 
 Locking
 +++++++
@@ -350,6 +480,17 @@ protocol will allow the following operations on the set:
   provided for convenience, it's redundant wrt. *list* and *update*. Immediate,
   never fails.
 
+Addidional restrictions due to lock implications:
+  Ganeti supports locks that act as if a lock on a whole group (like all nodes)
+  were held. To avoid dead locks caused by the additional blockage of those
+  group locks, we impose certain restrictions. Whenever `A` is a group lock and
+  `B` belongs to `A`, then the following holds.
+
+  - `A` is in lock order before `B`.
+  - All locks that are in the lock order between `A` and `B` also belong to `A`.
+  - It is considered a lock-order violation to ask for an exclusive lock on `B`
+    while holding a shared lock on `A`.
+
 After this step it'll be possible to use locks from jobs as separate processes.
 
 The above set of operations allows the clients to use various work-flows. In particular:
@@ -401,20 +542,13 @@ Further considerations
 
 There is a possibility that a job will finish performing its task while LuxiD
 and/or WConfD will not be available.
-In order to deal with this situation, each job will write the results of its
-execution on a file. The name of this file will be known to LuxiD before
-starting the job, and will be stored together with the job ID, and the
-name of the job-unique socket.
-
-The job, upon ending its execution, will signal LuxiD (through the socket), so
-that it can read the result of the execution and release the locks as needed.
-
-In case LuxiD is not available at that time, the job will just terminate without
-signalling it, and writing the results on file as usual. When a new LuxiD
-becomes available, it will have the most up-to-date list of running jobs
-(received via replication from the former LuxiD), and go through it, cleaning up
-all the terminated jobs.
-
+In order to deal with this situation, each job will update its job file
+in the queue. This is race free, as LuxiD will no longer touch the job file,
+once the job is started; a corollary of this is that the job also has to
+take care of replicating updates to the job file. LuxiD will watch job files for
+changes to determine when a job was cleanly finished. To determine jobs
+that died without having the chance of updating the job file, the `Job death
+detection`_ mechanism will be used.
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-disks.rst b/doc/design-disks.rst
new file mode 100644 (file)
index 0000000..ec9c63d
--- /dev/null
@@ -0,0 +1,166 @@
+=====
+Disks
+=====
+
+.. contents:: :depth: 4
+
+This is a design document detailing the implementation of disks as a new
+top-level citizen in the config file (just like instances, nodes etc).
+
+
+Current state and shortcomings
+==============================
+
+Currently, Disks are stored in Ganeti's config file as a list
+(container) of Disk objects under the Instance in which they belong.
+This implementation imposes a number of limitations:
+
+* A Disk object cannot live outside an Instance. This means that one
+  cannot detach a disk from an instance (without destroying the disk)
+  and then reattach it (to the same or even to a different instance).
+
+* Disks are not taggable objects, as only top-level citizens of the
+  config file can be made taggable. Having taggable disks will allow for
+  further customizations.
+
+
+Proposed changes
+================
+
+The implementation is going to be split in four parts:
+
+* Make disks a top-level citizen in config file. The Instance object
+  will no longer contain a list of Disk objects, but a list of disk
+  UUIDs.
+
+* Add locks for Disk objects and make them taggable.
+
+* Allow to attach/detach an existing disk to/from an instance.
+
+* Allow creation/modification/deletion of disks that are not attached to
+  any instance (requires new LUs for disks).
+
+
+Design decisions
+================
+
+Disks as config top-level citizens
+----------------------------------
+
+The first patch-series is going to add a new top-level citizen in the
+config object (namely ``disks``) and separate the disk objects from the
+instances. In doing so there are a number of problems that we have to
+overcome:
+
+* How the Disk object will be represented in the config file and how it
+  is going to be connected with the instance it belongs to.
+
+* How the existing code will get the disks belonging to an instance.
+
+* What it means for a disk to be attached/detached to/from an instance.
+
+* How disks are going to be created/deleted, attached/detached using
+  the existing code.
+
+
+Disk representation
+~~~~~~~~~~~~~~~~~~~
+
+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
+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``
+slot is going to be a list of disk UUIDs. The `Disk` object is not going
+to have a slot pointing to the `Instance` in which it belongs since this
+is redundant.
+
+
+Get instance's disks
+~~~~~~~~~~~~~~~~~~~~
+
+A new function ``GetInstanceDisks`` will be added to the config that given an
+instance will return a list of Disk objects with the disks attached to this
+instance. This list will be exactly the same as 'instance.disks' was before.
+Everywhere in the code we are going to replace the 'instance.disks' (which from
+now one will contain a list of disk UUIDs) with the function
+``GetInstanceDisks``.
+
+Since disks will not be part of the `Instance` object any more, 'all_nodes' and
+'secondary_nodes' can not be `Instance`'s properties. Instead we will use the
+functions ``GetInstanceNodes`` and ``GetInstanceSecondaryNodes`` from the
+config to compute these values.
+
+
+Configuration changes
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``ConfigData`` object gets one extra slot: ``disks``. Also there
+will be two new functions, ``AddDisk`` and ``RemoveDisk`` that will
+create/remove a disk objects from the config.
+
+The ``VerifyConfig`` function will be changed so it can check that there
+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
+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).
+
+
+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.
+
+* Remove an existing `Disk` object from the config.
+
+* Attach an existing `Disk` to an existing `Instance`.
+
+* Detach an existing `Disk` from an existing `Instance`.
+
+The first two operations will be performed using the config functions
+``AddDisk`` and ``RemoveDisk`` respectively where the last two operations
+will be performed using the functions ``AttachInstanceDisk`` and
+``DetachInstanceDisk``.
+
+Since Ganeti doesn't allow for a `Disk` object to not be attached anywhere (for
+now) we will create two wrapper functions (namely ``AddInstanceDisk`` and
+``RemoveInstanceDisk``) which will add and attach a disk at the same time
+(respectively detach and remove a disk).
+
+In addition since Ganeti doesn't allow for a `Disk` object to be attached to
+more than one `Instance` at once, when attaching a disk to an instance we have
+to make sure that the disk is not attached anywhere else.
+
+
+Backend changes
+~~~~~~~~~~~~~~~
+
+The backend needs access to the disks of an `Instance` but doesn't have access to
+the `GetInstanceDisks` function from the config file. Thus we will create a new
+`Instance` slot (namely ``disks_info``) that will get annotated (during RPC)
+with the instance's disk objects. So in the backend we will only have to
+replace the ``disks`` slot with ``disks_info``.
+
+
+.. TODO: Locks for Disk objects
+
+.. TODO: Attach/Detach disks
+
+.. TODO: LUs for disks
+
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index b5ad120..321871d 100644 (file)
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.11
+.. Last updated for Ganeti 2.12
 
 .. toctree::
    :maxdepth: 2
@@ -16,10 +16,11 @@ Design document drafts
    design-hugepages-support.rst
    design-optables.rst
    design-ceph-ganeti-support.rst
-   design-daemons.rst
    design-hsqueeze.rst
    design-os.rst
+   design-move-instance-improvements.rst
    design-node-security.rst
+   design-disks.rst
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-move-instance-improvements.rst b/doc/design-move-instance-improvements.rst
new file mode 100644 (file)
index 0000000..c64b4bf
--- /dev/null
@@ -0,0 +1,366 @@
+========================================
+Instance move improvements
+========================================
+
+.. contents:: :depth: 3
+
+Ganeti provides tools for moving instances within and between clusters. Through
+special export and import calls, a new instance is created with the disk data of
+the existing one.
+
+The tools work correctly and reliably, but depending on bandwidth and priority,
+an instance disk of considerable size requires a long time to transfer. The
+length of the transfer is inconvenient at best, but the problem becomes only
+worse if excessive locking causes a move operation to be delayed for a longer
+period of time, or to block other operations.
+
+The performance of moves is a complex topic, with available bandwidth,
+compression, and encryption all being candidates for choke points that bog down
+a transfer. Depending on the environment a move is performed in, tuning these
+can have significant performance benefits, but Ganeti does not expose many
+options needed for such tuning. The details of what to expose and what tradeoffs
+can be made will be presented in this document.
+
+Apart from existing functionality, some beneficial features can be introduced to
+help with instance moves. Zeroing empty space on instance disks can be useful
+for drastically improving the qualities of compression, effectively not needing
+to transfer unused disk space during moves. Compression itself can be improved
+by using different tools. The encryption used can be weakened or eliminated for
+certain moves. Using opportunistic locking during instance moves results in
+greater parallelization. As all of these approaches aim to tackle two different
+aspects of the problem, they do not exclude each other and will be presented
+independently.
+
+The performance of Ganeti moves
+===============================
+
+In the current implementation, there are three possible factors limiting the
+speed of an instance move. The first is the network bandwidth, which Ganeti can
+exploit better by using compression. The second is the encryption, which is
+obligatory, and which can throttle an otherwise fast connection. The third is
+surprisingly the compression, which can cause the connection to be
+underutilized.
+
+Example 1: some numbers present during an intra-cluster instance move:
+
+* Network bandwidth: 105MB/s, courtesy of a gigabit switch
+
+* Encryption performance: 40MB/s, provided by OpenSSL
+
+* Compression performance: 22.3MB/s input, 7.1MB/s gzip compressed output
+
+As can be seen in this example, the obligatory encryption results in 62% of
+available bandwidth being wasted, while using compression further lowers the
+throughput to 55% of what the encryption would allow. The following sections
+will talk about these numbers in more detail, and suggest improvements and best
+practices.
+
+Encryption and Ganeti security
+++++++++++++++++++++++++++++++
+
+Turning compression and encryption off would allow for an immediate improvement,
+and while that is possible for compression, there are good reasons why
+encryption is currently not a feature a user can disable.
+
+While it is impossible to secure instance data if an attacker gains SSH access
+to a node, the RAPI was designed to never allow user data to be accessed through
+it in case of being compromised. If moves could be performed unencrypted, this
+property would be broken. Instance moves can take place in environments which
+may be hostile, and where unencrypted traffic could be intercepted. As they can
+be instigated through the RAPI, an attacker could access all data on all
+instances in a cluster by moving them unencrypted and intercepting the data in
+flight. This is one of the few situations where the current speed of instance
+moves could be considered a perk.
+
+The performance of encryption can be increased by either using a less secure
+form of encryption, including no encryption, or using a faster encryption
+algorithm. The example listed above utilizes AES-256, one of the few ciphers
+that Ganeti deems secure enough to use. AES-128, also allowed by Ganeti's
+current settings, is weaker but 46% faster. A cipher that is not allowed due to
+its flaws, such as RC4, could offer a 208% increase in speed. On the other hand,
+using an OS capable of utilizing the AES_NI chip present on modern hardware
+can double the performance of AES, making it the best tradeoff between security
+and performance.
+
+Ganeti cannot and should not detect all the factors listed above, but should
+rather give its users some leeway in what to choose. A precedent already exists,
+as intra-cluster DRBD replication is already performed unencrypted, albeit on a
+separate VLAN. For intra-cluster moves, Ganeti should allow its users to set
+OpenSSL ciphers at will, while still enforcing high-security settings for moves
+between clusters.
+
+Thus, two settings will be introduced:
+
+* a cluster-level setting called ``--allow-cipher-bypassing``, a boolean that
+  cannot be set over RAPI
+
+* a gnt-instance move setting called ``--ciphers-to-use``, bypassing the default
+  cipher list with given ciphers, filtered to ensure no other OpenSSL options
+  are passed in within
+
+This change will serve to address the issues with moving non-redundant instances
+within the cluster, while keeping Ganeti security at its current level.
+
+Compression
++++++++++++
+
+Support for disk compression during instance moves was partially present before,
+but cleaned up and unified under the ``--compress`` option only as of Ganeti
+2.11. The only option offered by Ganeti is gzip with no options passed to it,
+resulting in a good compression ratio, but bad compression speed.
+
+As compression can affect the speed of instance moves significantly, it is
+worthwhile to explore alternatives. To test compression tool performance, an 8GB
+drive filled with data matching the expected usage patterns (taken from a
+workstation) was compressed by using various tools with various settings. The
+two top performers were ``lzop`` and, surprisingly, ``gzip``. The improvement in
+the performance of ``gzip`` was obtained by explicitly optimizing for speed
+rather than compression.
+
+* ``gzip -6``: 22.3MB/s in, 7.1MB/s out
+* ``gzip -1``: 44.1MB/s in, 15.1MB/s out
+* ``lzop``: 71.9MB/s in, 28.1MB/s out
+
+If encryption is the limiting factor, and as in the example, limits the
+bandwidth to 40MB/s, ``lzop`` allows for an effective 79% increase in transfer
+speed. The fast ``gzip`` would also prove to be beneficial, but much less than
+``lzop``. It should also be noted that as a rule of thumb, tools with a lower
+compression ratio had a lesser workload, with ``lzop`` straining the CPU much
+less than any of the competitors.
+
+With the test results present here, it is clear that ``lzop`` would be a very
+worthwhile addition to the compression options present in Ganeti, yet the
+problem is that it is not available by default on all distributions, as the
+option's presence might imply. In general, Ganeti may know how to use several
+tools, and check for their presence, but should add some way of at least hinting
+at which tools are available.
+
+Additionally, the user might want to use a tool that Ganeti did not account for.
+Allowing the tool to be named is also helpful, both for cases when multiple
+custom tools are to be used, and for distinguishing between various tools in
+case of e.g. inter-cluster moves.
+
+To this end, the ``--compression-tools`` cluster parameter will be added to
+Ganeti. It contains a list of names of compression tools that can be supplied as
+the parameter of ``--compress``, and by default it contains all the tools
+Ganeti knows how to use. The user can change the list as desired, removing
+entries that are not or should not be available on the cluster, and adding
+custom tools.
+
+Every custom tool is identified by its name, and Ganeti expects the name to
+correspond to a script invoking the compression tool. Without arguments, the
+script compresses input on stdin, outputting it on stdout. With the -d argument,
+the script does the same, only while decompressing. The -h argument is used to
+check for the presence of the script, and in this case, only the error code is
+examined. This syntax matches the ``gzip`` syntax well, which should allow most
+compression tools to be adapted to it easily.
+
+Ganeti will not allow arbitrary parameters to be passed to a compression tool,
+and will restrict the names to contain only a small but assuredly safe subset of
+characters - alphanumeric values and dashes and underscores. This minimizes the
+risk of security issues that could arise from an attacker smuggling a malicious
+command through RAPI. Common variations, like the speed/compression tradeoff of
+``gzip``, will be handled by aliases, e.g. ``gzip-fast`` or ``gzip-slow``.
+
+It should also be noted that for some purposes - e.g. the writing of OVF files,
+``gzip`` is the only allowed means of compression, and an appropriate error
+message should be displayed if the user attempts to use one of the other
+provided tools.
+
+Zeroing instance disks
+======================
+
+While compression lowers the amount of data sent, further reductions can be
+achieved by taking advantage of the structure of the disk - namely, sending only
+used disk sectors.
+
+There is no direct way to achieve this, as it would require that the
+move-instance tool is aware of the structure of the file system. Mounting the
+filesystem is not an option, primarily due to security issues. A disk primed to
+take advantage of a disk driver exploit could cause an attacker to breach
+instance isolation and gain control of a Ganeti node.
+
+An indirect way for this performance gain to be achieved is the zeroing of any
+hard disk space not in use. While this primarily means empty space, swap
+partitions can be zeroed as well.
+
+Sequences of zeroes can be compressed and thus transferred very efficiently, all
+without the host knowing that these are empty space. This approach can also be
+dangerous if a sparse disk is zeroed in this way, causing ballooning. As Ganeti
+does not seem to make special concessions for moving sparse disks, the only
+difference should be the disk space utilization on the current node.
+
+Zeroing approaches
+++++++++++++++++++
+
+Zeroing is a feasible approach, but the node cannot perform it as it cannot
+mount the disk. Only virtualization-based options remain, and of those, using
+Ganeti's own virtualization capabilities makes the most sense. There are two
+ways of doing this - creating a new helper instance, temporary or persistent, or
+reusing the target instance.
+
+Both approaches have their disadvantages. Creating a new helper instance
+requires managing its lifecycle, taking special care to make sure no helper
+instance remains left over due to a failed operation. Even if this were to be
+taken care of, disks are not yet separate entities in Ganeti, making the
+temporary transfer of disks between instances hard to implement and even harder
+to make robust. The reuse can be done by modifying the OS running on the
+instance to perform the zeroing itself when notified via the new instance
+communication mechanism, but this approach is neither generic, nor particularly
+safe. There is no guarantee that the zeroing operation will not interfere with
+the normal operation of the instance, nor that it will be completed if a
+user-initiated shutdown occurs.
+
+A better solution can be found by combining the two approaches - re-using the
+virtualized environment, but with a specifically crafted OS image. With the
+instance shut down as it should be in preparation for the move, it can be
+extended with an additional disk with the OS image on it. By prepending the
+disk and changing some instance parameters, the instance can boot from it. The
+OS can be configured to perform the zeroing on startup, attempting to mount any
+partitions with a filesystem present, and creating and deleting a zero-filled
+file on them. After the zeroing is complete, the OS should shut down, and the
+master should note the shutdown and restore the instance to its previous state.
+
+Note that the requirements above are very similar to the notion of a helper VM
+suggested in the OS install document. Some potentially unsafe actions are
+performed within a virtualized environment, acting on disks that belong or will
+belong to the instance. The mechanisms used will thus be developed with both
+approaches in mind.
+
+Implementation
+++++++++++++++
+
+There are two components to this solution - the Ganeti changes needed to boot
+the OS, and the OS image used for the zeroing. Due to the variety of filesystems
+and architectures that instances can use, no single ready-to-run disk image can
+satisfy the needs of all the Ganeti users. Instead, the instance-debootstrap
+scripts can be used to generate a zeroing-capable OS image. This might not be
+ideal, as there are lightweight distributions that take up less space and boot
+up more quickly. Generating those with the right set of drivers for the
+virtualization platform of choice is not easy. Thus we do not provide a script
+for this purpose, but the user is free to provide any OS image which performs
+the necessary steps: zero out all virtualization-provided devices on startup,
+shutdown immediately. The cluster-wide parameter controlling the image to be
+used would be called ``--zeroing-image``.
+
+The modifications to Ganeti code needed are minor. The zeroing functionality
+should be implemented as an extension of the instance export, and exposed as the
+``--zero-free-space option``. Prior to beginning the export, the instance
+configuration is temporarily extended with a new read-only disk of sufficient
+size to host the zeroing image, and the changes necessary for the image to be
+used as the boot drive. The temporary nature of the configuration changes
+requires that they are not propagated to other nodes. While this would normally
+not be feasible with an instance using a disk template offering multi-node
+redundancy, experiments with the code have shown that the restriction on
+diverse disk templates can be bypassed to temporarily allow a plain
+disk-template disk to host the zeroing image. Given that one of the planned
+changes in Ganeti is to have instance disks as separate entities, with no
+restriction on templates, this assumption is useful rather than harmful by
+asserting the desired behavior. The image is dumped to the disk, and the
+instance is started up.
+
+Once the instance is started up, the zeroing will proceed until completion, when
+a self-initiated shutdown will occur. The instance-shutdown detection
+capabilities of 2.11 should prevent the watcher from restarting the instance
+once this happens, allowing the host to take it as a sign the zeroing was
+completed. Either way, the host waits until the instance is shut down, or a
+timeout has been reached and the instance is forcibly shut down. As the time
+needed to zero an instance is dependent on the size of the disk of the instance,
+the user can provide a fixed and a per-size timeout, recommended to be set to
+twice the maximum write speed of the device hosting the instance.
+
+Better progress monitoring can be implemented with the instance-host
+communication channel proposed by the OS install design document. The first
+version will most likely use only the shutdown detection, and will be improved
+to account for the available communication channel at a later time.
+
+After the shutdown, the temporary disk is destroyed and the instance
+configuration is reverted to its original state. The very same action is done if
+any error is encountered during the zeroing process. In the case that the
+zeroing is interrupted while the zero-filled file is being written, the file may
+remain on the disk of the instance. The script that performs the zeroing will be
+made to react to system signals by deleting the zero-filled file, but there is
+little else that can be done to recover.
+
+When to use zeroing
++++++++++++++++++++
+
+The question of when it is useful to use zeroing is hard to answer because the
+effectiveness of the approach depends on many factors. All compression tools
+compress zeroes to almost nothingness, but compressing them takes time. If the
+time needed to compress zeroes were equal to zero, the approach would boil down
+to whether it is faster to zero unused space out, performing writes to disk, or
+to transfer it compressed. For the example used above, the average compression
+ratio, and write speeds of current disk drives, the answer would almost
+unanimously be yes.
+
+With a more realistic setup, where zeroes take time to compress, yet less time
+than ordinary data, the gains depend on the previously mentioned tradeoff and
+the free space available. Zeroing will definitely lessen the amount of bandwidth
+used, but it can lead to the connection being underutilized due to the time
+spent compressing data. It is up to the user to make these tradeoffs, but
+zeroing should be seen primarily as a means of further reducing the amount of
+data sent while increasing disk activity, with possible speed gains that should
+not be relied upon.
+
+In the future, the VM created for zeroing could also undertake other tasks
+related to the move, such as compression and encryption, and produce a stream
+of data rather than just modifying the disk. This would lessen the strain on
+the resources of the hypervisor, both disk I/O and CPU usage, and allow moves to
+obey the resource constraints placed on the instance being moved.
+
+Lock reduction
+==============
+
+An instance move as executed by the move-instance tool consists of several
+preparatory RAPI calls, leading up to two long-lasting opcodes: OpCreateInstance
+and OpBackupExport. While OpBackupExport locks only the instance, the locks of
+OpCreateInstance require more attention.
+
+When executed, this opcode attempts to lock all nodes on which the instance may
+be created and obtain shared locks on the groups they belong to. In the case
+that an IAllocator is used, this means all nodes must be locked. Any operation
+that requires a node lock to be present can delay the move operation, and there
+is no shortage of these.
+
+The concept of opportunistic locking has been introduced to remedy exactly this
+situation, allowing the IAllocator to lock as many nodes as possible. Depending
+whether the allocation can be made on these nodes, the operation either proceeds
+as expected, or fails noting that it is temporarily infeasible. The failure case
+would change the semantics of the move-instance tool, which is expected to fail
+only if the move is impossible. To yield the benefits of opportunistic locking
+yet satisfy this constraint, the move-instance tool can be extended with the
+--opportunistic-tries and --opportunistic-try-delay options. A number of
+opportunistic instance creations are attempted, with a delay between attempts.
+The delay is slightly altered every time to avoid timing issues. Should all
+attempts fail, a normal instance creation is requested, which blocks until all
+the locks can be acquired.
+
+While it may seem excessive to grab so many node locks, the early release
+mechanism is used to make the situation less dire, releasing all nodes that were
+not chosen as candidates for allocation. This is taken to the extreme as all the
+locks acquired are released prior to the start of the transfer, barring the
+newly-acquired lock over the new instance. This works because all operations
+that alter the node in a way which could affect the transfer:
+
+* are prevented by the instance lock or instance presence, e.g. gnt-node remove,
+  gnt-node evacuate,
+
+* do not interrupt the transfer, e.g. a PV on the node can be set as
+  unallocatable, and the transfer still proceeds as expected,
+
+* do not care, e.g. a gnt-node powercycle explicitly ignores all locks.
+
+This invariant should be kept in mind, and perhaps verified through tests.
+
+All in all, there is very little space to reduce the number of locks used, and
+the only improvement that can be made is introducing opportunistic locking as an
+option of move-instance.
+
+Introduction of changes
+=======================
+
+All the changes noted will be implemented in Ganeti 2.12, in the way described
+in the previous chapters. They will be implemented as separate changes, first
+the lock reduction, then the instance zeroing, then the compression
+improvements, and finally the encryption changes.
index 1c2c8a3..208bcb3 100644 (file)
@@ -67,39 +67,127 @@ be used by an attacker to for example bring down the VMs or exploit bugs
 in the virtualization stacks to gain access to the host machines as well.
 
 
-Proposal concerning SSH key distribution
-----------------------------------------
+Proposal concerning SSH host key distribution
+---------------------------------------------
+
+We propose the following design regarding the SSH host key handling. The
+root keys are untouched by this design.
+
+Each node gets its own ssh private/public key pair, but only the public
+keys of the master candidates get added to the ``authorized_keys`` file
+of all nodes. This has the advantages, that:
+
+- Only master candidates can ssh into other nodes, thus compromised
+  nodes cannot compromise the cluster further.
+- One can remove a compromised master candidate from a cluster
+  (including removing it's public key from all nodes' ``authorized_keys``
+  file) without having to regenerate and distribute new ssh keys for all
+  master candidates. (Even though it is be good practice to do that anyway,
+  since the compromising of the other master candidates might have taken
+  place already.)
+- If a (uncompromised) master candidate is offlined to be sent for
+  repair due to a hardware failure before Ganeti can remove any keys
+  from it (for example when the network adapter of the machine is broken),
+  we don't have to worry about the keys being on a machine that is
+  physically accessible.
+
+To ensure security while transferring public key information and
+updating the ``authorized_keys``, there are several other changes
+necessary:
+
+- Any distribution of keys (in this case only public keys) is done via
+  SSH and not via RPC. An attacker who has RPC control should not be
+  able to get SSH access where he did not have SSH access before
+  already.
+- The only RPC calls that are made in this context are from the master
+  daemon to the node daemon on its own host and noded ensures as much
+  as possible that the change to be made does not harm the cluster's
+  security boundary.
+- The nodes that are potential master candidates keep a list of public
+  keys of potential master candidates of the cluster in a separate
+  file called ``ganeti_pub_keys`` to keep track of which keys could
+  possibly be added ``authorized_keys`` files of the nodes. We come
+  to what "potential" means in this case in the next section. The key
+  list is only transferred via SSH or written directly by noded. It
+  is not stored in the cluster config, because the config is
+  distributed via RPC.
+
+The following sections describe in detail which Ganeti commands are
+affected by the proposed changes.
+
+
+RAPI
+~~~~
+
+The design goal to limit SSH powers to master candidates conflicts with
+the current powers a user of the RAPI interface would have. The
+``master_capable`` flag of nodes can be modified via RAPI.
+That means, an attacker that has access to the RAPI interface, can make
+all non-master-capable nodes master-capable, and then increase the master
+candidate pool size till all machines are master candidates (or at least
+a particular machine that he is aming for). This means that with RAPI
+access and a compromised normal node, one can make this node a master
+candidate and then still have the power to compromise the whole cluster.
+
+To mitigate this issue, we propose the following changes:
+
+- Add a flag ``master_capability_rapi_modifiable`` to the cluster
+  configuration which indicates whether or not it should be possible
+  to modify the ``master_capable`` flag of nodes via RAPI. The flag is
+  set to ``False`` by default and can itself only be changed on the
+  commandline. In this design doc, we refer to the flag as the
+  "rapi flag" from here on.
+- Only if the ``master_capabability_rapi_modifiable`` switch is set to
+  ``True``, it is possible to modify the master-capability flag of
+  nodes.
+
+With this setup, there are the following definitions of "potential
+master candidates" depending on the rapi flag:
+
+- If the rapi flag is set to ``True``, all cluster nodes are potential
+  master candidates, because as described above, all of them can
+  eventually be made master candidates via RAPI and thus security-wise,
+  we haven't won anything above the current SSH handling.
+- If the rapi flag is set to ``False``, only the master capable nodes
+  are considered potential master candidates, as it is not possible to
+  make them master candidates via RAPI at all.
+
+Note that when the rapi flag is changed, the state of the
+``ganeti_pub_keys`` file on all nodes  has to be updated accordingly.
+This should be done in the client script ``gnt_cluster`` before the
+RPC call to update the configuration is made, because this way, if
+someone would try to perform that RPC call on master to trick it into
+thinking that the flag is enabled, this would not help as the content of
+the ``ganeti_pub_keys`` file is a crucial part in the design of the
+distribution of the SSH keys.
+
+Note: One could think of always allowing to disable the master-capability
+via RAPI and just restrict the enabling of it, thus making it possible
+to RAPI-"freeze" the nodes' master-capability state once it disabled.
+However, we think these are rather confusing semantics of the involved
+flags and thus we go with proposed design.
+
+Note that this change will break RAPI compatibility, at least if the
+rapi flag is not explicitely set to ``True``. We made this choice to
+have the more secure option as default, because otherwise it is
+unlikely to be widely used.
 
-We propose two improvements regarding the ssh keys:
 
-#. Limit the distribution of the private ssh key to the master candidates.
-
-#. Use different ssh key pairs for each master candidate.
-
-We propose to limit the set of nodes holding the private root user SSH key
-to the master and the master candidates. This way, the security risk would
-be limited to a rather small set of nodes even though the cluster could
-consists of a lot more nodes. The set of master candidates could be protected
-better than the normal nodes (for example residing in a DMZ) to enhance
-security even more if the administrator wishes so. The following
-sections describe in detail which Ganeti commands are affected by this
-change and in what way.
+Cluster initialization
+~~~~~~~~~~~~~~~~~~~~~~
 
-Security will be even more increased if each master candidate gets
-its own ssh private/public key pair. This way, one can remove a
-compromised master candidate from a cluster (including removing it's
-public key from all nodes' ``authorized_keys`` file) without having to
-regenerate and distribute new ssh keys for all master candidates. (Even
-though it is be good practice to do that anyway, since the compromising
-of the other master candidates might have taken place already.) However,
-this improvement was not part of the original feature request and
-increases the complexity of node management even more. We therefore
-consider it as second step in this design and will address
-this after the other parts of this design are implemented.
+On cluster initialization, the following steps are taken in
+bootstrap.py:
 
-The following sections describe in detail which Ganeti commands are affected
-by the first part of ssh-related improvements, limiting the key
-distribution to master candidates only.
+- A public/private key pair is generated (as before), but only used
+  by the first (and thus master) node. In particular, the private key
+  never leaves the node.
+- A mapping of node UUIDs to public SSH keys is created and stored
+  as text file in ``/var/lib/ganeti/ganeti_pub_keys`` only accessible
+  by root (permissions 0600). The master node's uuid and its public
+  key is added as first entry. The format of the file is one
+  line per node, each line composed as ``node_uuid ssh_key``.
+- The node's public key is added to it's own ``authorized_keys`` file.
 
 
 (Re-)Adding nodes to a cluster
@@ -108,29 +196,64 @@ distribution to master candidates only.
 According to :doc:`design-node-add`, Ganeti transfers the ssh keys to
 every node that gets added to the cluster.
 
-We propose to change this procedure to treat master candidates and normal
-nodes differently. For master candidates, the procedure would stay as is.
-For normal nodes, Ganeti would transfer the public and private ssh host
-keys (as before) and only the public root key.
+Adding a new node will require the following steps.
+
+In gnt_node.py:
+
+- On the new node, a new public/private SSH key pair is generated.
+- The public key of the new node is fetched (via SSH) to the master
+  node and if it is a potential master candidate (see definition above),
+  it is added to the ``ganeti_pub_keys`` list on the master node.
+- The public keys of all current master candidates are added to the
+  new node's ``authorized_keys`` file (also via SSH).
+
+In LUNodeAdd in cmdlib/node.py:
 
-A normal node would not be able to connect via ssh to other nodes, but
-the master (and potentially master candidates) can connect to this node.
+- The LUNodeAdd determines whether or not the new node is a master
+  candidate and in any case updates the cluster's configuration with the
+  new nodes information. (This is not changed by the proposed design.)
+- If the new node is a master candidate, we make an RPC call to the node
+  daemon of the master node to add the new node's public key to all
+  nodes' ``authorized_keys`` files. The implementation of this RPC call
+  has to be extra careful as described in the next steps, because
+  compromised RPC security should not compromise SSH security.
+
+RPC call execution in noded (on master node):
+
+- Check that the public key of the new node is in the
+  ``ganeti_pub_keys`` file of the master node to make sure that no keys
+  of nodes outside the Ganeti cluster and no keys that are not potential
+  master candidates gain SSH access in the cluster.
+- Via SSH, transfer the new node's public key to all nodes (including
+  the new node) and add it to their ``authorized_keys`` file.
+- The ``ganeti_pub_keys`` file is transferred via SSH to all
+  potential master candidates nodes except the master node
+  (including the new one).
 
 In case of readding a node that used to be in the cluster before,
-handling of the ssh keys would basically be the same with the following
-additional modifications: if the node used to be a master or
-master-candidate node, but will be a normal node after readding, Ganeti
-should make sure that the private root key is deleted if it is still
-present on the node.
+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.
 
 
 Pro- and demoting a node to/from master candidate
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-If the role of a node is changed from 'normal' to 'master_candidate', the
-master node should at that point copy the private root ssh key. When demoting
-a node from master candidate to a normal node, the key that have been copied
-there on promotion or addition should be removed again.
+If the role of a node is changed from 'normal' to 'master_candidate',
+the procedure is the same as for adding nodes from the step "In
+LUNodeAdd ..." on.
+
+If a node gets demoted to 'normal', the master daemon makes a similar
+RPC call to the master node's node daemon as for adding a node.
+
+In the RPC call, noded will perform the following steps:
+
+- Check that the public key of the node to be demoted is indeed in the
+  ``ganeti_pub_keys`` file to avoid deleting ssh keys of machines that
+  don't belong to the cluster (and thus potentially lock out the
+  administrator).
+- Via SSH, remove the key from all node's ``authorized_keys`` files.
 
 This affected the behavior of the following commands:
 
@@ -176,11 +299,60 @@ The same behavior should be ensured for the corresponding rapi command.
 Cluster verify
 ~~~~~~~~~~~~~~
 
-To make sure the private root ssh key was not distributed to a normal
-node, 'gnt-cluster verify' will be extended by a check for the key
-on normal nodes. Additionally, it will check if the private key is
-indeed present on master candidates.
+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
+  in the cluster (other master candidates and normal nodes).
+- We check if the ``ganeti_pub_keys`` file contains keys of nodes that
+  are no longer in the cluster or that are not potential master
+  candidates.
+- For all normal nodes, we check if their key does not appear in other
+  node's ``authorized_keys``. For now, we will only emit a warning
+  rather than an error if this check fails, because Ganeti might be
+  run in a setup where Ganeti is not the only system manipulating the
+  SSH keys.
+
 
+Upgrades
+~~~~~~~~
+
+When upgrading from a version that has the previous SSH setup to the one
+proposed in this design, the upgrade procedure has to involve the
+following steps in the post-upgrade hook:
+
+- For all nodes, new SSH key pairs are generated.
+- All nodes and their public keys are added to the ``ganeti_pub_keys``
+  file and the file is copied to all nodes.
+- All keys of master candidate nodes are added to the
+  ``authorized_keys`` files of all other nodes.
+
+Since this upgrade significantly changes the configuration of the
+clusters' nodes, we will add a note to the UPGRADE notes to make the
+administrator aware of this fact (in case he intends to enable access
+from normal nodes to master candidates for other reasons than Ganeti
+uses the machines).
+
+Also, in any operation where Ganeti creates new SSH keys, the old keys
+will be backed up and not simply overridden.
+
+
+Downgrades
+~~~~~~~~~~
+
+These downgrading steps will be implemtented from 2.12 to 2.11:
+
+- 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.
+- The obsolete individual ssh keys will be removed from all nodes'
+  ``authorized_keys`` file.
+
+
+Renew-Crypto
+~~~~~~~~~~~~
+
+The ``gnt-cluster renew-crypto`` command is not affected by the proposed
+changes related to SSH.
 
 
 Proposal regarding node daemon certificates
@@ -192,10 +364,12 @@ in the design.
 - Instead of using the same certificate for all nodes as both, server
   and client certificate, we generate a common server certificate (and
   the corresponding private key) for all nodes and a different client
-  certificate (and the corresponding private key) for each node. All
-  those certificates will be self-signed for now. The client
-  certificates will use the node UUID as serial number to ensure
-  uniqueness within the cluster.
+  certificate (and the corresponding private key) for each node. The
+  server certificate will be self-signed. The client certficate will
+  be signed by the server certificate. The client certificates will
+  use the node UUID as serial number to ensure uniqueness within the
+  cluster. They will use the host's hostname as the certificate
+  common name (CN).
 - In addition, we store a mapping of
   (node UUID, client certificate digest) in the cluster's configuration
   and ssconf for hosts that are master or master candidate.
@@ -254,9 +428,21 @@ Drawbacks of this design:
 - Even though this proposal is an improvement towards the previous
   situation in Ganeti, it still does not use the full power of SSL. For
   further improvements, see Section "Related and future work".
+- Signing the client certificates with the server certificate will
+  increase the complexity of the renew-crypto, as a renewal of the
+  server certificates requires the renewal (and signing) of all client
+  certificates as well.
 
 Alternative proposals:
 
+- The initial version of this document described a setup where the
+  client certificates were also self-signed. This led to a serious
+  problem (Issue 1094), which would only have been solvable by
+  distributing all client certificates to all nodes and load them
+  as trusted CAs. As this would have resulted in having to restart
+  noded on all nodes every time a node is added, removed, demoted
+  or promoted, this was not feasible and we switched to client
+  certficates which are signed by the server certificate.
 - Instead of generating a client certificate per node, one could think
   of just generating two different client certificates, one for normal
   nodes and one for master candidates. Noded could then just check if
@@ -339,6 +525,8 @@ Cluster verify will be extended by the following checks:
 - Whether no node tries to use the certificate of another node. In
   particular, it is important to check that no normal node tries to
   use the certificate of a master candidate.
+- Whether there are still self-signed client certificates in use (from
+  a pre 2.12.4 Ganeti version).
 
 
 Crypto renewal
@@ -358,6 +546,18 @@ due inconsistent updating after a demotion or offlining), the user can use
 this option to renew the client certificates and update the candidate
 certificate map.
 
+Note that renewing the server certificate requires all client certificates
+being renewed and signed by the new server certificate, because
+otherwise their signature can not be verified by the server who only has
+the new server certificate then.
+
+As there was a different design in place in Ganeti 2.12.4 and previous
+versions, we have to ensure that renew-crypto works on pre 2.12 versions and
+2.12.1-4. Users that got hit by Issue 1094 will be encouraged to run
+renew-crypto at least once after switching to 2.12.5. Those who did not
+encounter this bug yet, will still get nagged friendly by gnt-cluster
+verify.
+
 
 Further considerations
 ----------------------
@@ -418,26 +618,19 @@ As a trade-off wrt to complexity and implementation effort, we did not
 implement them yet (as of version 2.11) but describe them here for
 future reference.
 
-- All SSL certificates that Ganeti uses so far are self-signed. It would
-  increase the security if they were signed by a common CA. There is
-  already a design doc for a Ganeti CA which was suggested in a
-  different context (related to import/export). This would also be a
-  benefit for the RPC calls. See design doc :doc:`design-impexp2` for
-  more information. Implementing a CA is rather complex, because it
-  would mean also to support renewing the CA certificate and providing
-  and supporting infrastructure to revoke compromised certificates.
+- The server certificate is currently self-signed and the client certificates
+  are signed by the server certificate. It would increase the security if they
+  were signed by a common CA. There is already a design doc for a Ganeti CA
+  which was suggested in a different context (related to import/export).
+  This would also be a benefit for the RPC calls. See design doc
+  :doc:`design-impexp2` for more information. Implementing a CA is rather
+  complex, because it would mean also to support renewing the CA certificate and
+  providing and supporting infrastructure to revoke compromised certificates.
 - An extension of the previous suggestion would be to even enable the
   system administrator to use an external CA. Especially in bigger
   setups, where already an SSL infrastructure exists, it would be useful
   if Ganeti can simply be integrated with it, rather than forcing the
   user to use the Ganeti CA.
-- A lighter version of using a CA would be to use the server certificate
-  to sign the client certificate instead of using self-signed
-  certificates for both. The probleme here is that this would make
-  renewing the server certificate rather complicated, because all client
-  certificates would need to be resigned and redistributed as well,
-  which leads to interesting chicken-and-egg problems when this is done
-  via RPC calls.
 - Ganeti RPC calls are currently done without checking if the hostname
   of the node complies with the common name of the certificate. This
   might be a desirable feature, but would increase the effort when a
index e3e2bf6..173289d 100644 (file)
@@ -87,8 +87,12 @@ number of instances and parallel jobs can be tested realistically.
 The following tests are added to the QA:
 
   * Submitting twice as many instance creation request as there are
-    nodes in the cluster, using DRBD as disk template. As soon as a
-    creation job succeeds, submit a removal job for this instance.
+    nodes in the cluster, using DRBD as disk template.
+    The job parameters are chosen according to best practice for
+    parallel instance creation without running the risk of instance
+    creation failing for too many parallel creation attempts.
+    As soon as a creation job succeeds, submit a removal job for
+    this instance.
   * Submitting twice as many instance creation request as there are
     nodes in the cluster, using Plain as disk template. As soon as a
     creation job succeeds, submit a removal job for this instance.
diff --git a/doc/design-systemd.rst b/doc/design-systemd.rst
new file mode 100644 (file)
index 0000000..5c7480d
--- /dev/null
@@ -0,0 +1,234 @@
+===================
+Systemd integration
+===================
+
+.. contents:: :depth: 4
+
+This design document outlines the implementation of native systemd
+support in Ganeti by providing systemd unit files. It also briefly
+discusses the possibility of supporting socket activation.
+
+
+Current state and shortcomings
+==============================
+
+Ganeti currently ships an example init script, compatible with Debian
+(and derivatives) and RedHat/Fedora (and derivatives). The initscript
+treats the whole Ganeti system as a single service wrt. starting and
+stopping (but allows starting/stopping/restarting individual daemons).
+
+The initscript is aided by ``daemon-util``, which takes care of correctly
+ordering the startup/shutdown of daemons using an explicit order.
+
+Finally, process supervision is achieved by (optionally) running
+``ganeti-watcher`` via cron every 5 minutes. ``ganeti-watcher`` will - among
+other things - try to start services that should be running but are not.
+
+The example initscript currently shipped with Ganeti will work with
+systemd's LSB compatibility wrappers out of the box, however there are
+a number of areas where we can benefit from providing native systemd
+unit files:
+
+  - systemd is the `de-facto choice`_ of almost all major Linux
+    distributions. Since it offers a stable API for service control,
+    providing our own systemd unit files means that Ganeti will run
+    out-of-the-box and in a predictable way in all distributions using
+    systemd.
+
+  - systemd performs constant process supervision with immediate
+    service restarts and configurable back-off. Ganeti currently offers
+    supervision only via ganeti-watcher, running via cron in 5-minute
+    intervals and unconditionally starting missing daemons even if they
+    have been manually stopped.
+
+  - systemd offers `socket activation`_ support, which may be of
+    interest for use at least with masterd, luxid and noded. Socket
+    activation offers two main advantages: no explicit service
+    dependencies or ordering needs to be defined as services will be
+    activated when needed; and seamless restarts / upgrades are possible
+    without rejecting new client connections.
+
+  - systemd offers a number of `security features`_, primarily using
+    the Linux kernel's namespace support, which may be of interest to
+    better restrict daemons running as root (noded and mond).
+
+.. _de-facto choice: https://en.wikipedia.org/wiki/Systemd#Adoption
+.. _socket activation: http://0pointer.de/blog/projects/socket-activation.html
+.. _security features: http://0pointer.de/blog/projects/security.html
+
+Proposed changes
+================
+
+We propose to extend Ganeti to natively support systemd, in addition to
+shipping the init-script as is. This requires the addition of systemd
+unit files, as well as some changes in daemon-util and ganeti-watcher to
+use ``systemctl`` on systems where Ganeti is managed by systemd.
+
+systemd unit files
+------------------
+
+Systemd uses unit files to store information about a service, device,
+mount point, or other resource it controls. Each unit file contains
+exactly one unit definition, consisting of a ``Unit`` an (optional)
+``Install`` section and an (optional) type-specific section (e.g.
+``Service``). Unit files are dropped in pre-determined locations in the
+system, where systemd is configured to read them from. Systemd allows
+complete or partial overrides of the unit files, using overlay
+directories. For more information, see `systemd.unit(5)`_.
+
+.. _systemd.unit(5): http://www.freedesktop.org/software/systemd/man/systemd.unit.html
+
+We will create one systemd `service unit`_ per daemon (masterd, noded,
+mond, luxid, confd, rapi) and an additional oneshot service for
+ensure-dirs (``ganeti-common.service``). All services will ``Require``
+``ganeti-common.service``, which will thus run exactly once per
+transaction (regardless of starting one or all daemons).
+
+.. _service unit: http://www.freedesktop.org/software/systemd/man/systemd.service.html
+
+All daemons will run in the foreground (already implemented by the
+``-f`` flag), directly supervised by systemd, using
+``Restart=on-failure`` in the respective units. Master role units will
+also treat ``EXIT_NOTMASTER`` as a successful exit and not trigger
+restarts. Additionally, systemd's conditional directives will be used to
+avoid starting daemons when they will certainly fail (e.g. because of
+missing configuration).
+
+Apart from the individual daemon units, we will also provide three
+`target units`_ as synchronization points:
+
+  - ``ganeti-node.target``: Regular node/master candidate functionality,
+    including ``ganeti-noded.service``, ``ganeti-mond.service`` and
+    ``ganeti-confd.service``.
+
+  - ``ganeti-master.target``: Master node functionality, including
+    ``ganeti-masterd.service``, ``ganeti-luxid.service`` and
+    ``ganeti-rapi.service``.
+
+  - ``ganeti.target``: A "meta-target" depending on
+    ``ganeti-node.target`` and ``ganti-master.target``.
+    ``ganeti.target`` itself will be ``WantedBy`` ``multi-user.target``,
+    so that Ganeti starts automatically on boot.
+
+.. _target units: http://www.freedesktop.org/software/systemd/man/systemd.target.html
+
+To allow starting/stopping/restarting the different roles, all units
+will include a ``PartOf`` directive referencing their direct ancestor
+target. In this way ``systemctl restart ganeti-node.target`` or ``systemctl
+restart ganeti.target`` will work as expected, i.e. restart only the node
+daemons or all daemons respectively.
+
+The full dependency tree is as follows:
+
+::
+
+    ganeti.target
+    ├─ganeti-master.target
+    │ ├─ganeti-luxid.service
+    │ │ └─ganeti-common.service
+    │ ├─ganeti-masterd.service
+    │ │ └─ganeti-common.service
+    │ └─ganeti-rapi.service
+    │   └─ganeti-common.service
+    └─ganeti-node.target
+      ├─ganeti-confd.service
+      │ └─ganeti-common.service
+      ├─ganeti-mond.service
+      │ └─ganeti-common.service
+      └─ganeti-noded.service
+        └─ganeti-common.service
+
+Installation
+~~~~~~~~~~~~
+The systemd unit files will be built from templates under
+doc/examples/systemd, much like what is currently done for the
+initscript. They will not be installed with ``make install``, but left
+up to the distribution packagers to ship them at the appropriate
+locations.
+
+SysV compatibility
+~~~~~~~~~~~~~~~~~~
+Systemd automatically creates a service for each SysV initscript on the
+system, appending ``.service`` to the initscript name, except if a
+service with the given name already exists. In our case however, the
+initscript's functionality is implemented by ``ganeti.target``.
+
+Systemd provides the ability to *mask* a given service, rendering it
+unusable, but in the case of SysV services this also results in
+failure to use tools like ``invoke-rc.d`` or ``service``. Thus we have
+to ship a ``ganeti.service`` (calling ``/bin/true``) of type
+``oneshot``, that depends on ``ganeti.target`` for these tools to
+continue working as expected.  ``ganeti.target`` on the other hand will
+be marked as ``PartOf = ganeti.service`` for stop and restart to be
+propagated to the whole service.
+
+The ``ganeti.service`` unit will not be marked to be enabled by systemd
+(i.e. will not be started at boot), but will be available for manual
+invocation and only be used for compatibility purposes.
+
+Changes to daemon-util
+----------------------
+
+``daemon-util`` is used wherever daemon control is required:
+
+  - In the sample initscript, to start and stop all daemons.
+  - In ``ganeti.backend`` to start the master daemons on master failover and
+    to stop confd when leaving the cluster.
+  - In ``ganeti.bootstrap``, to start the daemons on cluster initialization.
+  - In ``ganeti.cli``, to control the daemon run state during certain
+    operations (e.g. renew-crypto).
+
+Currently, ``daemon-util`` uses two auxiliary tools for managing daemons
+``start-stop-daemon`` and ``daemon``, in this order of preference.  In
+order not to confuse systemd in its process supervision, ``daemon-util``
+will have to be modified to start and stop the daemons via ``systemctl``
+in preference to ``start-stop-daemon`` and ``daemon``. This
+will require a basic check against run-time environment integrity:
+
+  - Make sure that ``systemd`` runs as PID 1, which is a `simple
+    check`_ against the existence of ``/run/systemd/system``.
+  - Make sure ``systemd`` knows how to handle Ganeti natively. This can
+    be a check against the ``LoadState`` of the ``ganeti.target`` unit.
+
+Unless both of these checks pass, ``daemon-util`` will fall back to its
+current behavior.
+
+.. _simple check: http://www.freedesktop.org/software/systemd/man/sd_booted.html
+
+Changes to ganeti-watcher
+-------------------------
+
+Since the daemon process supervision will be systemd's responsibility,
+the watcher must detect systemd's presence and not attempt to start any
+missing services. Again, systemd can be detected by the existence of
+``/run/systemd/system``.
+
+Future work
+===========
+
+Socket activation
+-----------------
+
+Systemd offers support for `socket activation`_. A daemon supporting
+socket-based activation, can inherit its listening socket(s) by systemd.
+This in turn means that the socket can be created and bound by systemd
+during early boot and it can be used to provide implicit startup
+ordering; as soon as a client connects to the listening socket, the
+respective service (and all its dependencies) will be started and the
+client will wait until its connection is accepted.
+
+Also, because the socket remains bound even if the service is
+restarting, new client connections will never be rejected, making
+service restarts and upgrades seamless.
+
+Socket activation support is trivial to implement (see
+`sd_listen_fds(3)`_) and relies on information passed by systemd via
+environment variables to the started processes.
+
+.. _sd_listen_fds(3): http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index daab9ad..4055878 100644 (file)
@@ -257,15 +257,17 @@ Imports
 Imports should be grouped into the following groups and inside each group they
 should be sorted alphabetically:
 
-1. standard library imports
-2. third-party imports
-3. local imports
+1. import of non-Ganeti libaries
+2. import of Ganeti libraries
 
 It is allowed to use qualified imports with short names for:
 
 * standard library (e.g. ``import qualified Data.Map as M``)
-* local imports (e.g. ``import qualified Ganeti.Constants as C``), although
-  this form should be kept to a minimum
+* local imports (e.g. ``import qualified Ganeti.Constants as C``)
+
+Whenever possible, prefer explicit imports, either in form of
+qualified imports, or by naming the imported functions
+(e.g., ``import Control.Arrow ((&&&))``, ``import Data.Foldable(fold, toList)``)
 
 Indentation
 ~~~~~~~~~~~
@@ -585,14 +587,14 @@ test on that, by default 500 of those big instances are generated for each
 property. In many cases, it would be sufficient to only generate those 500
 instances once and test all properties on those. To do this, create a property
 that uses ``conjoin`` to combine several properties into one. Use
-``printTestCase`` to add expressive error messages. For example::
+``counterexample`` to add expressive error messages. For example::
 
   prop_myMegaProp :: myBigType -> Property
   prop_myMegaProp b =
     conjoin
-      [ printTestCase
+      [ counterexample
           ("Something failed horribly here: " ++ show b) (subProperty1 b)
-      , printTestCase
+      , counterexample
           ("Something else failed horribly here: " ++ show b)
           (subProperty2 b)
       , -- more properties here ...
index a7d53c3..49b7d8a 100644 (file)
@@ -1,6 +1,7 @@
 # Default arguments for Ganeti daemons
 NODED_ARGS=""
-MASTERD_ARGS=""
 RAPI_ARGS=""
 CONFD_ARGS=""
+MOND_ARGS=""
+WCONFD_ARGS=""
 LUXID_ARGS=""
index 5497997..00dece4 100644 (file)
@@ -1,6 +1,7 @@
 # Default arguments for Ganeti daemons (debug mode)
 NODED_ARGS="-d"
-MASTERD_ARGS="-d"
 RAPI_ARGS="-d"
 CONFD_ARGS="-d"
+MOND_ARGS="-d"
+WCONFD_ARGS="-d"
 LUXID_ARGS="-d"
diff --git a/doc/examples/systemd/ganeti-common.service.in b/doc/examples/systemd/ganeti-common.service.in
new file mode 100644 (file)
index 0000000..e267323
--- /dev/null
@@ -0,0 +1,6 @@
+[Unit]
+Description = Ganeti one-off setup
+
+[Service]
+Type = oneshot
+ExecStart = @PKGLIBDIR@/ensure-dirs
diff --git a/doc/examples/systemd/ganeti-confd.service.in b/doc/examples/systemd/ganeti-confd.service.in
new file mode 100644 (file)
index 0000000..db3a913
--- /dev/null
@@ -0,0 +1,17 @@
+[Unit]
+Description = Ganeti configuration daemon (confd)
+Documentation = man:ganeti-confd(8)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-node.target
+ConditionPathExists = @LOCALSTATEDIR@/lib/ganeti/config.data
+
+[Service]
+Type = simple
+User = @GNTCONFDUSER@
+Group = @GNTCONFDGROUP@
+ExecStart = @SBINDIR@/ganeti-confd -f
+Restart = on-failure
+
+[Install]
+WantedBy = ganeti-node.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-kvmd.service.in b/doc/examples/systemd/ganeti-kvmd.service.in
new file mode 100644 (file)
index 0000000..7400f2c
--- /dev/null
@@ -0,0 +1,15 @@
+[Unit]
+Description = Ganeti KVM daemon (kvmd)
+Documentation = man:ganeti-kvmd(8)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-noded.target
+
+[Service]
+Type = simple
+Group = @GNTDAEMONSGROUP@
+ExecStart = @SBINDIR@/ganeti-kvmd -f
+Restart = on-failure
+
+[Install]
+WantedBy = ganeti-node.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-luxid.service.in b/doc/examples/systemd/ganeti-luxid.service.in
new file mode 100644 (file)
index 0000000..2fbcb4a
--- /dev/null
@@ -0,0 +1,18 @@
+[Unit]
+Description = Ganeti query daemon (luxid)
+Documentation = man:ganeti-luxid(8)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-master.target
+ConditionPathExists = @LOCALSTATEDIR@/lib/ganeti/config.data
+
+[Service]
+Type = simple
+User = @GNTLUXIDUSER@
+Group = @GNTLUXIDGROUP@
+ExecStart = @SBINDIR@/ganeti-luxid -f
+Restart = on-failure
+SuccessExitStatus = 0 11
+
+[Install]
+WantedBy = ganeti-master.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-master.target b/doc/examples/systemd/ganeti-master.target
new file mode 100644 (file)
index 0000000..ec1a980
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description = Ganeti master functionality
+Documentation = man:ganeti(7)
+After = syslog.target
+PartOf = ganeti.target
+
+[Install]
+WantedBy = ganeti.target
diff --git a/doc/examples/systemd/ganeti-metad.service.in b/doc/examples/systemd/ganeti-metad.service.in
new file mode 100644 (file)
index 0000000..30950dc
--- /dev/null
@@ -0,0 +1,14 @@
+[Unit]
+Description = Ganeti instance metadata daemon (metad)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-noded.target
+
+[Service]
+Type = simple
+Group = @GNTDAEMONSGROUP@
+ExecStart = @SBINDIR@/ganeti-metad -f
+Restart = on-failure
+
+# ganeti-metad is started on-demand by noded, so there must be no Install
+# section.
diff --git a/doc/examples/systemd/ganeti-mond.service.in b/doc/examples/systemd/ganeti-mond.service.in
new file mode 100644 (file)
index 0000000..bca40d4
--- /dev/null
@@ -0,0 +1,16 @@
+[Unit]
+Description = Ganeti monitoring daemon (mond)
+Documentation = man:ganeti-mond(8)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-node.target
+
+[Service]
+Type = simple
+User = @GNTMONDUSER@
+Group = @GNTMONDGROUP@
+ExecStart = @SBINDIR@/ganeti-mond -f
+Restart = on-failure
+
+[Install]
+WantedBy = ganeti-node.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-node.target b/doc/examples/systemd/ganeti-node.target
new file mode 100644 (file)
index 0000000..d38ba28
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description = Ganeti node functionality
+Documentation = man:ganeti(7)
+After = syslog.service
+PartOf = ganeti.target
+
+[Install]
+WantedBy = ganeti.target
diff --git a/doc/examples/systemd/ganeti-noded.service.in b/doc/examples/systemd/ganeti-noded.service.in
new file mode 100644 (file)
index 0000000..605e3f5
--- /dev/null
@@ -0,0 +1,19 @@
+[Unit]
+Description = Ganeti node daemon (noded)
+Documentation = man:ganeti-noded(8)
+After = ganeti-common.service
+Requires = ganeti-common.service
+PartOf = ganeti-node.target
+ConditionPathExists = @LOCALSTATEDIR@/lib/ganeti/server.pem
+
+[Service]
+Type = simple
+User = @GNTNODEDUSER@
+Group = @GNTNODEDGROUP@
+ExecStart = @SBINDIR@/ganeti-noded -f
+Restart = on-failure
+# Important: do not kill any KVM processes
+KillMode = process
+
+[Install]
+WantedBy = ganeti-node.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-rapi.service.in b/doc/examples/systemd/ganeti-rapi.service.in
new file mode 100644 (file)
index 0000000..a2ce1f5
--- /dev/null
@@ -0,0 +1,19 @@
+[Unit]
+Description = Ganeti RAPI daemon (rapi)
+Documentation = man:ganeti-rapi(8)
+Requires = ganeti-common.service
+Requisite = ganeti-luxid.service
+After = ganeti-common.service
+PartOf = ganeti-master.target
+ConditionPathExists = @LOCALSTATEDIR@/lib/ganeti/rapi.pem
+
+[Service]
+Type = simple
+User = @GNTRAPIUSER@
+Group = @GNTRAPIGROUP@
+ExecStart = @SBINDIR@/ganeti-rapi -f
+SuccessExitStatus = 0 11
+Restart = on-failure
+
+[Install]
+WantedBy = ganeti-master.target ganeti.target
diff --git a/doc/examples/systemd/ganeti-wconfd.service.in b/doc/examples/systemd/ganeti-wconfd.service.in
new file mode 100644 (file)
index 0000000..762c4f3
--- /dev/null
@@ -0,0 +1,18 @@
+[Unit]
+Description = Ganeti config writer daemon (wconfd)
+Documentation = man:ganeti-wconfd(8)
+Requires = ganeti-common.service
+After = ganeti-common.service
+PartOf = ganeti-master.target
+ConditionPathExists = @LOCALSTATEDIR@/lib/ganeti/config.data
+
+[Service]
+Type = simple
+User = @GNTWCONFDUSER@
+Group = @GNTWCONFDGROUP@
+ExecStart = @SBINDIR@/ganeti-wconfd -f
+Restart = on-failure
+SuccessExitStatus = 0 11
+
+[Install]
+WantedBy = ganeti-master.target ganeti.target
diff --git a/doc/examples/systemd/ganeti.service b/doc/examples/systemd/ganeti.service
new file mode 100644 (file)
index 0000000..f4ada5d
--- /dev/null
@@ -0,0 +1,18 @@
+# This is a dummy service, provided only for compatibility with SysV.
+# Systemd will automatically create a SysV service called
+# ganeti.service, attempting to start the initscript. Since there is no
+# way to tell systemd that the initscript acts as ganeti.target (and not
+# ganeti.service), we create a stub service requiring ganeti.target.
+#
+# This service is for compatibility only and so will not be marked for
+# installation.
+
+[Unit]
+Description = Dummy Ganeti SysV compatibility service
+Documentation = man:ganeti(7)
+After = ganeti.target
+Requires = ganeti.target
+
+[Service]
+Type = oneshot
+ExecStart = /bin/true
diff --git a/doc/examples/systemd/ganeti.target b/doc/examples/systemd/ganeti.target
new file mode 100644 (file)
index 0000000..fc2a3bd
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description = Ganeti virtualization cluster manager
+Documentation = man:ganeti(7)
+PartOf = ganeti.service
+
+[Install]
+WantedBy = multi-user.target
+Also = ganeti-node.target ganeti-master.target
index 7a0e6e4..dbdd173 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.11
+Documents Ganeti version 2.12
 
 .. contents::
 
index 1a12975..b8d7e44 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.11
+Documents Ganeti version 2.12
 
 .. contents::
 
index 7ba7a32..4dd62f3 100644 (file)
@@ -51,7 +51,7 @@ There are a few documents particularly useful for developers who want
 to modify Ganeti:
 
 - :doc:`locking`: Describes Ganeti's locking strategy and lock order dependencies.
-- :doc:`devnotes`: Details build dependencies and other useful development-related information. 
+- :doc:`devnotes`: Details build dependencies and other useful development-related information.
 
 
 Implemented designs
@@ -77,6 +77,7 @@ and draft versions (which are either incomplete or not implemented).
    design-2.9.rst
    design-2.10.rst
    design-2.11.rst
+   design-2.12.rst
 
 Draft designs
 -------------
@@ -95,8 +96,10 @@ Draft designs
    design-bulk-create.rst
    design-chained-jobs.rst
    design-cmdlib-unittests.rst
+   design-cpu-speed.rst
    design-cpu-pinning.rst
    design-device-uuid-name.rst
+   design-daemons.rst
    design-file-based-storage.rst
    design-hroller.rst
    design-hotplug.rst
@@ -105,6 +108,7 @@ Draft designs
    design-linuxha.rst
    design-lu-generated-jobs.rst
    design-monitoring-agent.rst
+   design-move-instance-improvements.rst
    design-multi-reloc.rst
    design-multi-version-tests.rst
    design-network.rst
@@ -124,6 +128,7 @@ Draft designs
    design-shared-storage.rst
    design-ssh-ports.rst
    design-storagetypes.rst
+   design-systemd.rst
    design-upgrade.rst
    design-virtual-clusters.rst
    devnotes.rst
index 4f91c6b..9ed84ab 100644 (file)
@@ -162,54 +162,24 @@ kernels. For KVM no reboot should be necessary.
 Xen settings
 ~~~~~~~~~~~~
 
-It's recommended that dom0 is restricted to a low amount of memory
-(512MiB or 1GiB is reasonable) and that memory ballooning is disabled in
-the file ``/etc/xen/xend-config.sxp`` by setting the value
-``dom0-min-mem`` to 0, like this::
+Depending on which toolstack you are using, the hypervisor parameter
+``xen_cmd`` has to be set to the matching value, either ``xm`` or
+``xl``.
 
-  (dom0-min-mem 0)
+Some useful best practices for Xen are to restrict the amount of memory
+dom0 has available, and pin the dom0 to a limited number of CPUs.
+Instructions for how to achieve this for various toolstacks can be found
+on the Xen wiki_.
 
-For optimum performance when running both CPU and I/O intensive
-instances, it's also recommended that the dom0 is restricted to one CPU
-only. For example you can add ``dom0_max_vcpus=1,dom0_vcpus_pin`` to your
-kernels boot command line and set ``dom0-cpus`` in
-``/etc/xen/xend-config.sxp`` like this::
+.. _wiki: http://wiki.xenproject.org/wiki/Xen_Project_Best_Practices
 
-  (dom0-cpus 1)
-
-It is recommended that you disable xen's automatic save of virtual
+It is recommended that you disable Xen's automatic save of virtual
 machines at system shutdown and subsequent restore of them at reboot.
 To obtain this make sure the variable ``XENDOMAINS_SAVE`` in the file
 ``/etc/default/xendomains`` is set to an empty value.
 
-If you want to use live migration make sure you have, in the xen config
-file, something that allows the nodes to migrate instances between each
-other. For example:
-
-.. code-block:: text
-
-  (xend-relocation-server yes)
-  (xend-relocation-port 8002)
-  (xend-relocation-address '')
-  (xend-relocation-hosts-allow '^192\\.0\\.2\\.[0-9]+$')
-
-
-The second line assumes that the hypervisor parameter
-``migration_port`` is set 8002, otherwise modify it to match. The last
-line assumes that all your nodes have secondary IPs in the
-192.0.2.0/24 network, adjust it accordingly to your setup.
-
-If you want to run HVM instances too with Ganeti and want VNC access to
-the console of your instances, set the following two entries in
-``/etc/xen/xend-config.sxp``:
-
-.. code-block:: text
-
-  (vnc-listen '0.0.0.0') (vncpasswd '')
-
-You need to restart the Xen daemon for these settings to take effect::
-
-  $ /etc/init.d/xend restart
+You may need to restart the Xen daemon for some of these settings to
+take effect. The best way to do this depends on your distribution.
 
 Selecting the instance kernel
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -251,7 +221,11 @@ your instances to DRBD to take advantage of the new features.
 Supported DRBD versions: 8.0-8.4. It's recommended to have at least
 version 8.0.12. Note that for version 8.2 and newer it is needed to pass
 the ``usermode_helper=/bin/true`` parameter to the module, either by
-configuring ``/etc/modules`` or when inserting it manually.
+configuring ``/etc/modules`` or when inserting it manually. When using
+Xen and DRBD 8.3.2 or higher, it is recommended_ to use the
+``disable_sendpage=1`` setting as well.
+
+.. _recommended: https://drbd.linbit.com/users-guide/s-xen-drbd-mod-params.html
 
 Now the bad news: unless your distribution already provides it
 installing DRBD might involve recompiling your kernel or anyway fiddling
@@ -286,9 +260,11 @@ instances on a node.
 
    Then to configure it for Ganeti::
 
-     $ echo drbd minor_count=128 usermode_helper=/bin/true >> /etc/modules
+     $ echo "options drbd minor_count=128 usermode_helper=/bin/true" \
+        > /etc/modprobe.d/drbd.conf
+     $ echo "drbd" >> /etc/modules
      $ depmod -a
-     $ modprobe drbd minor_count=128 usermode_helper=/bin/true
+     $ modprobe drbd
 
    It is also recommended that you comment out the default resources (if any)
    in the ``/etc/drbd.conf`` file, so that the init script doesn't try to
index 89618b5..714782f 100644 (file)
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.11
+Documents Ganeti version 2.12
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
index 07be505..d3bd703 100644 (file)
@@ -1,5 +1,6 @@
 @GNTMASTERUSER@ @GNTDAEMONSGROUP@
 @GNTCONFDUSER@ @GNTDAEMONSGROUP@
+@GNTWCONFDUSER@ @GNTDAEMONSGROUP@
 @GNTLUXIDUSER@ @GNTDAEMONSGROUP@
 @GNTRAPIUSER@ @GNTDAEMONSGROUP@
 @GNTMONDUSER@ @GNTDAEMONSGROUP@
index 667ba40..d10df70 100644 (file)
@@ -3,6 +3,7 @@
 @GNTMASTERUSER@
 @GNTRAPIUSER@
 @GNTCONFDUSER@
+@GNTWCONFDUSER@
 @GNTLUXIDUSER@
 @GNTLUXIDGROUP@
 @GNTMONDUSER@
index 83b6d32..f4db420 100644 (file)
@@ -1,6 +1,7 @@
 @GNTMASTERUSER@ @GNTMASTERDGROUP@
 @GNTRAPIUSER@ @GNTRAPIGROUP@
 @GNTCONFDUSER@ @GNTCONFDGROUP@
+@GNTWCONFDUSER@ @GNTWCONFDGROUP@
 @GNTLUXIDUSER@ @GNTLUXIDGROUP@
 @GNTMONDUSER@ @GNTMONDGROUP@
 @GNTNODEDUSER@
index adecf1f..1f84afb 100644 (file)
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.11
+Documents Ganeti version 2.12
 
 .. contents::
 
index ef386d2..eafb5d1 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# 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
 # C0302: This module has become too big and should be split up
 
 
+import base64
+import errno
+import logging
 import os
 import os.path
+import pycurl
+import random
+import re
 import shutil
-import time
+import signal
+import socket
 import stat
-import errno
-import re
-import random
-import logging
 import tempfile
+import time
 import zlib
-import base64
-import signal
 
 from ganeti import errors
+from ganeti import http
 from ganeti import utils
 from ganeti import ssh
 from ganeti import hypervisor
@@ -81,6 +84,8 @@ from ganeti import ht
 from ganeti.storage.base import BlockDev
 from ganeti.storage.drbd import DRBD8
 from ganeti import hooksmaster
+from ganeti.rpc import transport
+from ganeti.rpc.errors import NoMasterError, TimeoutError
 
 
 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
@@ -146,6 +151,10 @@ def _StoreInstReasonTrail(instance_name, trail):
 
   @type instance_name: string
   @param instance_name: The name of the instance
+
+  @type trail: list of reasons
+  @param trail: reason trail
+
   @rtype: None
 
   """
@@ -425,12 +434,13 @@ def StartMasterDaemons(no_voting):
   """
 
   if no_voting:
-    masterd_args = "--no-voting --yes-do-it"
+    daemon_args = "--no-voting --yes-do-it"
   else:
-    masterd_args = ""
+    daemon_args = ""
 
   env = {
-    "EXTRA_MASTERD_ARGS": masterd_args,
+    "EXTRA_LUXID_ARGS": daemon_args,
+    "EXTRA_WCONFD_ARGS": daemon_args,
     }
 
   result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
@@ -777,6 +787,7 @@ _STORAGE_TYPE_INFO_FN = {
   constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
   constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
   constants.ST_SHARED_FILE: None,
+  constants.ST_GLUSTER: None,
   constants.ST_RADOS: None,
 }
 
@@ -931,6 +942,12 @@ def _VerifyNodeInfo(what, vm_capable, result, all_hvparams):
 def _VerifyClientCertificate(cert_file=pathutils.NODED_CLIENT_CERT_FILE):
   """Verify the existance and validity of the client SSL certificate.
 
+  Also, verify that the client certificate is not self-signed. Self-
+  signed client certificates stem from Ganeti versions 2.12.0 - 2.12.4
+  and should be replaced by client certificates signed by the server
+  certificate. Hence we output a warning when we encounter a self-signed
+  one.
+
   """
   create_cert_cmd = "gnt-cluster renew-crypto --new-node-certificates"
   if not os.path.exists(cert_file):
@@ -941,9 +958,13 @@ def _VerifyClientCertificate(cert_file=pathutils.NODED_CLIENT_CERT_FILE):
   (errcode, msg) = utils.VerifyCertificate(cert_file)
   if errcode is not None:
     return (errcode, msg)
-  else:
-    # if everything is fine, we return the digest to be compared to the config
-    return (None, utils.GetCertificateDigest(cert_filename=cert_file))
+
+  (errcode, msg) = utils.IsCertificateSelfSigned(cert_file)
+  if errcode is not None:
+    return (errcode, msg)
+
+  # if everything is fine, we return the digest to be compared to the config
+  return (None, utils.GetCertificateDigest(cert_filename=cert_file))
 
 
 def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
@@ -1085,7 +1106,7 @@ def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
 
   if constants.NV_LVLIST in what and vm_capable:
     try:
-      val = GetVolumeList([what[constants.NV_LVLIST]])
+      val = GetVolumeList(utils.ListVolumeGroups().keys())
     except RPCFail, err:
       val = str(err)
     result[constants.NV_LVLIST] = val
@@ -1201,13 +1222,8 @@ def GetCryptoTokens(token_requests):
   @return: list of tuples of the token type and the public crypto token
 
   """
-  getents = runtime.GetEnts()
-  _VALID_CERT_FILES = [pathutils.NODED_CERT_FILE,
-                       pathutils.NODED_CLIENT_CERT_FILE,
-                       pathutils.NODED_CLIENT_CERT_FILE_TMP]
-  _DEFAULT_CERT_FILE = pathutils.NODED_CLIENT_CERT_FILE
   tokens = []
-  for (token_type, action, options) in token_requests:
+  for (token_type, action, _) in token_requests:
     if token_type not in constants.CRYPTO_TYPES:
       raise errors.ProgrammerError("Token type '%s' not supported." %
                                    token_type)
@@ -1215,46 +1231,8 @@ def GetCryptoTokens(token_requests):
       raise errors.ProgrammerError("Action '%s' is not supported." %
                                    action)
     if token_type == constants.CRYPTO_TYPE_SSL_DIGEST:
-      if action == constants.CRYPTO_ACTION_CREATE:
-
-        # extract file name from options
-        cert_filename = None
-        if options:
-          cert_filename = options.get(constants.CRYPTO_OPTION_CERT_FILE)
-        if not cert_filename:
-          cert_filename = _DEFAULT_CERT_FILE
-        # For security reason, we don't allow arbitrary filenames
-        if not cert_filename in _VALID_CERT_FILES:
-          raise errors.ProgrammerError(
-            "The certificate file name path '%s' is not allowed." %
-            cert_filename)
-
-        # extract serial number from options
-        serial_no = None
-        if options:
-          try:
-            serial_no = int(options[constants.CRYPTO_OPTION_SERIAL_NO])
-          except ValueError:
-            raise errors.ProgrammerError(
-              "The given serial number is not an intenger: %s." %
-              options.get(constants.CRYPTO_OPTION_SERIAL_NO))
-          except KeyError:
-            raise errors.ProgrammerError("No serial number was provided.")
-
-        if not serial_no:
-          raise errors.ProgrammerError(
-            "Cannot create an SSL certificate without a serial no.")
-
-        utils.GenerateNewSslCert(
-          True, cert_filename, serial_no,
-          "Create new client SSL certificate in %s." % cert_filename,
-          uid=getents.masterd_uid, gid=getents.masterd_gid)
-        tokens.append((token_type,
-                       utils.GetCertificateDigest(
-                         cert_filename=cert_filename)))
-      elif action == constants.CRYPTO_ACTION_GET:
-        tokens.append((token_type,
-                       utils.GetCertificateDigest()))
+      tokens.append((token_type,
+                     utils.GetCertificateDigest()))
   return tokens
 
 
@@ -1541,7 +1519,7 @@ def GetInstanceMigratable(instance):
   if iname not in hyper.ListInstances(hvparams=instance.hvparams):
     _Fail("Instance %s is not running", iname)
 
-  for idx in range(len(instance.disks)):
+  for idx in range(len(instance.disks_info)):
     link_name = _GetBlockDevSymlinkPath(iname, idx)
     if not os.path.islink(link_name):
       logging.warning("Instance %s is missing symlink %s for disk %d",
@@ -1827,7 +1805,7 @@ def _GatherAndLinkBlockDevs(instance):
 
   """
   block_devices = []
-  for idx, disk in enumerate(instance.disks):
+  for idx, disk in enumerate(instance.disks_info):
     device = _RecursiveFindBD(disk)
     if device is None:
       raise errors.BlockDeviceError("Block device '%s' is not set up." %
@@ -1886,7 +1864,7 @@ def StartInstance(instance, startup_paused, reason, store_reason=True):
   except errors.BlockDeviceError, err:
     _Fail("Block device error: %s", err, exc=True)
   except errors.HypervisorError, err:
-    _RemoveBlockDevLinks(instance.name, instance.disks)
+    _RemoveBlockDevLinks(instance.name, instance.disks_info)
     _Fail("Hypervisor error: %s", err, exc=True)
 
 
@@ -1960,7 +1938,7 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
   except errors.HypervisorError, err:
     logging.warning("Failed to execute post-shutdown cleanup step: %s", err)
 
-  _RemoveBlockDevLinks(instance.name, instance.disks)
+  _RemoveBlockDevLinks(instance.name, instance.disks_info)
 
 
 def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
@@ -2071,7 +2049,7 @@ def AcceptInstance(instance, info, target):
     hyper.AcceptInstance(instance, info, target)
   except errors.HypervisorError, err:
     if instance.disk_template in constants.DTS_EXT_MIRROR:
-      _RemoveBlockDevLinks(instance.name, instance.disks)
+      _RemoveBlockDevLinks(instance.name, instance.disks_info)
     _Fail("Failed to accept instance: %s", err, exc=True)
 
 
@@ -2204,6 +2182,55 @@ def HotplugSupported(instance):
     _Fail("Hotplug is not supported: %s", err)
 
 
+def ModifyInstanceMetadata(metadata):
+  """Sends instance data to the metadata daemon.
+
+  Uses the Luxi transport layer to communicate with the metadata
+  daemon configuration server.  It starts the metadata daemon if it is
+  not running.
+  The daemon must be enabled during at configuration time.
+
+  @type metadata: dict
+  @param metadata: instance metadata obtained by calling
+                   L{objects.Instance.ToDict} on an instance object
+
+  """
+  if not constants.ENABLE_METAD:
+    raise errors.ProgrammerError("The metadata deamon is disabled, yet"
+                                 " ModifyInstanceMetadata has been called")
+
+  if not utils.IsDaemonAlive(constants.METAD):
+    result = utils.RunCmd([pathutils.DAEMON_UTIL, "start", constants.METAD])
+    if result.failed:
+      raise errors.HypervisorError("Failed to start metadata daemon")
+
+  def _Connect():
+    return transport.Transport(pathutils.SOCKET_DIR + "/ganeti-metad",
+                               allow_non_master=True)
+
+  retries = 5
+
+  while True:
+    try:
+      trans = utils.Retry(_Connect, 1.0, constants.LUXI_DEF_CTMO)
+      break
+    except utils.RetryTimeout:
+      raise TimeoutError("Connection to metadata daemon timed out")
+    except (socket.error, NoMasterError), err:
+      if retries == 0:
+        logging.error("Failed to connect to the metadata daemon",
+                      exc_info=True)
+        raise TimeoutError("Failed to connect to metadata daemon: %s" % err)
+      else:
+        retries -= 1
+
+  data = serializer.DumpJson(metadata,
+                             private_encoder=serializer.EncodeWithPrivateFields)
+
+  trans.Send(data)
+  trans.Close()
+
+
 def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   """Creates a block device for an instance.
 
@@ -2269,12 +2296,26 @@ def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   return device.unique_id
 
 
-def _WipeDevice(path, offset, size):
-  """This function actually wipes the device.
+def _DumpDevice(source_path, target_path, offset, size, truncate):
+  """This function images/wipes the device using a local file.
 
-  @param path: The path to the device to wipe
-  @param offset: The offset in MiB in the file
-  @param size: The size in MiB to write
+  @type source_path: string
+  @param source_path: path of the image or data source (e.g., "/dev/zero")
+
+  @type target_path: string
+  @param target_path: path of the device to image/wipe
+
+  @type offset: int
+  @param offset: offset in MiB in the output file
+
+  @type size: int
+  @param size: maximum size in MiB to write (data source might be smaller)
+
+  @type truncate: bool
+  @param truncate: whether the file should be truncated
+
+  @return: None
+  @raise RPCFail: in case of failure
 
   """
   # Internal sizes are always in Mebibytes; if the following "dd" command
@@ -2282,16 +2323,72 @@ def _WipeDevice(path, offset, size):
   # function must be adjusted accordingly before being passed to "dd".
   block_size = 1024 * 1024
 
-  cmd = [constants.DD_CMD, "if=/dev/zero", "seek=%d" % offset,
-         "bs=%s" % block_size, "oflag=direct", "of=%s" % path,
+  cmd = [constants.DD_CMD, "if=%s" % source_path, "seek=%d" % offset,
+         "bs=%s" % block_size, "oflag=direct", "of=%s" % target_path,
          "count=%d" % size]
+
+  if not truncate:
+    cmd.append("conv=notrunc")
+
   result = utils.RunCmd(cmd)
 
   if result.failed:
-    _Fail("Wipe command '%s' exited with error: %s; output: %s", result.cmd,
+    _Fail("Dump command '%s' exited with error: %s; output: %s", result.cmd,
           result.fail_reason, result.output)
 
 
+def _DownloadAndDumpDevice(source_url, target_path, size):
+  """This function images a device using a downloaded image file.
+
+  @type source_url: string
+  @param source_url: URL of image to dump to disk
+
+  @type target_path: string
+  @param target_path: path of the device to image
+
+  @type size: int
+  @param size: maximum size in MiB to write (data source might be smaller)
+
+  @rtype: NoneType
+  @return: None
+  @raise RPCFail: in case of download or write failures
+
+  """
+  class DDParams(object):
+    def __init__(self, current_size, total_size):
+      self.current_size = current_size
+      self.total_size = total_size
+      self.image_size_error = False
+
+  def dd_write(ddparams, out):
+    if ddparams.current_size < ddparams.total_size:
+      ddparams.current_size += len(out)
+      target_file.write(out)
+    else:
+      ddparams.image_size_error = True
+      return -1
+
+  target_file = open(target_path, "r+")
+  ddparams = DDParams(0, 1024 * 1024 * size)
+
+  curl = pycurl.Curl()
+  curl.setopt(pycurl.VERBOSE, True)
+  curl.setopt(pycurl.NOSIGNAL, True)
+  curl.setopt(pycurl.USERAGENT, http.HTTP_GANETI_VERSION)
+  curl.setopt(pycurl.URL, source_url)
+  curl.setopt(pycurl.WRITEFUNCTION, lambda out: dd_write(ddparams, out))
+
+  try:
+    curl.perform()
+  except pycurl.error:
+    if ddparams.image_size_error:
+      _Fail("Disk image larger than the disk")
+    else:
+      raise
+
+  target_file.close()
+
+
 def BlockdevWipe(disk, offset, size):
   """Wipes a block device.
 
@@ -2309,19 +2406,56 @@ def BlockdevWipe(disk, offset, size):
     rdev = None
 
   if not rdev:
-    _Fail("Cannot execute wipe for device %s: device not found", disk.iv_name)
-
-  # Do cross verify some of the parameters
+    _Fail("Cannot wipe device %s: device not found", disk.iv_name)
   if offset < 0:
     _Fail("Negative offset")
   if size < 0:
     _Fail("Negative size")
   if offset > rdev.size:
-    _Fail("Offset is bigger than device size")
+    _Fail("Wipe offset is bigger than device size")
   if (offset + size) > rdev.size:
-    _Fail("The provided offset and size to wipe is bigger than device size")
+    _Fail("Wipe offset and size are bigger than device size")
+
+  _DumpDevice("/dev/zero", rdev.dev_path, offset, size, True)
+
 
-  _WipeDevice(rdev.dev_path, offset, size)
+def BlockdevImage(disk, image, size):
+  """Images a block device either by dumping a local file or
+  downloading a URL.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk object we want to image
+
+  @type image: string
+  @param image: file path to the disk image be dumped
+
+  @type size: int
+  @param size: The size in MiB to write
+
+  @rtype: NoneType
+  @return: None
+  @raise RPCFail: in case of failure
+
+  """
+  if not (utils.IsUrl(image) or os.path.exists(image)):
+    _Fail("Image '%s' not found", image)
+
+  try:
+    rdev = _RecursiveFindBD(disk)
+  except errors.BlockDeviceError:
+    rdev = None
+
+  if not rdev:
+    _Fail("Cannot image device %s: device not found", disk.iv_name)
+  if size < 0:
+    _Fail("Negative size")
+  if size > rdev.size:
+    _Fail("Image size is bigger than device size")
+
+  if utils.IsUrl(image):
+    _DownloadAndDumpDevice(image, rdev.dev_path, size)
+  else:
+    _DumpDevice(image, rdev.dev_path, 0, size, False)
 
 
 def BlockdevPauseResumeSync(disks, pause):
@@ -2792,7 +2926,7 @@ def _OSOndiskAPIVersion(os_dir):
   @param os_dir: the directory in which we should look for the OS
   @rtype: tuple
   @return: tuple (status, data) with status denoting the validity and
-      data holding either the vaid versions or an error message
+      data holding either the valid versions or an error message
 
   """
   api_file = utils.PathJoin(os_dir, constants.OS_API_FILE)
@@ -2861,11 +2995,13 @@ def DiagnoseOS(top_dirs=None):
           variants = os_inst.supported_variants
           parameters = os_inst.supported_parameters
           api_versions = os_inst.api_versions
+          trusted = False if os_inst.create_script_untrusted else True
         else:
           diagnose = os_inst
           variants = parameters = api_versions = []
+          trusted = True
         result.append((name, os_path, status, diagnose, variants,
-                       parameters, api_versions))
+                       parameters, api_versions, trusted))
 
   return result
 
@@ -2906,6 +3042,9 @@ def _TryOSFromDisk(name, base_dir=None):
   # an optional one
   os_files = dict.fromkeys(constants.OS_SCRIPTS, True)
 
+  os_files[constants.OS_SCRIPT_CREATE] = False
+  os_files[constants.OS_SCRIPT_CREATE_UNTRUSTED] = False
+
   if max(api_versions) >= constants.OS_API_V15:
     os_files[constants.OS_VARIANTS_FILE] = False
 
@@ -2935,6 +3074,15 @@ def _TryOSFromDisk(name, base_dir=None):
         return False, ("File '%s' under path '%s' is not executable" %
                        (filename, os_dir))
 
+  if not constants.OS_SCRIPT_CREATE in os_files and \
+        not constants.OS_SCRIPT_CREATE_UNTRUSTED in os_files:
+    return False, ("A create script (trusted or untrusted) under path '%s'"
+                   " must exist" % os_dir)
+
+  create_script = os_files.get(constants.OS_SCRIPT_CREATE, None)
+  create_script_untrusted = os_files.get(constants.OS_SCRIPT_CREATE_UNTRUSTED,
+                                         None)
+
   variants = []
   if constants.OS_VARIANTS_FILE in os_files:
     variants_file = os_files[constants.OS_VARIANTS_FILE]
@@ -2958,7 +3106,8 @@ def _TryOSFromDisk(name, base_dir=None):
     parameters = [v.split(None, 1) for v in parameters]
 
   os_obj = objects.OS(name=name, path=os_dir,
-                      create_script=os_files[constants.OS_SCRIPT_CREATE],
+                      create_script=create_script,
+                      create_script_untrusted=create_script_untrusted,
                       export_script=os_files[constants.OS_SCRIPT_EXPORT],
                       import_script=os_files[constants.OS_SCRIPT_IMPORT],
                       rename_script=os_files[constants.OS_SCRIPT_RENAME],
@@ -3032,7 +3181,7 @@ def OSCoreEnv(os_name, inst_os, os_params, debug=0):
 
   # OS params
   for pname, pvalue in os_params.items():
-    result["OSP_%s" % pname.upper()] = pvalue
+    result["OSP_%s" % pname.upper().replace("-", "_")] = pvalue
 
   # Set a default path otherwise programs called by OS scripts (or
   # even hooks called from OS scripts) might break, and we don't want
@@ -3057,19 +3206,20 @@ def OSEnvironment(instance, inst_os, debug=0):
       cannot be found
 
   """
-  result = OSCoreEnv(instance.os, inst_os, instance.osparams, debug=debug)
+  result = OSCoreEnv(instance.os, inst_os, objects.FillDict(instance.osparams,
+                     instance.osparams_private.Unprivate()), debug=debug)
 
   for attr in ["name", "os", "uuid", "ctime", "mtime", "primary_node"]:
     result["INSTANCE_%s" % attr.upper()] = str(getattr(instance, attr))
 
   result["HYPERVISOR"] = instance.hypervisor
-  result["DISK_COUNT"] = "%d" % len(instance.disks)
+  result["DISK_COUNT"] = "%d" % len(instance.disks_info)
   result["NIC_COUNT"] = "%d" % len(instance.nics)
   result["INSTANCE_SECONDARY_NODES"] = \
       ("%s" % " ".join(instance.secondary_nodes))
 
   # Disks
-  for idx, disk in enumerate(instance.disks):
+  for idx, disk in enumerate(instance.disks_info):
     real_disk = _OpenRealBD(disk)
     result["DISK_%d_PATH" % idx] = real_disk.dev_path
     result["DISK_%d_ACCESS" % idx] = disk.mode
@@ -3333,6 +3483,10 @@ def FinalizeExport(instance, snap_disks):
   for name, value in instance.osparams.items():
     config.set(constants.INISECT_OSP, name, str(value))
 
+  config.add_section(constants.INISECT_OSP_PRIVATE)
+  for name, value in instance.osparams_private.items():
+    config.set(constants.INISECT_OSP_PRIVATE, name, str(value.Get()))
+
   utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
                   data=config.Dumps())
   shutil.rmtree(finaldestdir, ignore_errors=True)
@@ -3660,8 +3814,38 @@ def _CheckOSPList(os_obj, parameters):
           " by the OS %s: %s" % (os_obj.name, utils.CommaJoin(delta)))
 
 
-def ValidateOS(required, osname, checks, osparams):
-  """Validate the given OS' parameters.
+def _CheckOSVariant(os_obj, name):
+  """Check whether an OS name conforms to the os variants specification.
+
+  @type os_obj: L{objects.OS}
+  @param os_obj: OS object to check
+
+  @type name: string
+  @param name: OS name passed by the user, to check for validity
+
+  @rtype: NoneType
+  @return: None
+  @raise RPCFail: if OS variant is not valid
+
+  """
+  variant = objects.OS.GetVariant(name)
+
+  if not os_obj.supported_variants:
+    if variant:
+      _Fail("OS '%s' does not support variants ('%s' passed)" %
+            (os_obj.name, variant))
+    else:
+      return
+
+  if not variant:
+    _Fail("OS name '%s' must include a variant" % name)
+
+  if variant not in os_obj.supported_variants:
+    _Fail("OS '%s' does not support variant '%s'" % (os_obj.name, variant))
+
+
+def ValidateOS(required, osname, checks, osparams, force_variant):
+  """Validate the given OS parameters.
 
   @type required: boolean
   @param required: whether absence of the OS should translate into
@@ -3671,7 +3855,8 @@ def ValidateOS(required, osname, checks, osparams):
   @type checks: list
   @param checks: list of the checks to run (currently only 'parameters')
   @type osparams: dict
-  @param osparams: dictionary with OS parameters
+  @param osparams: dictionary with OS parameters, some of which may be
+                   private.
   @rtype: boolean
   @return: True if the validation passed, or False if the OS was not
       found and L{required} was false
@@ -3690,6 +3875,9 @@ def ValidateOS(required, osname, checks, osparams):
     else:
       return False
 
+  if not force_variant:
+    _CheckOSVariant(tbv, osname)
+
   if max(tbv.api_versions) < constants.OS_API_V20:
     return True
 
@@ -3708,6 +3896,61 @@ def ValidateOS(required, osname, checks, osparams):
   return True
 
 
+def ExportOS(instance, override_env):
+  """Creates a GZIPed tarball with an OS definition and environment.
+
+  The archive contains a file with the environment variables needed by
+  the OS scripts.
+
+  @type instance: L{objects.Instance}
+  @param instance: instance for which the OS definition is exported
+
+  @type override_env: dict of string to string
+  @param override_env: if supplied, it overrides the environment on a
+                       key-by-key basis that is part of the archive
+
+  @rtype: string
+  @return: filepath of the archive
+
+  """
+  assert instance
+  assert instance.os
+
+  temp_dir = tempfile.mkdtemp()
+  inst_os = OSFromDisk(instance.os)
+
+  result = utils.RunCmd(["ln", "-s", inst_os.path,
+                         utils.PathJoin(temp_dir, "os")])
+  if result.failed:
+    _Fail("Failed to copy OS package '%s' to '%s': %s, output '%s'",
+          inst_os, temp_dir, result.fail_reason, result.output)
+
+  env = OSEnvironment(instance, inst_os)
+  env.update(override_env)
+
+  with open(utils.PathJoin(temp_dir, "environment"), "w") as f:
+    for var in env:
+      f.write(var + "=" + env[var] + "\n")
+
+  (fd, os_package) = tempfile.mkstemp(suffix=".tgz")
+  os.close(fd)
+
+  result = utils.RunCmd(["tar", "--dereference", "-czv",
+                         "-f", os_package,
+                         "-C", temp_dir,
+                         "."])
+  if result.failed:
+    _Fail("Failed to create OS archive '%s': %s, output '%s'",
+          os_package, result.fail_reason, result.output)
+
+  result = utils.RunCmd(["rm", "-rf", temp_dir])
+  if result.failed:
+    _Fail("Failed to remove copy of OS package '%s' in '%s': %s, output '%s'",
+          inst_os, temp_dir, result.fail_reason, result.output)
+
+  return os_package
+
+
 def DemoteFromMC():
   """Demotes the current node from master candidate role.
 
@@ -3749,9 +3992,11 @@ def CreateX509Certificate(validity, cryptodir=pathutils.CRYPTO_KEYS_DIR):
   @return: Certificate name and public part
 
   """
+  serial_no = int(time.time())
   (key_pem, cert_pem) = \
     utils.GenerateSelfSignedX509Cert(netutils.Hostname.GetSysName(),
-                                     min(validity, _MAX_SSL_CERT_VALIDITY), 1)
+                                     min(validity, _MAX_SSL_CERT_VALIDITY),
+                                     serial_no)
 
   cert_dir = tempfile.mkdtemp(dir=cryptodir,
                               prefix="x509-%s-" % utils.TimestampForFilename())
@@ -4522,6 +4767,30 @@ def ConfigureOVS(ovs_name, ovs_link):
             result.output), log=True)
 
 
+def GetFileInfo(file_path):
+  """ Checks if a file exists and returns information related to it.
+
+  Currently returned information:
+    - file size: int, size in bytes
+
+  @type file_path: string
+  @param file_path: Name of file to examine.
+
+  @rtype: tuple of bool, dict
+  @return: Whether the file exists, and a dictionary of information about the
+           file gathered by os.stat.
+
+  """
+  try:
+    stat_info = os.stat(file_path)
+    values_dict = {
+      constants.STAT_SIZE: stat_info.st_size,
+    }
+    return True, values_dict
+  except IOError:
+    return False, {}
+
+
 class HooksRunner(object):
   """Hook runner.
 
index bb9b473..4f0aecf 100644 (file)
@@ -57,6 +57,7 @@ from ganeti import luxi
 from ganeti import jstore
 from ganeti import pathutils
 from ganeti import runtime
+from ganeti import vcluster
 
 
 # ec_id for InitConfig's temporary reservation manager
@@ -103,10 +104,12 @@ def GenerateHmacKey(file_name):
 
 # pylint: disable=R0913
 def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
-                          new_confd_hmac_key, new_cds,
+                          new_confd_hmac_key, new_cds, new_client_cert,
+                          master_name,
                           rapi_cert_pem=None, spice_cert_pem=None,
                           spice_cacert_pem=None, cds=None,
                           nodecert_file=pathutils.NODED_CERT_FILE,
+                          clientcert_file=pathutils.NODED_CLIENT_CERT_FILE,
                           rapicert_file=pathutils.RAPI_CERT_FILE,
                           spicecert_file=pathutils.SPICE_CERT_FILE,
                           spicecacert_file=pathutils.SPICE_CACERT_FILE,
@@ -124,6 +127,10 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
   @param new_confd_hmac_key: Whether to generate a new HMAC key
   @type new_cds: bool
   @param new_cds: Whether to generate a new cluster domain secret
+  @type new_client_cert: bool
+  @param new_client_cert: Whether to generate a new client certificate
+  @type master_name: string
+  @param master_name: FQDN of the master node
   @type rapi_cert_pem: string
   @param rapi_cert_pem: New RAPI certificate in PEM format
   @type spice_cert_pem: string
@@ -151,6 +158,12 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
     new_cluster_cert, nodecert_file, 1,
     "Generating new cluster certificate at %s" % nodecert_file)
 
+  # If the cluster certificate was renewed, the client cert has to be
+  # renewed and resigned.
+  if new_cluster_cert or new_client_cert:
+    utils.GenerateNewClientSslCert(clientcert_file, nodecert_file,
+                                   master_name)
+
   # confd HMAC key
   if new_confd_hmac_key or not os.path.exists(hmackey_file):
     logging.debug("Writing new confd HMAC key to %s", hmackey_file)
@@ -201,7 +214,7 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
     GenerateHmacKey(cds_file)
 
 
-def _InitGanetiServerSetup(master_name):
+def _InitGanetiServerSetup(master_name, cfg):
   """Setup the necessary configuration for the initial node daemon.
 
   This creates the nodepass file containing the shared password for
@@ -209,11 +222,35 @@ def _InitGanetiServerSetup(master_name):
 
   @type master_name: str
   @param master_name: Name of the master node
+  @type cfg: ConfigWriter
+  @param cfg: the configuration writer
 
   """
   # Generate cluster secrets
-  GenerateClusterCrypto(True, False, False, False, False)
+  GenerateClusterCrypto(True, False, False, False, False, False, master_name)
+
+  # Add the master's SSL certificate digest to the configuration.
+  master_uuid = cfg.GetMasterNode()
+  master_digest = utils.GetCertificateDigest()
+  cfg.AddNodeToCandidateCerts(master_uuid, master_digest)
+  cfg.Update(cfg.GetClusterInfo(), logging.error)
+  ssconf.WriteSsconfFiles(cfg.GetSsconfValues())
+
+  if not os.path.exists(
+      os.path.join(pathutils.DATA_DIR,
+                   "%s%s" % (constants.SSCONF_FILEPREFIX,
+                             constants.SS_MASTER_CANDIDATES_CERTS))):
+    raise errors.OpExecError("Ssconf file for master candidate certificates"
+                             " was not written.")
 
+  if not os.path.exists(pathutils.NODED_CERT_FILE):
+    raise errors.OpExecError("The server certficate was not created properly.")
+
+  if not os.path.exists(pathutils.NODED_CLIENT_CERT_FILE):
+    raise errors.OpExecError("The client certificate was not created"
+                             " properly.")
+
+  # set up the inter-node password and certificate
   result = utils.RunCmd([pathutils.DAEMON_UTIL, "start", constants.NODED])
   if result.failed:
     raise errors.OpExecError("Could not start the node daemon, command %s"
@@ -402,7 +439,7 @@ def _InitFileStorageDir(file_storage_dir):
 
 def _PrepareFileBasedStorage(
     enabled_disk_templates, file_storage_dir,
-    default_dir, file_disk_template,
+    default_dir, file_disk_template, _storage_path_acceptance_fn,
     init_fn=_InitFileStorageDir, acceptance_fn=None):
   """Checks if a file-base storage type is enabled and inits the dir.
 
@@ -414,14 +451,19 @@ def _PrepareFileBasedStorage(
   @param default_dir: default file storage directory when C{file_storage_dir}
       is 'None'
   @type file_disk_template: string
-  @param file_disk_template: a disk template whose storage type is 'ST_FILE' or
-      'ST_SHARED_FILE'
+  @param file_disk_template: a disk template whose storage type is 'ST_FILE',
+      'ST_SHARED_FILE' or 'ST_GLUSTER'
+  @type _storage_path_acceptance_fn: function
+  @param _storage_path_acceptance_fn: checks whether the given file-based
+      storage directory is acceptable
+  @see: C{cluster.CheckFileBasedStoragePathVsEnabledDiskTemplates} for details
+
   @rtype: string
   @returns: the name of the actual file storage directory
 
   """
   assert (file_disk_template in utils.storage.GetDiskTemplatesOfStorageTypes(
-            constants.ST_FILE, constants.ST_SHARED_FILE
+            constants.ST_FILE, constants.ST_SHARED_FILE, constants.ST_GLUSTER
          ))
 
   if file_storage_dir is None:
@@ -431,8 +473,8 @@ def _PrepareFileBasedStorage(
         lambda path: filestorage.CheckFileStoragePathAcceptance(
             path, exact_match_ok=True)
 
-  cluster.CheckFileStoragePathVsEnabledDiskTemplates(
-      logging.warning, file_storage_dir, enabled_disk_templates)
+  _storage_path_acceptance_fn(logging.warning, file_storage_dir,
+                              enabled_disk_templates)
 
   file_storage_enabled = file_disk_template in enabled_disk_templates
   if file_storage_enabled:
@@ -457,6 +499,7 @@ def _PrepareFileStorage(
   return _PrepareFileBasedStorage(
       enabled_disk_templates, file_storage_dir,
       pathutils.DEFAULT_FILE_STORAGE_DIR, constants.DT_FILE,
+      cluster.CheckFileStoragePathVsEnabledDiskTemplates,
       init_fn=init_fn, acceptance_fn=acceptance_fn)
 
 
@@ -471,6 +514,7 @@ def _PrepareSharedFileStorage(
   return _PrepareFileBasedStorage(
       enabled_disk_templates, file_storage_dir,
       pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR, constants.DT_SHARED_FILE,
+      cluster.CheckSharedFileStoragePathVsEnabledDiskTemplates,
       init_fn=init_fn, acceptance_fn=acceptance_fn)
 
 
@@ -485,6 +529,7 @@ def _PrepareGlusterStorage(
   return _PrepareFileBasedStorage(
       enabled_disk_templates, file_storage_dir,
       pathutils.DEFAULT_GLUSTER_STORAGE_DIR, constants.DT_GLUSTER,
+      cluster.CheckGlusterStoragePathVsEnabledDiskTemplates,
       init_fn=init_fn, acceptance_fn=acceptance_fn)
 
 
@@ -561,6 +606,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                 primary_ip_version=None, ipolicy=None,
                 prealloc_wipe_disks=False, use_external_mip_script=False,
                 hv_state=None, disk_state=None, enabled_disk_templates=None,
+                install_image=None, zeroing_image=None, compression_tools=None,
                 enabled_user_shutdown=False):
   """Initialise the cluster.
 
@@ -581,6 +627,18 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     raise errors.OpPrereqError("Cluster is already initialised",
                                errors.ECODE_STATE)
 
+  data_dir = vcluster.AddNodePrefix(pathutils.DATA_DIR)
+  queue_dir = vcluster.AddNodePrefix(pathutils.QUEUE_DIR)
+  archive_dir = vcluster.AddNodePrefix(pathutils.JOB_QUEUE_ARCHIVE_DIR)
+  for ddir in [queue_dir, data_dir, archive_dir]:
+    if os.path.isdir(ddir):
+      for entry in os.listdir(ddir):
+        if not os.path.isdir(os.path.join(ddir, entry)):
+          raise errors.OpPrereqError(
+            "%s contains non-directory enries like %s. Remove left-overs of an"
+            " old cluster before initialising a new one" % (ddir, entry),
+            errors.ECODE_STATE)
+
   if not enabled_hypervisors:
     raise errors.OpPrereqError("Enabled hypervisors list must contain at"
                                " least one member", errors.ECODE_INVAL)
@@ -668,6 +726,8 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                                          file_storage_dir)
   shared_file_storage_dir = _PrepareSharedFileStorage(enabled_disk_templates,
                                                       shared_file_storage_dir)
+  gluster_storage_dir = _PrepareGlusterStorage(enabled_disk_templates,
+                                               gluster_storage_dir)
 
   if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
     raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix,
@@ -793,6 +853,9 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
 
   now = time.time()
 
+  if compression_tools is not None:
+    cluster.CheckCompressionTools(compression_tools)
+
   # init of cluster config file
   cluster_config = objects.Cluster(
     serial_no=1,
@@ -833,6 +896,11 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     disk_state_static=disk_state,
     enabled_disk_templates=enabled_disk_templates,
     candidate_certs=candidate_certs,
+    osparams={},
+    osparams_private_cluster={},
+    install_image=install_image,
+    zeroing_image=zeroing_image,
+    compression_tools=compression_tools,
     enabled_user_shutdown=enabled_user_shutdown,
     )
   master_node_config = objects.Node(name=hostname.name,
@@ -850,7 +918,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   ssconf.WriteSsconfFiles(cfg.GetSsconfValues())
 
   # set up the inter-node password and certificate
-  _InitGanetiServerSetup(hostname.name)
+  _InitGanetiServerSetup(hostname.name, cfg)
 
   logging.debug("Starting daemons")
   result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-all"])
@@ -904,6 +972,7 @@ def InitConfig(version, cluster_config, master_node_config,
                                    nodes=nodes,
                                    instances={},
                                    networks={},
+                                   disks={},
                                    serial_no=1,
                                    ctime=now, mtime=now)
   utils.WriteFile(cfg_file,
@@ -918,7 +987,8 @@ def FinalizeClusterDestroy(master_uuid):
   begun in cmdlib.LUDestroyOpcode.
 
   """
-  cfg = config.ConfigWriter()
+  livelock = utils.livelock.LiveLock("bootstrap_destroy")
+  cfg = config.GetConfig(None, livelock)
   modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
   runner = rpc.BootstrapRunner()
 
@@ -964,6 +1034,7 @@ def SetupNodeDaemon(opts, cluster_name, node, ssh_port):
       utils.ReadFile(pathutils.NODED_CERT_FILE),
     constants.NDS_SSCONF: ssconf.SimpleStore().ReadAll(),
     constants.NDS_START_NODE_DAEMON: True,
+    constants.NDS_NODE_NAME: node,
     }
 
   RunNodeSetupCmd(cluster_name, node, pathutils.NODE_DAEMON_SETUP,
@@ -1031,9 +1102,20 @@ def MasterFailover(no_voting=False):
   logging.info("Setting master to %s, old master: %s", new_master, old_master)
 
   try:
+    # Forcefully start WConfd so that we can access the configuration
+    result = utils.RunCmd([pathutils.DAEMON_UTIL,
+                           "start", constants.WCONFD, "--force-node",
+                           "--no-voting", "--yes-do-it"])
+    if result.failed:
+      raise errors.OpPrereqError("Could not start the configuration daemon,"
+                                 " command %s had exitcode %s and error %s" %
+                                 (result.cmd, result.exit_code, result.output),
+                                 errors.ECODE_NOENT)
+
     # instantiate a real config writer, as we now know we have the
     # configuration data
-    cfg = config.ConfigWriter(accept_foreign=True)
+    livelock = utils.livelock.LiveLock("bootstrap_failover")
+    cfg = config.GetConfig(None, livelock, accept_foreign=True)
 
     old_master_node = cfg.GetNodeInfoByName(old_master)
     if old_master_node is None:
@@ -1052,38 +1134,47 @@ def MasterFailover(no_voting=False):
     # this will also regenerate the ssconf files, since we updated the
     # cluster info
     cfg.Update(cluster_info, logging.error)
-  except errors.ConfigurationError, err:
-    logging.error("Error while trying to set the new master: %s",
-                  str(err))
-    return 1
 
-  # if cfg.Update worked, then it means the old master daemon won't be
-  # able now to write its own config file (we rely on locking in both
-  # backend.UploadFile() and ConfigWriter._Write(); hence the next
-  # step is to kill the old master
+    # if cfg.Update worked, then it means the old master daemon won't be
+    # able now to write its own config file (we rely on locking in both
+    # backend.UploadFile() and ConfigWriter._Write(); hence the next
+    # step is to kill the old master
 
-  logging.info("Stopping the master daemon on node %s", old_master)
+    logging.info("Stopping the master daemon on node %s", old_master)
 
-  runner = rpc.BootstrapRunner()
-  master_params = cfg.GetMasterNetworkParameters()
-  master_params.uuid = old_master_node.uuid
-  ems = cfg.GetUseExternalMipScript()
-  result = runner.call_node_deactivate_master_ip(old_master,
-                                                 master_params, ems)
+    runner = rpc.BootstrapRunner()
+    master_params = cfg.GetMasterNetworkParameters()
+    master_params.uuid = old_master_node.uuid
+    ems = cfg.GetUseExternalMipScript()
+    result = runner.call_node_deactivate_master_ip(old_master,
+                                                   master_params, ems)
 
-  msg = result.fail_msg
-  if msg:
-    warning = "Could not disable the master IP: %s" % (msg,)
-    logging.warning("%s", warning)
-    warnings.append(warning)
+    msg = result.fail_msg
+    if msg:
+      warning = "Could not disable the master IP: %s" % (msg,)
+      logging.warning("%s", warning)
+      warnings.append(warning)
 
-  result = runner.call_node_stop_master(old_master)
-  msg = result.fail_msg
-  if msg:
-    warning = ("Could not disable the master role on the old master"
-               " %s, please disable manually: %s" % (old_master, msg))
-    logging.error("%s", warning)
-    warnings.append(warning)
+    result = runner.call_node_stop_master(old_master)
+    msg = result.fail_msg
+    if msg:
+      warning = ("Could not disable the master role on the old master"
+                 " %s, please disable manually: %s" % (old_master, msg))
+      logging.error("%s", warning)
+      warnings.append(warning)
+  except errors.ConfigurationError, err:
+    logging.error("Error while trying to set the new master: %s",
+                  str(err))
+    return 1, warnings
+  finally:
+    # stop WConfd again:
+    result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop", constants.WCONFD])
+    if result.failed:
+      warning = ("Could not stop the configuration daemon,"
+                 " command %s had exitcode %s and error %s"
+                 % (result.cmd, result.exit_code, result.output))
+      logging.error("%s", warning)
+      rcode = 1
 
   logging.info("Checking master IP non-reachability...")
 
@@ -1163,8 +1254,7 @@ def GatherMasterVotes(node_names):
   (if some nodes vote for another master).
 
   @type node_names: list
-  @param node_names: the list of nodes to query for master info; the current
-      node will be removed if it is in the list
+  @param node_names: the list of nodes to query for master info
   @rtype: list
   @return: list of (node, votes)
 
@@ -1200,3 +1290,24 @@ def GatherMasterVotes(node_names):
   vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
 
   return vote_list
+
+
+def MajorityHealthy():
+  """Check if the majority of nodes is healthy
+
+  Gather master votes from all nodes known to this node;
+  return True if a strict majority of nodes is reachable and
+  has some opinion on which node is master. Note that this will
+  not guarantee any node to win an election but it ensures that
+  a standard master-failover is still possible.
+
+  """
+  node_names = ssconf.SimpleStore().GetNodeList()
+  node_count = len(node_names)
+  vote_list = GatherMasterVotes(node_names)
+  if vote_list is None:
+    return False
+  total_votes = sum([count for (node, count) in vote_list if node is not None])
+  logging.info("Total %d nodes, %d votes: %s", node_count, total_votes,
+               vote_list)
+  return 2 * total_votes > node_count
index 12188cc..2380ff6 100644 (file)
@@ -485,10 +485,10 @@ def _GetHandlerMethods(handler):
   @rtype: list of strings
 
   """
-  return sorted(method
-                for (method, op_attr, _, _, _) in rapi.baserlib.OPCODE_ATTRS
+  return sorted(m_attrs.method for m_attrs in rapi.baserlib.OPCODE_ATTRS
                 # Only if handler supports method
-                if hasattr(handler, method) or hasattr(handler, op_attr))
+                if hasattr(handler, m_attrs.method) or
+                   hasattr(handler, m_attrs.opcode))
 
 
 def _DescribeHandlerAccess(handler, method):
index 81e362a..5e0b68f 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
+# 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
@@ -53,6 +53,7 @@ from ganeti import netutils
 from ganeti import qlang
 from ganeti import objects
 from ganeti import pathutils
+from ganeti import serializer
 
 from ganeti.runtime import (GetClient)
 
@@ -79,6 +80,7 @@ __all__ = [
   "CLUSTER_DOMAIN_SECRET_OPT",
   "CONFIRM_OPT",
   "CP_SIZE_OPT",
+  "COMPRESSION_TOOLS_OPT",
   "DEBUG_OPT",
   "DEBUG_SIMERR_OPT",
   "DISKIDX_OPT",
@@ -126,10 +128,14 @@ __all__ = [
   "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",
@@ -173,8 +179,10 @@ __all__ = [
   "ON_PRIMARY_OPT",
   "ON_SECONDARY_OPT",
   "OFFLINE_OPT",
-  "OSPARAMS_OPT",
   "OS_OPT",
+  "OSPARAMS_OPT",
+  "OSPARAMS_PRIVATE_OPT",
+  "OSPARAMS_SECRET_OPT",
   "OS_SIZE_OPT",
   "OOB_TIMEOUT_OPT",
   "POWER_DELAY_OPT",
@@ -213,6 +221,7 @@ __all__ = [
   "IPOLICY_STD_SPECS_OPT",
   "IPOLICY_DISK_TEMPLATES",
   "IPOLICY_VCPU_RATIO",
+  "IPOLICY_SPINDLE_RATIO",
   "SEQUENTIAL_OPT",
   "SPICE_CACERT_OPT",
   "SPICE_CERT_OPT",
@@ -235,6 +244,12 @@ __all__ = [
   "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",
@@ -253,12 +268,14 @@ __all__ = [
   "JobSubmittedException",
   "ParseTimespec",
   "RunWhileClusterStopped",
+  "RunWhileDaemonsStopped",
   "SubmitOpCode",
   "SubmitOpCodeToDrainedQueue",
   "SubmitOrSend",
   "UsesRPC",
   # Formatting functions
   "ToStderr", "ToStdout",
+  "ToStdoutAndLoginfo",
   "FormatError",
   "FormatQueryResult",
   "FormatParamsDictInfo",
@@ -356,6 +373,7 @@ _QFT_NAMES = {
   constants.QFT_TEXT: "Text",
   constants.QFT_BOOL: "Boolean",
   constants.QFT_NUMBER: "Number",
+  constants.QFT_NUMBER_FLOAT: "Floating-point number",
   constants.QFT_UNIT: "Storage size",
   constants.QFT_TIMESTAMP: "Timestamp",
   constants.QFT_OTHER: "Custom",
@@ -540,7 +558,7 @@ def ListTags(opts, args):
 
   """
   kind, name = _ExtractTagsObject(opts, args)
-  cl = GetClient(query=True)
+  cl = GetClient()
   result = cl.QueryTags(kind, name)
   result = list(result)
   result.sort()
@@ -691,6 +709,15 @@ def check_key_val(option, opt, value):  # pylint: disable=W0613
   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("/"):
@@ -793,6 +820,7 @@ class CliOption(Option):
     "multilistidentkeyval",
     "identkeyval",
     "keyval",
+    "keyprivateval",
     "unit",
     "bool",
     "list",
@@ -802,6 +830,7 @@ class CliOption(Option):
   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
@@ -952,6 +981,21 @@ 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")
@@ -1301,6 +1345,17 @@ 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"
@@ -1407,13 +1462,12 @@ TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
                          help="Maximum time to wait")
 
 COMPRESS_OPT = cli_option("--compress", dest="compress",
-                          default=constants.IEC_NONE,
-                          help="The compression mode to use",
-                          choices=list(constants.IEC_ALL))
+                          type="string", default=constants.IEC_NONE,
+                          help="The compression mode to use")
 
 TRANSPORT_COMPRESSION_OPT = \
     cli_option("--transport-compression", dest="transport_compression",
-               default=constants.IEC_NONE, choices=list(constants.IEC_ALL),
+               type="string", default=constants.IEC_NONE,
                help="The compression mode to use during transport")
 
 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
@@ -1572,6 +1626,11 @@ PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
                           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")
@@ -1720,6 +1779,59 @@ HOTPLUG_IF_POSSIBLE_OPT = cli_option("--hotplug-if-possible",
                                      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]
 
@@ -1748,7 +1860,10 @@ COMMON_CREATE_OPTS = [
   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,
@@ -2591,7 +2706,7 @@ def GenericMain(commands, override=None, aliases=None,
   utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
                      stderr_logging=True)
 
-  logging.info("Command line: %s",