Merge branch 'stable-2.10' into stable-2.11
authorHrvoje Ribicic <riba@google.com>
Thu, 3 Sep 2015 12:10:34 +0000 (14:10 +0200)
committerHrvoje Ribicic <riba@google.com>
Thu, 3 Sep 2015 12:36:05 +0000 (14:36 +0200)
* stable-2.10
  (no changes)

* stable-2.9
  Document quoting of special values in key-value parameters
  replace-disks: fix --ignore-ipolicy

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

273 files changed:
.gitignore
INSTALL
Makefile.am
NEWS
README
UPGRADE
autotools/build-bash-completion
configure.ac
daemons/daemon-util.in
devel/build_chroot
doc/admin.rst
doc/design-2.11.rst [new file with mode: 0644]
doc/design-daemons.rst
doc/design-draft.rst
doc/design-glusterfs-ganeti-support.rst
doc/design-internal-shutdown.rst
doc/design-kvmd.rst [new file with mode: 0644]
doc/design-multi-version-tests.rst [new file with mode: 0644]
doc/design-node-security.rst [new file with mode: 0644]
doc/design-os.rst [new file with mode: 0644]
doc/design-query-splitting.rst
doc/design-ssh-ports.rst [new file with mode: 0644]
doc/design-upgrade.rst
doc/examples/ganeti.default
doc/examples/ganeti.default-debug
doc/hooks.rst
doc/iallocator.rst
doc/index.rst
doc/install.rst
doc/move-instance.rst
doc/security.rst
doc/users/groups.in
doc/virtual-cluster.rst
lib/backend.py
lib/bootstrap.py
lib/build/sphinx_ext.py
lib/cli.py
lib/client/gnt_backup.py
lib/client/gnt_cluster.py
lib/client/gnt_debug.py
lib/client/gnt_instance.py
lib/client/gnt_network.py
lib/client/gnt_node.py
lib/cmdlib/__init__.py
lib/cmdlib/backup.py
lib/cmdlib/cluster.py
lib/cmdlib/common.py
lib/cmdlib/group.py
lib/cmdlib/instance.py
lib/cmdlib/instance_operation.py
lib/cmdlib/instance_query.py
lib/cmdlib/instance_storage.py
lib/cmdlib/instance_utils.py
lib/cmdlib/network.py
lib/cmdlib/node.py
lib/cmdlib/query.py
lib/config.py
lib/daemon.py
lib/ht.py
lib/http/__init__.py
lib/http/server.py
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
lib/jqueue.py
lib/luxi.py
lib/masterd/iallocator.py
lib/masterd/instance.py
lib/netutils.py
lib/network.py
lib/objects.py
lib/opcodes.py.in_before
lib/opcodes_base.py
lib/outils.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/__init__.py [copied from lib/server/__init__.py with 97% similarity]
lib/rpc/client.py [new file with mode: 0644]
lib/rpc/errors.py [copied from test/py/cmdlib/testsupport/iallocator_mock.py with 55% similarity]
lib/rpc/node.py [moved from lib/rpc.py with 97% similarity]
lib/rpc/transport.py [new file with mode: 0644]
lib/rpc_defs.py
lib/runtime.py
lib/server/masterd.py
lib/server/noded.py
lib/server/rapi.py
lib/ssconf.py
lib/ssh.py
lib/storage/base.py
lib/storage/bdev.py
lib/storage/container.py
lib/storage/drbd.py
lib/storage/filestorage.py
lib/storage/gluster.py [new file with mode: 0644]
lib/tools/burnin.py
lib/tools/ensure_dirs.py
lib/utils/__init__.py
lib/utils/io.py
lib/utils/process.py
lib/utils/security.py [new file with mode: 0644]
lib/utils/storage.py
lib/utils/version.py
lib/utils/x509.py
lib/vcluster.py
lib/watcher/__init__.py
lib/watcher/state.py
man/ganeti-extstorage-interface.rst
man/ganeti-kvmd.rst [new file with mode: 0644]
man/ganeti-mond.rst
man/ganeti.rst
man/gnt-backup.rst
man/gnt-cluster.rst
man/gnt-instance.rst
man/gnt-network.rst
man/hail.rst
man/harep.rst
man/hbal.rst
man/hsqueeze.rst [new file with mode: 0644]
man/htools.rst
qa/colors.py [new file with mode: 0644]
qa/ganeti-qa.py
qa/qa-sample.json
qa/qa_cluster.py
qa/qa_config.py
qa/qa_daemon.py
qa/qa_group.py
qa/qa_instance.py
qa/qa_iptables.py [new file with mode: 0644]
qa/qa_job.py
qa/qa_job_utils.py
qa/qa_network.py
qa/qa_node.py
qa/qa_rapi.py
qa/qa_utils.py
qa/rapi-workload.py [new file with mode: 0755]
src/AutoConf.hs.in
src/Ganeti/BasicTypes.hs
src/Ganeti/Config.hs
src/Ganeti/ConfigReader.hs
src/Ganeti/ConstantUtils.hs
src/Ganeti/Constants.hs
src/Ganeti/Daemon.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/Instance.hs
src/Ganeti/HTools/Program/Harep.hs
src/Ganeti/HTools/Program/Hbal.hs
src/Ganeti/HTools/Program/Hscan.hs
src/Ganeti/HTools/Program/Hsqueeze.hs [new file with mode: 0644]
src/Ganeti/HTools/Program/Main.hs
src/Ganeti/HTools/Types.hs
src/Ganeti/Hs2Py/GenOpCodes.hs
src/Ganeti/Hs2Py/ListConstants.hs.in
src/Ganeti/Hs2Py/OpDoc.hs
src/Ganeti/JQScheduler.hs [new file with mode: 0644]
src/Ganeti/JQueue.hs
src/Ganeti/JSON.hs
src/Ganeti/Kvmd.hs [new file with mode: 0644]
src/Ganeti/Logging.hs
src/Ganeti/Luxi.hs
src/Ganeti/Monitoring/Server.hs
src/Ganeti/Objects.hs
src/Ganeti/OpCodes.hs
src/Ganeti/OpParams.hs
src/Ganeti/Path.hs
src/Ganeti/PyValue.hs [moved from src/Ganeti/PyValueInstances.hs with 73% similarity]
src/Ganeti/Query/Cluster.hs
src/Ganeti/Query/Common.hs
src/Ganeti/Query/Filter.hs
src/Ganeti/Query/Group.hs
src/Ganeti/Query/Instance.hs [new file with mode: 0644]
src/Ganeti/Query/Locks.hs [copied from src/Ganeti/Hash.hs with 52% similarity]
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/Storage/Utils.hs
src/Ganeti/THH.hs
src/Ganeti/THH/PyType.hs [new file with mode: 0644]
src/Ganeti/Types.hs
src/Ganeti/UDSServer.hs [new file with mode: 0644]
src/Ganeti/Utils.hs
src/Ganeti/VCluster.hs [copied from src/Ganeti/Query/Cluster.hs with 58% similarity]
src/ganeti-kvmd.hs [copied from src/ganeti-mond.hs with 77% similarity]
src/ganeti-mond.hs
src/rpc-test.hs
test/data/cluster_config_2.10.json [new file with mode: 0644]
test/data/htools/hail-alloc-drbd.json
test/data/htools/hail-alloc-restricted-network.json
test/data/htools/hail-alloc-spindles.json
test/data/htools/hail-change-group.json
test/data/htools/hail-node-evac.json
test/data/htools/hail-reloc-drbd.json
test/data/htools/hsqueeze-mixed-instances.data [new file with mode: 0644]
test/data/htools/hsqueeze-overutilized.data [new file with mode: 0644]
test/data/htools/hsqueeze-underutilized.data [new file with mode: 0644]
test/data/htools/rapi/instances.json
test/data/instance-prim-sec.txt
test/hs/Test/Ganeti/Common.hs
test/hs/Test/Ganeti/Kvmd.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Luxi.hs
test/hs/Test/Ganeti/Objects.hs
test/hs/Test/Ganeti/OpCodes.hs
test/hs/Test/Ganeti/Query/Aliases.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Query/Instance.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Rpc.hs
test/hs/Test/Ganeti/Runtime.hs
test/hs/Test/Ganeti/Ssconf.hs
test/hs/Test/Ganeti/TestCommon.hs
test/hs/Test/Ganeti/Utils.hs
test/hs/htest.hs
test/hs/shelltests/htools-hsqueeze.test [new file with mode: 0644]
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_query_unittest.py
test/py/cmdlib/instance_unittest.py
test/py/cmdlib/node_unittest.py
test/py/cmdlib/testsupport/__init__.py
test/py/cmdlib/testsupport/config_mock.py
test/py/cmdlib/testsupport/pathutils_mock.py [copied from test/py/cmdlib/testsupport/ssh_mock.py with 90% similarity]
test/py/cmdlib/testsupport/rpc_runner_mock.py
test/py/daemon-util_unittest.bash
test/py/docs_unittest.py
test/py/ganeti.backend_unittest.py
test/py/ganeti.client.gnt_cluster_unittest.py
test/py/ganeti.config_unittest.py
test/py/ganeti.hooks_unittest.py
test/py/ganeti.hypervisor.hv_chroot_unittest.py
test/py/ganeti.hypervisor.hv_fake_unittest.py
test/py/ganeti.hypervisor.hv_kvm_unittest.py
test/py/ganeti.hypervisor.hv_lxc_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py
test/py/ganeti.jqueue_unittest.py
test/py/ganeti.luxi_unittest.py
test/py/ganeti.mcpu_unittest.py
test/py/ganeti.netutils_unittest.py
test/py/ganeti.objects_unittest.py
test/py/ganeti.opcodes_unittest.py
test/py/ganeti.query_unittest.py
test/py/ganeti.rapi.baserlib_unittest.py
test/py/ganeti.rapi.rlib2_unittest.py
test/py/ganeti.rapi.testutils_unittest.py
test/py/ganeti.rpc.client_unittest.py [copied from test/py/ganeti.luxi_unittest.py with 51% similarity]
test/py/ganeti.rpc_unittest.py
test/py/ganeti.ssconf_unittest.py
test/py/ganeti.storage.filestorage_unittest.py
test/py/ganeti.storage.gluster_unittest.py [new file with mode: 0644]
test/py/ganeti.utils.security_unittest.py [new file with mode: 0755]
test/py/ganeti.utils.storage_unittest.py
test/py/ganeti.utils.version_unittest.py
test/py/ganeti.utils.x509_unittest.py
test/py/import-export_unittest-helper
tools/cfgupgrade
tools/cfgupgrade12
tools/move-instance
tools/post-upgrade

index 7b26697..588af9b 100644 (file)
@@ -9,7 +9,9 @@
 *.swp
 *~
 *.o
+*.hpc_o
 *.hi
+*.hpc_hi
 *.hp
 *.tix
 *.prof
 .hpc/
 
 # /
+/.hsenv
 /Makefile
+/Makefile.ghc
+/Makefile.ghc.bak
 /Makefile.in
 /Makefile.local
-/TAGS
+/Session.vim
+/TAGS*
 /aclocal.m4
 /autom4te.cache
 /autotools/install-sh
 /test/hs/hroller
 /test/hs/hscan
 /test/hs/hspace
+/test/hs/hsqueeze
 /test/hs/hpc-htools
 /test/hs/hpc-mon-collector
 /test/hs/htest
 /src/hs2py
 /src/hs2py-constants
 /src/ganeti-confd
+/src/ganeti-kvmd
 /src/ganeti-luxid
 /src/ganeti-mond
 /src/rpc-test
diff --git a/INSTALL b/INSTALL
index fe2d1dc..b43d77c 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -76,7 +76,8 @@ If bitarray is missing it can be installed from easy-install::
 
   $ easy_install bitarray
 
-Note that this does not install optional packages::
+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
 
@@ -146,18 +147,34 @@ deploy Ganeti on production machines). More specifically:
 - `bytestring <http://hackage.haskell.org/package/bytestring>`_ and
   `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>`_
 - `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)
+- `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>`_
+- `vector <http://hackage.haskell.org/package/vector>`_
+- `process <http://hackage.haskell.org/package/process>`_, version 1.0.1.1 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-utf8-string-dev libghc-curl-dev \
-                    libghc-hslogger-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
 
 Or in older versions of these distributions (using GHC 6.x)::
 
@@ -168,57 +185,51 @@ Or in older versions of these distributions (using GHC 6.x)::
 In Fedora, some of them are available via packages as well::
 
   $ yum install ghc ghc-json-devel ghc-network-devel \
-                    ghc-parallel-devel ghc-deepseq-devel
+                    ghc-parallel-devel ghc-deepseq-devel \
+                    ghc-hslogger-devel ghc-text-devel \
+                    ghc-regex-pcre-devel
 
-If using a distribution which does not provide them, first install
-the Haskell platform. You can also install ``cabal`` manually::
+The most recent Fedora doesn't provide ``crypto``, ``inotify``. So these
+need to be installed using ``cabal``.
+
+If using a distribution which does not provide these libraries, first
+install the Haskell platform. You can also install ``cabal`` manually::
 
   $ apt-get install cabal-install
   $ cabal update
 
-Then install the additional libraries (only the ones not available in your
-distribution packages) via ``cabal``::
+Then install the additional native libraries::
+
+  $ apt-get install libpcre3-dev libcurl4-openssl-dev
+
+And finally the libraries required for building the packages (only the
+ones not available in your distribution packages) via ``cabal``::
 
-  $ cabal install json network parallel utf8-string curl hslogger
+  $ cabal install json network parallel utf8-string curl hslogger \
+                  Crypto text hinotify==0.3.2 regex-pcre \
+                  attoparsec vector base64-bytestring
 
 Haskell optional features
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Optionally, more functionality can be enabled if your build machine has
-a few more Haskell libraries enabled: the ``ganeti-confd`` and
-``ganeti-luxid`` daemon (``--enable-confd``) and the monitoring daemon
-(``--enable-mond``). The list of extra dependencies for these is:
+a few more Haskell libraries enabled: the ``ganeti-confd`` daemon
+(``--enable-confd``) and the monitoring daemon (``--enable-mond``).
+The extra dependency for these is:
 
-- `Crypto <http://hackage.haskell.org/package/Crypto>`_, tested with
-  version 4.2.4
-- `text <http://hackage.haskell.org/package/text>`_
-- `hinotify <http://hackage.haskell.org/package/hinotify>`_, tested with
-  version 0.3.2
-- `regex-pcre <http://hackage.haskell.org/package/regex-pcre>`_,
-  bindings for the ``pcre`` library
-- `attoparsec <http://hackage.haskell.org/package/attoparsec>`_
-- `vector <http://hackage.haskell.org/package/vector>`_
 - `snap-server` <http://hackage.haskell.org/package/snap-server>`_, version
   0.8.1 and above.
-- `process <http://hackage.haskell.org/package/process>`_, version 1.0.1.1 and
-  above
 
-These libraries are available in Debian Wheezy (but not in Squeeze), so you
+This library is available in Debian Wheezy (but not in Squeeze), so you
 can use either apt::
 
-  $ apt-get install libghc-crypto-dev libghc-text-dev \
-                    libghc-hinotify-dev libghc-regex-pcre-dev \
-                    libpcre3-dev \
-                    libghc-attoparsec-dev libghc-vector-dev \
-                    libghc-snap-server-dev
+  $ apt-get install libghc-snap-server-dev
 
-or ``cabal``, after installing a required non-Haskell dependency::
+or ``cabal``::
 
-  $ apt-get install libpcre3-dev libcurl4-openssl-dev
-  $ cabal install Crypto text hinotify==0.3.2 regex-pcre \
-                  attoparsec vector snap-server
+  $ cabal install snap-server
 
-to install them.
+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
@@ -229,13 +240,6 @@ own, explicitly forcing the installation of compatible versions::
                   hashable==1.1.2.0 case-insensitive==0.3 parsec==3.0.1 \
                   network==2.3 snap-server==0.8.1
 
-The most recent Fedora doesn't provide ``crypto``, ``inotify``. So these
-need to be installed using ``cabal``, if desired. The other packages can
-be installed via ``yum``::
-
-  $ yum install ghc-hslogger-devel ghc-text-devel \
-                ghc-regex-pcre-devel
-
 .. _cabal-note:
 .. note::
   If one of the cabal packages fails to install due to unfulfilled
@@ -256,7 +260,10 @@ To install, simply run the following command::
 This will install the software under ``/usr/local``. You then need to
 copy ``doc/examples/ganeti.initd`` to ``/etc/init.d/ganeti`` and
 integrate it into your boot sequence (``chkconfig``, ``update-rc.d``,
-etc.).
+etc.). Also, Ganeti uses symbolic links in the sysconfdir to determine,
+which of potentially many installed versions currently is used. If these
+symbolic links should be added by the install as well, add the
+option ``--enable-symlinks`` to the ``configure`` call.
 
 
 Cluster initialisation
index b1a8509..a384351 100644 (file)
@@ -86,6 +86,7 @@ httpdir = $(pkgpythondir)/http
 masterddir = $(pkgpythondir)/masterd
 confddir = $(pkgpythondir)/confd
 rapidir = $(pkgpythondir)/rapi
+rpcdir = $(pkgpythondir)/rpc
 serverdir = $(pkgpythondir)/server
 watcherdir = $(pkgpythondir)/watcher
 impexpddir = $(pkgpythondir)/impexpd
@@ -134,6 +135,7 @@ HS_DIRS = \
        src/Ganeti/Storage/Diskstats \
        src/Ganeti/Storage/Drbd \
        src/Ganeti/Storage/Lvm \
+       src/Ganeti/THH \
        test/hs \
        test/hs/Test \
        test/hs/Test/Ganeti \
@@ -177,6 +179,7 @@ DIRS = \
        lib/impexpd \
        lib/masterd \
        lib/rapi \
+       lib/rpc \
        lib/server \
        lib/storage \
        lib/tools \
@@ -244,6 +247,10 @@ CLEANFILES = \
        $(addsuffix /*.py[co],$(DIRS)) \
        $(addsuffix /*.hi,$(HS_DIRS)) \
        $(addsuffix /*.o,$(HS_DIRS)) \
+       $(addsuffix /*.$(HTEST_SUFFIX)_hi,$(HS_DIRS)) \
+       $(addsuffix /*.$(HTEST_SUFFIX)_o,$(HS_DIRS)) \
+       Makefile.ghc \
+       Makefile.ghc.bak \
        $(PYTHON_BOOTSTRAP) \
        $(gnt_python_sbin_SCRIPTS) \
        epydoc.conf \
@@ -289,17 +296,13 @@ GENERATED_FILES = \
 clean-local:
        rm -rf tools/shebang
 
-HS_GENERATED_FILES =
-if WANT_HTOOLS
-HS_GENERATED_FILES += $(HS_PROGS)
+HS_GENERATED_FILES = $(HS_PROGS) src/hluxid src/ganeti-luxid
 if ENABLE_CONFD
-HS_GENERATED_FILES += src/hconfd src/ganeti-confd src/hluxid src/ganeti-luxid
+HS_GENERATED_FILES += src/hconfd src/ganeti-confd
 endif
-
 if ENABLE_MOND
 HS_GENERATED_FILES += src/ganeti-mond
 endif
-endif
 
 built_base_sources = \
        stamp-directories \
@@ -369,7 +372,6 @@ pkgpython_PYTHON = \
        lib/pathutils.py \
        lib/qlang.py \
        lib/query.py \
-       lib/rpc.py \
        lib/rpc_defs.py \
        lib/runtime.py \
        lib/serializer.py \
@@ -431,7 +433,8 @@ storage_PYTHON = \
        lib/storage/drbd.py \
        lib/storage/drbd_info.py \
        lib/storage/drbd_cmdgen.py \
-       lib/storage/filestorage.py
+       lib/storage/filestorage.py \
+       lib/storage/gluster.py
 
 rapi_PYTHON = \
        lib/rapi/__init__.py \
@@ -471,6 +474,13 @@ server_PYTHON = \
        lib/server/noded.py \
        lib/server/rapi.py
 
+rpc_PYTHON = \
+       lib/rpc/__init__.py \
+       lib/rpc/client.py \
+       lib/rpc/errors.py \
+       lib/rpc/node.py \
+       lib/rpc/transport.py
+
 pytools_PYTHON = \
        lib/tools/__init__.py \
        lib/tools/burnin.py \
@@ -491,6 +501,7 @@ utils_PYTHON = \
        lib/utils/nodesetup.py \
        lib/utils/process.py \
        lib/utils/retry.py \
+       lib/utils/security.py \
        lib/utils/storage.py \
        lib/utils/text.py \
        lib/utils/version.py \
@@ -514,6 +525,7 @@ docinput = \
        doc/design-2.8.rst \
        doc/design-2.9.rst \
        doc/design-2.10.rst \
+       doc/design-2.11.rst \
        doc/design-autorepair.rst \
        doc/design-bulk-create.rst \
        doc/design-ceph-ganeti-support.rst \
@@ -533,25 +545,31 @@ docinput = \
        doc/design-hugepages-support.rst \
        doc/design-impexp2.rst \
        doc/design-internal-shutdown.rst \
+       doc/design-kvmd.rst \
        doc/design-linuxha.rst \
        doc/design-lu-generated-jobs.rst \
        doc/design-monitoring-agent.rst \
        doc/design-multi-reloc.rst \
+       doc/design-multi-version-tests.rst \
        doc/design-network.rst \
        doc/design-node-add.rst \
+       doc/design-node-security.rst \
        doc/design-oob.rst \
        doc/design-openvswitch.rst \
        doc/design-opportunistic-locking.rst \
        doc/design-optables.rst \
+       doc/design-os.rst \
        doc/design-ovf-support.rst \
        doc/design-partitioned.rst \
        doc/design-performance-tests.rst \
        doc/design-query-splitting.rst \
        doc/design-query2.rst \
+       doc/design-query-splitting.rst \
        doc/design-reason-trail.rst \
        doc/design-resource-model.rst \
        doc/design-restricted-commands.rst \
        doc/design-shared-storage.rst \
+       doc/design-ssh-ports.rst \
        doc/design-storagetypes.rst \
        doc/design-upgrade.rst \
        doc/design-virtual-clusters.rst \
@@ -590,17 +608,22 @@ HS_MYEXECLIB_PROGS=
 endif
 
 # Haskell programs to be compiled by "make really-all"
-HS_COMPILE_PROGS= \
-       src/ganeti-mond \
-       src/hconfd \
+HS_COMPILE_PROGS = \
+       src/ganeti-kvmd \
        src/hluxid \
        src/hs2py \
        src/rpc-test
+if ENABLE_CONFD
+HS_COMPILE_PROGS += src/hconfd
+endif
+if ENABLE_MOND
+HS_COMPILE_PROGS += src/ganeti-mond
+endif
 
 # All Haskell non-test programs to be compiled but not automatically installed
 HS_PROGS = $(HS_BIN_PROGS) $(HS_MYEXECLIB_PROGS)
 
-HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller
+HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller hsqueeze
 HS_HTOOLS_PROGS = $(HS_BIN_ROLES) hail
 
 # Haskell programs that cannot be disabled at configure (e.g., unlike
@@ -614,6 +637,9 @@ HS_DEFAULT_PROGS = \
 
 HS_ALL_PROGS = $(HS_DEFAULT_PROGS) $(HS_MYEXECLIB_PROGS)
 
+HS_TEST_PROGS = $(filter test/%,$(HS_ALL_PROGS))
+HS_SRC_PROGS = $(filter-out test/%,$(HS_ALL_PROGS))
+
 HS_PROG_SRCS = $(patsubst %,%.hs,$(HS_DEFAULT_PROGS)) src/mon-collector.hs
 HS_BUILT_TEST_HELPERS = $(HS_BIN_ROLES:%=test/hs/%) test/hs/hail
 
@@ -626,6 +652,22 @@ if DEVELOPER_MODE
 HFLAGS += -Werror
 endif
 
+if HPROFILE
+HFLAGS += -prof -auto-all
+endif
+if HCOVERAGE
+HFLAGS += -fhpc
+endif
+if HTEST
+HFLAGS += -DTEST
+endif
+
+HTEST_SUFFIX = hpc
+
+HTEST_FLAGS = $(HFLAGS) -fhpc -itest/hs \
+       -osuf .$(HTEST_SUFFIX)_o \
+       -hisuf .$(HTEST_SUFFIX)_hi
+
 # extra flags that can be overriden on the command line (e.g. -Wwarn, etc.)
 HEXTRA =
 # internal extra flags (used for test/hs/htest mainly)
@@ -695,6 +737,7 @@ HS_LIB_SRCS = \
        src/Ganeti/HTools/Program/Hinfo.hs \
        src/Ganeti/HTools/Program/Hscan.hs \
        src/Ganeti/HTools/Program/Hspace.hs \
+       src/Ganeti/HTools/Program/Hsqueeze.hs \
        src/Ganeti/HTools/Program/Hroller.hs \
        src/Ganeti/HTools/Program/Main.hs \
        src/Ganeti/HTools/Types.hs \
@@ -706,8 +749,10 @@ HS_LIB_SRCS = \
        src/Ganeti/Hs2Py/GenOpCodes.hs \
        src/Ganeti/Hs2Py/OpDoc.hs \
        src/Ganeti/JQueue.hs \
+       src/Ganeti/JQScheduler.hs \
        src/Ganeti/JSON.hs \
        src/Ganeti/Jobs.hs \
+       src/Ganeti/Kvmd.hs \
        src/Ganeti/Logging.hs \
        src/Ganeti/Luxi.hs \
        src/Ganeti/Monitoring/Server.hs \
@@ -717,14 +762,16 @@ HS_LIB_SRCS = \
        src/Ganeti/OpParams.hs \
        src/Ganeti/Path.hs \
        src/Ganeti/Parsers.hs \
-       src/Ganeti/PyValueInstances.hs \
+       src/Ganeti/PyValue.hs \
        src/Ganeti/Query/Cluster.hs \
        src/Ganeti/Query/Common.hs \
        src/Ganeti/Query/Export.hs \
        src/Ganeti/Query/Filter.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 \
@@ -741,8 +788,11 @@ HS_LIB_SRCS = \
        src/Ganeti/Storage/Lvm/Types.hs \
        src/Ganeti/Storage/Utils.hs \
        src/Ganeti/THH.hs \
+       src/Ganeti/THH/PyType.hs \
        src/Ganeti/Types.hs \
-       src/Ganeti/Utils.hs
+       src/Ganeti/UDSServer.hs \
+       src/Ganeti/Utils.hs\
+       src/Ganeti/VCluster.hs
 
 HS_TEST_SRCS = \
        test/hs/Test/AutoConf.hs \
@@ -770,11 +820,14 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/JSON.hs \
        test/hs/Test/Ganeti/Jobs.hs \
        test/hs/Test/Ganeti/JQueue.hs \
+       test/hs/Test/Ganeti/Kvmd.hs \
        test/hs/Test/Ganeti/Luxi.hs \
        test/hs/Test/Ganeti/Network.hs \
        test/hs/Test/Ganeti/Objects.hs \
        test/hs/Test/Ganeti/OpCodes.hs \
+       test/hs/Test/Ganeti/Query/Aliases.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/Network.hs \
        test/hs/Test/Ganeti/Query/Query.hs \
@@ -973,11 +1026,10 @@ qa_scripts = \
        qa/qa_os.py \
        qa/qa_rapi.py \
        qa/qa_tags.py \
-       qa/qa_utils.py
+       qa/qa_utils.py \
+        qa/rapi-workload.py
 
-bin_SCRIPTS =
-if WANT_HTOOLS
-bin_SCRIPTS += $(HS_BIN_PROGS)
+bin_SCRIPTS = $(HS_BIN_PROGS)
 install-exec-hook:
        @mkdir_p@ $(DESTDIR)$(iallocatorsdir)
 # FIXME: this is a hardcoded logic, instead of auto-resolving
@@ -986,56 +1038,62 @@ install-exec-hook:
        for role in $(HS_BIN_ROLES); do \
          $(LN_S) -f htools $(DESTDIR)$(bindir)/$$role ; \
        done
+
+HS_SRCS = $(HS_LIBTESTBUILT_SRCS)
+
+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 \
+              | $(built_base_sources) $(HS_BUILT_SRCS)
+       $(GHC) -M -dep-makefile $@ -dep-suffix $(HTEST_SUFFIX) $(HFLAGS) -itest/hs \
+               $(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
+# finally will be linked to the target object (e.g. src/Ganeti/Daemon.o) are
+# missing in Makefile.ghc.
+# see: https://www.haskell.org/ghc/docs/7.6.2/html/users_guide/separate-compilation.html#makefile-dependencies
+# Following substitutions will add dependencies between object files which
+# corresponds to the interface file already there as a dependency for each
+# 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/' $@
+
+@include_makefile_ghc@
+
+%.o:
+       @echo '[GHC]: $@ <- $^'
+       @$(GHC) -c $(HFLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(@:%.o=%.hs)
+
+%.$(HTEST_SUFFIX)_o:
+       @echo '[GHC]: $@ <- $^'
+       @$(GHC) -c $(HTEST_FLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) $(@:%.$(HTEST_SUFFIX)_o=%.hs)
+
+%.hi: %.o ;
+%.$(HTEST_SUFFIX)_hi: %.$(HTEST_SUFFIX)_o ;
+
+$(HS_SRC_PROGS): %: %.o | stamp-directories
+       $(GHC) $(HFLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) --make $(@:%=%.hs)
+       @rm -f $(notdir $@).tix
+       @touch "$@"
 
-$(HS_ALL_PROGS): %: %.hs $(HS_LIBTESTBUILT_SRCS) Makefile
-       @if [ "$(notdir $@)" = "test" ] && [ "$(HS_NODEV)" ]; then \
+$(HS_TEST_PROGS): %: %.$(HTEST_SUFFIX)_o \
+                          | 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
+       $(GHC) $(HTEST_FLAGS) \
+               $(HS_PARALLEL3) $(HS_REGEX_PCRE) $(HEXTRA_COMBINED) --make $(@:%=%.hs)
        @rm -f $(notdir $@).tix
-       $(GHC) --make \
-         $(HFLAGS) \
-         $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
-         -osuf $(notdir $@).o -hisuf $(notdir $@).hi \
-         $(HEXTRA_COMBINED) $(HEXTRA_INT) $@
        @touch "$@"
 
-# for the test/hs/htest binary, we need to enable profiling/coverage
-test/hs/htest: HEXTRA_INT=-fhpc -itest/hs
-
-# we compile the hpc-htools binary with the program coverage
-test/hs/hpc-htools: HEXTRA_INT=-fhpc
-
-# we compile the hpc-mon-collector binary with the program coverage
-test/hs/hpc-mon-collector: HEXTRA_INT=-fhpc
-
-# test dependency
-test/hs/offline-test.sh: test/hs/hpc-htools test/hs/hpc-mon-collector
-
-# rules for building profiling-enabled versions of the haskell
-# programs: hs-prof does the full two-step build, whereas
-# hs-prof-quick does only the final rebuild (hs-prof must have been
-# run before)
-.PHONY: hs-prof hs-prof-quick
-hs-prof:
-       @if [ -z "$(TARGET)" ]; then \
-         echo "You need to define TARGET when running this rule" 1>&2; \
-         exit 1; \
-       fi
-       $(MAKE) $(AM_MAKEFLAGS) clean
-       $(MAKE) $(AM_MAKEFLAGS) $(TARGET) HEXTRA="-osuf o"
-       rm -f $(HS_ALL_PROGS)
-       $(MAKE) $(AM_MAKEFLAGS) hs-prof-quick
-
-hs-prof-quick:
-       @if [ -z "$(TARGET)" ]; then \
-         echo "You need to define TARGET when running this rule" 1>&2; \
-         exit 1; \
-       fi
-       $(MAKE) $(AM_MAKEFLAGS) $(TARGET) HEXTRA="-osuf prof_o -prof -auto-all"
-
 dist_sbin_SCRIPTS = \
        tools/ganeti-listrunner
 
@@ -1056,6 +1114,7 @@ src/ganeti-luxid: src/hluxid
 
 nodist_sbin_SCRIPTS += src/ganeti-confd
 nodist_sbin_SCRIPTS += src/ganeti-luxid
+nodist_sbin_SCRIPTS += src/ganeti-kvmd
 endif
 
 if ENABLE_MOND
@@ -1198,6 +1257,7 @@ man_MANS = \
        man/ganeti-confd.8 \
        man/ganeti-luxid.8 \
        man/ganeti-listrunner.8 \
+       man/ganeti-kvmd.8 \
        man/ganeti-masterd.8 \
        man/ganeti-mond.8 \
        man/ganeti-noded.8 \
@@ -1223,6 +1283,7 @@ man_MANS = \
        man/hinfo.1 \
        man/hscan.1 \
        man/hspace.1 \
+       man/hsqueeze.1 \
        man/hroller.1 \
        man/htools.1 \
        man/mon-collector.7
@@ -1284,6 +1345,9 @@ TEST_FILES = \
        test/data/htools/hroller-nodegroups.data \
        test/data/htools/hroller-nonredundant.data \
        test/data/htools/hroller-online.data \
+       test/data/htools/hsqueeze-mixed-instances.data \
+       test/data/htools/hsqueeze-overutilized.data \
+       test/data/htools/hsqueeze-underutilized.data \
        test/data/htools/unique-reboot-order.data \
        test/hs/shelltests/htools-balancing.test \
        test/hs/shelltests/htools-basic.test \
@@ -1293,6 +1357,7 @@ TEST_FILES = \
        test/hs/shelltests/htools-hbal-evac.test \
        test/hs/shelltests/htools-hroller.test \
        test/hs/shelltests/htools-hspace.test \
+       test/hs/shelltests/htools-hsqueeze.test \
        test/hs/shelltests/htools-invalid.test \
        test/hs/shelltests/htools-multi-group.test \
        test/hs/shelltests/htools-no-backend.test \
@@ -1324,6 +1389,7 @@ TEST_FILES = \
        test/data/cluster_config_2.7.json \
        test/data/cluster_config_2.8.json \
        test/data/cluster_config_2.9.json \
+       test/data/cluster_config_2.10.json \
        test/data/instance-minor-pairing.txt \
        test/data/instance-prim-sec.txt \
        test/data/ip-addr-show-dummy0.txt \
@@ -1456,6 +1522,7 @@ python_tests = \
        test/py/ganeti.rapi.rlib2_unittest.py \
        test/py/ganeti.rapi.testutils_unittest.py \
        test/py/ganeti.rpc_unittest.py \
+       test/py/ganeti.rpc.client_unittest.py \
        test/py/ganeti.runtime_unittest.py \
        test/py/ganeti.serializer_unittest.py \
        test/py/ganeti.server.rapi_unittest.py \
@@ -1465,6 +1532,7 @@ python_tests = \
        test/py/ganeti.storage.container_unittest.py \
        test/py/ganeti.storage.drbd_unittest.py \
        test/py/ganeti.storage.filestorage_unittest.py \
+       test/py/ganeti.storage.gluster_unittest.py \
        test/py/ganeti.tools.burnin_unittest.py \
        test/py/ganeti.tools.ensure_dirs_unittest.py \
        test/py/ganeti.tools.node_daemon_setup_unittest.py \
@@ -1481,6 +1549,7 @@ python_tests = \
        test/py/ganeti.utils.nodesetup_unittest.py \
        test/py/ganeti.utils.process_unittest.py \
        test/py/ganeti.utils.retry_unittest.py \
+       test/py/ganeti.utils.security_unittest.py \
        test/py/ganeti.utils.storage_unittest.py \
        test/py/ganeti.utils.text_unittest.py \
        test/py/ganeti.utils.version_unittest.py \
@@ -1505,6 +1574,7 @@ python_test_support = \
        test/py/cmdlib/testsupport/iallocator_mock.py \
        test/py/cmdlib/testsupport/lock_manager_mock.py \
        test/py/cmdlib/testsupport/netutils_mock.py \
+       test/py/cmdlib/testsupport/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 \
@@ -1530,6 +1600,8 @@ check_SCRIPTS =
 
 if WANT_HSTESTS
 nodist_TESTS += $(haskell_tests)
+# test dependency
+test/hs/offline-test.sh: test/hs/hpc-htools test/hs/hpc-mon-collector
 dist_TESTS += test/hs/offline-test.sh
 check_SCRIPTS += \
        test/hs/hpc-htools \
@@ -1563,6 +1635,7 @@ all_python_code = \
        $(storage_PYTHON) \
        $(rapi_PYTHON) \
        $(server_PYTHON) \
+       $(rpc_PYTHON) \
        $(pytools_PYTHON) \
        $(http_PYTHON) \
        $(confd_PYTHON) \
@@ -1774,7 +1847,8 @@ src/Ganeti/Hs2Py/ListConstants.hs: src/Ganeti/Hs2Py/ListConstants.hs.in \
 ##   'adminstDown
        NAMES=$$(sed -e "/^--/ d" $(abs_top_srcdir)/src/Ganeti/Constants.hs |\
                 sed -n -e "/=/ s/\(.*\) =.*/    '\1:/g p"); \
-       m4 -DPY_CONSTANT_NAMES="$$NAMES" $(abs_top_srcdir)/$< > $@
+       m4 -DPY_CONSTANT_NAMES="$$NAMES" \
+               $(abs_top_srcdir)/src/Ganeti/Hs2Py/ListConstants.hs.in > $@
 
 src/Ganeti/Curl/Internal.hs: src/Ganeti/Curl/Internal.hsc | stamp-directories
        hsc2hs -o $@ $<
@@ -1844,6 +1918,8 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DRAPI_GROUP="$(RAPI_GROUP)" \
            -DCONFD_USER="$(CONFD_USER)" \
            -DCONFD_GROUP="$(CONFD_GROUP)" \
+           -DKVMD_USER="$(KVMD_USER)" \
+           -DKVMD_GROUP="$(KVMD_GROUP)" \
            -DLUXID_USER="$(LUXID_USER)" \
            -DLUXID_GROUP="$(LUXID_GROUP)" \
            -DNODED_USER="$(NODED_USER)" \
@@ -1852,10 +1928,8 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
            -DMOND_GROUP="$(MOND_GROUP)" \
            -DDISK_SEPARATOR="$(DISK_SEPARATOR)" \
            -DQEMUIMG_PATH="$(QEMUIMG_PATH)" \
-           -DHTOOLS="True" \
            -DENABLE_CONFD="$(ENABLE_CONFD)" \
            -DXEN_CMD="$(XEN_CMD)" \
-           -DENABLE_SPLIT_QUERY="$(ENABLE_SPLIT_QUERY)" \
            -DENABLE_RESTRICTED_COMMANDS="$(ENABLE_RESTRICTED_COMMANDS)" \
            -DENABLE_MOND="$(ENABLE_MOND)" \
            -DHAS_GNU_LN="$(HAS_GNU_LN)" \
@@ -2087,12 +2161,12 @@ check-local: check-dirs check-news $(GENERATED_FILES)
        test -z "$$error"
 
 .PHONY: hs-test-%
-hs-test-%: test/hs/htest | $(BUILT_PYTHON_SOURCES)
+hs-test-%: test/hs/htest
        @rm -f htest.tix
        test/hs/htest -t $*
 
 .PHONY: hs-tests
-hs-tests: test/hs/htest | $(BUILT_PYTHON_SOURCES)
+hs-tests: test/hs/htest
        @rm -f htest.tix
        ./test/hs/htest
 
@@ -2338,19 +2412,22 @@ $(APIDOC_HS_DIR)/index.html: $(HS_LIBTESTBUILT_SRCS) Makefile
 .PHONY: TAGS
 TAGS: $(GENERATED_FILES)
        rm -f TAGS
-       $(GHC) -e ":etags" -v0 \
+       $(GHC) -e ":etags TAGS_hs" -v0 \
          $(filter-out -O -Werror,$(HFLAGS)) \
+               -osuf tags.o \
+               -hisuf tags.hi \
+    -lcurl \
          $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
          $(HS_LIBTEST_SRCS)
        find . -path './lib/*.py' -o -path './scripts/gnt-*' -o \
          -path './daemons/ganeti-*' -o -path './tools/*' -o \
          -path './qa/*.py' | \
-         etags -l python -a -
+         etags --etags-include=TAGS_hs -L -
 
 .PHONY: coverage
 
 COVERAGE_TESTS=
-if WANT_HTOOLS
+if HS_UNIT
 COVERAGE_TESTS += hs-coverage
 endif
 if PY_UNIT
@@ -2359,6 +2436,8 @@ endif
 
 coverage: $(COVERAGE_TESTS)
 
+test/py/docs_unittest.py: $(gnt_scripts)
+
 .PHONY: py-coverage
 py-coverage: $(GENERATED_FILES) $(python_tests)
        @test -n "$(PYCOVERAGE)" || \
diff --git a/NEWS b/NEWS
index c6cb44b..c3f54e0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,360 @@ News
 ====
 
 
+Version 2.11.7
+--------------
+
+*(Released Fri, 17 Apr 2015)*
+
+- 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.
+
+
+Version 2.11.6
+--------------
+
+*(Released Mon, 22 Sep 2014)*
+
+- Ganeti is now distributed under the 2-clause BSD license.
+  See the COPYING file.
+- Fix userspace access checks.
+- Various documentation fixes have been added.
+
+Inherited from the 2.10 branch:
+
+- The --online option now works as documented.
+- The watcher is paused during cluster upgrades; also, upgrade
+  checks for upgrades to resume first.
+- Instance disks can be added with --no-wait-for-sync.
+
+
+Version 2.11.5
+--------------
+
+*(Released Thu, 7 Aug 2014)*
+
+Inherited from the 2.10 branch:
+
+Important security release. In 2.10.0, the
+'gnt-cluster upgrade' command was introduced. Before
+performing an upgrade, the configuration directory of
+the cluster is backed up. Unfortunately, the archive was
+written with permissions that make it possible for
+non-privileged users to read the archive and thus have
+access to cluster and RAPI keys. After this release,
+the archive will be created with privileged access only.
+
+We strongly advise you to restrict the permissions of
+previously created archives. The archives are found in
+/var/lib/ganeti*.tar (unless otherwise configured with
+--localstatedir or --with-backup-dir).
+
+If you suspect that non-privileged users have accessed
+your archives already, we advise you to renew the
+cluster's crypto keys using 'gnt-cluster renew-crypto'
+and to reset the RAPI credentials by editing
+/var/lib/ganeti/rapi_users (respectively under a
+different path if configured differently with
+--localstatedir).
+
+Other changes included in this release:
+
+- Fix handling of Xen instance states.
+- Fix NIC configuration with absent NIC VLAN
+- Adapt relative path expansion in PATH to new environment
+- Exclude archived jobs from configuration backups
+- Fix RAPI for split query setup
+- Allow disk hot-remove even with chroot or SM
+
+Inherited from the 2.9 branch:
+
+- Make htools tolerate missing 'spfree' on luxi
+
+
+Version 2.11.4
+--------------
+
+*(Released Thu, 31 Jul 2014)*
+
+- Improved documentation of the instance shutdown behavior.
+
+Inherited from the 2.10 branch:
+
+- KVM: fix NIC configuration with absent NIC VLAN (Issue 893)
+- Adapt relative path expansion in PATH to new environment
+- Exclude archived jobs from configuration backup
+- Expose early_release for ReplaceInstanceDisks
+- Add backup directory for configuration backups for upgrades
+- Fix BlockdevSnapshot in case of non lvm-based disk
+- Improve RAPI error handling for queries in non-existing items
+- Allow disk hot-remove even with chroot or SM
+- Remove superflous loop in instance queries (Issue 875)
+
+Inherited from the 2.9 branch:
+
+- Make ganeti-cleaner switch to save working directory (Issue 880)
+
+
+Version 2.11.3
+--------------
+
+*(Released Wed, 9 Jul 2014)*
+
+- Readd nodes to their previous node group
+- Remove old-style gnt-network connect
+
+Inherited from the 2.10 branch:
+
+- Make network_vlan an optional OpParam
+- hspace: support --accept-existing-errors
+- Make hspace support --independent-groups
+- Add a modifier for a group's allocation policy
+- Export VLAN nicparam to NIC configuration scripts
+- Fix gnt-network client to accept vlan info
+- Support disk hotplug with userspace access
+
+Inherited from the 2.9 branch:
+
+- Make htools tolerate missing "spfree" on luxi
+- Move the design for query splitting to the implemented list
+- Add tests for DRBD setups with empty first resource
+
+Inherited from the 2.8 branch:
+
+- DRBD parser: consume initial empty resource lines
+
+
+Version 2.11.2
+--------------
+
+*(Released Fri, 13 Jun 2014)*
+
+- Improvements to KVM wrt to the kvmd and instance shutdown behavior.
+  WARNING: In contrast to our standard policy, this bug fix update
+  introduces new parameters to the configuration. This means in
+  particular that after an upgrade from 2.11.0 or 2.11.1, 'cfgupgrade'
+  needs to be run, either manually or explicitly by running
+  'gnt-cluster upgrade --to 2.11.2' (which requires that they
+  had configured the cluster with --enable-versionfull).
+  This also means, that it is not easily possible to downgrade from
+  2.11.2 to 2.11.1 or 2.11.0. The only way is to go back to 2.10 and
+  back.
+
+Inherited from the 2.10 branch:
+
+- Check for SSL encoding inconsistencies
+- Check drbd helper only in VM capable nodes
+- Improvements in statistics utils
+
+Inherited from the 2.9 branch:
+
+- check-man-warnings: use C.UTF-8 and set LC_ALL
+
+
+Version 2.11.1
+--------------
+
+*(Released Wed, 14 May 2014)*
+
+- Add design-node-security.rst to docinput
+- kvm: use a dedicated QMP socket for kvmd
+
+Inherited from the 2.10 branch:
+
+- Set correct Ganeti version on setup commands
+- Add a utility to combine shell commands
+- Add design doc for performance tests
+- Fix failed DRBD disk creation cleanup
+- Hooking up verification for shared file storage
+- Fix --shared-file-storage-dir option of gnt-cluster modify
+- Clarify default setting of 'metavg'
+- Fix invocation of GetCommandOutput in QA
+- Clean up RunWithLocks
+- Add an exception-trapping thread class
+- Wait for delay to provide interruption information
+- Add an expected block option to RunWithLocks
+- Track if a QA test was blocked by locks
+- Add a RunWithLocks QA utility function
+- Add restricted migration
+- Add an example for node evacuation
+- Add a test for parsing version strings
+- Tests for parallel job execution
+- Fail in replace-disks if attaching disks fails
+- Fix passing of ispecs in cluster init during QA
+- Move QAThreadGroup to qa_job_utils.py
+- Extract GetJobStatuses and use an unified version
+- Run disk template specific tests only if possible
+
+Inherited from the 2.9 branch:
+
+- If Automake version > 1.11, force serial tests
+- KVM: set IFF_ONE_QUEUE on created tap interfaces
+- Add configure option to pass GHC flags
+
+
+Version 2.11.0
+--------------
+
+*(Released Fri, 25 Apr 2014)*
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``gnt-node list`` no longer shows disk space information for shared file
+  disk templates because it is not a node attribute. (For example, if you have
+  both the file and shared file disk templates enabled, ``gnt-node list`` now
+  only shows information about the file disk template.)
+- The shared file disk template is now in the new 'sharedfile' storage type.
+  As a result, ``gnt-node list-storage -t file`` now only shows information
+  about the file disk template and you may use ``gnt-node list-storage -t
+  sharedfile`` to query storage information for the shared file disk template.
+- Over luxi, syntactially incorrect queries are now rejected as a whole;
+  before, a 'SumbmitManyJobs' request was partially executed, if the outer
+  structure of the request was syntactically correct. As the luxi protocol
+  is internal (external applications are expected to use RAPI), the impact
+  of this incompatible change should be limited.
+- Queries for nodes, instances, groups, backups and networks are now
+  exclusively done via the luxi daemon. Legacy python code was removed,
+  as well as the --enable-split-queries configuration option.
+- Orphan volumes errors are demoted to warnings and no longer affect the exit
+  code of ``gnt-cluster verify``.
+- RPC security got enhanced by using different client SSL certificates
+  for each node. In this context 'gnt-cluster renew-crypto' got a new
+  option '--renew-node-certificates', which renews the client
+  certificates of all nodes. After a cluster upgrade from pre-2.11, run
+  this to create client certificates and activate this feature.
+
+New features
+~~~~~~~~~~~~
+
+- Instance moves, backups and imports can now use compression to transfer the
+  instance data.
+- Node groups can be configured to use an SSH port different than the
+  default 22.
+- Added experimental support for Gluster distributed file storage as the
+  ``gluster`` disk template under the new ``sharedfile`` storage type through
+  automatic management of per-node FUSE mount points. You can configure the
+  mount point location at ``gnt-cluster init`` time by using the new
+  ``--gluster-storage-dir`` switch.
+- Job scheduling is now handled by luxid, and the maximal number of jobs running
+  in parallel is a run-time parameter of the cluster.
+- A new tool for planning dynamic power management, called ``hsqueeze``, has
+  been added. It suggests nodes to power up or down and corresponding instance
+  moves.
+
+New dependencies
+~~~~~~~~~~~~~~~~
+
+The following new dependencies have been added:
+
+For Haskell:
+
+- ``zlib`` library (http://hackage.haskell.org/package/base64-bytestring)
+
+- ``base64-bytestring`` library (http://hackage.haskell.org/package/zlib),
+  at least version 1.0.0.0
+
+Since 2.11.0 rc1
+~~~~~~~~~~~~~~~~
+
+- Fix Xen instance state
+
+Inherited from the 2.10 branch:
+
+- Fix conflict between virtio + spice or soundhw
+- Fix bitarray ops wrt PCI slots
+- Allow releases scheduled 5 days in advance
+- Make watcher submit queries low priority
+- Fix specification of TIDiskParams
+- Add unittests for instance modify parameter renaming
+- Add renaming of instance custom params
+- Add RAPI symmetry tests for groups
+- Extend RAPI symmetry tests with RAPI-only aliases
+- Add test for group custom parameter renaming
+- Add renaming of group custom ndparams, ipolicy, diskparams
+- Add the RAPI symmetry test for nodes
+- Add aliases for nodes
+- Allow choice of HTTP method for modification
+- Add cluster RAPI symmetry test
+- Fix failing cluster query test
+- Add aliases for cluster parameters
+- Add support for value aliases to RAPI
+- Provide tests for GET/PUT symmetry
+- Sort imports
+- Also consider filter fields for deciding if using live data
+- Document the python-fdsend dependency
+- Verify configuration version number before parsing
+- KVM: use running HVPs to calc blockdev options
+- KVM: reserve a PCI slot for the SCSI controller
+- Check for LVM-based verification results only when enabled
+- Fix "existing" typos
+- Fix output of gnt-instance info after migration
+- Warn in UPGRADE about not tar'ing exported insts
+- Fix non-running test and remove custom_nicparams rename
+- Account for NODE_RES lock in opportunistic locking
+- Fix request flooding of noded during disk sync
+
+Inherited from the 2.9 branch:
+
+- Make watcher submit queries low priority
+- Fix failing gnt-node list-drbd command
+- Update installation guide wrt to DRBD version
+- Fix list-drbd QA test
+- Add messages about skipped QA disk template tests
+- Allow QA asserts to produce more messages
+- Set exclusion tags correctly in requested instance
+- Export extractExTags and updateExclTags
+- Document spindles in the hbal man page
+- Sample logrotate conf breaks permissions with split users
+- Fix 'gnt-cluster' and 'gnt-node list-storage' outputs
+
+Inherited from the 2.8 branch:
+
+- Add reason parameter to RAPI client functions
+- Include qa/patch in Makefile
+- Handle empty patches better
+- Move message formatting functions to separate file
+- Add optional ordering of QA patch files
+- Allow multiple QA patches
+- Refactor current patching code
+
+
+Version 2.11.0 rc1
+------------------
+
+*(Released Thu, 20 Mar 2014)*
+
+This was the first RC release of the 2.11 series. Since 2.11.0 beta1:
+
+- Convert int to float when checking config. consistency
+- Rename compression option in gnt-backup export
+
+Inherited from the 2.9 branch:
+
+- Fix error introduced during merge
+- gnt-cluster copyfile: accept relative paths
+
+Inherited from the 2.8 branch:
+
+- Improve RAPI detection of the watcher
+- Add patching QA configuration files on buildbots
+- Enable a timeout for instance shutdown
+- Allow KVM commands to have a timeout
+- Allow xen commands to have a timeout
+- Fix wrong docstring
+
+
+Version 2.11.0 beta1
+--------------------
+
+*(Released Wed, 5 Mar 2014)*
+
+This was the first beta release of the 2.11 series. All important changes
+are listed in the latest 2.11 entry.
+
+
 Version 2.10.7
 --------------
 
diff --git a/README b/README
index 6ef2668..bfd1cb0 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.10
+Ganeti 2.11
 ===========
 
 For installation instructions, read the INSTALL and the doc/install.rst
diff --git a/UPGRADE b/UPGRADE
index aef642c..62624ff 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -12,6 +12,46 @@ following command on all nodes::
 
     $ /etc/init.d/ganeti restart
 
+2.11 and above
+--------------
+
+Starting from 2.10 onwards, Ganeti has support for parallely installed versions
+and automated upgrades. The default configuration for 2.11 and higher already is
+to install as a parallel version without changing the running version. If both
+versions, the installed one and the one to upgrade to, are 2.10 or higher, the
+actual switch of the live version can be carried out by the following command
+on the master node.::
+
+   $ gnt-cluster upgrade --to 2.11
+
+This will carry out the steps described below in the section on upgrades from
+2.1 and above. Downgrades to the previous minor version can be done in the same
+way, specifiying the smaller version on the ``--to`` argument.
+
+Note that ``gnt-cluster upgrade`` only manages the actual switch between
+versions as described below on upgrades from 2.1 and above. It does not install
+or remove any binaries. Having the new binaries installed is a prerequisite of
+calling ``gnt-cluster upgrade`` (and the command will abort if the prerequisite
+is not met). The old binaries can be used to downgrade back to the previous
+version; once the system administrator decides that going back to the old
+version is not needed any more, they can be removed. Addition and removal of
+the Ganeti binaries should happen in the same way as for all other binaries on
+your system.
+
+
+2.11
+----
+
+When upgrading to 2.11, first apply the instructions of ``2.11 and
+above``. 2.11 comes with the new feature of enhanced RPC security
+through client certificates. This features needs to be enabled after the
+upgrade by::
+
+   $ gnt-cluster renew-crypto --new-node-certificates
+
+Note that new node certificates are generated automatically without
+warning when upgrading with ``gnt-cluster upgrade``.
+
 
 2.1 and above
 -------------
@@ -109,6 +149,17 @@ course, but you get a warning about it. If there is any new feature and
 you haven't changed from its default value, you don't have to worry
 about it, as it will get the same value whenever you'll upgrade again.
 
+Automatic downgrades
+....................
+
+From version 2.11 onwards, downgrades can be done by using the
+``gnt-cluster upgrade`` command.::
+
+  gnt-cluster upgrade --to 2.10
+
+Manual downgrades
+.................
+
 The procedure is similar to upgrading, but please notice that you have to
 revert the configuration **before** installing the old version.
 
@@ -168,6 +219,14 @@ revert the configuration **before** installing the old version.
 
     $ gnt-cluster verify
 
+Specific tasks for 2.11 to 2.10 downgrade
+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+
+After running ``cfgupgrade``, the ``client.pem`` and
+``ssconf_master_candidates_certs`` files need to be removed
+from Ganeti's data directory on all nodes. While this step is
+not necessary for 2.10 to run cleanly, leaving them will cause
+problems when upgrading again after the downgrade.
 
 2.0 releases
 ------------
index d8cddd9..48687ea 100755 (executable)
@@ -891,10 +891,9 @@ def main():
   WriteHaskellCompletion(sw, "daemons/ganeti-cleaner", htools=False,
                          debug=not options.compact)
 
-  # htools, if enabled
-  if _constants.HTOOLS:
-    for script in _constants.HTOOLS_PROGS:
-      WriteHaskellCompletion(sw, script, htools=True, debug=debug)
+  # htools
+  for script in _constants.HTOOLS_PROGS:
+    WriteHaskellCompletion(sw, script, htools=True, debug=debug)
 
   # ganeti-confd, if enabled
   if _constants.ENABLE_CONFD:
index fda4298..2548018 100644 (file)
@@ -1,6 +1,6 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [10])
+m4_define([gnt_version_minor], [11])
 m4_define([gnt_version_revision], [7])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version_full],
@@ -28,22 +28,6 @@ AC_SUBST([BINDIR], $bindir)
 AC_SUBST([SBINDIR], $sbindir)
 AC_SUBST([MANDIR], $mandir)
 
-# --enable-developer-mode
-AC_ARG_ENABLE([developer-mode],
-  [AS_HELP_STRING([--enable-developer-mode],
-                  m4_normalize([do a developper build with additional
-                  checks and fatal warnings]))],
-  [[if test "$enableval" != no; then
-      DEVELOPER_MODE=yes
-    else
-      DEVELOPER_MODE=no
-    fi
-  ]],
-  [DEVELOPER_MODE=no
-  ])
-AC_SUBST(DEVELOPER_MODE, $DEVELOPER_MODE)
-AM_CONDITIONAL([DEVELOPER_MODE], [test "$DEVELOPER_MODE" = yes])
-
 # --enable-versionfull
 AC_ARG_ENABLE([versionfull],
   [AS_HELP_STRING([--enable-versionfull],
@@ -64,18 +48,84 @@ AM_CONDITIONAL([USE_VERSION_FULL], [test "$USE_VERSION_FULL" = yes])
 AC_ARG_ENABLE([symlinks],
   [AS_HELP_STRING([--enable-symlinks],
                   m4_normalize([also install version-dependent symlinks under
-                  $sysconfdir (default: enabled)]))],
-  [[if test "$enableval" != no; then
-      INSTALL_SYMLINKS=yes
-    else
+                  $sysconfdir (default: disabled)]))],
+  [[if test "$enableval" != yes; then
       INSTALL_SYMLINKS=no
+    else
+      INSTALL_SYMLINKS=yes
     fi
   ]],
-  [INSTALL_SYMLINKS=yes
+  [INSTALL_SYMLINKS=no
   ])
 AC_SUBST(INSTALL_SYMLINKS, $INSTALL_SYMLINKS)
 AM_CONDITIONAL([INSTALL_SYMLINKS], [test "$INSTALL_SYMLINKS" = yes])
 
+# --enable-haskell-profiling
+AC_ARG_ENABLE([haskell-profiling],
+  [AS_HELP_STRING([--enable-haskell-profiling],
+                  m4_normalize([enable profiling for Haskell binaries
+                  (default: disabled)]))],
+  [[if test "$enableval" != yes; then
+      HPROFILE=no
+    else
+      HPROFILE=yes
+    fi
+  ]],
+  [HPROFILE=no
+  ])
+AC_SUBST(HPROFILE, $HPROFILE)
+AM_CONDITIONAL([HPROFILE], [test "$HPROFILE" = yes])
+
+# --enable-haskell-coverage
+AC_ARG_ENABLE([haskell-coverage],
+  [AS_HELP_STRING([--enable-haskell-coverage],
+                  m4_normalize([enable coverage for Haskell binaries
+                  (default: disabled)]))],
+  [[if test "$enableval" != yes; then
+      HCOVERAGE=no
+    else
+      HCOVERAGE=yes
+    fi
+  ]],
+  [HCOVERAGE=no
+  ])
+AC_SUBST(HCOVERAGE, $HCOVERAGE)
+AM_CONDITIONAL([HCOVERAGE], [test "$HCOVERAGE" = yes])
+
+# --enable-haskell-tests
+AC_ARG_ENABLE([haskell-tests],
+  [AS_HELP_STRING([--enable-haskell-tests],
+                  m4_normalize([enable additinal Haskell development test code
+                  (default: disabled)]))],
+  [[if test "$enableval" != yes; then
+      HTEST=no
+    else
+      HTEST=yes
+    fi
+  ]],
+  [HTEST=no
+  ])
+AC_SUBST(HTEST, $HTEST)
+AM_CONDITIONAL([HTEST], [test "$HTEST" = yes])
+
+# --enable-developer-mode
+AC_ARG_ENABLE([developer-mode],
+  [AS_HELP_STRING([--enable-developer-mode],
+                  m4_normalize([do a developper build with additional
+                  checks and fatal warnings; this is implied by enabling
+                  the haskell tests]))],
+  [[if test "$enableval" != no; then
+      DEVELOPER_MODE=yes
+    else
+      DEVELOPER_MODE=no
+    fi
+  ]],
+  [DEVELOPER_MODE=no
+  ])
+AC_SUBST(DEVELOPER_MODE, $DEVELOPER_MODE)
+AM_CONDITIONAL([DEVELOPER_MODE],
+               [test "$DEVELOPER_MODE" = yes -o "$HTEST" = yes])
+
 # --with-haskell-flags=
 AC_ARG_WITH([haskell-flags],
   [AS_HELP_STRING([--with-haskell-flags=FLAGS],
@@ -271,18 +321,21 @@ AC_ARG_WITH([user-prefix],
   [user_masterd="${withval}masterd";
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
-   user_luxid="${withval}luxid";
+   user_kvmd="$user_default";
+   user_luxid="${withval}masterd";
    user_noded="$user_default";
    user_mond="$user_default"],
   [user_masterd="$user_default";
    user_rapi="$user_default";
    user_confd="$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(RAPI_USER, $user_rapi)
 AC_SUBST(CONFD_USER, $user_confd)
+AC_SUBST(KVMD_USER, $user_kvmd)
 AC_SUBST(LUXID_USER, $user_luxid)
 AC_SUBST(NODED_USER, $user_noded)
 AC_SUBST(MOND_USER, $user_mond)
@@ -297,6 +350,7 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="${withval}rapi";
    group_admin="${withval}admin";
    group_confd="${withval}confd";
+   group_kvmd="$group_default";
    group_luxid="${withval}luxid";
    group_masterd="${withval}masterd";
    group_noded="$group_default";
@@ -305,6 +359,7 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="$group_default";
    group_admin="$group_default";
    group_confd="$group_default";
+   group_kvmd="$group_default";
    group_luxid="$group_default";
    group_masterd="$group_default";
    group_noded="$group_default";
@@ -313,6 +368,7 @@ AC_ARG_WITH([group-prefix],
 AC_SUBST(RAPI_GROUP, $group_rapi)
 AC_SUBST(ADMIN_GROUP, $group_admin)
 AC_SUBST(CONFD_GROUP, $group_confd)
+AC_SUBST(KVMD_GROUP, $group_kvmd)
 AC_SUBST(LUXID_GROUP, $group_luxid)
 AC_SUBST(MASTERD_GROUP, $group_masterd)
 AC_SUBST(NODED_GROUP, $group_noded)
@@ -572,20 +628,28 @@ AC_GHC_PKG_REQUIRE(json)
 AC_GHC_PKG_REQUIRE(network)
 AC_GHC_PKG_REQUIRE(mtl)
 AC_GHC_PKG_REQUIRE(bytestring)
+AC_GHC_PKG_REQUIRE(base64-bytestring-1.*, t)
 AC_GHC_PKG_REQUIRE(utf8-string)
+AC_GHC_PKG_REQUIRE(zlib)
 AC_GHC_PKG_REQUIRE(hslogger)
-
-# extra modules for confd functionality
+AC_GHC_PKG_REQUIRE(process)
+AC_GHC_PKG_REQUIRE(attoparsec)
+AC_GHC_PKG_REQUIRE(vector)
+AC_GHC_PKG_REQUIRE(text)
+AC_GHC_PKG_REQUIRE(hinotify)
+AC_GHC_PKG_REQUIRE(Crypto)
+
+# extra modules for confd functionality; also needed for tests
+HS_NODEV=
+CONFD_PKG=
+# if a new confd dependency is needed, add it here like:
+# AC_GHC_PKG_CHECK([somepkg], [], [HS_NODEV=1; CONFD_PKG="$CONFD_PKG somepkg"])
 HS_REGEX_PCRE=-DNO_REGEX_PCRE
+AC_GHC_PKG_CHECK([regex-pcre], [HS_REGEX_PCRE=],
+                 [HS_NODEV=1; CONFD_PKG="$CONFD_PKG regex-pcre"])
+
 has_confd=False
 if test "$enable_confd" != no; then
-  CONFD_PKG=
-  AC_GHC_PKG_CHECK([regex-pcre], [HS_REGEX_PCRE=],
-                   [CONFD_PKG="$CONFD_PKG regex-pcre"])
-  AC_GHC_PKG_CHECK([Crypto], [], [CONFD_PKG="$CONFD_PKG Crypto"])
-  AC_GHC_PKG_CHECK([text], [], [CONFD_PKG="$CONFD_PKG text"])
-  AC_GHC_PKG_CHECK([hinotify], [], [CONFD_PKG="$CONFD_PKG hinotify"])
-  AC_GHC_PKG_CHECK([vector], [], [CONFD_PKG="$CONFD_PKG vector"])
   if test -z "$CONFD_PKG"; then
     has_confd=True
   elif test "$enable_confd" = check; then
@@ -604,16 +668,13 @@ fi
 AC_SUBST(ENABLE_CONFD, $has_confd)
 AM_CONDITIONAL([ENABLE_CONFD], [test x$has_confd = xTrue])
 
-#extra modules for monitoring daemon functionality
+#extra modules for monitoring daemon functionality; also needed for tests
+MONITORING_PKG=
+AC_GHC_PKG_CHECK([snap-server], [],
+                 [NS_NODEV=1; MONITORING_PKG="$MONITORING_PKG snap-server"])
+
 has_monitoring=False
 if test "$enable_monitoring" != no; then
-  MONITORING_PKG=
-  AC_GHC_PKG_CHECK([attoparsec], [],
-                   [MONITORING_PKG="$MONITORING_PKG attoparsec"])
-  AC_GHC_PKG_CHECK([snap-server], [],
-                   [MONITORING_PKG="$MONITORING_PKG snap-server"])
-  AC_GHC_PKG_CHECK([process], [],
-                   [MONITORING_PKG="$MONITORING_PKG process"])
   MONITORING_DEP=
   if test "$has_confd" = False; then
     MONITORING_DEP="$MONITORING_DEP confd"
@@ -652,23 +713,11 @@ AC_SUBST(ENABLE_MOND, $has_monitoring)
 AM_CONDITIONAL([ENABLE_MOND], [test "$has_monitoring" = True])
 
 # development modules
-HS_NODEV=
 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-hunit], [], [HS_NODEV=1])
 AC_GHC_PKG_CHECK([test-framework-quickcheck2], [], [HS_NODEV=1])
 AC_GHC_PKG_CHECK([temporary], [], [HS_NODEV=1])
-# FIXME: unify checks for non-test libraries (attoparsec, hinotify, ...)
-#        that are needed to execute the tests, avoiding the duplication
-#        of the checks.
-AC_GHC_PKG_CHECK([attoparsec], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([vector], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([process], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([snap-server], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([regex-pcre], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([Crypto], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([text], [], [HS_NODEV=1])
-AC_GHC_PKG_CHECK([hinotify], [], [HS_NODEV=1])
 if test -n "$HS_NODEV"; then
    AC_MSG_WARN(m4_normalize([Required development modules were not found,
                              you won't be able to run Haskell unittests]))
@@ -676,45 +725,7 @@ else
    AC_MSG_NOTICE([Haskell development modules found, unittests enabled])
 fi
 AC_SUBST(HS_NODEV)
-
-HTOOLS=yes
-AC_SUBST(HTOOLS)
-
-# --enable-split-query
-ENABLE_SPLIT_QUERY=
-AC_ARG_ENABLE([split-query],
-  [AS_HELP_STRING([--enable-split-query],
-  [enable use of custom query daemon via confd])],
-  [[case "$enableval" in
-      no)
-        enable_split_query=False
-        ;;
-      yes)
-        enable_split_query=True
-        ;;
-      *)
-        echo "Invalid value for enable-confd '$enableval'"
-        exit 1
-        ;;
-    esac
-  ]],
-  [[case "x${has_confd}x" in
-     xTruex)
-       enable_split_query=True
-       ;;
-     *)
-       enable_split_query=False
-       ;;
-   esac]])
-AC_SUBST(ENABLE_SPLIT_QUERY, $enable_split_query)
-
-if test x$enable_split_query = xTrue -a x$has_confd != xTrue; then
-  AC_MSG_ERROR([Split queries require the confd daemon])
-fi
-
-if test x$enable_split_query = xTrue; then
-  AC_MSG_NOTICE([Split query functionality enabled])
-fi
+AM_CONDITIONAL([HS_UNIT], [test -n $HS_NODEV])
 
 # Check for HsColour
 HS_APIDOC=no
@@ -744,12 +755,6 @@ if test -z "$HLINT"; then
   AC_MSG_WARN([hlint not found, checking code will not be possible])
 fi
 
-if test "$HTOOLS" != yes && test "$ENABLE_CONFD" = True; then
-  AC_MSG_ERROR(m4_normalize([cannot enable ganeti-confd if
-                             htools support is not enabled]))
-fi
-
-AM_CONDITIONAL([WANT_HTOOLS], [test "$HTOOLS" = yes])
 AM_CONDITIONAL([WANT_HSTESTS], [test "x$HS_NODEV" = x])
 AM_CONDITIONAL([WANT_HSAPIDOC], [test "$HS_APIDOC" = yes])
 AM_CONDITIONAL([HAS_HLINT], [test "$HLINT"])
@@ -850,6 +855,18 @@ fi
 AC_SUBST(PY_NODEV)
 AM_CONDITIONAL([PY_UNIT], [test -n $PY_NODEV])
 
+include_makefile_ghc='
+ifneq ($(MAKECMDGOALS),ganeti)
+ifneq ($(MAKECMDGOALS),clean)
+ifneq ($(MAKECMDGOALS),distclean)
+include Makefile.ghc
+endif
+endif
+endif
+'
+AC_SUBST([include_makefile_ghc])
+AM_SUBST_NOTMAKE([include_makefile_ghc])
+
 AC_CONFIG_FILES([ Makefile ])
 
 AC_OUTPUT
index bb8c79c..73fb21b 100644 (file)
@@ -40,6 +40,8 @@ DAEMONS=(
   ganeti-noded
   ganeti-masterd
   ganeti-rapi
+  ganeti-luxid
+  ganeti-kvmd
   )
 
 _confd_enabled() {
@@ -48,7 +50,6 @@ _confd_enabled() {
 
 if _confd_enabled; then
   DAEMONS+=( ganeti-confd )
-  DAEMONS+=( ganeti-luxid )
 fi
 
 _mond_enabled() {
@@ -242,7 +243,7 @@ start() {
   local usergroup=$(_daemon_usergroup $plain_name)
   local daemonexec=$(_daemon_executable $name)
 
-  if ( [[ "$name" == ganeti-confd ]] || [[ "$name" == ganeti-luxid ]] ) \
+  if [[ "$name" == ganeti-confd ]] \
       && ! _confd_enabled; then
     echo 'ganeti-confd disabled at build time' >&2
     return 1
@@ -301,18 +302,12 @@ check_and_start() {
 start_master() {
   start ganeti-masterd
   start ganeti-rapi
-  if _confd_enabled; then
-      start ganeti-luxid
-  else
-      return 0
-  fi
+  start ganeti-luxid
 }
 
 # Stops the master role
 stop_master() {
-  if _confd_enabled ; then
-      stop ganeti-luxid
-  fi
+  stop ganeti-luxid
   stop ganeti-rapi
   stop ganeti-masterd
 }
index f34ef19..39bec38 100755 (executable)
@@ -137,6 +137,7 @@ case $DIST_RELEASE in
         libghc6-text-dev \
         libghc6-vector-dev \
         libpcre3-dev \
+        libghc6-zlib-dev \
         hlint hscolour pandoc \
         graphviz qemu-utils \
         python-docutils \
@@ -170,7 +171,7 @@ case $DIST_RELEASE in
 
     in_chroot -- \
       cabal install --global \
-    blaze-builder==0.3.1.1 \
+        blaze-builder==0.3.1.1 \
         network==2.3 \
         regex-pcre==0.94.4 \
         hinotify==0.3.2 \
@@ -203,6 +204,9 @@ case $DIST_RELEASE in
     in_chroot -- \
       cabal install --global shelltestrunner
 
+    in_chroot -- \
+      cabal install --global base64-bytestring
+
     #Install selected packages from backports
     in_chroot -- \
       $APT_INSTALL -t squeeze-backports \
@@ -224,6 +228,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\
       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 \
@@ -242,6 +248,11 @@ case $DIST_RELEASE in
     in_chroot -- \
       easy_install pyinotify==0.9.4
 
+     in_chroot -- \
+       cabal update
+
+     in_chroot -- \
+       cabal install --global base64-bytestring
 ;;
 
   *)
@@ -281,6 +292,11 @@ in_chroot -- \
   easy_install affinity
 
 in_chroot -- \
+  easy_install jsonpointer \
+    jsonpointer \
+    jsonpatch
+
+in_chroot -- \
   $APT_INSTALL \
   python-epydoc debhelper quilt
 
index fbe4db4..e9c016c 100644 (file)
@@ -115,34 +115,28 @@ The are multiple options for the storage provided to an instance; while
 the instance sees the same virtual drive in all cases, the node-level
 configuration varies between them.
 
-There are five disk templates you can choose from:
+There are several disk templates you can choose from:
 
-diskless
+``diskless``
   The instance has no disks. Only used for special purpose operating
   systems or for testing.
 
-file
+``file`` *****
   The instance will use plain files as backend for its disks. No
   redundancy is provided, and this is somewhat more difficult to
-  configure for high performance. Note that for security reasons the
-  file storage directory must be listed under
-  ``/etc/ganeti/file-storage-paths``, and that file is not copied
-  automatically to all nodes by Ganeti. The format of that file is a
-  newline-separated list of directories.
+  configure for high performance.
 
-sharedfile
+``sharedfile`` *****
   The instance will use plain files as backend, but Ganeti assumes that
   those files will be available and in sync automatically on all nodes.
   This allows live migration and failover of instances using this
-  method. As for ``file`` the file storage directory must be listed under
-  ``/etc/ganeti/file-storage-paths`` or ganeti will refuse to create
-  instances under it.
+  method.
 
-plain
+``plain``
   The instance will use LVM devices as backend for its disks. No
   redundancy is provided.
 
-drbd
+``drbd``
   .. note:: This is only valid for multi-node clusters using DRBD 8.0+
 
   A mirror is set between the local node and a remote one, which must be
@@ -154,14 +148,37 @@ drbd
      DRBD stacked setup is not fully symmetric and as such it is
      not working with live migration.
 
-rbd
+``rbd``
   The instance will use Volumes inside a RADOS cluster as backend for its
   disks. It will access them using the RADOS block device (RBD).
 
-ext
+``gluster`` *****
+  The instance will use a Gluster volume for instance storage. Disk
+  images will be stored in the top-level ``ganeti/`` directory of the
+  volume. This directory will be created automatically for you.
+
+``ext``
   The instance will use an external storage provider. See
   :manpage:`ganeti-extstorage-interface(7)` for how to implement one.
 
+.. note::
+  Disk templates marked with an asterisk require Ganeti to access the
+  file system. Ganeti will refuse to do so unless you whitelist the
+  relevant paths in :pyeval:`pathutils.FILE_STORAGE_PATHS_FILE`.
+
+  The default paths used by Ganeti are:
+
+  =============== ===================================================
+  Disk template   Default path
+  =============== ===================================================
+  ``file``        :pyeval:`pathutils.DEFAULT_FILE_STORAGE_DIR`
+  ``sharedfile``  :pyeval:`pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR`
+  ``gluster``     :pyeval:`pathutils.DEFAULT_GLUSTER_STORAGE_DIR`
+  =============== ===================================================
+
+  Those paths can be changed at ``gnt-cluster init`` time. See
+  :manpage:`gnt-cluster(8)` for details.
+
 
 IAllocator
 ~~~~~~~~~~
@@ -341,6 +358,11 @@ after shutting down an instance, execute the following::
    on an instance Ganeti will automatically restart it (via
    the :command:`ganeti-watcher(8)` command which is launched via cron).
 
+Instances can also be shutdown by the user from within the instance, in
+which case they will marked accordingly and the
+:command:`ganeti-watcher(8)` will not restart them.  See
+:manpage:`gnt-cluster(8)` for details.
+
 Querying instances
 ~~~~~~~~~~~~~~~~~~
 
diff --git a/doc/design-2.11.rst b/doc/design-2.11.rst
new file mode 100644 (file)
index 0000000..2b2ca22
--- /dev/null
@@ -0,0 +1,13 @@
+==================
+Ganeti 2.11 design
+==================
+
+The following design documents have been implemented in Ganeti 2.11.
+
+- :doc:`design-internal-shutdown`
+- :doc:`design-kvmd`
+
+The following designs have been partially implemented in Ganeti 2.11.
+
+- :doc:`design-node-security`
+- :doc:`design-hsqueeze`
index 2fb266c..db72d72 100644 (file)
@@ -239,8 +239,8 @@ leaving the codebase in a consistent and usable state.
    MasterD into LuxiD.
    Actually executing jobs will still be done by MasterD, that contains all the
    logic for doing that and for properly managing locks and the configuration.
-   A separate design document will detail how the system will decide which jobs
-   to send over for execution, and how to rate-limit them.
+   At this stage, scheduling will simply consist in starting jobs until a fixed
+   maximum number of simultaneously running jobs is reached.
 
 #. Extract WConfD from MasterD.
    The logic for managing the configuration file is factored out to the
@@ -261,6 +261,141 @@ leaving the codebase in a consistent and usable state.
    MasterD will cease to exist as a deamon on its own at this point, but not
    before.
 
+#. Improve job scheduling algorithm.
+   The simple algorithm for scheduling jobs will be replaced by a more
+   intelligent one. Also, the implementation of :doc:`design-optables` can be
+   started.
+
+WConfD details
+--------------
+
+WConfD will communicate with its clients through a Unix domain socket for both
+configuration management and locking. Clients can issue multiple RPC calls
+through one socket. For each such a call the client sends a JSON request
+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
+will track each client's process start time along with its process ID to be
+able detect if a process dies and it's process ID is reused.  WConfD will clear
+all locks and other state associated with a client if it detects it's process
+no longer exists.
+
+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:
+
+-  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
++++++++
+
+The new locking protocol will be implemented as follows:
+
+Re-implement the current locking mechanism in WConfD and expose it for RPC
+calls. All current locks will be mapped into a data structure that will
+uniquely identify them (storing lock's level together with it's name).
+
+WConfD will impose a linear order on locks. The order will be compatible
+with the current ordering of lock levels so that existing code will work
+without changes.
+
+WConfD will keep the set of currently held locks for each client. The
+protocol will allow the following operations on the set:
+
+*Update:*
+  Update the current set of locks according to a given list. The list contains
+  locks and their desired level (release / shared / exclusive). To prevent
+  deadlocks, WConfD will check that all newly requested locks (or already held
+  locks requested to be upgraded to *exclusive*) are greater in the sense of
+  the linear order than all currently held locks, and fail the operation if
+  not. Only the locks in the list will be updated, other locks already held
+  will be left intact. If the operation fails, the client's lock set will be
+  left intact.
+*Opportunistic union:*
+  Add as much as possible locks from a given set to the current set within a
+  given timeout. WConfD will again check the proper order of locks and
+  acquire only the ones that are allowed wrt. the current set.  Returns the
+  set of acquired locks, possibly empty. Immediate. Never fails. (It would also
+  be possible to extend the operation to try to wait until a given number of
+  locks is available, or a given timeout elapses.)
+*List:*
+  List the current set of held locks. Immediate, never fails.
+*Intersection:*
+  Retain only a given set of locks in the current one. This function is
+  provided for convenience, it's redundant wrt. *list* and *update*. Immediate,
+  never fails.
+
+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:
+
+Pessimistic strategy:
+  Lock all potentially relevant resources (for example all nodes), determine
+  which will be needed, and release all the others.
+Optimistic strategy:
+  Determine what locks need to be acquired without holding any. Lock the
+  required set of locks. Determine the set of required locks again and check if
+  they are all held. If not, release everything and restart.
+
+.. COMMENTED OUT:
+  Start with the smallest set of locks and when determining what more
+  relevant resources will be needed, expand the set. If an *union* operation
+  fails, release all locks, acquire the desired union and restart the
+  operation so that all preconditions and possible concurrent changes are
+  checked again.
+
+Future aims:
+
+-  Add more fine-grained locks to prevent unnecessary blocking of jobs. This
+   could include locks on parameters of entities or locks on their states (so that
+   a node remains online, but otherwise can change, etc.). In particular,
+   adding, moving and removing instances currently blocks the whole node.
+-  Add checks that all modified configuration parameters belong to entities
+   the client has locked and log violations.
+-  Make the above checks mandatory.
+-  Automate optimistic locking and checking the locks in logical units.
+   For example, this could be accomplished by allowing some of the initial
+   phases of `LogicalUnit` (such as `ExpandNames` and `DeclareLocks`) to be run
+   repeatedly, checking if the set of locks requested the second time is
+   contained in the set acquired after the first pass.
+-  Add the possibility for a job to reserve hardware resources such as disk
+   space or memory on nodes. Most likely as a new, special kind of instances
+   that would only block its resources and allow to be converted to a regular
+   instance. This would allow long-running jobs such as instance creation or
+   move to lock the corresponding nodes, acquire the resources and turn the
+   locks into shared ones, keeping an exclusive lock only on the instance.
+-  Use more sophisticated algorithm for preventing deadlocks such as a
+   `wait-for graph`_. This would allow less *union* failures and allow more
+   optimistic, scalable acquisition of locks.
+
+.. _`wait-for graph`: http://en.wikipedia.org/wiki/Wait-for_graph
+
+
 Further considerations
 ======================
 
index d7811b0..b5ad120 100644 (file)
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.10
+.. Last updated for Ganeti 2.11
 
 .. toctree::
    :maxdepth: 2
@@ -12,13 +12,14 @@ Design document drafts
    design-impexp2.rst
    design-resource-model.rst
    design-storagetypes.rst
-   design-internal-shutdown.rst
    design-glusterfs-ganeti-support.rst
    design-hugepages-support.rst
    design-optables.rst
    design-ceph-ganeti-support.rst
    design-daemons.rst
    design-hsqueeze.rst
+   design-os.rst
+   design-node-security.rst
 
 .. vim: set textwidth=72 :
 .. Local Variables:
index a26074e..e327327 100644 (file)
@@ -7,90 +7,323 @@ This document describes the plan for adding GlusterFS support inside Ganeti.
 .. contents:: :depth: 4
 .. highlight:: shell-example
 
-Objective
-=========
-
-The aim is to let Ganeti support GlusterFS as one of its backend storage.
-This includes three aspects to finish:
-
-- Add Gluster as a storage backend.
-- Make sure Ganeti VMs can use GlusterFS backends in userspace mode (for
-  newer QEMU/KVM which has this support) and otherwise, if possible, through
-  some kernel exported block device.
-- Make sure Ganeti can configure GlusterFS by itself, by just joining
-  storage space on new nodes to a GlusterFS nodes pool. Note that this
-  may need another design document that explains how it interacts with
-  storage pools, and that the node might or might not host VMs as well.
-
-Background
-==========
-
-There are two possible ways to implement "GlusterFS Ganeti Support". One is
-GlusterFS as one of external backend storage, the other one is realizing
-GlusterFS inside Ganeti, that is, as a new disk type for Ganeti. The benefit
-of the latter one is that it would not be opaque but fully supported and
-integrated in Ganeti, which would not need to add infrastructures for
-testing/QAing and such. Having it internal we can also provide a monitoring
-agent for it and more visibility into what's going on. For these reasons,
-GlusterFS support will be added directly inside Ganeti.
-
-Implementation Plan
-===================
-
-Ganeti Side
------------
-
-To realize an internal storage backend for Ganeti, one should realize
-BlockDev class in `ganeti/lib/storage/base.py` that is a specific
-class including create, remove and such. These functions should be
-realized in `ganeti/lib/storage/bdev.py`. Actually, the differences
-between implementing inside and outside (external) Ganeti are how to
-finish these functions in BlockDev class and how to combine with Ganeti
-itself. The internal implementation is not based on external scripts
-and combines with Ganeti in a more compact way. RBD patches may be a
-good reference here. Adding a backend storage steps are as follows:
-
-- Implement the BlockDev interface in bdev.py.
-- Add the logic in cmdlib (eg, migration, verify).
-- Add the new storage type name to constants.
-- Modify objects.Disk to support GlusterFS storage type.
-- The implementation will be performed similarly to the RBD one (see
-  commit 7181fba).
-
-GlusterFS side
---------------
-
-GlusterFS is a distributed file system implemented in user space.
-The way to access GlusterFS namespace is via FUSE based Gluster native
-client except NFS and CIFS. The efficiency of this way is lower because
-the data would be pass the kernel space and then come to user space.
-Now, there are two specific enhancements:
-
-- A new library called libgfapi is now available as part of GlusterFS
-  that provides POSIX-like C APIs for accessing Gluster volumes.
-  libgfapi support will be available from GlusterFS-3.4 release.
-- QEMU/KVM (starting from QEMU-1.3) will have GlusterFS block driver that
-  uses libgfapi and hence there is no FUSE overhead any longer when QEMU/KVM
-  works with VM images on Gluster volumes.
-
-Proposed implementation
------------------------
-
-QEMU/KVM includes support for GlusterFS and Ganeti could support GlusterFS
-through QEMU/KVM. However, this way could just let VMs of QEMU/KVM use GlusterFS
-backend storage but not other VMs like XEN and such. There are two parts that need
-to be implemented for supporting GlusterFS inside Ganeti so that it can not only
-support QEMU/KVM VMs, but also XEN and other VMs. One part is GlusterFS for XEN VM,
-which is similar to sharedfile disk template. The other part is GlusterFS for
-QEMU/KVM VM, which is supported by the GlusterFS driver for QEMU/KVM. After
-``gnt-instance add -t gluster instance.example.com`` command is executed, the added
-instance should be checked. If the instance is a XEN VM, it would run the GlusterFS
-sharedfile way. However, if the instance is a QEMU/KVM VM, it would run the
-QEMU/KVM + GlusterFS way. For the first part (GlusterFS for XEN VMs), sharedfile
-disk template would be a good reference. For the second part (GlusterFS for QEMU/KVM
-VMs), RBD disk template would be a good reference. The first part would be finished
-at first and then the second part would be completed, which is based on the first
-part.
+Gluster overview
+================
+
+Gluster is a "brick" "translation" service that can turn a number of LVM logical
+volume or disks (so-called "bricks") into an unified "volume" that can be
+mounted over the network through FUSE or NFS.
+
+This is a simplified view of what components are at play and how they
+interconnect as data flows from the actual disks to the instances. The parts in
+grey are available for Ganeti to use and included for completeness but not
+targeted for implementation at this stage.
+
+.. digraph:: "gluster-ganeti-overview"
+
+  graph [ spline=ortho ]
+  node [ shape=rect ]
+
+  {
+
+    node [ shape=none ]
+    _volume [ label=volume ]
+
+    bricks -> translators -> _volume
+    _volume -> network [label=transport]
+    network -> instances
+  }
+
+  { rank=same; brick1 [ shape=oval ]
+               brick2 [ shape=oval ]
+               brick3 [ shape=oval ]
+               bricks }
+  { rank=same; translators distribute }
+  { rank=same; volume [ shape=oval ]
+               _volume }
+  { rank=same; instances instanceA instanceB instanceC instanceD }
+  { rank=same; network FUSE NFS QEMUC QEMUD }
+
+  {
+    node [ shape=oval ]
+    brick1 [ label=brick ]
+    brick2 [ label=brick ]
+    brick3 [ label=brick ]
+  }
+
+  {
+    node [ shape=oval ]
+    volume
+  }
+
+  brick1 -> distribute
+  brick2 -> distribute
+  brick3 -> distribute -> volume
+  volume -> FUSE [ label=<TCP<br/><font color="grey">UDP</font>>
+                   color="black:grey" ]
+
+  NFS [ color=grey fontcolor=grey ]
+  volume -> NFS [ label="TCP" color=grey fontcolor=grey ]
+  NFS -> mountpoint [ color=grey fontcolor=grey ]
+
+  mountpoint [ shape=oval ]
+
+  FUSE -> mountpoint
+
+  instanceA [ label=instances ]
+  instanceB [ label=instances ]
+
+  mountpoint -> instanceA
+  mountpoint -> instanceB
+
+  mountpoint [ shape=oval ]
+
+  QEMUC [ label=QEMU ]
+  QEMUD [ label=QEMU ]
+
+  {
+    instanceC [ label=instances ]
+    instanceD [ label=instances ]
+  }
+
+  volume -> QEMUC [ label=<TCP<br/><font color="grey">UDP</font>>
+                    color="black:grey" ]
+  volume -> QEMUD [ label=<TCP<br/><font color="grey">UDP</font>>
+                    color="black:grey" ]
+  QEMUC -> instanceC
+  QEMUD -> instanceD
+
+brick:
+  The unit of storage in gluster. Typically a drive or LVM logical volume
+  formatted using, for example, XFS.
+
+distribute:
+  One of the translators in Gluster, it assigns files to bricks based on the
+  hash of their full path inside the volume.
+
+volume:
+  A filesystem you can mount on multiple machines; all machines see the same
+  directory tree and files.
+
+FUSE/NFS:
+  Gluster offers two ways to mount volumes: through FUSE or a custom NFS server
+  that is incompatible with other NFS servers. FUSE is more compatible with
+  other services running on the storage nodes; NFS gives better performance.
+  For now, FUSE is a priority.
+
+QEMU:
+  QEMU 1.3 has the ability to use Gluster volumes directly in userspace without
+  the need for mounting anything. Ganeti still needs kernelspace access at disk
+  creation and OS install time.
+
+transport:
+  FUSE and QEMU allow you to connect using TCP and UDP, whereas NFS only
+  supports TCP. Those protocols are called transports in Gluster. For now, TCP
+  is a priority.
+
+It is the administrator's duty to set up the bricks, the translators and thus
+the volume as they see fit. Ganeti will take care of connecting the instances to
+a given volume.
+
+.. note::
+
+  The gluster mountpoint must be whitelisted by the administrator in
+  ``/etc/ganeti/file-storage-paths`` for security reasons in order to allow
+  Ganeti to modify the filesystem.
+
+Why not use a ``sharedfile`` disk template?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Gluster volumes `can` be used by Ganeti using the generic shared file disk
+template. There is a number of reasons why that is probably not a good idea,
+however:
+
+* Shared file, being a generic solution, cannot offer userspace access support.
+* Even with userspace support, Ganeti still needs kernelspace access in order to
+  create disks and install OSes on them. Ganeti can manage the mounting for you
+  so that the Gluster servers only have as many connections as necessary.
+* Experiments showed that you can't trust ``mount.glusterfs`` to give useful
+  return codes or error messages. Ganeti can work around its oddities so
+  administrators don't have to.
+* The shared file folder scheme (``../{instance.name}/disk{disk.id}``) does not
+  work well with Gluster. The ``distribute`` translator distributes files across
+  bricks, but directories need to be replicated on `all` bricks. As a result, if
+  we have a dozen hundred instances, that means a dozen hundred folders being
+  replicated on all bricks. This does not scale well.
+* This frees up the shared file disk template to use a different, unsupported
+  replication scheme together with Gluster. (Storage pools are the long term
+  solution for this, however.)
+
+So, while gluster `is` a shared file disk template, essentially, Ganeti can
+provide better support for it than that.
+
+Implementation strategy
+=======================
+
+Working with GlusterFS in kernel space essentially boils down to:
+
+1. Ask FUSE to mount the Gluster volume.
+2. Check that the mount succeeded.
+3. Use files stored in the volume as instance disks, just like sharedfile does.
+4. When the instances are spun down, attempt unmounting the volume. If the
+   gluster connection is still required, the mountpoint is allowed to remain.
+
+Since it is not strictly necessary for Gluster to mount the disk if all that's
+needed is userspace access, however, it is inappropriate for the Gluster storage
+class to inherit from FileStorage. So the implementation should resort to
+composition rather than inheritance:
+
+1. Extract the ``FileStorage`` disk-facing logic into a ``FileDeviceHelper``
+   class.
+
+ * In order not to further inflate bdev.py, Filestorage should join its helper
+   functions in filestorage.py (thus reducing their visibility) and add Gluster
+   to its own file, gluster.py. Moving the other classes to their own files
+   like it's been done in ``lib/hypervisor/``) is not addressed as part of this
+   design.
+
+2. Use the ``FileDeviceHelper`` class to implement a ``GlusterStorage`` class in
+   much the same way.
+3. Add Gluster as a disk template that behaves like SharedFile in every way.
+4. Provide Ganeti knowledge about what a ``GlusterVolume`` is and how to mount,
+   unmount and reference them.
+
+ * Before attempting a mount, we should check if the volume is not mounted
+   already. Linux allows mounting partitions multiple times, but then you also
+   have to unmount them as many times as you mounted them to actually free the
+   resources; this also makes the output of commands such as ``mount`` less
+   useful.
+ * Every time the device could be released (after instance shutdown, OS
+   installation scripts or file creation), a single unmount is attempted. If
+   the device is still busy (e.g. from other instances, jobs or open
+   administrator shells), the failure is ignored.
+
+5. Modify ``GlusterStorage`` and customize the disk template behavior to fit
+   Gluster's needs.
+
+Directory structure
+~~~~~~~~~~~~~~~~~~~
+
+In order to address the shortcomings of the generic shared file handling of
+instance disk directory structure, Gluster uses a different scheme for
+determining a disk's logical id and therefore path on the file system.
+
+The naming scheme is::
+
+    /ganeti/{instance.uuid}.{disk.id}
+
+...bringing the actual path on a node's file system to::
+
+    /var/run/ganeti/gluster/ganeti/{instance.uuid}.{disk.id}
+
+This means Ganeti only uses one folder on the Gluster volume (allowing other
+uses of the Gluster volume in the meantime) and works better with how Gluster
+distributes storage over its bricks.
+
+Changes to the storage types system
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Ganeti has a number of storage types that abstract over disk templates. This
+matters mainly in terms of disk space reporting. Gluster support is improved by
+a rethinking of how disk templates are assigned to storage types in Ganeti.
+
+This is the summary of the changes:
+
++--------------+---------+---------+-------------------------------------------+
+| Disk         | Current | New     | Does it report storage information to...  |
+| template     | storage | storage +-------------+----------------+------------+
+|              | type    | type    | ``gnt-node  | ``gnt-node     | iallocator |
+|              |         |         | list``      | list-storage`` |            |
++==============+=========+=========+=============+================+============+
+| File         | File    | File    | Yes.        | Yes.           | Yes.       |
++--------------+---------+---------+-------------+----------------+------------+
+| Shared file  | File    | Shared  | No.         | Yes.           | No.        |
++--------------+---------+ file    |             |                |            |
+| Gluster (new)| N/A     | (new)   |             |                |            |
++--------------+---------+---------+-------------+----------------+------------+
+| RBD (for     | RBD               | No.         | No.            | No.        |
+| reference)   |                   |             |                |            |
++--------------+-------------------+-------------+----------------+------------+
+
+Gluster or Shared File should not, like RBD, report storage information to
+gnt-node list or to IAllocators. Regrettably, the simplest way to do so right
+now is by claiming that storage reporting for the relevant storage type is not
+implemented. An effort was made to claim that the shared storage type did support
+disk reporting while refusing to provide any value, but it was not successful
+(``hail`` does not support this combination.)
+
+To do so without breaking the File disk template, a new storage type must be
+added. Like RBD, it does not claim to support disk reporting. However, we can
+still make an effort of reporting stats to ``gnt-node list-storage``.
+
+The rationale is simple. For shared file and gluster storage, disk space is not
+a function of any one node. If storage types with disk space reporting are used,
+Hail expects them to give useful numbers for allocation purposes, but a shared
+storage system means disk balancing is not affected by node-instance allocation
+any longer. Moreover, it would be wasteful to mount a Gluster volume on each
+node just for running statvfs() if no machine was actually running gluster VMs.
+
+As a result, Gluster support for gnt-node list-storage is necessarily limited
+and nodes on which Gluster is available but not in use will report failures.
+Additionally, running ``gnt-node list`` will give an output like this::
+
+  Node              DTotal DFree MTotal MNode MFree Pinst Sinst
+  node1.example.com      ?     ?   744M  273M  477M     0     0
+  node2.example.com      ?     ?   744M  273M  477M     0     0
+
+This is expected and consistent with behaviour in RBD.
+
+An alternative would have been to report DTotal and DFree as 0 in order to allow
+``hail`` to ignore the disk information, but this incorrectly populates the
+``gnt-node list`` DTotal and DFree fields with 0s as well.
+
+New configuration switches
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Configurable at the cluster and node group level (``gnt-cluster modify``,
+``gnt-group modify`` and other commands that support the `-D` switch to edit
+disk parameters):
+
+``gluster:host``
+  The IP address or hostname of the Gluster server to connect to. In the default
+  deployment of Gluster, that is any machine that is hosting a brick.
+
+  Default: ``"127.0.0.1"``
+
+``gluster:port``
+  The port where the Gluster server is listening to.
+
+  Default: ``24007``
+
+``gluster:volume``
+  The volume Ganeti should use.
+
+  Default: ``"gv0"``
+
+Configurable at the cluster level only (``gnt-cluster init``) and stored in
+ssconf for all nodes to read (just like shared file):
+
+``--gluster-dir``
+  Where the Gluster volume should be mounted.
+
+  Default: ``/var/run/ganeti/gluster``
+
+The default values work if all of the Ganeti nodes also host Gluster bricks.
+This is possible, but `not` recommended as it can cause the host to hardlock due
+to deadlocks in the kernel memory (much in the same way RBD works).
+
+Future work
+===========
+
+In no particular order:
+
+* Support the UDP transport.
+* Support mounting through NFS.
+* Filter ``gnt-node list`` so DTotal and DFree are not shown for RBD and shared
+  file disk types, or otherwise report the disk storage values as "-" or some
+  other special value to clearly distinguish it from the result of a
+  communication failure between nodes.
+* Allow configuring the in-volume path Ganeti uses.
 
 .. vim: set textwidth=72 :
 .. Local Variables:
index e1cc864..8d5ba3a 100644 (file)
@@ -5,16 +5,17 @@ Detection of user-initiated shutdown from inside an instance
 .. contents:: :depth: 2
 
 This is a design document detailing the implementation of a way for Ganeti to
-detect whether a machine marked as up but not running was shutdown gracefully
-by the user from inside the machine itself.
+detect whether an instance marked as up but not running was shutdown gracefully
+by the user from inside the instance itself.
 
 Current state and shortcomings
 ==============================
 
 Ganeti keeps track of the desired status of instances in order to be able to
-take proper actions (e.g.: reboot) on the ones that happen to crash.
-Currently, the only way to properly shut down a machine is through Ganeti's own
-commands, that will mark an instance as ``ADMIN_down``.
+take proper action (e.g.: reboot) on the instances that happen to crash.
+Currently, the only way to properly shut down an instance is through Ganeti's
+own commands, which can be used to mark an instance as ``ADMIN_down``.
+
 If a user shuts down an instance from inside, through the proper command of the
 operating system it is running, the instance will be shutdown gracefully, but
 Ganeti is not aware of that: the desired status of the instance will still be
@@ -25,18 +26,16 @@ Proposed changes
 ================
 
 We propose to modify Ganeti in such a way that it will detect when an instance
-was shutdown because of an explicit user request. When such a situation is
-detected, instead of presenting an error as it happens now, either the state
-of the instance will be set to ADMIN_down, or the instance will be
-automatically rebooted, depending on a instance-specific configuration value.
-The default behavior in case no such parameter is found will be to follow
-the apparent will of the user, and setting to ADMIN_down an instance that
-was shut down correctly from inside.
-
-This design document applies to the Xen backend of Ganeti, because it uses
-features specific of such hypervisor. Initial analysis suggests that a similar
-approach might be used for KVM as well, so this design document will be later
-extended to add more details about it.
+was shutdown as a result of an explicit request from the user. When such a
+situation is detected, instead of presenting an error as it happens now, either
+the state of the instance will be set to ``ADMIN_down``, or the instance will be
+automatically rebooted, depending on an instance-specific configuration value.
+The default behavior in case no such parameter is found will be to follow the
+apparent will of the user, and setting to ``ADMIN_down`` an instance that was
+shut down correctly from inside.
+
+The rest of this design document details the implementation of instance shutdown
+detection for Xen.  The KVM implementation is detailed in :doc:`design-kvmd`.
 
 Implementation
 ==============
@@ -60,26 +59,26 @@ If the state is ``----c-`` it means the instance has crashed.
 If the state is ``---s--`` it means the instance was properly shutdown.
 
 If the instance was properly shutdown and it is still marked as ``running`` by
-Ganeti, it means that it was shutdown from inside by the user, and the ganeti
+Ganeti, it means that it was shutdown from inside by the user, and the Ganeti
 status of the instance needs to be changed to ``ADMIN_down``.
 
 This will be done at regular intervals by the group watcher, just before
 deciding which instances to reboot.
 
-On top of that, at the same times, the watcher will also need to issue ``xm
-destroy`` commands for all the domains that are in crashed or shutdown state,
+On top of that, at the same time, the watcher will also need to issue ``xm
+destroy`` commands for all the domains that are in a crashed or shutdown state,
 since this will not be done automatically by Xen anymore because of the
 ``preserve`` setting in their config files.
 
 This behavior will be limited to the domains shut down from inside, because it
 will actually keep the resources of the domain busy until the watcher will do
 the cleaning job (that, with the default setting, is up to every 5 minutes).
-Still, this is considered acceptable, because it is not frequent for a domain
-to be shut down this way. The cleanup function will be also run
-automatically just before performing any job that requires resources to be
-available (such as when creating a new instance), in order to ensure that the
-new resource allocation happens starting from a clean state. Functionalities
-that only query the state of instances will not run the cleanup function.
+Still, this is considered acceptable, because it is not frequent for a domain to
+be shut down this way. The cleanup function will be also run automatically just
+before performing any job that requires resources to be available (such as when
+creating a new instance), in order to ensure that the new resource allocation
+happens starting from a clean state. Functionalities that only query the state
+of instances will not run the cleanup function.
 
 The cleanup operation includes both node-specific operations (the actual
 destruction of the stopped domains) and configuration changes, to be performed
@@ -112,8 +111,8 @@ situation, destroying the instance and carrying out the rest of the Ganeti
 shutdown procedure as usual.
 
 The ``gnt-instance list`` command will need to be able to handle the situation
-where an instance was shutdown internally but not yet cleaned up.
-The ``admin_state`` field will maintain the current meaning unchanged. The
+where an instance was shutdown internally but not yet cleaned up.  The
+``admin_state`` field will maintain the current meaning unchanged. The
 ``oper_state`` field will get a new possible state, ``S``, meaning that the
 instance was shutdown internally.
 
diff --git a/doc/design-kvmd.rst b/doc/design-kvmd.rst
new file mode 100644 (file)
index 0000000..b627b35
--- /dev/null
@@ -0,0 +1,216 @@
+==========
+KVM daemon
+==========
+
+.. toctree::
+   :maxdepth: 2
+
+This design document describes the KVM daemon, which is responsible for
+determining whether a given KVM instance was shutdown by an
+administrator or a user.
+
+
+Current state and shortcomings
+==============================
+
+This design document describes the KVM daemon which addresses the KVM
+side of the user-initiated shutdown problem introduced in
+:doc:`design-internal-shutdown`.  We are also interested in keeping this
+functionality optional.  That is, an administrator does not necessarily
+have to run the KVM daemon if either he is running Xen or even, if he
+is running KVM, he is not interested in instance shutdown detection.
+This requirement is important because it means the KVM daemon should
+be a modular component in the overall Ganeti design, i.e., it should
+be easy to enable and disable it.
+
+Proposed changes
+================
+
+The instance shutdown feature for KVM requires listening on events from
+the Qemu Machine Protocol (QMP) Unix socket, which is created together
+with a KVM instance.  A QMP socket typically looks like
+``/var/run/ganeti/kvm-hypervisor/ctrl/<instance>.qmp`` and implements
+the QMP protocol.  This is a bidirectional protocol that allows Ganeti
+to send commands, such as, system powerdown, as well as, receive events,
+such as, the powerdown and shutdown events.
+
+Listening in on these events allows Ganeti to determine whether a given
+KVM instance was shutdown by an administrator, either through
+``gnt-instance stop|remove <instance>`` or ``kill -KILL
+<instance-pid>``, or by a user, through ``poweroff`` from inside the
+instance.  Upon an administrator powerdown, the QMP protocol sends two
+events, namely, a powerdown event and a shutdown event, whereas upon a
+user shutdown only the shutdown event is sent.  This is enough to
+distinguish between an administrator and a user shutdown.  However,
+there is one limitation, which is, ``kill -TERM <instance-pid>``.  Even
+though this is an action performed by the administrator, it will be
+considered a user shutdown by the approach described in this document.
+
+Several design strategies were considered.  Most of these strategies
+consisted of spawning some process listening on the QMP socket when a
+KVM instance is created.  However, having a listener process per KVM
+instance is not scalable.  Therefore, a different strategy is proposed,
+namely, having a single process, called the KVM daemon, listening on the
+QMP sockets of all KVM instances within a node.  That also means there
+is an instance of the KVM daemon on each node.
+
+In order to implement the KVM daemon, two problems need to be addressed,
+namely, how the KVM daemon knows when to open a connection to a given
+QMP socket and how the KVM daemon communicates with Ganeti whether a
+given instance was shutdown by an administrator or a user.
+
+QMP connections management
+--------------------------
+
+As mentioned before, the QMP sockets reside in the KVM control
+directory, which is usually located under
+``/var/run/ganeti/kvm-hypervisor/ctrl/``.  When a KVM instance is
+created, a new QMP socket for this instance is also created in this
+directory.
+
+In order to simplify the design of the KVM daemon, instead of having
+Ganeti communicate to this daemon through a pipe or socket the creation
+of a new KVM instance, and thus a new QMP socket, this daemon will
+monitor the KVM control directory using ``inotify``.  As a result, the
+daemon is not only able to deal with KVM instances being created and
+removed, but also capable of overcoming other problematic situations
+concerning the filesystem, such as, the case when the KVM control
+directory does not exist because, for example, Ganeti was not yet
+started, or the KVM control directory was removed, for example, as a
+result of a Ganeti reinstallation.
+
+Shutdown detection
+------------------
+
+As mentioned before, the KVM daemon is responsible for opening a
+connection to the QMP socket of a given instance and listening in on the
+shutdown and powerdown events, which allow the KVM daemon to determine
+whether the instance stopped because of an administrator or user
+shutdown.  Once the instance is stopped, the KVM daemon needs to
+communicate to Ganeti whether the user was responsible for shutting down
+the instance.
+
+In order to achieve this, the KVM daemon writes an empty file, called
+the shutdown file, in the KVM control directory with a name similar to
+the QMP socket file but with the extension ``.qmp`` replaced with
+``.shutdown``.  The presence of this file indicates that the shutdown
+was initiated by a user, whereas the absence of this file indicates that
+the shutdown was caused by an administrator.  This strategy also handles
+crashes and signals, such as, ``SIGKILL``, to be handled correctly,
+given that in these cases the KVM daemon never receives the powerdown
+and shutdown events and, therefore, never creates the shutdown file.
+
+KVM daemon launch
+-----------------
+
+With the above issues addressed, a question remains as to when the KVM
+daemon should be started.  The KVM daemon is different from other Ganeti
+daemons, which start together with the Ganeti service, because the KVM
+daemon is optional, given that it is specific to KVM and should not be
+run on installations containing only Xen, and, even in a KVM
+installation, the user might still choose not to enable it.  And finally
+because the KVM daemon is not really necessary until the first KVM
+instance is started.  For these reasons, the KVM daemon is started from
+within Ganeti when a KVM instance is started.  And the job process
+spawned by the node daemon is responsible for starting the KVM daemon.
+
+Given the current design of Ganeti, in which the node daemon spawns a
+job process to handle the creation of the instance, when launching the
+KVM daemon it is necessary to first check whether an instance of this
+daemon is already running and, if this is not the case, then the KVM
+daemon can be safely started.
+
+Design alternatives
+===================
+
+At first, it might seem natural to include the instance shutdown
+detection for KVM in the node daemon.  After all, the node daemon is
+already responsible for managing instances, for example, starting and
+stopping an instance.  Nevertheless, the node daemon is more complicated
+than it might seem at first.
+
+The node daemon is composed of the main loop, which runs in the main
+thread and is responsible for receiving requests and spawning jobs for
+handling these requests, and the jobs, which are independent processes
+spawned for executing the actual tasks, such as, creating an instance.
+
+Including instance shutdown detection in the node daemon is not viable
+because adding it to the main loop would cause KVM specific code to
+taint the generality of the node daemon.  In order to add it to the job
+processes, it would be possible to spawn either a foreground or a
+background process.  However, these options are also not viable because
+they would lead to the situation described before where there would be a
+monitoring process per instance, which is not scalable.  Moreover, the
+foreground process has an additional disadvantage: it would require
+modifications the node daemon in order not to expect a terminating job,
+which is the current node daemon design.
+
+There is another design issue to have in mind.  We could reconsider the
+place where to write the data that tell Ganeti whether an instance was
+shutdown by an administrator or the user.  Instead of using the KVM
+shutdown files presented above, in which the presence of the file
+indicates a user shutdown and its absence an administrator shutdown, we
+could store a value in the KVM runtime state file, which is where the
+relevant KVM state information is.  The advantage of this approach is
+that it would keep the KVM related information in one place, thus making
+it easier to manage.  However, it would lead to a more complex
+implementation and, in the context of the general transition in Ganeti
+from Python to Haskell, a simpler implementation is preferred.
+
+Finally, it should be noted that the KVM runtime state file benefits
+from automatic migration.  That is, when an instance is migrated so is
+the KVM state file.  However, the instance shutdown detection for KVM
+does not require this feature and, in fact, migrating the instance
+shutdown state would be incorrect.
+
+Further considerations
+======================
+
+There are potential race conditions between Ganeti and the KVM daemon,
+however, in practice they seem unlikely.  For example, the KVM daemon
+needs to add and remove watches to the parent directories of the KVM
+control directory until this directory is finally created.  It is
+possible that Ganeti creates this directory and a KVM instance before
+the KVM daemon has a chance to add a watch to the KVM control directory,
+thus causing this daemon to miss the ``inotify`` creation event for the
+QMP socket.
+
+There are other problems which arise from the limitations of
+``inotify``.  For example, if the KVM daemon is started after the first
+Ganeti instance has been created, then the ``inotify`` will not produce
+any event for the creation of the QMP socket.  This can happen, for
+example, if the KVM daemon needs to be restarted or upgraded.  As a
+result, it might be necessary to have an additional mechanism that runs
+at KVM daemon startup or at regular intervals to ensure that the current
+KVM internal state is consistent with the actual contents of the KVM
+control directory.
+
+Another race condition occurs when Ganeti shuts down a KVM instance
+using force.  Ganeti uses ``TERM`` signals to stop KVM instances when
+force is specified or ACPI is not enabled.  However, as mentioned
+before, ``TERM`` signals are interpreted by the KVM daemon as a user
+shutdown.  As a result, the KVM daemon creates a shutdown file which
+then must be removed by Ganeti.  The race condition occurs because the
+KVM daemon might create the shutdown file after the hypervisor code that
+tries to remove this file has already run.  In practice, the race
+condition seems unlikely because Ganeti stops the KVM instance in a
+retry loop, which allows Ganeti to stop the instance and cleanup its
+runtime information.
+
+It is possible to determine if a process, in this particular case the
+KVM process, was terminated by a ``TERM`` signal, using the `proc
+connector and socket filters
+<https://web.archive.org/web/20121025062848/http://netsplit.com/2011/02/09/the-proc-connector-and-socket-filters/>`_.
+The proc connector is a socket connected between a userspace process and
+the kernel through the netlink protocol and can be used to receive
+notifications of process events, and the socket filters is a mechanism
+for subscribing only to events that are relevant.  There are several
+`process events <http://lwn.net/Articles/157150/>`_ which can be
+subscribed to, however, in this case, we are interested only in the exit
+event, which carries information about the exit signal.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-multi-version-tests.rst b/doc/design-multi-version-tests.rst
new file mode 100644 (file)
index 0000000..155348f
--- /dev/null
@@ -0,0 +1,162 @@
+===================
+Multi-version tests
+===================
+
+.. contents:: :depth: 4
+
+This is a design document describing how tests which use multiple
+versions of Ganeti can be introduced into the current build
+infrastructure.
+
+Desired improvements
+====================
+
+The testing of Ganeti is currently done by using two different
+approaches - unit tests and QA. While the former are useful for ensuring
+that the individual parts of the system work as expected, most errors
+are discovered only when all the components of Ganeti interact during
+QA.
+
+However useful otherwise, until now the QA has failed to provide support
+for testing upgrades and version compatibility as it was limited to
+using only one version of Ganeti. While these can be tested for every
+release manually, a systematic approach is preferred and none can exist
+with this restriction in place. To lift it, the buildbot scripts and QA
+utilities must be extended to allow a way of specifying and using
+diverse multi-version checks.
+
+Required use cases
+==================
+
+There are two classes of multi-version tests that are interesting in
+Ganeti, and this chapter provides an example from each to highlight what
+should be accounted for in the design.
+
+Compatibility tests
+-------------------
+
+One interface Ganeti exposes to clients interested in interacting with
+it is the RAPI. Its stability has always been a design principle
+followed during implementation, but whether it held true in practice was
+not asserted through tests.
+
+An automatic test of RAPI compatibility would have to take a diverse set
+of RAPI requests and perform them on two clusters of different versions,
+one of which would be the reference version. If the clusters had been
+identically configured, all of the commands successfully executed on the
+reference version should succeed on the newer version as well.
+
+To achieve this, two versions of Ganeti can be run separately on a
+cleanly setup cluster. With no guarantee that the versions can coexist,
+the deployment of these has to be separate. A proxy placed between the
+client and Ganeti records all the requests and responses. Using this
+data, a testing utility can decide if the newer version is compatible or
+not, and provide additional information to assist with debugging.
+
+Upgrade / downgrade tests
+-------------------------
+
+An upgrade / downgrade test serves to examine whether the state of the
+cluster is unchanged after its configuration has been upgraded or
+downgraded to another version of Ganeti.
+
+The test works with two consecutive versions of Ganeti, both installed
+on the same machine. It examines whether the configuration data and
+instances survive the downgrade and upgrade procedures. This is done by
+creating a cluster with the newer version, downgrading it to the older
+one, and upgrading it to the newer one again. After every step, the
+integrity of the cluster is checked by running various operations and
+ensuring everything still works.
+
+Design and implementation
+=========================
+
+Although the previous examples have not been selected to show use cases
+as diverse as possible, they still show a number of dissimilarities:
+
+- Parallel installation vs sequential deployments
+- Comparing with reference version vs comparing consecutive versions
+- Examining result dumps vs trying a sequence of operations
+
+With the first two real use cases demonstrating such diversity, it does
+not make sense to design multi-version test classes. Instead, the
+programmability of buildbot's configuration files can be leveraged to
+implement each test as a separate builder with a custom sequence of
+steps. The individual steps such as checking out a given or previous
+version, or installing and removing Ganeti, will be provided as utility
+functions for any test writer to use.
+
+Current state
+-------------
+
+An upgrade / downgrade test is a part of the QA suite as of commit
+aa104b5e. The test and the corresponding buildbot changes are a very
+good first step, both by showing that multi-version tests can be done,
+and by providing utilities needed for builds of multiple branches.
+Previously, the same folder was used as the base directory of any build,
+and now a directory structure more accommodating to multiple builds is
+in place.
+
+The builder running the test has one flaw - regardless of the branch
+submitted, it compares versions 2.10 and 2.11 (current master). This
+behaviour is different from any of the other builders, which may
+restrict the branches a test can be performed on, but do not
+differentiate between them otherwise. While additional builders for
+different versions pairs may be added, this is not a good long-term
+solution.
+
+The test can be improved by making it compare the current and the
+previous version. As the buildbot has no notion of what a previous
+version is, additional utilities to handle this logic will have to be
+introduced.
+
+Planned changes
+---------------
+
+The upgrade / downgrade test should be generalized to work for any
+version which can be downgraded from and upgraded to automatically,
+meaning versions from 2.11 onwards. This will be made challenging by
+the fact that the previous version has to be checked out by reading the
+version of the currently checked out code, identifying the previous
+version, and then making yet another checkout.
+
+The major and minor version can be read from a Ganeti repository in
+multiple ways. The two are present as constants defined in source files,
+but due to refactorings shifting constants from the Python to the
+Haskell side, their position varies across versions. A more reliable way
+of fetching them is by examining the news file, as it obeys strict
+formatting restrictions.
+
+With the version found, a script that acts as a previous version
+lookup table can be invoked. This script can be constructed dynamically
+upon buildbot startup, and specified as a build step. The checkout
+following it proceeds as expected.
+
+The RAPI compatibility test should be added as a separate builder
+afterwards. As the test requires additional comparison and proxy logic
+to be used, it will be enabled only on 2.11 onwards, comparing the
+versions to 2.6 - the reference version for the RAPI. Details on the
+design of this test will be added in a separate document.
+
+Potential issues
+================
+
+While there are many advantages to having a single builder representing
+a multi-version test, working on every branch, there is at least one
+disadvantage: the need to define a base or reference version, which is
+the only version that can be used to trigger the test, and the only one
+on which code changes can be tried.
+
+If an error is detected while running a test, and the issue lies with
+a version other than the one used to invoke the test, the fix would
+have to make it into the repository before the test could be tried
+again.
+
+For simple tests, the issue might be mitigated by running them locally.
+However, the multi-version tests are more likely to be complicated than
+not, and it could be difficult to reproduce a test by hand.
+
+The situation can be made simpler by requiring that any multi-version
+test can use only versions lower than the reference version. As errors
+are more likely to be found in new rather than old code, this would at
+least reduce the number of troublesome cases.
diff --git a/doc/design-node-security.rst b/doc/design-node-security.rst
new file mode 100644 (file)
index 0000000..1c2c8a3
--- /dev/null
@@ -0,0 +1,466 @@
+=============================
+Improvements of Node Security
+=============================
+
+This document describes an enhancement of Ganeti's security by restricting
+the distribution of security-sensitive data to the master and master
+candidates only.
+
+Note: In this document, we will use the term 'normal node' for a node that
+is neither master nor master-candidate.
+
+.. contents:: :depth: 4
+
+Objective
+=========
+
+Up till 2.10, Ganeti distributes security-relevant keys to all nodes,
+including nodes that are neither master nor master-candidates. Those
+keys are the private and public SSH keys for node communication and the
+SSL certficate and private key for RPC communication. Objective of this
+design is to limit the set of nodes that can establish ssh and RPC
+connections to the master and master candidates.
+
+As pointed out in
+`issue 377 <https://code.google.com/p/ganeti/issues/detail?id=377>`_, this
+is a security risk. Since all nodes have these keys, compromising
+any of those nodes would possibly give an attacker access to all other
+machines in the cluster. Reducing the set of nodes that are able to
+make ssh and RPC connections to the master and master candidates would
+significantly reduce the risk simply because fewer machines would be a
+valuable target for attackers.
+
+Note: For bigger installations of Ganeti, it is advisable to run master
+candidate nodes as non-vm-capable nodes. This would reduce the attack
+surface for the hypervisor exploitation.
+
+
+Detailed design
+===============
+
+
+Current state and shortcomings
+------------------------------
+
+Currently (as of 2.10), all nodes hold the following information:
+
+- the ssh host keys (public and private)
+- the ssh root keys (public and private)
+- node daemon certificate (the SSL client certificate and its
+  corresponding private key)
+
+Concerning ssh, this setup contains the following security issue. Since
+all nodes of a cluster can ssh as root into any other cluster node, one
+compromised node can harm all other nodes of a cluster.
+
+Regarding the SSL encryption of the RPC communication with the node
+daemon, we currently have the following setup. There is only one
+certificate which is used as both, client and server certificate. Besides
+the SSL client verification, we check if the used client certificate is
+the same as the certificate stored on the server.
+
+This means that any node running a node daemon can also act as an RPC
+client and use it to issue RPC calls to other cluster nodes. This in
+turn means that any compromised node could be used to make RPC calls to
+any node (including itself) to gain full control over VMs. This could
+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
+----------------------------------------
+
+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.
+
+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.
+
+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.
+
+
+(Re-)Adding nodes to a cluster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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.
+
+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.
+
+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.
+
+
+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.
+
+This affected the behavior of the following commands:
+
+::
+  gnt-node modify --master-candidate=yes
+  gnt-node modify --master-candidate=no [--auto-promote]
+
+If the node has been master candidate already before the command to promote
+it was issued, Ganeti does not do anything.
+
+Note that when you demote a node from master candidate to normal node, another
+master-capable and normal node will be promoted to master candidate. For this
+newly promoted node, the same changes apply as if it was explicitely promoted.
+
+The same behavior should be ensured for the corresponding rapi command.
+
+
+Offlining and onlining a node
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When offlining a node, it immediately loses its role as master or master
+candidate as well. When it is onlined again, it will become master
+candidate again if it was so before. The handling of the keys should be done
+in the same way as when the node is explicitely promoted or demoted to or from
+master candidate. See the previous section for details.
+
+This affects the command:
+
+::
+  gnt-node modify --offline=yes
+  gnt-node modify --offline=no [--auto-promote]
+
+For offlining, the removal of the keys is particularly important, as the
+detection of a compromised node might be the very reason for the offlining.
+Of course we cannot guarantee that removal of the key is always successful,
+because the node might not be reachable anymore. Even though it is a
+best-effort operation, it is still an improvement over the status quo,
+because currently Ganeti does not even try to remove any keys.
+
+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.
+
+
+
+Proposal regarding node daemon certificates
+-------------------------------------------
+
+Regarding the node daemon certificates, we propose the following changes
+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.
+- 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.
+  The client certificate digest is a hash of the client certificate.
+  We suggest a 'sha1' hash here. We will call this mapping 'candidate map'
+  from here on.
+- The node daemon will be modified in a way that on an incoming RPC
+  request, it first performs a client verification (same as before) to
+  ensure that the requesting host is indeed the holder of the
+  corresponding private key. Additionally, it compares the digest of
+  the certificate of the incoming request to the respective entry of
+  the candidate map. If the digest does not match the entry of the host
+  in the mapping or is not included in the mapping at all, the SSL
+  connection is refused.
+
+This design has the following advantages:
+
+- A compromised normal node cannot issue RPC calls, because it will
+  not be in the candidate map. (See the ``Drawbacks`` section regarding
+  an indirect way of achieving this though.)
+- A compromised master candidate would be able to issue RPC requests,
+  but on detection of its compromised state, it can be removed from the
+  cluster (and thus from the candidate map) without the need for
+  redistribution of any certificates, because the other master candidates
+  can continue using their own certificates. However, it is best
+  practice to issue a complete key renewal even in this case, unless one
+  can ensure no actions compromising other nodes have not already been
+  carried out.
+- A compromised node would not be able to use the other (possibly master
+  candidate) nodes' information from the candidate map to issue RPCs,
+  because the config just stores the digests and not the certificate
+  itself.
+- A compromised node would be able to obtain another node's certificate
+  by waiting for incoming RPCs from this other node. However, the node
+  cannot use the certificate to issue RPC calls, because the SSL client
+  verification would require the node to hold the corresponding private
+  key as well.
+
+Drawbacks of this design:
+
+- Complexity of node and certificate management will be increased (see
+  following sections for details).
+- If the candidate map is not distributed fast enough to all nodes after
+  an update of the configuration, it might be possible to issue RPC calls
+  from a compromised master candidate node that has been removed
+  from the Ganeti cluster already. However, this is still a better
+  situation than before and an inherent problem when one wants to
+  distinguish between master candidates and normal nodes.
+- A compromised master candidate would still be able to issue RPC calls,
+  if it uses ssh to retrieve another master candidate's client
+  certificate and the corresponding private SSL key. This is an issue
+  even with the first part of the improved handling of ssh keys in this
+  design (limiting ssh keys to master candidates), but it will be
+  eliminated with the second part of the design (separate ssh keys for
+  each master candidate).
+- 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".
+
+Alternative proposals:
+
+- 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
+  the requesting node has the master candidate certificate. Drawback of
+  this proposal is that once one master candidate gets compromised, all
+  master candidates would need to get a new certificate even if the
+  compromised master candidate had not yet fetched the certificates
+  from the other master candidates via ssh.
+- In addition to our main proposal, one could think of including a
+  piece of data (for example the node's host name or UUID) in the RPC
+  call which is encrypted with the requesting node's private key. The
+  node daemon could check if the datum can be decrypted using the node's
+  certificate. However, this would ensure similar functionality as
+  SSL's built-in client verification and add significant complexity
+  to Ganeti's RPC protocol.
+
+In the following sections, we describe how our design affects various
+Ganeti operations.
+
+
+Cluster initialization
+~~~~~~~~~~~~~~~~~~~~~~
+
+On cluster initialization, so far only the node daemon certificate was
+created. With our design, two certificates (and corresponding keys)
+need to be created, a server certificate to be distributed to all nodes
+and a client certificate only to be used by this particular node. In the
+following, we use the term node daemon certificate for the server
+certficate only.
+
+In the cluster configuration, the candidate map is created. It is
+populated with the respective entry for the master node. It is also
+written to ssconf.
+
+
+(Re-)Adding nodes
+~~~~~~~~~~~~~~~~~
+
+When a node is added, the server certificate is copied to the node (as
+before). Additionally, a new client certificate (and the corresponding
+private key) is created on the new node to be used only by the new node
+as client certifcate.
+
+If the new node is a master candidate, the candidate map is extended by
+the new node's data. As before, the updated configuration is distributed
+to all nodes (as complete configuration on the master candidates and
+ssconf on all nodes). Note that distribution of the configuration after
+adding a node is already implemented, since all nodes hold the list of
+nodes in the cluster in ssconf anyway.
+
+If the configuration for whatever reason already holds an entry for this
+node, it will be overriden.
+
+When readding a node, the procedure is the same as for adding a node.
+
+
+Promotion and demotion of master candidates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When a normal node gets promoted to be master candidate, an entry to the
+candidate map has to be added and the updated configuration has to be
+distributed to all nodes. If there was already an entry for the node,
+we override it.
+
+On demotion of a master candidate, the node's entry in the candidate map
+gets removed and the updated configuration gets redistibuted.
+
+The same procedure applied to onlining and offlining master candidates.
+
+
+Cluster verify
+~~~~~~~~~~~~~~
+
+Cluster verify will be extended by the following checks:
+
+- Whether each entry in the candidate map indeed corresponds to a master
+  candidate.
+- Whether the master candidate's certificate digest match their entry
+  in the candidate map.
+- 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.
+
+
+Crypto renewal
+~~~~~~~~~~~~~~
+
+Currently, when the cluster's cryptographic tokens are renewed using the
+``gnt-cluster renew-crypto`` command the node daemon certificate is
+renewed (among others). Option ``--new-cluster-certificate`` renews the
+node daemon certificate only.
+
+By adding an option ``--new-node-certificates`` we offer to renew the
+client certificate. Whenever the client certificates are renewed, the
+candidate map has to be updated and redistributed.
+
+If for whatever reason, the candidate map becomes inconsistent, for example
+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.
+
+
+Further considerations
+----------------------
+
+Watcher
+~~~~~~~
+
+The watcher is a script that is run on all nodes in regular intervals. The
+changes proposed in this design will not affect the watcher's implementation,
+because it behaves differently on the master than on non-master nodes.
+
+Only on the master, it issues query calls which would require a client
+certificate of a node in the candidate mapping. This is the case for the
+master node. On non-master node, it's only external communication is done via
+the ConfD protocol, which uses the hmac key, which is present on all nodes.
+Besides that, the watcher does not make any ssh connections, and thus is
+not affected by the changes in ssh key handling either.
+
+
+Other Keys and Daemons
+~~~~~~~~~~~~~~~~~~~~~~
+
+Ganeti handles a couple of other keys/certificates that have not been mentioned
+in this design so far. Also, other daemons than the ones mentioned so far
+perform intra-cluster communication. Neither the keys nor the daemons will
+be affected by this design for several reasons:
+
+- The hmac key used by ConfD (see :doc:`design-2.1`): the hmac key is still
+  distributed to all nodes, because it was designed to be used for
+  communicating with ConfD, which should be possible from all nodes.
+  For example, the monitoring daemon which runs on all nodes uses it to
+  retrieve information from ConfD. However, since communication with ConfD
+  is read-only, a compromised node holding the hmac key does not enable an
+  attacker to change the cluster's state.
+
+- The WConfD daemon writes the configuration to all master candidates
+  via RPC. Since it only runs on the master node, it's ability to run
+  RPC requests is maintained with this design.
+
+- The rapi SSL key certificate and rapi user/password file 'rapi_users' is
+  already only copied to the master candidates (see :doc:`design-2.1`,
+  Section ``Redistribute Config``).
+
+- The spice certificates are still distributed to all nodes, since it should
+  be possible to use spice to access VMs on any cluster node.
+
+- The cluster domain secret is used for inter-cluster instance moves.
+  Since instances can be moved from any normal node of the source cluster to
+  any normal node of the destination cluster, the presence of this
+  secret on all nodes is necessary.
+
+
+Related and Future Work
+~~~~~~~~~~~~~~~~~~~~~~~
+
+There a couple of suggestions on how to improve the SSL setup even more.
+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.
+- 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
+  node is renamed.
+- The typical use case for SSL is to have one certificate per node
+  rather than one shared certificate (Ganeti's noded server certificate)
+  and a client certificate. One could change the design in a way that
+  only one certificate per node is used, but this would require a common
+  CA so that the validity of the certificate can be established by every
+  node in the cluster.
+- With the proposed design, the serial numbers of the client
+  certificates are set to the node UUIDs. This is technically also not
+  complying to how SSL is supposed to be used, as the serial numbers
+  should reflect the enumeration of certificates created by the CA. Once
+  a CA is implemented, it might be reasonable to change this
+  accordingly. The implementation of the proposed design also has the
+  drawback of the serial number not changing even if the certificate is
+  replaced by a new one (for example when calling ``gnt-cluster renew-
+  crypt``), which also does not comply to way SSL was designed to be
+  used.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-os.rst b/doc/design-os.rst
new file mode 100644 (file)
index 0000000..1ff09c0
--- /dev/null
@@ -0,0 +1,605 @@
+===============================
+Ganeti OS installation redesign
+===============================
+
+.. contents:: :depth: 3
+
+This is a design document detailing a new OS installation procedure, which is
+more secure, able to provide more features and easier to use for many common
+tasks w.r.t. the current one.
+
+Current state and shortcomings
+==============================
+
+As of Ganeti 2.10, each instance is associated with an OS definition. An OS
+definition is a set of scripts (i.e., ``create``, ``export``, ``import``,
+``rename``) that are executed with root privileges on the primary host of the
+instance.  These scripts are responsible for performing all the OS-related
+tasks, namely, create an instance, setup an operating system on the instance's
+disks, export/import the instance, and rename the instance.
+
+These scripts receive, through environment variables, a fixed set of instance
+parameters (such as, the hypervisor, the name of the instance, the number of
+disks and their location) and a set of user defined parameters.  Both the
+instance and user defined parameters are written in the configuration file of
+Ganeti, to allow future reinstalls of the instance, and in various log files,
+namely:
+
+* node daemon log file: contains DEBUG strings of the ``/os_validate``,
+  ``/instance_os_add`` and ``/instance_start`` RPC calls.
+
+* master daemon log file: DEBUG strings related to the same RPC calls are stored
+  here as well.
+
+* commands log: the CLI commands that create a new instance, including their
+  parameters, are logged here.
+
+* RAPI log: the RAPI commands that create a new instance, including their
+  parameters, are logged here.
+
+* job logs: the job files stored in the job queue, or in its archive, contain
+  the parameters.
+
+The current situation presents a number of shortcomings:
+
+* Having the installation scripts run as root on the nodes does not allow
+  user-defined OS scripts, as they would pose a huge security risk.
+  Furthermore, even a script without malicious intentions might end up
+  disrupting a node because of due to a bug.
+
+* Ganeti cannot be used to create instances starting from user provided disk
+  images: even in the (hypothetical) case in which the scripts are completely
+  secure and run not by root but by an unprivileged user with only the power to
+  mount arbitrary files as disk images, this is still a security issue. It has
+  been proven that a carefully crafted file system might exploit kernel
+  vulnerabilities to gain control of the system. Therefore, directly mounting
+  images on the Ganeti nodes is not an option.
+
+* There is no way to inject files into an existing disk image. A common use case
+  is for the system administrator to provide a standard image of the system, to
+  be later personalized with the network configuration, private keys identifying
+  the machine, ssh keys of the users, and so on. A possible workaround would be
+  for the scripts to mount the image (only if this is trusted!) and to receive
+  the configurations and ssh keys as user defined OS parameters. Unfortunately,
+  this is also not an option for security sensitive material (such as the ssh
+  keys) because the OS parameters are stored in many places on the system, as
+  already described above.
+
+* Most other virtualization software allow only instance images, but no
+  installation scripts. This difference makes the interaction between Ganeti and
+  other software difficult.
+
+Proposed changes
+================
+
+In order to fix the shortcomings of the current state, we plan to introduce the
+following changes.
+
+OS parameter categories
++++++++++++++++++++++++
+
+Change the OS parameters to have three categories:
+
+* ``public``: the current behavior. The parameter is logged and stored freely.
+
+* ``private``: the parameter is saved inside the Ganeti configuration (to allow
+  for instance reinstall) but it is not shown in logs, job logs, or passed back
+  via RAPI.
+
+* ``secret``: the parameter is not saved inside the Ganeti configuration.
+  Reinstalls are impossible unless the data is passed again. The parameter will
+  not appear in any log file. When a functionality is performed jointly by
+  multiple daemons (such as MasterD and LuxiD), currently Ganeti sometimes
+  serializes jobs on disk and later reloads them. Secret parameters will not be
+  serialized to disk. They will be passed around as part of the LUXI calls
+  exchanged by the daemons, and only kept in memory, in order to reduce their
+  accessibility as much as possible. In case of failure of the master node,
+  these parameters will be lost and cannot be recovered because they are not
+  serialized. As a result, the job cannot be taken over by the new master.  This
+  is an expected and accepted side effect of jobs with secret parameters: if
+  they fail, they'll have to be restarted manually.
+
+Metadata
+++++++++
+
+In order to allow metadata to be sent inside the instance, a communication
+mechanism between the instance and the host will be created.  This mechanism
+will be bidirectional (e.g.: to allow the setup process going on inside the
+instance to communicate its progress to the host). Each instance will have
+access exclusively to its own metadata, and it will be only able to communicate
+with its host over this channel.  This is the approach followed the
+``cloud-init`` tool and more details will be provided in the `Communication
+mechanism`_ and `Metadata service`_ sections.
+
+Installation procedure
+++++++++++++++++++++++
+
+A new installation procedure will be introduced.  There will be two sets of
+parameters, namely, installation parameters, which are used mainly for installs
+and reinstalls, and execution parameters, which are used in all the other runs
+that are not part of an installation procedure.  Also, it will be possible to
+use an installation medium and/or run the OS scripts in an optional virtualized
+environment, and optionally use a personalization package.  This section details
+all of these options.
+
+The set of installation parameters will allow, for example, to attach an
+installation floppy/cdrom/network, change the boot device order, or specify a
+disk image to be used.  Through this set of parameters, the administrator will
+have to provide the hypervisor a location for an installation medium for the
+instance (e.g., a boot disk, a network image, etc).  This medium will carry out
+the installation of the instance onto the instance's disks and will then be
+responsible for getting the parameters for configuring the instance, such as,
+network interfaces, IP address, and hostname.  These parameters are taken from
+the metadata.  The installation parameters will be stored in the configuration
+of Ganeti and used in future reinstalls, but not during normal execution.
+
+The instance is reinstalled using the same installation parameters from the
+first installation.  However, it will be the administrator's responsibility to
+ensure that the installation media is still available at the proper location
+when a reinstall occurs.
+
+The parameter ``--os-parameters`` can still be used to specify the OS
+parameters.  However, without OS scripts, Ganeti cannot do more than a syntactic
+check to validate the supplied OS parameter string.  As a result, this string
+will be passed directly to the instance as part of the metadata.  If OS scripts
+are used and the installation procedure is running inside a virtualized
+environment, Ganeti will take these parameters from the metadata and pass them
+to the OS scripts as environment variables.
+
+Ganeti allows the following installation options:
+
+* Use a disk image:
+
+  Currently, it is already possible to specify an installation medium, such as,
+  a cdrom, but not a disk image.  Therefore, a new parameter ``--os-image`` will
+  be used to specify the location of a disk image which will be dumped to the
+  instance's first disk before the instance is started.  The location of the
+  image can be a URL and, if this is the case, Ganeti will download this image.
+
+* Run OS scripts:
+
+  The parameter ``--os-type`` (short version: ``-o``), is currently used to
+  specify the OS scripts.  This parameter will still be used to specify the OS
+  scripts with the difference that these scripts may optionally run inside a
+  virtualized environment for safety reasons, depending on whether they are
+  trusted or not.  For more details on trusted and untrusted OS scripts, refer
+  to the `Installation process in a virtualized environment`_ section.  Note
+  that this parameter will become optional thus allowing a user to create an
+  instance specifying only, for example, a disk image or a cdrom image to boot
+  from.
+
+* Personalization package
+
+  As part of the instance creation command, it will be possible to indicate a
+  URL for a "personalization package", which is an archive containing a set of
+  files meant to be overlayed on top of the OS file system at the end of the
+  setup process and before the VM is started for the first time in normal mode.
+  Ganeti will provide a mechanism for receiving and unpacking this archive,
+  independently of whether the installation is being performed inside the
+  virtualized environment or not.
+
+  The archive will be in TAR-GZIP format (with extension ``.tar.gz`` or
+  ``.tgz``) and contain the files according to the directory structure that will
+  be recreated on the installation disk.  Files contained in this archive will
+  overwrite files with the same path created during the installation procedure
+  (if any).  The URL of the "personalization package" will have to specify an
+  extension to identify the file format (in order to allow for more formats to
+  be supported in the future).  The URL will be stored as part of the
+  configuration of the instance (therefore, the URL should not contain
+  confidential information, but the files there available can).
+
+  It is up to the system administrator to ensure that a package is actually
+  available at that URL at install and reinstall time.  The contents of the
+  package are allowed to change.  E.g.: a system administrator might create a
+  package containing the private keys of the instance being created.  When the
+  instance is reinstalled, a new package with new keys can be made available
+  there, thus allowing instance reinstall without the need to store keys.  A
+  username and a password can be specified together with the URL.  If the URL is
+  a HTTP(S) URL, they will be used as basic access authentication credentials to
+  access that URL.  The username and password will not be saved in the config,
+  and will have to be provided again in case a reinstall is requested.
+
+  The downloaded personalization package will not be stored locally on the node
+  for longer than it is needed while unpacking it and adding its files to the
+  instance being created.  The personalization package will be overlayed on top
+  of the instance filesystem after the scripts that created it have been
+  executed.  In order for the files in the package to be automatically overlayed
+  on top of the instance filesystem, it is required that the appliance is
+  actually able to mount the instance's disks.  As a result, this will not work
+  for every filesystem.
+
+* Combine a disk image, OS scripts, and a personalization package
+
+  It will possible to combine a disk image, OS scripts, and a personalization
+  package, both with or without a virtualized environment (see the exception
+  below). At least, an installation medium or OS scripts should be specified.
+
+  The disk image of the actual virtual appliance, which bootstraps the virtual
+  environment used in the installation procedure, will be read only, so that a
+  pristine copy of the appliance can be started every time a new instance needs
+  to be created and to further increase security.  The data the instance needs
+  to write at runtime will only be stored in RAM and disappear as soon as the
+  instance is stopped.
+
+  The parameter ``--enable-safe-install=yes|no`` will be used to give the
+  administrator control over whether to use a virtualized environment for the
+  installation procedure.  By default, a virtualized environment will be used.
+  Note that some feature combinations, such as, using untrusted scripts, will
+  require the virtualized environment.  In this case, Ganeti will not allow
+  disabling the virtualized environment.
+
+Implementation
+==============
+
+The implementation of this design will happen as an ordered sequence of steps,
+of increasing impact on the system and, in some cases, dependent on each other:
+
+#. Private and secret instance parameters
+#. Communication mechanism between host and instance
+#. Metadata service
+#. Personalization package (inside a virtualization environment)
+#. Instance creation via a disk image
+#. Instance creation inside a virtualized environment
+
+Some of these steps need to be more deeply specified w.r.t. what is already
+written in the `Proposed changes`_ Section. Extra details will be provided in
+the following subsections.
+
+Communication mechanism
++++++++++++++++++++++++
+
+The communication mechanism will be an exclusive, generic, bidirectional
+communication channel between Ganeti hosts and guests.
+
+exclusive
+  The communication mechanism allows communication between a guest and its host,
+  but it does not allow a guest to communicate with other guests or reach the
+  outside world.
+
+generic
+  The communication mechanism allows a guest to reach any service on the host,
+  not just the metadata service.  Examples of valid communication include, but
+  are not limited to, access to the metadata service, send commands to Ganeti,
+  request changes to parameters, such as, those related to the distribution
+  upgrades, and let Ganeti control a helper instance, such as, the one for
+  performing OS installs inside a safe environment.
+
+bidirectional
+  The communication mechanism allows communication to be initiated from either
+  party, namely, from a host to a guest or guest to host.
+
+Note that Ganeti will allow communication with any service (e.g., daemon) running
+on the host and, as a result, Ganeti will not be responsible for ensuring that
+only the metadata service is reachable.  It is the responsibility of each system
+administrator to ensure that the extra firewalling and routing rules specified
+on the host provide the necessary protection on a given Ganeti installation and,
+at the same time, do not accidentally override the behaviour hereby described
+which makes the communication between the host and the guest exclusive, generic,
+and bidirectional, unless intended.
+
+The communication mechanism will be enabled automatically during an installation
+procedure that requires a virtualized environment, but, for backwards
+compatibility, it will be disabled when the instance is running normally, unless
+explicitly requested.  Specifically, a new parameter ``--communication=yes|no``
+(short version: ``-C``) will be added to ``gnt-instance add`` and ``gnt-instance
+modify``.  This parameter will determine whether the communication mechanism is
+enabled for a particular instance.  The value of this parameter will be saved as
+part of the instance's configuration.
+
+The communication mechanism will be implemented through network interfaces on
+the host and the guest, and Ganeti will be responsible for the host side,
+namely, creating a TAP interface for each guest and configuring these interfaces
+to have name ``gnt.com.%d``, where ``%d`` is a unique number within the host
+(e.g., ``gnt.com.0`` and ``gnt.com.1``), IP address ``169.254.169.254``, and
+netmask ``255.255.255.255``.  The interface's name allows DHCP servers to
+recognize which interfaces are part of the communication mechanism.
+
+This network interface will be connected to the guest's last network interface,
+which is meant to be used exclusively for the communication mechanism and is
+defined after all the used-defined interfaces.  The last interface was chosen
+(as opposed to the first one, for example) because the first interface is
+generally understood and the main gateway out, and also because it minimizes the
+impact on existing systems, for example, in a scenario where the system
+administrator has a running cluster and wants to enable the communication
+mechanism for already existing instances, which might have been created with
+older versions of Ganeti.  Further, DBus should assist in keeping the guest
+network interfaces more stable.
+
+On the guest side, each instance will have its own MAC address and IP address.
+Both the guest's MAC address and IP address must be unique within a single
+cluster.  An IP is unique within a single cluster, and not within a single host,
+in order to minimize disruption of connectivity, for example, during live
+migration, in particular since an instance is not aware when it changes host.
+Unfortunately, a side-effect of this decision is that a cluster can have a
+maximum of a ``/16`` network allowed instances (with communication enabled).  If
+necessary to overcome this limit, it should be possible to allow different
+networks to be configured link-local only.
+
+The guest will use the DHCP protocol on its last network interface to contact a
+DHCP server running on the host and thus determine its IP address.  The DHCP
+server is configured, started, and stopped, by Ganeti and it will be listening
+exclusively on the TAP network interfaces of the guests in order not to
+interfere with a potential DHCP server running on the same host.  Furthermore,
+the DHCP server will only recognize MAC and IP address pairs that have been
+approved by Ganeti.
+
+The TAP network interfaces created for each guest share the same IP address.
+Therefore, it will be necessary to extend the routing table with rules specific
+to each guest.  This can be achieved with the following command, which takes the
+guest's unique IP address and its TAP interface::
+
+  route add -host <ip> dev <ifname>
+
+This rule has the additional advantage of preventing guests from trying to lease
+IP addresses from the DHCP server other than the own that has been assigned to
+them by Ganeti.  The guest could lie about its MAC address to the DHCP server
+and try to steal another guest's IP address, however, this routing rule will
+block traffic (i.e., IP packets carrying the wrong IP) from the DHCP server to
+the malicious guest.  Similarly, the guest could lie about its IP address (i.e.,
+simply assign a predefined IP address, perhaps from another guest), however,
+replies from the host will not be routed to the malicious guest.
+
+This routing rule ensures that the communication channel is exclusive but, as
+mentioned before, it will not prevent guests from accessing any service on the
+host.  It is the system administrator's responsibility to employ the necessary
+``iptables`` rules.  In order to achieve this, Ganeti will provide ``ifup``
+hooks associated with the guest network interfaces which will give system
+administrator's the opportunity to customize their own ``iptables``, if
+necessary.  Ganeti will also provide examples of such hooks.  However, these are
+meant to personalized to each Ganeti installation and not to be taken as
+production ready scripts.
+
+For KVM, an instance will be started with a unique MAC address and the file
+descriptor for the TAP network interface meant to be used by the communication
+mechanism.  Ganeti will be responsible for generating a unique MAC address for
+the guest, opening the TAP interface, and passing its file descriptor to KVM::
+
+  kvm -net nic,macaddr=<mac> -net tap,fd=<tap-fd> ...
+
+For Xen, a network interface will be created on the host (using the ``vif``
+parameter of the Xen configuration file).  Each instance will have its
+corresponding ``vif`` network interface on the host.  The ``vif-route`` script
+of Xen might be helpful in implementing this.
+
+dnsmasq
++++++++
+
+The previous section describes the communication mechanism and explains the role
+of the DHCP server.  Note that any DHCP server can be used in the implementation
+of the communication mechanism.  However, the DHCP server employed should not
+violate the properties described in the previous section, which state that the
+communication mechanism should be exclusive, generic, and bidirectional, unless
+this is intentional.
+
+In our experiments, we have used dnsmasq.  In this section, we describe how to
+properly configure dnsmasq to work on a given Ganeti installation.  This is
+particularly important if, in this Ganeti installation, dnsmasq will share the
+node with one or more DHCP servers running in parallel.
+
+First, it is important to become familiar with the operational modes of dnsmasq,
+which are well explained in the `FAQ
+<http://www.thekelleys.org.uk/dnsmasq/docs/FAQ>`_ under the question ``What are
+these strange "bind-interface" and "bind-dynamic" options?``.  The rest of this
+section assumes the reader is familiar with these operational modes.
+
+bind-dynamic
+  dnsmasq SHOULD be configured in the ``bind-dynamic`` mode (if supported) in
+  order to allow other DHCP servers to run on the same node.  In this mode,
+  dnsmasq can listen on the TAP interfaces for the communication mechanism by
+  listening on the TAP interfaces that match the pattern ``gnt.com.*`` (e.g.,
+  ``interface=gnt.com.*``).  For extra safety, interfaces matching the pattern
+  ``eth*`` and the name ``lo`` should be configured such that dnsmasq will
+  always ignore them (e.g., ``except-interface=eth*`` and
+  ``except-interface=lo``).
+
+bind-interfaces
+  dnsmasq MAY be configured in the ``bind-interfaces`` mode (if supported) in
+  order to allow other DHCP servers to run on the same node.  Unfortunately,
+  because dnsmasq cannot dynamically adjust to TAP interfaces that are created
+  and destroyed by the system, dnsmasq must be restarted with a new
+  configuration file each time an instance is created or destroyed.
+
+  Also, the interfaces cannot be patterns, such as, ``gnt.com.*``.  Instead, the
+  interfaces must be explictly specified, for example,
+  ``interface=gnt.com.0,gnt.com.1``.  Moreover, dnsmasq cannot bind to the TAP
+  interfaces if they have all the same IPv4 address.  As a result, it is
+  necessary to configure these TAP interfaces to enable IPv6 and an IPv6 address
+  must be assigned to them.
+
+wildcard
+  dnsmasq CANNOT be configured in the ``wildcard`` mode if there is
+  (at least) another DHCP server running on the same node.
+
+Metadata service
+++++++++++++++++
+
+An instance will be able to reach metadata service on ``169.254.169.254:80`` in
+order to, for example, retrieve its metadata.  This IP address and port were
+chosen for compatibility with the OpenStack and Amazon EC2 metadata service.
+The metadata service will be provided by a single daemon, which will determine
+the source instance for a given request and reply with the metadata pertaining
+to that instance.
+
+Where possible, the metadata will be provided in a way compatible with Amazon
+EC2, at::
+
+  http://169.254.169.254/<version>/meta-data/*
+
+Ganeti-specific metadata, that does not fit this structure, will be provided
+at::
+
+  http://169.254.169.254/ganeti/<version>/meta_data.json
+
+where ``<version>`` is either a date in YYYY-MM-DD format, or ``latest`` to
+indicate the most recent available protocol version.
+
+If needed in the future, this structure also allows us to support OpenStack's
+metadata at::
+
+  http://169.254.169.254/openstack/<version>/meta_data.json
+
+A bi-directional, pipe-like communication channel will also be provided.  The
+instance will be able to receive data from the host by a GET request at::
+
+  http://169.254.169.254/ganeti/<version>/read
+
+and to send data to the host by a POST request at::
+
+  http://169.254.169.254/ganeti/<version>/write
+
+As in a pipe, once the data are read, they will not be in the buffer anymore, so
+subsequent GET requests to ``read`` will not return the same data.  However,
+unlike a pipe, it will not be possible to perform blocking I/O operations.
+
+The OS parameters will be accessible through a GET request at::
+
+  http://169.254.169.254/ganeti/<version>/os/parameters.json
+
+as a JSON serialized dictionary having the parameter name as the key, and the
+pair ``(<value>, <visibility>)`` as the value, where ``<value>`` is the
+user-provided value of the parameter, and ``<visibility>`` is either ``public``,
+``private`` or ``secret``.
+
+The installation scripts to be run inside the virtualized environment will be
+available at::
+
+  http://169.254.169.254/ganeti/<version>/os/scripts/<script_name>
+
+where ``<script_name>`` is the name of the script.
+
+Rationale
+---------
+
+The choice of using a network interface for instance-host communication, as
+opposed to VirtIO, XenBus or other methods, is due to the will of having a
+generic, hypervisor-independent way of creating a communication channel, that
+doesn't require unusual (para)virtualization drivers.
+At the same time, a network interface was preferred over solutions involving
+virtual floppy or USB devices because the latter tend to be detected and
+configured by the guest operating systems, sometimes even in prominent positions
+in the user interface, whereas it is fairly common to have an unconfigured
+network interface in a system, usually without any negative side effects.
+
+Installation process in a virtualized environment
++++++++++++++++++++++++++++++++++++++++++++++++++
+
+In the new OS installation scenario, we distinguish between trusted and
+untrusted code.
+
+The trusted installation code maintains the behavior of the current one and
+requires no modifications, with the scripts running on the node the instance is
+being created on. The untrusted code is stored in a subdirectory of the OS
+definition called ``untrusted``.  This directory contains scripts that are
+equivalent to the already existing ones (``create``, ``export``, ``import``,
+``rename``) but that will be run inside an virtualized environment, to protect
+the host from malicious tampering.
+
+The ``untrusted`` code is meant to either be untrusted itself, or to be trusted
+code running operations that might be dangerous (such as mounting a
+user-provided image).
+
+By default, all new OS definitions will have to be explicitly marked as trusted
+by the cluster administrator (with a new ``gnt-os modify`` command) before they
+can run code on the host. Otherwise, only the untrusted part of the code will be
+allowed to run, inside the virtual appliance. For backwards compatibility
+reasons, when upgrading an existing cluster, all the installed OSes will be
+marked as trusted, so that they can keep running with no changes.
+
+In order to allow for the highest flexibility, if both a trusted and an
+untrusted script are provided for the same operation (i.e. ``create``), both of
+them will be executed at the same time, one on the host, and one inside the
+installation appliance. They will be allowed to communicate with each other
+through the already described communication mechanism, in order to orchestrate
+their execution (e.g.: the untrusted code might execute the installation, while
+the trusted one receives status updates from it and delivers them to a user
+interface).
+
+The cluster administrator will have an option to completely disable scripts
+running on the host, leaving only the ones running in the VM.
+
+Ganeti will provide a script to be run at install time that can be used to
+create the virtualized environment that will perform the OS installation of new
+instances.
+This script will build a debootstrapped basic Debian system including a software
+that will read the metadata, setup the environment variables and launch the
+installation scripts inside the virtualized environment. The script will also
+provide hooks for personalization.
+
+It will also be possible to use other self-made virtualized environments, as
+long as they connect to Ganeti over the described communication mechanism and
+they know how to read and use the provided metadata to create a new instance.
+
+While performing an installation in the virtualized environment, a customizable
+timeout will be used to detect possible problems with the installation process,
+and to kill the virtualized environment. The timeout will be optional and set on
+a cluster basis by the administrator. If set, it will be the total time allowed
+to setup an instance inside the appliance. It is mainly meant as a safety
+measure to prevent an instance taken over by malicious scripts to be available
+for a long time.
+
+Alternatives to design and implementation
+=========================================
+
+This section lists alternatives to design and implementation, which came up
+during the development of this design document, that will not be implemented.
+Please read carefully through the limitations and security concerns of each of
+these alternatives.
+
+Port forwarding in KVM
+++++++++++++++++++++++
+
+The communication mechanism could have been implemented in KVM using guest port
+forwarding, as opposed to network interfaces.  There are two alternatives in
+KVM's guest port forwarding, namely, creating a forwarding device, such as, a
+TCP/IP connection, or executing a command.  However, we have determined that
+both of these options are not viable.
+
+A TCP/IP forwarding device can be created through the following KVM invocation::
+
+  kvm -net nic -net \
+    user,restrict=on,net=169.254.0.0/16,host=169.254.169.253,
+    guestfwd=tcp:169.254.169.254:80-tcp:127.0.0.1:8080 ...
+
+This invocation even has the advantage that it can block undesired traffic
+(i.e., traffic that is not explicitly specified in the arguments) and it can
+remap ports, which would have allowed the metadata service daemon to run in port
+8080 instead of 80.  However, in this scheme, KVM opens the TCP connection only
+once, when it is started, and, if the connection breaks, KVM will not
+reestablish the connection.  Furthermore, opening the TCP connection only once
+interferes with the HTTP protocol, which needs to dynamically establish and
+close connections.
+
+The alternative to the TCP/IP forwarding device is to execute a command.  The
+KVM invocation for this is, for example, the following::
+
+  kvm -net nic -net \
+    "user,restrict=on,net=169.254.0.0/16,host=169.254.169.253,
+    guestfwd=tcp:169.254.169.254:80-netcat 127.0.0.1 8080" ...
+
+The advantage of this approach is that the command is executed each time the
+guest initiates a connection.  This is the ideal situation, however, it is only
+supported in KVM 1.2 and above, and, therefore, not viable because we want to
+provide support for at least KVM version 1.0, which is the version provided by
+Ubuntu LTS.
+
+Alternatives to the DHCP server
++++++++++++++++++++++++++++++++
+
+There are alternatives to using the DHCP server, for example, by assigning a
+fixed IP address to guests, such as, the IP address ``169.254.169.253``.
+However, this introduces a routing problem, namely, how to route incoming
+packets from the same source IP to the host.  This problem can be overcome in a
+number of ways.
+
+The first solution is to use NAT to translate the incoming guest IP address, for
+example, ``169.254.169.253``, to a unique IP address, for example,
+``169.254.0.1``.  Given that NAT through ``ip rule`` is deprecated, users can
+resort to ``iptables``.  Note that this has not yet been tested.
+
+Another option, which has been tested, but only in a prototype, is to connect
+the TAP network interfaces of the guests to a bridge.  The bridge takes the
+configuration from the TAP network interfaces, namely, IP address
+``169.254.169.254`` and netmask ``255.255.255.255``, thus leaving those
+interfaces without an IP address.  Note that in this setting, guests will be
+able to reach each other, therefore, if necessary, additional ``iptables`` rules
+can be put in place to prevent it.
index e0718bc..6e54562 100644 (file)
@@ -129,6 +129,11 @@ execute any query from any node; except maybe practical reasons
 we want to change the cluster security model). In any case, it should
 be possible to do this in a reliable way from all master candidates.
 
+Update: We decided to keep the restriction to run queries on the master
+node. The reason is that it is confusing from a usability point of view
+that querying will work on any node and suddenly, when the user tries
+to submit a job, it won't work.
+
 Some implementation details
 ---------------------------
 
diff --git a/doc/design-ssh-ports.rst b/doc/design-ssh-ports.rst
new file mode 100644 (file)
index 0000000..42c6c30
--- /dev/null
@@ -0,0 +1,69 @@
+================================================
+Design for supporting custom SSH ports for nodes
+================================================
+
+.. contents:: :depth: 4
+
+This design document describes the intention of supporting running SSH servers
+on nodes with non-standard port numbers.
+
+
+Current state and shortcomings
+==============================
+
+All SSH deamons are expected to be running on the default port 22. It has been
+requested by Ganeti users (`Issue 235`_) to allow SSH daemons run on
+non-standard ports as well.
+
+.. _`Issue 235`: https://code.google.com/p/ganeti/issues/detail?id=235
+
+
+Proposed Changes
+================
+
+Allow users to configure groups with custom SSH ports. All nodes in such a
+group will then be using its configured SSH port.
+
+The configuration will be on the group level only as we expect all nodes in a group
+to have identical configurations.
+
+Users will be responsible for configuring the SSH daemons on machines before
+adding them as nodes to a group with a non-standard port number, or when
+modifying the port number of an existing group. Ganeti will not update SSH
+configuration by itself.
+
+
+Implementation Details
+======================
+
+We must ensure that all operations that use SSH will use custom ports as configured. This includes:
+
+- gnt-cluster verify
+- gnt-cluster renew-crypto
+- gnt-cluster upgrade
+- gnt-node add
+- gnt-instance console
+
+Configuration Changes
+~~~~~~~~~~~~~~~~~~~~~
+
+The node group *ndparams* will get an additional integer valued parameter *ssh_port*.
+
+Upgrades/downgrades
+~~~~~~~~~~~~~~~~~~~
+
+To/from version 2.10
+--------------------
+
+During upgrade from 2.10, the default value 22 will be supplemented.
+
+During downgrade to 2.10 the downgrading script will check that there are no
+configured ports other than 22 (because this would result in a broken cluster)
+and then will remove the corresponding key/value pairs from the configuration.
+
+Future versions
+---------------
+
+For future versions the up/downgrade operation will need to know the configured
+SSH ports. Because all daemons are stopped during the process, it will be
+necessary to include SSH ports in *ssconf*.
index 1d62a0b..7a02407 100644 (file)
@@ -209,6 +209,9 @@ following actions.
 - If the action is a downgrade to the previous minor version, the
   configuration is downgraded now, using ``cfgupgrade --downgrade``.
 
+- If the action is downgrade, any version-specific additional downgrade
+  actions are carried out.
+
 - The ``${sysconfdir}/ganeti/lib`` and ``${sysconfdir}/ganeti/share``
   symbolic links are updated.
 
@@ -281,16 +284,19 @@ disappear, once :doc:`design-optables` is implemented, as then the
 undrain will then be restricted to filters by gnt-upgrade.
 
 
-Requirement of opcode backwards compatibility
-==============================================
+Requirement of job queue update
+===============================
 
 Since for upgrades we only pause jobs and do not fully drain the
 queue, we need to be able to transform the job queue into a queue for
-the new version. The way this is achieved is by keeping the
-serialization format backwards compatible. This is in line with
-current practice that opcodes do not change between versions, and at
-most new fields are added. Whenever we add a new field to an opcode,
-we will make sure that the deserialization function will provide a
-default value if the field is not present.
-
-
+the new version. The preferred way to obtain this is to keep the
+serialization format backwards compatible, i.e., only adding new
+opcodes and new optional fields.
+
+However, even with soft drain, no job is running at the moment `cfgupgrade`
+is running. So, if we change the queue representation, including the
+representation of individual opcodes in any way, `cfgupgrade` will also
+modify the queue accordingly. In a jobs-as-processes world, pausing a job
+will be implemented in such a way that the corresponding process stops after
+finishing the current opcode, and a new process is created if and when the
+job is unpaused again.
index 7f7414e..a7d53c3 100644 (file)
@@ -3,3 +3,4 @@ NODED_ARGS=""
 MASTERD_ARGS=""
 RAPI_ARGS=""
 CONFD_ARGS=""
+LUXID_ARGS=""
index f28adbe..5497997 100644 (file)
@@ -3,3 +3,4 @@ NODED_ARGS="-d"
 MASTERD_ARGS="-d"
 RAPI_ARGS="-d"
 CONFD_ARGS="-d"
+LUXID_ARGS="-d"
index 3fe1b3b..7a0e6e4 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.10
+Documents Ganeti version 2.11
 
 .. contents::
 
index 456d5ee..1a12975 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.10
+Documents Ganeti version 2.11
 
 .. contents::
 
index a8206de..7ba7a32 100644 (file)
@@ -76,6 +76,7 @@ and draft versions (which are either incomplete or not implemented).
    design-2.8.rst
    design-2.9.rst
    design-2.10.rst
+   design-2.11.rst
 
 Draft designs
 -------------
@@ -99,15 +100,20 @@ Draft designs
    design-file-based-storage.rst
    design-hroller.rst
    design-hotplug.rst
+   design-internal-shutdown.rst
+   design-kvmd.rst
    design-linuxha.rst
    design-lu-generated-jobs.rst
    design-monitoring-agent.rst
    design-multi-reloc.rst
+   design-multi-version-tests.rst
    design-network.rst
    design-node-add.rst
+   design-node-security.rst
    design-oob.rst
    design-openvswitch.rst
    design-opportunistic-locking.rst
+   design-os.rst
    design-ovf-support.rst
    design-partitioned
    design-performance-tests.rst
@@ -116,6 +122,7 @@ Draft designs
    design-reason-trail.rst
    design-restricted-commands.rst
    design-shared-storage.rst
+   design-ssh-ports.rst
    design-storagetypes.rst
    design-upgrade.rst
    design-virtual-clusters.rst
index 3b47bec..4f91c6b 100644 (file)
@@ -378,11 +378,13 @@ avoid potential deadlocks_ in low memory scenarios.
 .. _deadlocks: http://tracker.ceph.com/issues/3076
 
 To initialize a cluster with support for this feature, use a command
-such as::
+like the following. Note, that you possibly need to follow the more
+general installation instructions before invoking this command (see
+`Initializing the cluster`_ ).
 
   $ gnt-cluster init \
-      --enabled-disk-templates rbd \
-      --ipolicy-disk-templates rbd \
+      --enabled-disk-templates=rbd \
+      --ipolicy-disk-templates=rbd \
       --enabled-hypervisors=kvm \
       -D rbd:access=userspace
 
@@ -418,6 +420,29 @@ only need to specify the IP addresses of the RADOS Cluster monitors.
 For more information, please see the `Ceph Docs
 <http://ceph.newdream.net/docs/latest/>`_
 
+Installing Gluster
+++++++++++++++++++
+
+For Gluster integration, Ganeti requires that ``mount.glusterfs`` is
+installed on each and every node. On Debian Wheezy and newer, you can
+satisfy this requirement with the ``glusterfs-client`` package; see
+`this guide
+<http://gluster.org/community/documentation/index.php/Gluster_3.2:_Installing_the_Gluster_Native_Client>`_
+for details.
+
+KVM userspace access
+~~~~~~~~~~~~~~~~~~~~
+
+If your cluster uses a sufficiently new version of KVM (you will need at
+least QEMU 1.3 with Gluster support compiled in), you can take advantage
+of KVM's native support for gluster in order to have better performance
+and avoid potential deadlocks in low memory scenarios.
+
+Please be aware that QEMU 1.3 was released in December 3, 2012, and as
+such this feature is not available out of the box in any distribution
+older than Ubuntu 13.04; this excludes Ubuntu 12.04 LTS and Debian
+Wheezy.
+
 Other required software
 +++++++++++++++++++++++
 
index a6317d6..f8a9588 100644 (file)
@@ -87,6 +87,9 @@ destination-related options default to the source value (e.g. setting
   When moving a single instance: Secondary node on destination cluster.
 ``--dest-disk-template``
   Disk template to use after the move. Can be used to change disk templates.
+``--compress``
+  Compression mode to use during the instance move. This mode has to be
+  supported by both the source and the destination cluster.
 ``--iallocator``
   Iallocator for creating instance on destination cluster.
 ``--hypervisor-parameters``/``--backend-parameters``/``--os-parameters``/``--net``
index 81969e9..b6c36f0 100644 (file)
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.10
+Documents Ganeti version 2.11
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
@@ -33,8 +33,8 @@ changes:
 - Communication between nodes is encrypted using SSL/TLS. A common key
   and certificate combo is shared between all nodes of the cluster.  At
   this time, no CA is used.
-- The Ganeti node daemon will accept RPC requests from any host within
-  the cluster with the correct certificate, and the operations it will
+- The Ganeti node daemon will accept RPC requests from any host that is
+  master candidate within the cluster, and the operations it will
   do as a result of these requests are:
 
   - running commands under the ``/etc/ganeti/hooks`` directory
@@ -42,7 +42,8 @@ changes:
   - overwrite a defined list of files on the host
 
 As you can see, as soon as a node is joined, it becomes equal to all
-other nodes in the cluster, and the security of the cluster is
+other nodes in the cluster wrt to SSH and equal to all non-master
+candidate nodes wrt to RPC, and the security of the cluster is
 determined by the weakest node.
 
 Note that only the SSH key will allow other machines to run any command
@@ -100,10 +101,13 @@ The SSH traffic is protected (after the initial login to a new node) by
 the cluster-wide shared SSH key.
 
 RPC communication between the master and nodes is protected using
-SSL/TLS encryption. Both the client and the server must have the
-cluster-wide shared SSL/TLS certificate and verify it when establishing
-the connection by comparing fingerprints. We decided not to use a CA to
-simplify the key handling.
+SSL/TLS encryption. The server must have must have the cluster-wide
+shared SSL/TLS certificate. When acting as a client, the nodes use an
+individual SSL/TLS certificate. On incoming requests, the server checks
+whether the client's certificate is that of a master candidate by
+verifying its finterprint to a list of known master candidate
+certificates. We decided not to use a CA (yet) to simplify the key
+handling.
 
 The DRBD traffic is not protected by encryption, as DRBD does not
 support this. It's therefore recommended to implement host-level
index 09306b5..667ba40 100644 (file)
@@ -4,4 +4,5 @@
 @GNTRAPIUSER@
 @GNTCONFDUSER@
 @GNTLUXIDUSER@
+@GNTLUXIDGROUP@
 @GNTMONDUSER@
index 6b11c03..adecf1f 100644 (file)
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.10
+Documents Ganeti version 2.11
 
 .. contents::
 
index d4c6b49..ef386d2 100644 (file)
@@ -64,6 +64,7 @@ from ganeti import errors
 from ganeti import utils
 from ganeti import ssh
 from ganeti import hypervisor
+from ganeti.hypervisor import hv_base
 from ganeti import constants
 from ganeti.storage import bdev
 from ganeti.storage import drbd
@@ -292,29 +293,18 @@ def JobQueuePurge():
   _CleanDirectory(pathutils.JOB_QUEUE_ARCHIVE_DIR)
 
 
-def GetMasterInfo():
-  """Returns master information.
+def GetMasterNodeName():
+  """Returns the master node name.
 
-  This is an utility function to compute master information, either
-  for consumption here or from the node daemon.
-
-  @rtype: tuple
-  @return: master_netdev, master_ip, master_name, primary_ip_family,
-    master_netmask
+  @rtype: string
+  @return: name of the master node
   @raise RPCFail: in case of errors
 
   """
   try:
-    cfg = _GetConfig()
-    master_netdev = cfg.GetMasterNetdev()
-    master_ip = cfg.GetMasterIP()
-    master_netmask = cfg.GetMasterNetmask()
-    master_node = cfg.GetMasterNode()
-    primary_ip_family = cfg.GetPrimaryIPFamily()
+    return _GetConfig().GetMasterNode()
   except errors.ConfigurationError, err:
     _Fail("Cluster configuration incomplete: %s", err, exc=True)
-  return (master_netdev, master_ip, master_node, primary_ip_family,
-          master_netmask)
 
 
 def RunLocalHooks(hook_opcode, hooks_path, env_builder_fn):
@@ -577,6 +567,7 @@ def LeaveCluster(modify_ssh_setup):
 
   utils.StopDaemon(constants.CONFD)
   utils.StopDaemon(constants.MOND)
+  utils.StopDaemon(constants.KVMD)
 
   # Raise a custom exception (handled in ganeti-noded)
   raise errors.QuitGanetiException(True, "Shutdown scheduled")
@@ -785,6 +776,7 @@ _STORAGE_TYPE_INFO_FN = {
   constants.ST_FILE: _GetFileStorageSpaceInfo,
   constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
   constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
+  constants.ST_SHARED_FILE: None,
   constants.ST_RADOS: None,
 }
 
@@ -936,7 +928,25 @@ def _VerifyNodeInfo(what, vm_capable, result, all_hvparams):
     result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)
 
 
-def VerifyNode(what, cluster_name, all_hvparams):
+def _VerifyClientCertificate(cert_file=pathutils.NODED_CLIENT_CERT_FILE):
+  """Verify the existance and validity of the client SSL certificate.
+
+  """
+  create_cert_cmd = "gnt-cluster renew-crypto --new-node-certificates"
+  if not os.path.exists(cert_file):
+    return (constants.CV_ERROR,
+            "The client certificate does not exist. Run '%s' to create"
+            " client certificates for all nodes." % create_cert_cmd)
+
+  (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))
+
+
+def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
   """Verify the status of the local node.
 
   Based on the input L{what} parameter, various checks are done on the
@@ -964,6 +974,11 @@ def VerifyNode(what, cluster_name, all_hvparams):
   @param cluster_name: the cluster's name
   @type all_hvparams: dict of dict of strings
   @param all_hvparams: a dictionary mapping hypervisor names to hvparams
+  @type node_groups: a dict of strings
+  @param node_groups: node _names_ mapped to their group uuids (it's enough to
+      have only those nodes that are in `what["nodelist"]`)
+  @type groups_cfg: a dict of dict of strings
+  @param groups_cfg: a dictionary mapping group uuids to their configuration
   @rtype: dict
   @return: a dictionary with the same keys as the input dict, and
       values representing the result of the checks
@@ -984,6 +999,9 @@ def VerifyNode(what, cluster_name, all_hvparams):
       dict((vcluster.MakeVirtualPath(key), value)
            for (key, value) in fingerprints.items())
 
+  if constants.NV_CLIENT_CERT in what:
+    result[constants.NV_CLIENT_CERT] = _VerifyClientCertificate()
+
   if constants.NV_NODELIST in what:
     (nodes, bynode) = what[constants.NV_NODELIST]
 
@@ -999,7 +1017,12 @@ def VerifyNode(what, cluster_name, all_hvparams):
     # Try to contact all nodes
     val = {}
     for node in nodes:
-      success, message = _GetSshRunner(cluster_name).VerifyNodeHostname(node)
+      params = groups_cfg.get(node_groups.get(node))
+      ssh_port = params["ndparams"].get(constants.ND_SSH_PORT)
+      logging.debug("Ssh port %s (None = default) for node %s",
+                    str(ssh_port), node)
+      success, message = _GetSshRunner(cluster_name). \
+                            VerifyNodeHostname(node, ssh_port)
       if not success:
         val[node] = message
 
@@ -1158,6 +1181,109 @@ def VerifyNode(what, cluster_name, all_hvparams):
   return result
 
 
+def GetCryptoTokens(token_requests):
+  """Perform actions on the node's cryptographic tokens.
+
+  Token types can be 'ssl' or 'ssh'. So far only some actions are implemented
+  for 'ssl'. Action 'get' returns the digest of the public client ssl
+  certificate. Action 'create' creates a new client certificate and private key
+  and also returns the digest of the certificate. The third parameter of a
+  token request are optional parameters for the actions, so far only the
+  filename is supported.
+
+  @type token_requests: list of tuples of (string, string, dict), where the
+    first string is in constants.CRYPTO_TYPES, the second in
+    constants.CRYPTO_ACTIONS. The third parameter is a dictionary of string
+    to string.
+  @param token_requests: list of requests of cryptographic tokens and actions
+    to perform on them. The actions come with a dictionary of options.
+  @rtype: list of tuples (string, string)
+  @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:
+    if token_type not in constants.CRYPTO_TYPES:
+      raise errors.ProgrammerError("Token type '%s' not supported." %
+                                   token_type)
+    if action not in constants.CRYPTO_ACTIONS:
+      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()))
+  return tokens
+
+
+def EnsureDaemon(daemon_name, run):
+  """Ensures the given daemon is running or stopped.
+
+  @type daemon_name: string
+  @param daemon_name: name of the daemon (e.g., constants.KVMD)
+
+  @type run: bool
+  @param run: whether to start or stop the daemon
+
+  @rtype: bool
+  @return: 'True' if daemon successfully started/stopped,
+           'False' otherwise
+
+  """
+  allowed_daemons = [constants.KVMD]
+
+  if daemon_name not in allowed_daemons:
+    fn = lambda _: False
+  elif run:
+    fn = utils.EnsureDaemon
+  else:
+    fn = utils.StopDaemon
+
+  return fn(daemon_name)
+
+
 def GetBlockDevSizes(devices):
   """Return the size of the given block devices
 
@@ -1333,15 +1459,11 @@ def GetInstanceListForHypervisor(hname, hvparams=None,
     - instance2.example.com
 
   """
-  results = []
   try:
-    hv = get_hv_fn(hname)
-    names = hv.ListInstances(hvparams=hvparams)
-    results.extend(names)
+    return get_hv_fn(hname).ListInstances(hvparams=hvparams)
   except errors.HypervisorError, err:
     _Fail("Error enumerating instances (hypervisor %s): %s",
           hname, err, exc=True)
-  return results
 
 
 def GetInstanceList(hypervisor_list, all_hvparams=None,
@@ -1384,7 +1506,7 @@ def GetInstanceInfo(instance, hname, hvparams=None):
   @rtype: dict
   @return: dictionary with the following keys:
       - memory: memory size of instance (int)
-      - state: xen state of instance (string)
+      - state: state of instance (HvInstanceState)
       - time: cpu time of instance (float)
       - vcpus: the number of vcpus (int)
 
@@ -1416,7 +1538,7 @@ def GetInstanceMigratable(instance):
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
   iname = instance.name
-  if iname not in hyper.ListInstances(instance.hvparams):
+  if iname not in hyper.ListInstances(hvparams=instance.hvparams):
     _Fail("Instance %s is not running", iname)
 
   for idx in range(len(instance.disks)):
@@ -1447,7 +1569,6 @@ def GetAllInstancesInfo(hypervisor_list, all_hvparams):
 
   """
   output = {}
-
   for hname in hypervisor_list:
     hvparams = all_hvparams[hname]
     iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
@@ -1472,6 +1593,59 @@ def GetAllInstancesInfo(hypervisor_list, all_hvparams):
   return output
 
 
+def GetInstanceConsoleInfo(instance_param_dict,
+                           get_hv_fn=hypervisor.GetHypervisor):
+  """Gather data about the console access of a set of instances of this node.
+
+  This function assumes that the caller already knows which instances are on
+  this node, by calling a function such as L{GetAllInstancesInfo} or
+  L{GetInstanceList}.
+
+  For every instance, a large amount of configuration data needs to be
+  provided to the hypervisor interface in order to receive the console
+  information. Whether this could or should be cut down can be discussed.
+  The information is provided in a dictionary indexed by instance name,
+  allowing any number of instance queries to be done.
+
+  @type instance_param_dict: dict of string to tuple of dictionaries, where the
+    dictionaries represent: L{objects.Instance}, L{objects.Node},
+    L{objects.NodeGroup}, HvParams, BeParams
+  @param instance_param_dict: mapping of instance name to parameters necessary
+    for console information retrieval
+
+  @rtype: dict
+  @return: dictionary of instance: data, with data having the following keys:
+      - instance: instance name
+      - kind: console kind
+      - message: used with kind == CONS_MESSAGE, indicates console to be
+                 unavailable, supplies error message
+      - host: host to connect to
+      - port: port to use
+      - user: user for login
+      - command: the command, broken into parts as an array
+      - display: unknown, potentially unused?
+
+  """
+
+  output = {}
+  for inst_name in instance_param_dict:
+    instance = instance_param_dict[inst_name]["instance"]
+    pnode = instance_param_dict[inst_name]["node"]
+    group = instance_param_dict[inst_name]["group"]
+    hvparams = instance_param_dict[inst_name]["hvParams"]
+    beparams = instance_param_dict[inst_name]["beParams"]
+
+    instance = objects.Instance.FromDict(instance)
+    pnode = objects.Node.FromDict(pnode)
+    group = objects.NodeGroup.FromDict(group)
+
+    h = get_hv_fn(instance.hypervisor)
+    output[inst_name] = h.GetInstanceConsole(instance, pnode, group,
+                                             hvparams, beparams).ToDict()
+
+  return output
+
+
 def _InstanceLogName(kind, os_name, instance, component):
   """Compute the OS log filename for a given instance and operation.
 
@@ -1671,6 +1845,18 @@ def _GatherAndLinkBlockDevs(instance):
   return block_devices
 
 
+def _IsInstanceUserDown(instance_info):
+  return instance_info and \
+      "state" in instance_info and \
+      hv_base.HvInstanceState.IsShutdown(instance_info["state"])
+
+
+def _GetInstanceInfo(instance):
+  """Helper function L{GetInstanceInfo}"""
+  return GetInstanceInfo(instance.name, instance.hypervisor,
+                         hvparams=instance.hvparams)
+
+
 def StartInstance(instance, startup_paused, reason, store_reason=True):
   """Start an instance.
 
@@ -1685,11 +1871,10 @@ def StartInstance(instance, startup_paused, reason, store_reason=True):
   @rtype: None
 
   """
-  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
-                                                   instance.hvparams)
+  instance_info = _GetInstanceInfo(instance)
 
-  if instance.name in running_instances:
-    logging.info("Instance %s already running, not starting", instance.name)
+  if instance_info and not _IsInstanceUserDown(instance_info):
+    logging.info("Instance '%s' already running, not starting", instance.name)
     return
 
   try:
@@ -1721,12 +1906,10 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
   @rtype: None
 
   """
-  hv_name = instance.hypervisor
-  hyper = hypervisor.GetHypervisor(hv_name)
-  iname = instance.name
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
-  if instance.name not in hyper.ListInstances(instance.hvparams):
-    logging.info("Instance %s not running, doing nothing", iname)
+  if not _GetInstanceInfo(instance):
+    logging.info("Instance '%s' not running, doing nothing", instance.name)
     return
 
   class _TryShutdown(object):
@@ -1734,7 +1917,7 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
       self.tried_once = False
 
     def __call__(self):
-      if iname not in hyper.ListInstances(instance.hvparams):
+      if not _GetInstanceInfo(instance):
         return
 
       try:
@@ -1742,12 +1925,12 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
         if store_reason:
           _StoreInstReasonTrail(instance.name, reason)
       except errors.HypervisorError, err:
-        if iname not in hyper.ListInstances(instance.hvparams):
-          # if the instance is no longer existing, consider this a
-          # success and go to cleanup
+        # if the instance is no longer existing, consider this a
+        # success and go to cleanup
+        if not _GetInstanceInfo(instance):
           return
 
-        _Fail("Failed to stop instance %s: %s", iname, err)
+        _Fail("Failed to stop instance '%s': %s", instance.name, err)
 
       self.tried_once = True
 
@@ -1757,27 +1940,27 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
     utils.Retry(_TryShutdown(), 5, timeout)
   except utils.RetryTimeout:
     # the shutdown did not succeed
-    logging.error("Shutdown of '%s' unsuccessful, forcing", iname)
+    logging.error("Shutdown of '%s' unsuccessful, forcing", instance.name)
 
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
-      if iname in hyper.ListInstances(instance.hvparams):
-        # only raise an error if the instance still exists, otherwise
-        # the error could simply be "instance ... unknown"!
-        _Fail("Failed to force stop instance %s: %s", iname, err)
+      # only raise an error if the instance still exists, otherwise
+      # the error could simply be "instance ... unknown"!
+      if _GetInstanceInfo(instance):
+        _Fail("Failed to force stop instance '%s': %s", instance.name, err)
 
     time.sleep(1)
 
-    if iname in hyper.ListInstances(instance.hvparams):
-      _Fail("Could not shutdown instance %s even by destroy", iname)
+    if _GetInstanceInfo(instance):
+      _Fail("Could not shutdown instance '%s' even by destroy", instance.name)
 
   try:
     hyper.CleanupInstance(instance.name)
   except errors.HypervisorError, err:
     logging.warning("Failed to execute post-shutdown cleanup step: %s", err)
 
-  _RemoveBlockDevLinks(iname, instance.disks)
+  _RemoveBlockDevLinks(instance.name, instance.disks)
 
 
 def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
@@ -1803,18 +1986,18 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   @rtype: None
 
   """
-  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
-                                                   instance.hvparams)
-
-  if instance.name not in running_instances:
-    _Fail("Cannot reboot instance %s that is not running", instance.name)
+  # TODO: this is inconsistent with 'StartInstance' and 'InstanceShutdown'
+  # because those functions simply 'return' on error whereas this one
+  # raises an exception with '_Fail'
+  if not _GetInstanceInfo(instance):
+    _Fail("Cannot reboot instance '%s' that is not running", instance.name)
 
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
   if reboot_type == constants.INSTANCE_REBOOT_SOFT:
     try:
       hyper.RebootInstance(instance)
     except errors.HypervisorError, err:
-      _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
+      _Fail("Failed to soft reboot instance '%s': %s", instance.name, err)
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
       InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
@@ -1822,9 +2005,9 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
       _StoreInstReasonTrail(instance.name, reason)
       return result
     except errors.HypervisorError, err:
-      _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
+      _Fail("Failed to hard reboot instance '%s': %s", instance.name, err)
   else:
-    _Fail("Invalid reboot_type received: %s", reboot_type)
+    _Fail("Invalid reboot_type received: '%s'", reboot_type)
 
 
 def InstanceBalloonMemory(instance, memory):
@@ -1838,7 +2021,7 @@ def InstanceBalloonMemory(instance, memory):
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
-  running = hyper.ListInstances(instance.hvparams)
+  running = hyper.ListInstances(hvparams=instance.hvparams)
   if instance.name not in running:
     logging.info("Instance %s is not running, cannot balloon", instance.name)
     return
@@ -2532,50 +2715,6 @@ def BlockdevGetdimensions(disks):
   return result
 
 
-def BlockdevExport(disk, dest_node_ip, dest_path, cluster_name):
-  """Export a block device to a remote node.
-
-  @type disk: L{objects.Disk}
-  @param disk: the description of the disk to export
-  @type dest_node_ip: str
-  @param dest_node_ip: the destination node IP to export to
-  @type dest_path: str
-  @param dest_path: the destination path on the target node
-  @type cluster_name: str
-  @param cluster_name: the cluster name, needed for SSH hostalias
-  @rtype: None
-
-  """
-  real_disk = _OpenRealBD(disk)
-
-  # the block size on the read dd is 1MiB to match our units
-  expcmd = utils.BuildShellCmd("set -e; set -o pipefail; "
-                               "dd if=%s bs=1048576 count=%s",
-                               real_disk.dev_path, str(disk.size))
-
-  # we set here a smaller block size as, due to ssh buffering, more
-  # than 64-128k will mostly ignored; we use nocreat to fail if the
-  # device is not already there or we pass a wrong path; we use
-  # notrunc to no attempt truncate on an LV device; we use oflag=dsync
-  # to not buffer too much memory; this means that at best, we flush
-  # every 64k, which will not be very fast
-  destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536"
-                                " oflag=dsync", dest_path)
-
-  remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node_ip,
-                                                   constants.SSH_LOGIN_USER,
-                                                   destcmd)
-
-  # all commands have been checked, so we're safe to combine them
-  command = "|".join([expcmd, utils.ShellQuoteArgs(remotecmd)])
-
-  result = utils.RunCmd(["bash", "-c", command])
-
-  if result.failed:
-    _Fail("Disk copy command '%s' returned error: %s"
-          " output: %s", command, result.fail_reason, result.output)
-
-
 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
   """Write a file to the filesystem.
 
@@ -2942,7 +3081,7 @@ def OSEnvironment(instance, inst_os, debug=0):
         instance.hvparams[constants.HV_DISK_TYPE]
     if disk.dev_type in constants.DTS_BLOCK:
       result["DISK_%d_BACKEND_TYPE" % idx] = "block"
-    elif disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
+    elif disk.dev_type in constants.DTS_FILEBASED:
       result["DISK_%d_BACKEND_TYPE" % idx] = \
         "file:%s" % disk.logical_id[0]
 
@@ -3612,7 +3751,7 @@ def CreateX509Certificate(validity, cryptodir=pathutils.CRYPTO_KEYS_DIR):
   """
   (key_pem, cert_pem) = \
     utils.GenerateSelfSignedX509Cert(netutils.Hostname.GetSysName(),
-                                     min(validity, _MAX_SSL_CERT_VALIDITY))
+                                     min(validity, _MAX_SSL_CERT_VALIDITY), 1)
 
   cert_dir = tempfile.mkdtemp(dir=cryptodir,
                               prefix="x509-%s-" % utils.TimestampForFilename())
@@ -3705,16 +3844,11 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
     real_disk = _OpenRealBD(disk)
 
     if mode == constants.IEM_IMPORT:
-      # we set here a smaller block size as, due to transport buffering, more
-      # than 64-128k will mostly ignored; we use nocreat to fail if the device
-      # is not already there or we pass a wrong path; we use notrunc to no
-      # attempt truncate on an LV device; we use oflag=dsync to not buffer too
-      # much memory; this means that at best, we flush every 64k, which will
-      # not be very fast
-      suffix = utils.BuildShellCmd(("| dd of=%s conv=nocreat,notrunc"
-                                    " bs=%s oflag=dsync"),
-                                    real_disk.dev_path,
-                                    str(64 * 1024))
+      # we use nocreat to fail if the device is not already there or we pass a
+      # wrong path; we use notrunc to no attempt truncate on an LV device
+      suffix = utils.BuildShellCmd("| dd of=%s conv=nocreat,notrunc bs=%s",
+                                   real_disk.dev_path,
+                                   str(1024 * 1024)) # 1 MB
 
     elif mode == constants.IEM_EXPORT:
       # the block size on the read dd is 1MiB to match our units
@@ -4490,13 +4624,15 @@ class IAllocatorRunner(object):
 
   """
   @staticmethod
-  def Run(name, idata):
+  def Run(name, idata, ial_params):
     """Run an iallocator script.
 
     @type name: str
     @param name: the iallocator script name
     @type idata: str
     @param idata: the allocator input data
+    @type ial_params: list
+    @param ial_params: the iallocator parameters
 
     @rtype: tuple
     @return: two element tuple of:
@@ -4513,7 +4649,7 @@ class IAllocatorRunner(object):
     try:
       os.write(fd, idata)
       os.close(fd)
-      result = utils.RunCmd([alloc_script, fin_name])
+      result = utils.RunCmd([alloc_script, fin_name] + ial_params)
       if result.failed:
         _Fail("iallocator module '%s' failed: %s, output '%s'",
               name, result.fail_reason, result.output)
index 0c101e2..bb9b473 100644 (file)
@@ -40,7 +40,7 @@ import time
 import tempfile
 
 from ganeti.cmdlib import cluster
-from ganeti import rpc
+import ganeti.rpc.node as rpc
 from ganeti import ssh
 from ganeti import utils
 from ganeti import errors
@@ -56,6 +56,7 @@ from ganeti import netutils
 from ganeti import luxi
 from ganeti import jstore
 from ganeti import pathutils
+from ganeti import runtime
 
 
 # ec_id for InitConfig's temporary reservation manager
@@ -100,6 +101,7 @@ def GenerateHmacKey(file_name):
                   backup=True)
 
 
+# pylint: disable=R0913
 def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
                           new_confd_hmac_key, new_cds,
                           rapi_cert_pem=None, spice_cert_pem=None,
@@ -143,34 +145,26 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
   @param hmackey_file: optional override of the hmac key file path
 
   """
+  # pylint: disable=R0913
   # noded SSL certificate
-  cluster_cert_exists = os.path.exists(nodecert_file)
-  if new_cluster_cert or not cluster_cert_exists:
-    if cluster_cert_exists:
-      utils.CreateBackup(nodecert_file)
-
-    logging.debug("Generating new cluster certificate at %s", nodecert_file)
-    utils.GenerateSelfSignedSslCert(nodecert_file)
+  utils.GenerateNewSslCert(
+    new_cluster_cert, nodecert_file, 1,
+    "Generating new cluster certificate at %s" % nodecert_file)
 
   # 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)
     GenerateHmacKey(hmackey_file)
 
-  # RAPI
-  rapi_cert_exists = os.path.exists(rapicert_file)
-
   if rapi_cert_pem:
     # Assume rapi_pem contains a valid PEM-formatted certificate and key
     logging.debug("Writing RAPI certificate at %s", rapicert_file)
     utils.WriteFile(rapicert_file, data=rapi_cert_pem, backup=True)
 
-  elif new_rapi_cert or not rapi_cert_exists:
-    if rapi_cert_exists:
-      utils.CreateBackup(rapicert_file)
-
-    logging.debug("Generating new RAPI certificate at %s", rapicert_file)
-    utils.GenerateSelfSignedSslCert(rapicert_file)
+  else:
+    utils.GenerateNewSslCert(
+      new_rapi_cert, rapicert_file, 1,
+      "Generating new RAPI certificate at %s" % rapicert_file)
 
   # SPICE
   spice_cert_exists = os.path.exists(spicecert_file)
@@ -189,7 +183,7 @@ def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_spice_cert,
 
     logging.debug("Generating new self-signed SPICE certificate at %s",
                   spicecert_file)
-    (_, cert_pem) = utils.GenerateSelfSignedSslCert(spicecert_file)
+    (_, cert_pem) = utils.GenerateSelfSignedSslCert(spicecert_file, 1)
 
     # Self-signed certificate -> the public certificate is also the CA public
     # certificate
@@ -267,10 +261,11 @@ def _WaitForMasterDaemon():
                              " %s seconds" % _DAEMON_READY_TIMEOUT)
 
 
-def _WaitForSshDaemon(hostname, port, family):
+def _WaitForSshDaemon(hostname, port):
   """Wait for SSH daemon to become responsive.
 
   """
+  family = ssconf.SimpleStore().GetPrimaryIPFamily()
   hostip = netutils.GetHostname(name=hostname, family=family).ip
 
   def _CheckSshDaemon():
@@ -289,7 +284,8 @@ def _WaitForSshDaemon(hostname, port, family):
 
 
 def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
-                    use_cluster_key, ask_key, strict_host_check, data):
+                    use_cluster_key, ask_key, strict_host_check,
+                    port, data):
   """Runs a command to configure something on a remote machine.
 
   @type cluster_name: string
@@ -308,6 +304,8 @@ def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
   @param ask_key: See L{ssh.SshRunner.BuildCmd}
   @type strict_host_check: bool
   @param strict_host_check: See L{ssh.SshRunner.BuildCmd}
+  @type port: int
+  @param port: The SSH port of the remote machine or None for the default
   @param data: JSON-serializable input data for script (passed to stdin)
 
   """
@@ -344,15 +342,17 @@ def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
                       os.path.join(pathutils.SYSCONFDIR, "ganeti/share")]])
   all_cmds.append(cmd)
 
-  family = ssconf.SimpleStore().GetPrimaryIPFamily()
-  srun = ssh.SshRunner(cluster_name,
-                       ipv6=(family == netutils.IP6Address.family))
+  if port is None:
+    port = netutils.GetDaemonPort(constants.SSH)
+
+  srun = ssh.SshRunner(cluster_name)
   scmd = srun.BuildCmd(node, constants.SSH_LOGIN_USER,
                        utils.ShellQuoteArgs(
                            utils.ShellCombineCommands(all_cmds)),
                        batch=False, ask_key=ask_key, quiet=False,
                        strict_host_check=strict_host_check,
-                       use_cluster_key=use_cluster_key)
+                       use_cluster_key=use_cluster_key,
+                       port=port)
 
   tempfh = tempfile.TemporaryFile()
   try:
@@ -367,7 +367,7 @@ def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
     raise errors.OpExecError("Command '%s' failed: %s" %
                              (result.cmd, result.fail_reason))
 
-  _WaitForSshDaemon(node, netutils.GetDaemonPort(constants.SSH), family)
+  _WaitForSshDaemon(node, port)
 
 
 def _InitFileStorageDir(file_storage_dir):
@@ -414,13 +414,16 @@ 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'
+  @param file_disk_template: a disk template whose storage type is 'ST_FILE' or
+      'ST_SHARED_FILE'
   @rtype: string
   @returns: the name of the actual file storage directory
 
   """
-  assert (file_disk_template in
-          utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE))
+  assert (file_disk_template in utils.storage.GetDiskTemplatesOfStorageTypes(
+            constants.ST_FILE, constants.ST_SHARED_FILE
+         ))
+
   if file_storage_dir is None:
     file_storage_dir = default_dir
   if not acceptance_fn:
@@ -471,6 +474,20 @@ def _PrepareSharedFileStorage(
       init_fn=init_fn, acceptance_fn=acceptance_fn)
 
 
+def _PrepareGlusterStorage(
+    enabled_disk_templates, file_storage_dir, init_fn=_InitFileStorageDir,
+    acceptance_fn=None):
+  """Checks if gluster storage is enabled and inits the dir.
+
+  @see: C{_PrepareFileBasedStorage}
+
+  """
+  return _PrepareFileBasedStorage(
+      enabled_disk_templates, file_storage_dir,
+      pathutils.DEFAULT_GLUSTER_STORAGE_DIR, constants.DT_GLUSTER,
+      init_fn=init_fn, acceptance_fn=acceptance_fn)
+
+
 def _InitCheckEnabledDiskTemplates(enabled_disk_templates):
   """Checks the sanity of the enabled disk templates.
 
@@ -534,22 +551,30 @@ def _InitCheckDrbdHelper(drbd_helper, drbd_enabled):
 
 def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                 master_netmask, master_netdev, file_storage_dir,
-                shared_file_storage_dir, candidate_pool_size, secondary_ip=None,
+                shared_file_storage_dir, gluster_storage_dir,
+                candidate_pool_size, secondary_ip=None,
                 vg_name=None, beparams=None, nicparams=None, ndparams=None,
                 hvparams=None, diskparams=None, enabled_hypervisors=None,
                 modify_etc_hosts=True, modify_ssh_setup=True,
                 maintain_node_health=False, drbd_helper=None, uid_pool=None,
-                default_iallocator=None, primary_ip_version=None, ipolicy=None,
+                default_iallocator=None, default_iallocator_params=None,
+                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):
+                hv_state=None, disk_state=None, enabled_disk_templates=None,
+                enabled_user_shutdown=False):
   """Initialise the cluster.
 
   @type candidate_pool_size: int
   @param candidate_pool_size: master candidate pool size
+
   @type enabled_disk_templates: list of string
   @param enabled_disk_templates: list of disk_templates to be used in this
     cluster
 
+  @type enabled_user_shutdown: bool
+  @param enabled_user_shutdown: whether user shutdown is enabled cluster
+                                wide
+
   """
   # TODO: complete the docstring
   if config.ConfigWriter.IsCluster():
@@ -750,13 +775,22 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
       raise errors.OpPrereqError("Invalid default iallocator script '%s'"
                                  " specified" % default_iallocator,
                                  errors.ECODE_INVAL)
-  elif constants.HTOOLS:
-    # htools was enabled at build-time, we default to it
+  else:
+    # default to htools
     if utils.FindFile(constants.IALLOC_HAIL,
                       constants.IALLOCATOR_SEARCH_PATH,
                       os.path.isfile):
       default_iallocator = constants.IALLOC_HAIL
 
+  # check if we have all the users we need
+  try:
+    runtime.GetEnts()
+  except errors.ConfigurationError, err:
+    raise errors.OpPrereqError("Required system user/group missing: %s" %
+                               err, errors.ECODE_ENVIRON)
+
+  candidate_certs = {}
+
   now = time.time()
 
   # init of cluster config file
@@ -774,6 +808,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     cluster_name=clustername.name,
     file_storage_dir=file_storage_dir,
     shared_file_storage_dir=shared_file_storage_dir,
+    gluster_storage_dir=gluster_storage_dir,
     enabled_hypervisors=enabled_hypervisors,
     beparams={constants.PP_DEFAULT: beparams},
     nicparams={constants.PP_DEFAULT: nicparams},
@@ -789,6 +824,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     maintain_node_health=maintain_node_health,
     drbd_usermode_helper=drbd_helper,
     default_iallocator=default_iallocator,
+    default_iallocator_params=default_iallocator_params,
     primary_ip_family=ipcls.family,
     prealloc_wipe_disks=prealloc_wipe_disks,
     use_external_mip_script=use_external_mip_script,
@@ -796,6 +832,8 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     hv_state_static=hv_state,
     disk_state_static=disk_state,
     enabled_disk_templates=enabled_disk_templates,
+    candidate_certs=candidate_certs,
+    enabled_user_shutdown=enabled_user_shutdown,
     )
   master_node_config = objects.Node(name=hostname.name,
                                     primary_ip=hostname.ip,
@@ -908,7 +946,7 @@ def FinalizeClusterDestroy(master_uuid):
                     " the node: %s", msg)
 
 
-def SetupNodeDaemon(opts, cluster_name, node):
+def SetupNodeDaemon(opts, cluster_name, node, ssh_port):
   """Add a node to the cluster.
 
   This function must be called before the actual opcode, and will ssh
@@ -917,6 +955,7 @@ def SetupNodeDaemon(opts, cluster_name, node):
 
   @param cluster_name: the cluster name
   @param node: the name of the new node
+  @param ssh_port: the SSH port of the new node
 
   """
   data = {
@@ -929,7 +968,8 @@ def SetupNodeDaemon(opts, cluster_name, node):
 
   RunNodeSetupCmd(cluster_name, node, pathutils.NODE_DAEMON_SETUP,
                   opts.debug, opts.verbose,
-                  True, opts.ssh_key_check, opts.ssh_key_check, data)
+                  True, opts.ssh_key_check, opts.ssh_key_check,
+                  ssh_port, data)
 
   _WaitForNodeDaemon(node)
 
@@ -1122,10 +1162,6 @@ def GatherMasterVotes(node_names):
   knows, whereas the number of entries in the list could be different
   (if some nodes vote for another master).
 
-  We remove ourselves from the list since we know that (bugs aside)
-  since we use the same source for configuration information for both
-  backend and boostrap, we'll always vote for ourselves.
-
   @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
@@ -1133,15 +1169,10 @@ def GatherMasterVotes(node_names):
   @return: list of (node, votes)
 
   """
-  myself = netutils.Hostname.GetSysName()
-  try:
-    node_names.remove(myself)
-  except ValueError:
-    pass
   if not node_names:
-    # no nodes left (eventually after removing myself)
+    # no nodes
     return []
-  results = rpc.BootstrapRunner().call_master_info(node_names)
+  results = rpc.BootstrapRunner().call_master_node_name(node_names)
   if not isinstance(results, dict):
     # this should not happen (unless internal error in rpc)
     logging.critical("Can't complete rpc call, aborting master startup")
@@ -1149,27 +1180,18 @@ def GatherMasterVotes(node_names):
   votes = {}
   for node_name in results:
     nres = results[node_name]
-    data = nres.payload
     msg = nres.fail_msg
-    fail = False
+
     if msg:
       logging.warning("Error contacting node %s: %s", node_name, msg)
-      fail = True
-    # for now we accept both length 3, 4 and 5 (data[3] is primary ip version
-    # and data[4] is the master netmask)
-    elif not isinstance(data, (tuple, list)) or len(data) < 3:
-      logging.warning("Invalid data received from node %s: %s",
-                      node_name, data)
-      fail = True
-    if fail:
-      if None not in votes:
-        votes[None] = 0
-      votes[None] += 1
-      continue
-    master_node = data[2]
-    if master_node not in votes:
-      votes[master_node] = 0
-    votes[master_node] += 1
+      node = None
+    else:
+      node = nres.payload
+
+    if node not in votes:
+      votes[node] = 1
+    else:
+      votes[node] += 1
 
   vote_list = [v for v in votes.items()]
   # sort first on number of votes then on name, since we want None
index 8731ad3..12188cc 100644 (file)
@@ -67,6 +67,7 @@ from ganeti import rapi
 from ganeti import luxi
 from ganeti import objects
 from ganeti import http
+from ganeti import pathutils
 
 import ganeti.rapi.rlib2 # pylint: disable=W0611
 import ganeti.rapi.connector # pylint: disable=W0611
@@ -103,7 +104,7 @@ COMMON_PARAM_NAMES = _GetCommonParamNames()
 #: Namespace for evaluating expressions
 EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors,
                rlib2=rapi.rlib2, luxi=luxi, rapi=rapi, objects=objects,
-               http=http)
+               http=http, pathutils=pathutils)
 
 # Constants documentation for man pages
 CV_ECODES_DOC = "ecodes"
index 02ff3fd..2eee68b 100644 (file)
@@ -45,9 +45,8 @@ from ganeti import utils
 from ganeti import errors
 from ganeti import constants
 from ganeti import opcodes
-from ganeti import luxi
-from ganeti import ssconf
-from ganeti import rpc
+import ganeti.rpc.errors as rpcerr
+import ganeti.rpc.node as rpc
 from ganeti import ssh
 from ganeti import compat
 from ganeti import netutils
@@ -55,6 +54,8 @@ from ganeti import qlang
 from ganeti import objects
 from ganeti import pathutils
 
+from ganeti.runtime import (GetClient)
+
 from optparse import (OptionParser, TitledHelpFormatter,
                       Option, OptionValueError)
 
@@ -91,6 +92,7 @@ __all__ = [
   "EARLY_RELEASE_OPT",
   "ENABLED_HV_OPT",
   "ENABLED_DISK_TEMPLATES_OPT",
+  "ENABLED_USER_SHUTDOWN_OPT",
   "ERROR_CODES_OPT",
   "FAILURE_ONLY_OPT",
   "FIELDS_OPT",
@@ -104,6 +106,7 @@ __all__ = [
   "GATEWAY6_OPT",
   "GLOBAL_FILEDIR_OPT",
   "HID_OS_OPT",
+  "GLOBAL_GLUSTER_FILEDIR_OPT",
   "GLOBAL_SHARED_FILEDIR_OPT",
   "HOTPLUG_OPT",
   "HOTPLUG_IF_POSSIBLE_OPT",
@@ -112,6 +115,7 @@ __all__ = [
   "HYPERVISOR_OPT",
   "IALLOCATOR_OPT",
   "DEFAULT_IALLOCATOR_OPT",
+  "DEFAULT_IALLOCATOR_PARAMS_OPT",
   "IDENTIFY_DEFAULTS_OPT",
   "IGNORE_CONSIST_OPT",
   "IGNORE_ERRORS_OPT",
@@ -133,6 +137,7 @@ __all__ = [
   "NETWORK_OPT",
   "NETWORK6_OPT",
   "NEW_CLUSTER_CERT_OPT",
+  "NEW_NODE_CERT_OPT",
   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
   "NEW_CONFD_HMAC_KEY_OPT",
   "NEW_RAPI_CERT_OPT",
@@ -151,7 +156,6 @@ __all__ = [
   "NOIPCHECK_OPT",
   "NO_INSTALL_OPT",
   "NONAMECHECK_OPT",
-  "NOLVM_STORAGE_OPT",
   "NOMODIFY_ETCHOSTS_OPT",
   "NOMODIFY_SSH_SETUP_OPT",
   "NONICS_OPT",
@@ -187,6 +191,7 @@ __all__ = [
   "REMOVE_RESERVED_IPS_OPT",
   "REMOVE_UIDS_OPT",
   "RESERVED_LVS_OPT",
+  "RQL_OPT",
   "RUNTIME_MEM_OPT",
   "ROMAN_OPT",
   "SECONDARY_IP_OPT",
@@ -195,6 +200,8 @@ __all__ = [
   "SEP_OPT",
   "SHOWCMD_OPT",
   "SHOW_MACHINE_OPT",
+  "COMPRESS_OPT",
+  "TRANSPORT_COMPRESSION_OPT",
   "SHUTDOWN_TIMEOUT_OPT",
   "SINGLE_NODE_OPT",
   "SPECS_CPU_COUNT_OPT",
@@ -241,6 +248,7 @@ __all__ = [
   "GenericListFields",
   "GetClient",
   "GetOnlineNodes",
+  "GetNodesSshPorts",
   "JobExecutor",
   "JobSubmittedException",
   "ParseTimespec",
@@ -927,6 +935,15 @@ DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
                                     default=None, type="string",
                                     completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
 
+DEFAULT_IALLOCATOR_PARAMS_OPT = cli_option("--default-iallocator-params",
+                                           dest="default_iallocator_params",
+                                           help="iallocator template"
+                                           " parameters, in the format"
+                                           " template:option=value,"
+                                           " option=value,...",
+                                           type="keyval",
+                                           default={})
+
 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
                     metavar="<os>",
                     completion_suggest=OPT_COMPL_ONE_OS)
@@ -1255,11 +1272,6 @@ ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
                              type="bool", default=None, metavar=_YORNO,
                              help="Set the allocatable flag on a volume")
 
-NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
-                               help="Disable support for lvm based instances"
-                               " (cluster-wide)",
-                               action="store_false", default=True)
-
 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
                             dest="enabled_hypervisors",
                             help="Comma-separated list of hypervisors",
@@ -1271,6 +1283,12 @@ ENABLED_DISK_TEMPLATES_OPT = cli_option("--enabled-disk-templates",
                                              "disk templates",
                                         type="string", default=None)
 
+ENABLED_USER_SHUTDOWN_OPT = cli_option("--user-shutdown",
+                                       default=None,
+                                       dest="enabled_user_shutdown",
+                                       help="Whether user shutdown is enabled",
+                                       type="bool")
+
 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
                             type="keyval", default={},
                             help="NIC parameters")
@@ -1279,6 +1297,10 @@ CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
                          dest="candidate_pool_size", type="int",
                          help="Set the candidate pool size")
 
+RQL_OPT = cli_option("--max-running-jobs", dest="max_running_jobs",
+                     type="int", help="Set the maximal number of jobs to "
+                                      "run simultaneously")
+
 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
                          help=("Enables LVM and specifies the volume group"
                                " name (cluster-wide) for disk allocation"
@@ -1334,6 +1356,15 @@ GLOBAL_SHARED_FILEDIR_OPT = cli_option(
   pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
   metavar="SHAREDDIR", default=None)
 
+GLOBAL_GLUSTER_FILEDIR_OPT = cli_option(
+  "--gluster-storage-dir",
+  dest="gluster_storage_dir",
+  help="Specify the default directory (cluster-wide) for mounting Gluster"
+  " file systems [%s]" %
+  pathutils.DEFAULT_GLUSTER_STORAGE_DIR,
+  metavar="GLUSTERDIR",
+  default=pathutils.DEFAULT_GLUSTER_STORAGE_DIR)
+
 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
                                    help="Don't modify %s" % pathutils.ETC_HOSTS,
                                    action="store_false", default=True)
@@ -1375,6 +1406,16 @@ TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
                          help="Maximum time to wait")
 
+COMPRESS_OPT = cli_option("--compress", dest="compress",
+                          default=constants.IEC_NONE,
+                          help="The compression mode to use",
+                          choices=list(constants.IEC_ALL))
+
+TRANSPORT_COMPRESSION_OPT = \
+    cli_option("--transport-compression", dest="transport_compression",
+               default=constants.IEC_NONE, choices=list(constants.IEC_ALL),
+               help="The compression mode to use during transport")
+
 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
                                   dest="shutdown_timeout", type="int",
                                   default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
@@ -1397,6 +1438,10 @@ NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
                                   default=False, action="store_true",
                                   help="Generate a new cluster certificate")
 
+NEW_NODE_CERT_OPT = cli_option(
+  "--new-node-certificates", dest="new_node_cert", default=False,
+  action="store_true", help="Generate new node certificates (for all nodes)")
+
 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
                            default=None,
                            help="File containing new RAPI certificate")
@@ -2389,51 +2434,6 @@ def SetGenericOpcodeOpts(opcode_list, options):
     _InitReasonTrail(op, options)
 
 
-def GetClient(query=False):
-  """Connects to the a luxi socket and returns a client.
-
-  @type query: boolean
-  @param query: this signifies that the client will only be
-      used for queries; if the build-time parameter
-      enable-split-queries is enabled, then the client will be
-      connected to the query socket instead of the masterd socket
-
-  """
-  override_socket = os.getenv(constants.LUXI_OVERRIDE, "")
-  if override_socket:
-    if override_socket == constants.LUXI_OVERRIDE_MASTER:
-      address = pathutils.MASTER_SOCKET
-    elif override_socket == constants.LUXI_OVERRIDE_QUERY:
-      address = pathutils.QUERY_SOCKET
-    else:
-      address = override_socket
-  elif query and constants.ENABLE_SPLIT_QUERY:
-    address = pathutils.QUERY_SOCKET
-  else:
-    address = None
-  # TODO: Cache object?
-  try:
-    client = luxi.Client(address=address)
-  except luxi.NoMasterError:
-    ss = ssconf.SimpleStore()
-
-    # Try to read ssconf file
-    try:
-      ss.GetMasterNode()
-    except errors.ConfigurationError:
-      raise errors.OpPrereqError("Cluster not initialized or this machine is"
-                                 " not part of a cluster",
-                                 errors.ECODE_INVAL)
-
-    master, myself = ssconf.GetMasterAndMyself(ss=ss)
-    if master != myself:
-      raise errors.OpPrereqError("This is not the master node, please connect"
-                                 " to node '%s' and rerun the command" %
-                                 master, errors.ECODE_INVAL)
-    raise
-  return client
-
-
 def FormatError(err):
   """Return a formatted error message for a given error.
 
@@ -2492,7 +2492,7 @@ def FormatError(err):
     obuf.write("Parameter Error: %s" % msg)
   elif isinstance(err, errors.ParameterError):
     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
-  elif isinstance(err, luxi.NoMasterError):
+  elif isinstance(err, rpcerr.NoMasterError):
     if err.args[0] == pathutils.MASTER_SOCKET:
       daemon = "the master daemon"
     elif err.args[0] == pathutils.QUERY_SOCKET:
@@ -2501,16 +2501,16 @@ def FormatError(err):
       daemon = "socket '%s'" % str(err.args[0])
     obuf.write("Cannot communicate with %s.\nIs the process running"
                " and listening for connections?" % daemon)
-  elif isinstance(err, luxi.TimeoutError):
+  elif isinstance(err, rpcerr.TimeoutError):
     obuf.write("Timeout while talking to the master daemon. Jobs might have"
                " been submitted and will continue to run even if the call"
                " timed out. Useful commands in this situation are \"gnt-job"
                " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
     obuf.write(msg)
-  elif isinstance(err, luxi.PermissionError):
+  elif isinstance(err, rpcerr.PermissionError):
     obuf.write("It seems you don't have permissions to connect to the"
                " master daemon.\nPlease retry as a different user.")
-  elif isinstance(err, luxi.ProtocolError):
+  elif isinstance(err, rpcerr.ProtocolError):
     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
                "%s" % msg)
   elif isinstance(err, errors.JobLost):
@@ -2595,7 +2595,7 @@ def GenericMain(commands, override=None, aliases=None,
 
   try:
     result = func(options, args)
-  except (errors.GenericError, luxi.ProtocolError,
+  except (errors.GenericError, rpcerr.ProtocolError,
           JobSubmittedException), err:
     result, err_msg = FormatError(err)
     logging.exception("Error during command processing")
@@ -2738,6 +2738,9 @@ def GenericInstanceCreate(mode, opts, args):
       else:
         raise errors.OpPrereqError("Missing size or adoption source for"
                                    " disk %d" % didx, errors.ECODE_INVAL)
+      if constants.IDISK_SPINDLES in ddict:
+        ddict[constants.IDISK_SPINDLES] = int(ddict[constants.IDISK_SPINDLES])
+
       disks[didx] = ddict
 
   if opts.tags is not None:
@@ -2757,6 +2760,7 @@ def GenericInstanceCreate(mode, opts, args):
     src_path = None
     no_install = opts.no_install
     identify_defaults = False
+    compress = constants.IEC_NONE
   elif mode == constants.INSTANCE_IMPORT:
     start = False
     os_type = None
@@ -2765,6 +2769,7 @@ def GenericInstanceCreate(mode, opts, args):
     src_path = opts.src_dir
     no_install = None
     identify_defaults = opts.identify_defaults
+    compress = opts.compress
   else:
     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
 
@@ -2790,6 +2795,7 @@ def GenericInstanceCreate(mode, opts, args):
                                 force_variant=force_variant,
                                 src_node=src_node,
                                 src_path=src_path,
+                                compress=compress,
                                 tags=tags,
                                 no_install=no_install,
                                 identify_defaults=identify_defaults,
@@ -2803,7 +2809,8 @@ class _RunWhileClusterStoppedHelper(object):
   """Helper class for L{RunWhileClusterStopped} to simplify state management
 
   """
-  def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
+  def __init__(self, feedback_fn, cluster_name, master_node,
+               online_nodes, ssh_ports):
     """Initializes this class.
 
     @type feedback_fn: callable
@@ -2814,12 +2821,15 @@ class _RunWhileClusterStoppedHelper(object):
     @param master_node Master node name
     @type online_nodes: list
     @param online_nodes: List of names of online nodes
+    @type ssh_ports: list
+    @param ssh_ports: List of SSH ports of online nodes
 
     """
     self.feedback_fn = feedback_fn
     self.cluster_name = cluster_name
     self.master_node = master_node
     self.online_nodes = online_nodes
+    self.ssh_ports = dict(zip(online_nodes, ssh_ports))
 
     self.ssh = ssh.SshRunner(self.cluster_name)
 
@@ -2842,7 +2852,8 @@ class _RunWhileClusterStoppedHelper(object):
       result = utils.RunCmd(cmd)
     else:
       result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
-                            utils.ShellQuoteArgs(cmd))
+                            utils.ShellQuoteArgs(cmd),
+                            port=self.ssh_ports[node_name])
 
     if result.failed:
       errmsg = ["Failed to run command %s" % result.cmd]
@@ -2908,19 +2919,23 @@ def RunWhileClusterStopped(feedback_fn, fn, *args):
 
   # This ensures we're running on the master daemon
   cl = GetClient()
+  # Query client
+  qcl = GetClient(query=True)
 
   (cluster_name, master_node) = \
     cl.QueryConfigValues(["cluster_name", "master_node"])
 
-  online_nodes = GetOnlineNodes([], cl=cl)
+  online_nodes = GetOnlineNodes([], cl=qcl)
+  ssh_ports = GetNodesSshPorts(online_nodes, qcl)
 
   # Don't keep a reference to the client. The master daemon will go away.
   del cl
+  del qcl
 
   assert master_node in online_nodes
 
   return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
-                                       online_nodes).Call(fn, *args)
+                                       online_nodes, ssh_ports).Call(fn, *args)
 
 
 def GenerateTable(headers, fields, separator, data,
@@ -3526,7 +3541,7 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
 
   """
   if cl is None:
-    cl = GetClient()
+    cl = GetClient(query=True)
 
   qfilter = []
 
@@ -3577,6 +3592,22 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
   return map(fn, online)
 
 
+def GetNodesSshPorts(nodes, cl):
+  """Retrieves SSH ports of given nodes.
+
+  @param nodes: the names of nodes
+  @type nodes: a list of strings
+  @param cl: a client to use for the query
+  @type cl: L{Client}
+  @return: the list of SSH ports corresponding to the nodes
+  @rtype: a list of tuples
+  """
+  return map(lambda t: t[0],
+             cl.QueryNodes(names=nodes,
+                           fields=["ndp/ssh_port"],
+                           use_locking=False))
+
+
 def _ToStream(stream, txt, *args):
   """Write a message to a stream, bypassing the logging system
 
@@ -3743,7 +3774,7 @@ class JobExecutor(object):
         ToStderr("Job %s%s has been archived, cannot check its result",
                  jid, self._IfName(name, " for %s"))
         success = False
-      except (errors.GenericError, luxi.ProtocolError), err:
+      except (errors.GenericError, rpcerr.ProtocolError), err:
         _, job_result = FormatError(err)
         success = False
         # the error message will always be shown, verbose or not
index 81f7c1b..379a883 100644 (file)
@@ -101,6 +101,7 @@ def ExportInstance(opts, args):
 
   op = opcodes.OpBackupExport(instance_name=args[0],
                               target_node=opts.node,
+                              compress=opts.transport_compression,
                               shutdown=opts.shutdown,
                               shutdown_timeout=opts.shutdown_timeout,
                               remove_instance=opts.remove_instance,
@@ -141,6 +142,7 @@ import_opts = [
   IDENTIFY_DEFAULTS_OPT,
   SRC_DIR_OPT,
   SRC_NODE_OPT,
+  COMPRESS_OPT,
   IGNORE_IPOLICY_OPT,
   ]
 
@@ -157,9 +159,9 @@ commands = {
     "Lists all available fields for exports"),
   "export": (
     ExportInstance, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT,
-     REMOVE_INSTANCE_OPT, IGNORE_REMOVE_FAILURES_OPT, DRY_RUN_OPT,
-     PRIORITY_OPT] + SUBMIT_OPTS,
+    [FORCE_OPT, SINGLE_NODE_OPT, TRANSPORT_COMPRESSION_OPT, NOSHUTDOWN_OPT,
+     SHUTDOWN_TIMEOUT_OPT, REMOVE_INSTANCE_OPT, IGNORE_REMOVE_FAILURES_OPT,
+     DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "-n <target_node> [opts...] <name>",
     "Exports an instance to an image"),
   "import": (
index 84cd0f8..cc4b990 100644 (file)
@@ -88,19 +88,6 @@ _EPO_PING_TIMEOUT = 1 # 1 second
 _EPO_REACHABLE_TIMEOUT = 15 * 60 # 15 minutes
 
 
-def _CheckNoLvmStorageOptDeprecated(opts):
-  """Checks if the legacy option '--no-lvm-storage' is used.
-
-  """
-  if not opts.lvm_storage:
-    ToStderr("The option --no-lvm-storage is no longer supported. If you want"
-             " to disable lvm-based storage cluster-wide, use the option"
-             " --enabled-disk-templates to disable all of these lvm-base disk "
-             "  templates: %s" %
-             utils.CommaJoin(constants.DTS_LVM))
-    return 1
-
-
 def _InitEnabledDiskTemplates(opts):
   """Initialize the list of enabled disk templates.
 
@@ -166,9 +153,6 @@ def InitCluster(opts, args):
   @return: the desired exit code
 
   """
-  if _CheckNoLvmStorageOptDeprecated(opts):
-    return 1
-
   enabled_disk_templates = _InitEnabledDiskTemplates(opts)
 
   try:
@@ -290,6 +274,13 @@ def InitCluster(opts, args):
 
   hv_state = dict(opts.hv_state)
 
+  default_ialloc_params = opts.default_iallocator_params
+
+  if opts.enabled_user_shutdown:
+    enabled_user_shutdown = True
+  else:
+    enabled_user_shutdown = False
+
   bootstrap.InitCluster(cluster_name=args[0],
                         secondary_ip=opts.secondary_ip,
                         vg_name=vg_name,
@@ -298,6 +289,7 @@ def InitCluster(opts, args):
                         master_netdev=master_netdev,
                         file_storage_dir=opts.file_storage_dir,
                         shared_file_storage_dir=opts.shared_file_storage_dir,
+                        gluster_storage_dir=opts.gluster_storage_dir,
                         enabled_hypervisors=hvlist,
                         hvparams=hvparams,
                         beparams=beparams,
@@ -312,12 +304,14 @@ def InitCluster(opts, args):
                         drbd_helper=drbd_helper,
                         uid_pool=uid_pool,
                         default_iallocator=opts.default_iallocator,
+                        default_iallocator_params=default_ialloc_params,
                         primary_ip_version=primary_ip_version,
                         prealloc_wipe_disks=opts.prealloc_wipe_disks,
                         use_external_mip_script=external_ip_setup_script,
                         hv_state=hv_state,
                         disk_state=disk_state,
                         enabled_disk_templates=enabled_disk_templates,
+                        enabled_user_shutdown=enabled_user_shutdown,
                         )
   op = opcodes.OpClusterPostInit()
   SubmitOpCode(op, opts=opts)
@@ -538,6 +532,9 @@ def ShowClusterConfig(opts, args):
       ("candidate pool size",
        compat.TryToRoman(result["candidate_pool_size"],
                          convert=opts.roman_integers)),
+      ("maximal number of jobs running simultaneously",
+       compat.TryToRoman(result["max_running_jobs"],
+                         convert=opts.roman_integers)),
       ("master netdev", result["master_netdev"]),
       ("master netmask", result["master_netmask"]),
       ("use external master IP address setup script",
@@ -547,9 +544,12 @@ def ShowClusterConfig(opts, args):
       ("drbd usermode helper", result["drbd_usermode_helper"]),
       ("file storage path", result["file_storage_dir"]),
       ("shared file storage path", result["shared_file_storage_dir"]),
+      ("gluster storage path", result["gluster_storage_dir"]),
       ("maintenance of node health", result["maintain_node_health"]),
       ("uid pool", uidpool.FormatUidPool(result["uid_pool"])),
       ("default instance allocator", result["default_iallocator"]),
+      ("default instance allocator parameters",
+       result["default_iallocator_params"]),
       ("primary ip version", result["primary_ip_version"]),
       ("preallocation wipe disks", result["prealloc_wipe_disks"]),
       ("OS search path", utils.CommaJoin(pathutils.OS_SEARCH_PATH)),
@@ -557,6 +557,7 @@ def ShowClusterConfig(opts, args):
        utils.CommaJoin(pathutils.ES_SEARCH_PATH)),
       ("enabled disk templates",
        utils.CommaJoin(result["enabled_disk_templates"])),
+      ("enabled user shutdown", result["enabled_user_shutdown"]),
       ]),
 
     ("Default node parameters",
@@ -598,17 +599,22 @@ def ClusterCopyFile(opts, args):
                                errors.ECODE_INVAL)
 
   cl = GetClient()
+  qcl = GetClient(query=True)
+  try:
+    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
 
-  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
-
-  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
-                           secondary_ips=opts.use_replication_network,
-                           nodegroup=opts.nodegroup)
+    results = GetOnlineNodes(nodes=opts.nodes, cl=qcl, filter_master=True,
+                             secondary_ips=opts.use_replication_network,
+                             nodegroup=opts.nodegroup)
+    ports = GetNodesSshPorts(opts.nodes, qcl)
+  finally:
+    cl.Close()
+    qcl.Close()
 
   srun = ssh.SshRunner(cluster_name)
-  for node in results:
-    if not srun.CopyFileToNode(node, filename):
-      ToStderr("Copy of file %s to node %s failed", filename, node)
+  for (node, port) in zip(results, ports):
+    if not srun.CopyFileToNode(node, port, filename):
+      ToStderr("Copy of file %s to node %s:%d failed", filename, node, port)
 
   return 0
 
@@ -624,10 +630,12 @@ def RunClusterCommand(opts, args):
 
   """
   cl = GetClient()
+  qcl = GetClient(query=True)
 
   command = " ".join(args)
 
-  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl, nodegroup=opts.nodegroup)
+  nodes = GetOnlineNodes(nodes=opts.nodes, cl=qcl, nodegroup=opts.nodegroup)
+  ports = GetNodesSshPorts(nodes, qcl)
 
   cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
                                                     "master_node"])
@@ -639,8 +647,8 @@ def RunClusterCommand(opts, args):
     nodes.remove(master_node)
     nodes.append(master_node)
 
-  for name in nodes:
-    result = srun.Run(name, constants.SSH_LOGIN_USER, command)
+  for (name, port) in zip(nodes, ports):
+    result = srun.Run(name, constants.SSH_LOGIN_USER, command, port=port)
 
     if opts.failure_only and result.exit_code == constants.EXIT_SUCCESS:
       # Do not output anything for successful commands
@@ -903,7 +911,7 @@ def _ReadAndVerifyCert(cert_filename, verify_private_key=False):
 def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
                  rapi_cert_filename, new_spice_cert, spice_cert_filename,
                  spice_cacert_filename, new_confd_hmac_key, new_cds,
-                 cds_filename, force):
+                 cds_filename, force, new_node_cert):
   """Renews cluster certificates, keys and secrets.
 
   @type new_cluster_cert: bool
@@ -927,6 +935,8 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
   @param cds_filename: Path to file containing new cluster domain secret
   @type force: bool
   @param force: Whether to ask user for confirmation
+  @type new_node_cert: string
+  @param new_node_cert: Whether to generate new node certificates
 
   """
   if new_rapi_cert and rapi_cert_filename:
@@ -979,6 +989,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
 
   def _RenewCryptoInner(ctx):
     ctx.feedback_fn("Updating certificates and keys")
+    # Note: the node certificate will be generated in the LU
     bootstrap.GenerateClusterCrypto(new_cluster_cert,
                                     new_rapi_cert,
                                     new_spice_cert,
@@ -1009,16 +1020,22 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
 
     if files_to_copy:
       for node_name in ctx.nonmaster_nodes:
-        ctx.feedback_fn("Copying %s to %s" %
-                        (", ".join(files_to_copy), node_name))
+        port = ctx.ssh_ports[node_name]
+        ctx.feedback_fn("Copying %s to %s:%d" %
+                        (", ".join(files_to_copy), node_name, port))
         for file_name in files_to_copy:
-          ctx.ssh.CopyFileToNode(node_name, file_name)
+          ctx.ssh.CopyFileToNode(node_name, port, file_name)
 
   RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
 
   ToStdout("All requested certificates and keys have been replaced."
            " Running \"gnt-cluster verify\" now is recommended.")
 
+  if new_node_cert:
+    cl = GetClient()
+    renew_op = opcodes.OpClusterRenewCrypto()
+    SubmitOpCode(renew_op, cl=cl)
+
   return 0
 
 
@@ -1035,7 +1052,8 @@ def RenewCrypto(opts, args):
                       opts.new_confd_hmac_key,
                       opts.new_cluster_domain_secret,
                       opts.cluster_domain_secret,
-                      opts.force)
+                      opts.force,
+                      opts.new_node_cert)
 
 
 def _GetEnabledDiskTemplates(opts):
@@ -1096,11 +1114,13 @@ def SetClusterParams(opts, args):
           opts.beparams or opts.nicparams or
           opts.ndparams or opts.diskparams or
           opts.candidate_pool_size is not None or
+          opts.max_running_jobs is not None or
           opts.uid_pool is not None or
           opts.maintain_node_health is not None or
           opts.add_uids is not None or
           opts.remove_uids is not None or
           opts.default_iallocator is not None or
+          opts.default_iallocator_params or
           opts.reserved_lvs is not None or
           opts.master_netdev is not None or
           opts.master_netmask is not None or
@@ -1116,13 +1136,11 @@ def SetClusterParams(opts, args):
           opts.ipolicy_spindle_ratio is not None or
           opts.modify_etc_hosts is not None or
           opts.file_storage_dir is not None or
-          opts.shared_file_storage_dir is not None):
+          opts.shared_file_storage_dir is not None or
+          opts.enabled_user_shutdown is not None):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
-  if _CheckNoLvmStorageOptDeprecated(opts):
-    return 1
-
   enabled_disk_templates = _GetEnabledDiskTemplates(opts)
   vg_name = _GetVgName(opts, enabled_disk_templates)
 
@@ -1212,12 +1230,14 @@ def SetClusterParams(opts, args):
     diskparams=diskparams,
     ipolicy=ipolicy,
     candidate_pool_size=opts.candidate_pool_size,
+    max_running_jobs=opts.max_running_jobs,
     maintain_node_health=mnh,
     modify_etc_hosts=opts.modify_etc_hosts,
     uid_pool=uid_pool,
     add_uids=add_uids,
     remove_uids=remove_uids,
     default_iallocator=opts.default_iallocator,
+    default_iallocator_params=opts.default_iallocator_params,
     prealloc_wipe_disks=opts.prealloc_wipe_disks,
     master_netdev=opts.master_netdev,
     master_netmask=opts.master_netmask,
@@ -1229,6 +1249,7 @@ def SetClusterParams(opts, args):
     force=opts.force,
     file_storage_dir=opts.file_storage_dir,
     shared_file_storage_dir=opts.shared_file_storage_dir,
+    enabled_user_shutdown=opts.enabled_user_shutdown,
     )
   SubmitOrSend(op, opts)
   return 0
@@ -1551,7 +1572,7 @@ def _EpoOff(opts, node_list, inst_map):
     return constants.EXIT_FAILURE
 
 
-def Epo(opts, args, cl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
+def Epo(opts, args, qcl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
         _confirm_fn=ConfirmOperation,
         _stdout_fn=ToStdout, _stderr_fn=ToStderr):
   """EPO operations.
@@ -1570,18 +1591,19 @@ def Epo(opts, args, cl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
     _stderr_fn("Arguments in combination with --all are not allowed")
     return constants.EXIT_FAILURE
 
-  if cl is None:
-    cl = GetClient()
+  if qcl is None:
+    # Query client
+    qcl = GetClient(query=True)
 
   if opts.groups:
     node_query_list = \
-      itertools.chain(*cl.QueryGroups(args, ["node_list"], False))
+      itertools.chain(*qcl.QueryGroups(args, ["node_list"], False))
   else:
     node_query_list = args
 
-  result = cl.QueryNodes(node_query_list, ["name", "master", "pinst_list",
-                                           "sinst_list", "powered", "offline"],
-                         False)
+  result = qcl.QueryNodes(node_query_list, ["name", "master", "pinst_list",
+                                            "sinst_list", "powered", "offline"],
+                          False)
 
   all_nodes = map(compat.fst, result)
   node_list = []
@@ -1889,6 +1911,39 @@ def _UpgradeBeforeConfigurationChange(versionstring):
   return (True, rollback)
 
 
+def _VersionSpecificDowngrade():
+  """
+  Perform any additional downrade tasks that are version specific
+  and need to be done just after the configuration downgrade. This
+  function needs to be idempotent, so that it can be redone if the
+  downgrade procedure gets interrupted after changing the
+  configuration.
+
+  Note that this function has to be reset with every version bump.
+
+  @return: True upon success
+  """
+  ToStdout("Performing version-specific downgrade tasks.")
+
+  ToStdout("...removing client certificates ssconf file")
+  ssconffile = ssconf.SimpleStore().KeyToFilename(
+    constants.SS_MASTER_CANDIDATES_CERTS)
+  badnodes = _VerifyCommand(["rm", "-f", ssconffile])
+  if badnodes:
+    ToStderr("Warning: failed to clean up ssconf on %s."
+             % (", ".join(badnodes),))
+    return False
+
+  ToStdout("...removing client certificates")
+  badnodes = _VerifyCommand(["rm", "-f", pathutils.NODED_CLIENT_CERT_FILE])
+  if badnodes:
+    ToStderr("Warning: failed to clean up certificates on %s."
+             % (", ".join(badnodes),))
+    return False
+
+  return True
+
+
 def _SwitchVersionAndConfig(versionstring, downgrade):
   """
   Switch to the new Ganeti version and change the configuration,
@@ -1908,6 +1963,11 @@ def _SwitchVersionAndConfig(versionstring, downgrade):
     ToStdout("Downgrading configuration")
     if not _RunCommandAndReport([pathutils.CFGUPGRADE, "--downgrade", "-f"]):
       return (False, rollback)
+    # Note: version specific downgrades need to be done before switching
+    # binaries, so that we still have the knowledgeable binary if the downgrade
+    # process gets interrupted at this point.
+    if not _VersionSpecificDowngrade():
+      return (False, rollback)
 
   # Configuration change is the point of no return. From then onwards, it is
   # safer to push through the up/dowgrade than to try to roll it back.
@@ -2097,13 +2157,15 @@ commands = {
     InitCluster, [ArgHost(min=1, max=1)],
     [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
      HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, MASTER_NETMASK_OPT,
-     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT,
-     NOMODIFY_SSH_SETUP_OPT, SECONDARY_IP_OPT, VG_NAME_OPT,
-     MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT,
-     DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
-     NODE_PARAMS_OPT, GLOBAL_SHARED_FILEDIR_OPT, USE_EXTERNAL_MIP_SCRIPT,
-     DISK_PARAMS_OPT, HV_STATE_OPT, DISK_STATE_OPT, ENABLED_DISK_TEMPLATES_OPT,
-     IPOLICY_STD_SPECS_OPT] + INSTANCE_POLICY_OPTS + SPLIT_ISPECS_OPTS,
+     NIC_PARAMS_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
+     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT,
+     DRBD_HELPER_OPT, DEFAULT_IALLOCATOR_OPT, DEFAULT_IALLOCATOR_PARAMS_OPT,
+     PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT,
+     GLOBAL_SHARED_FILEDIR_OPT, USE_EXTERNAL_MIP_SCRIPT, DISK_PARAMS_OPT,
+     HV_STATE_OPT, DISK_STATE_OPT, ENABLED_DISK_TEMPLATES_OPT,
+     ENABLED_USER_SHUTDOWN_OPT, IPOLICY_STD_SPECS_OPT,
+     GLOBAL_GLUSTER_FILEDIR_OPT]
+     + INSTANCE_POLICY_OPTS + SPLIT_ISPECS_OPTS,
     "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
   "destroy": (
     DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
@@ -2177,14 +2239,16 @@ commands = {
   "modify": (
     SetClusterParams, ARGS_NONE,
     [FORCE_OPT,
-     BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
-     MASTER_NETMASK_OPT, NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT,
-     MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT,
-     DRBD_HELPER_OPT, DEFAULT_IALLOCATOR_OPT,
-     RESERVED_LVS_OPT, DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT,
-     NODE_PARAMS_OPT, USE_EXTERNAL_MIP_SCRIPT, DISK_PARAMS_OPT, HV_STATE_OPT,
-     DISK_STATE_OPT] + SUBMIT_OPTS +
-     [ENABLED_DISK_TEMPLATES_OPT, IPOLICY_STD_SPECS_OPT, MODIFY_ETCHOSTS_OPT] +
+     BACKEND_OPT, CP_SIZE_OPT, RQL_OPT,
+     ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
+     MASTER_NETMASK_OPT, NIC_PARAMS_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
+     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
+     DEFAULT_IALLOCATOR_OPT, DEFAULT_IALLOCATOR_PARAMS_OPT, RESERVED_LVS_OPT,
+     DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT,
+     USE_EXTERNAL_MIP_SCRIPT, DISK_PARAMS_OPT, HV_STATE_OPT, DISK_STATE_OPT] +
+     SUBMIT_OPTS +
+     [ENABLED_DISK_TEMPLATES_OPT, IPOLICY_STD_SPECS_OPT, MODIFY_ETCHOSTS_OPT,
+      ENABLED_USER_SHUTDOWN_OPT] +
      INSTANCE_POLICY_OPTS + [GLOBAL_FILEDIR_OPT, GLOBAL_SHARED_FILEDIR_OPT],
     "[opts...]",
     "Alters the parameters of the cluster"),
@@ -2193,7 +2257,8 @@ commands = {
     [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
      NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
      NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT,
-     NEW_SPICE_CERT_OPT, SPICE_CERT_OPT, SPICE_CACERT_OPT],
+     NEW_SPICE_CERT_OPT, SPICE_CERT_OPT, SPICE_CACERT_OPT,
+     NEW_NODE_CERT_OPT],
     "[opts...]",
     "Renews cluster certificates, keys and secrets"),
   "epo": (
index 558d928..0bfe1f0 100644 (file)
@@ -198,9 +198,8 @@ def _TestJobDependency(opts):
   """
   ToStdout("Testing job dependencies")
 
-  cl = cli.GetClient()
-
   try:
+    cl = cli.GetClient()
     SubmitOpCode(opcodes.OpTestDelay(duration=0, depends=[(-1, None)]), cl=cl)
   except errors.GenericError, err:
     if opts.debug:
@@ -228,6 +227,7 @@ def _TestJobDependency(opts):
                                            ht.TOr(ht.TNonEmptyString,
                                                   ht.TJobId)])))
 
+  cl = cli.GetClient()
   result = cl.SubmitManyJobs(jobs)
   if not check_fn(result):
     raise errors.OpExecError("Job submission doesn't match %s: %s" %
@@ -297,8 +297,6 @@ def _TestJobSubmission(opts):
         (4, 2, priority + offset),
         ])
 
-  cl = cli.GetClient()
-
   for before, after, failpriority in testdata:
     ops = []
     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(before)])
@@ -306,6 +304,7 @@ def _TestJobSubmission(opts):
     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(after)])
 
     try:
+      cl = cli.GetClient()
       cl.SubmitJob(ops)
     except errors.GenericError, err:
       if opts.debug:
@@ -321,16 +320,15 @@ def _TestJobSubmission(opts):
        opcodes.OpTestDelay(duration=0, dry_run=True)],
       ops,
       ]
-    result = cl.SubmitManyJobs(jobs)
-    if not (len(result) == 2 and
-            compat.all(len(i) == 2 for i in result) and
-            isinstance(result[0][1], int) and
-            isinstance(result[1][1], basestring) and
-            result[0][0] and not result[1][0]):
-      raise errors.OpExecError("Submitting multiple jobs did not work as"
-                               " expected, result %s" % result)
-    assert len(result) == 2
-
+    try:
+      cl = cli.GetClient()
+      cl.SubmitManyJobs(jobs)
+    except errors.GenericError, err:
+      if opts.debug:
+        ToStdout("Ignoring error for 'wrong priority' test: %s", err)
+    else:
+      raise errors.OpExecError("Submitting manyjobs with an incorrect one"
+                               " did not fail when it should.")
   ToStdout("Job submission tests were successful")
 
 
index 176d032..1cf6e3f 100644 (file)
@@ -104,7 +104,7 @@ def _ExpandMultiNames(mode, names, client=None):
   # pylint: disable=W0142
 
   if client is None:
-    client = GetClient()
+    client = GetClient(query=True)
   if mode == _EXPAND_CLUSTER:
     if names:
       raise errors.OpPrereqError("Cluster filter mode takes no arguments",
@@ -191,7 +191,8 @@ def GenericManyOps(operation, fn):
     if opts.multi_mode is None:
       opts.multi_mode = _EXPAND_INSTANCES
     cl = GetClient()
-    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
+    qcl = GetClient(query=True)
+    inames = _ExpandMultiNames(opts.multi_mode, args, client=qcl)
     if not inames:
       if opts.multi_mode == _EXPAND_CLUSTER:
         ToStdout("Cluster is empty, no instances to shutdown")
@@ -232,10 +233,12 @@ def ListInstances(opts, args):
                                                       for item in value),
                                False))
 
+  cl = GetClient(query=True)
+
   return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
                      opts.separator, not opts.no_headers,
                      format_override=fmtoverride, verbose=opts.verbose,
-                     force_filter=opts.force_filter)
+                     force_filter=opts.force_filter, cl=cl)
 
 
 def ListInstanceFields(opts, args):
@@ -426,9 +429,10 @@ def RemoveInstance(opts, args):
   instance_name = args[0]
   force = opts.force
   cl = GetClient()
+  qcl = GetClient(query=True)
 
   if not force:
-    _EnsureInstancesExist(cl, [instance_name])
+    _EnsureInstancesExist(qcl, [instance_name])
 
     usertext = ("This will remove the volumes of the instance %s"
                 " (including mirrors), thus removing all the data"
@@ -546,6 +550,14 @@ def RecreateDisks(opts, args):
           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
                                      (didx, err), errors.ECODE_INVAL)
 
+      if constants.IDISK_SPINDLES in ddict:
+        try:
+          ddict[constants.IDISK_SPINDLES] = \
+              int(ddict[constants.IDISK_SPINDLES])
+        except ValueError, err:
+          raise errors.OpPrereqError("Invalid spindles for disk %d: %s" %
+                                     (didx, err), errors.ECODE_INVAL)
+
       disks.append((didx, ddict))
 
     # TODO: Verify modifyable parameters (already done in
@@ -829,6 +841,7 @@ def MoveInstance(opts, args):
 
   op = opcodes.OpInstanceMove(instance_name=instance_name,
                               target_node=opts.node,
+                              compress=opts.compress,
                               shutdown_timeout=opts.shutdown_timeout,
                               ignore_consistency=opts.ignore_consistency,
                               ignore_ipolicy=opts.ignore_ipolicy)
@@ -849,17 +862,21 @@ def ConnectToInstanceConsole(opts, args):
   instance_name = args[0]
 
   cl = GetClient()
+  qcl = GetClient(query=True)
   try:
     cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
-    idata = cl.QueryInstances([instance_name], ["console", "oper_state"], False)
+    idata = \
+        qcl.QueryInstances([instance_name], ["console", "oper_state"], False)
     if not idata:
       raise errors.OpPrereqError("Instance '%s' does not exist" % instance_name,
                                  errors.ECODE_NOENT)
   finally:
     # Ensure client connection is closed while external commands are run
     cl.Close()
+    qcl.Close()
 
   del cl
+  del qcl
 
   ((console_data, oper_state), ) = idata
   if not console_data:
@@ -887,7 +904,7 @@ def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
   @param cluster_name: Cluster name as retrieved from master daemon
 
   """
-  assert console.Validate()
+  console.Validate()
 
   if console.kind == constants.CONS_MESSAGE:
     feedback_fn(console.message)
@@ -908,6 +925,7 @@ def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
 
     srun = ssh.SshRunner(cluster_name=cluster_name)
     ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
+                            port=console.port,
                             batch=True, quiet=False, tty=True)
 
     if show_command:
@@ -1280,6 +1298,9 @@ def _ParseDiskSizes(mods):
 
   """
   for (action, _, params) in mods:
+    if params and constants.IDISK_SPINDLES in params:
+      params[constants.IDISK_SPINDLES] = \
+          int(params[constants.IDISK_SPINDLES])
     if params and constants.IDISK_SIZE in params:
       params[constants.IDISK_SIZE] = \
         utils.ParseUnit(params[constants.IDISK_SIZE])
@@ -1511,7 +1532,7 @@ commands = {
   "move": (
     MoveInstance, ARGS_ONE_INSTANCE,
     [FORCE_OPT] + SUBMIT_OPTS +
-    [SINGLE_NODE_OPT,
+    [SINGLE_NODE_OPT, COMPRESS_OPT,
      SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT,
      IGNORE_IPOLICY_OPT],
     "[-f] <instance>", "Move instance to an arbitrary node"
index 3603042..8c77c30 100644 (file)
@@ -149,9 +149,10 @@ def DisconnectNetwork(opts, args):
 
   """
   cl = GetClient()
+  qcl = GetClient(query=True)
 
   (network, ) = args[:1]
-  groups = _GetDefaultGroups(cl, args[1:])
+  groups = _GetDefaultGroups(qcl, args[1:])
 
   # TODO: Change logic to support "--submit"
   for group in groups:
@@ -215,7 +216,7 @@ def ShowNetworkConfig(_, args):
   @return: the desired exit code
 
   """
-  cl = GetClient()
+  cl = GetClient(query=True)
   result = cl.QueryNetworks(fields=["name", "network", "gateway",
                                     "network6", "gateway6",
                                     "mac_prefix",
@@ -241,9 +242,11 @@ def ShowNetworkConfig(_, args):
     ToStdout("  Free: %d (%.2f%%)", free_count,
              100 * float(free_count) / float(size))
     ToStdout("  Usage map:")
+    lenmapping = len(mapping)
     idx = 0
-    for line in textwrap.wrap(mapping, width=64):
-      ToStdout("     %s %s %d", str(idx).rjust(3), line.ljust(64), idx + 63)
+    while idx < lenmapping:
+      line = mapping[idx: idx + 64]
+      ToStdout("     %s %s %d", str(idx).rjust(4), line.ljust(64), idx + 63)
       idx += 64
     ToStdout("         (X) used    (.) free")
 
index ed96e5b..2eb2094 100644 (file)
@@ -98,6 +98,7 @@ _USER_STORAGE_TYPE = {
   constants.ST_FILE: "file",
   constants.ST_LVM_PV: "lvm-pv",
   constants.ST_LVM_VG: "lvm-vg",
+  constants.ST_SHARED_FILE: "sharedfile",
   }
 
 _STORAGE_TYPE_OPT = \
@@ -190,7 +191,7 @@ def _ReadSshKeys(keyfiles, _tostderr_fn=ToStderr):
   return result
 
 
-def _SetupSSH(options, cluster_name, node):
+def _SetupSSH(options, cluster_name, node, ssh_port):
   """Configures a destination node's SSH daemon.
 
   @param options: Command line options
@@ -198,6 +199,8 @@ def _SetupSSH(options, cluster_name, node):
   @param cluster_name: Cluster name
   @type node: string
   @param node: Destination node name
+  @type ssh_port: int
+  @param ssh_port: Destination node ssh port
 
   """
   if options.force_join:
@@ -223,7 +226,8 @@ def _SetupSSH(options, cluster_name, node):
 
   bootstrap.RunNodeSetupCmd(cluster_name, node, pathutils.PREPARE_NODE_JOIN,
                             options.debug, options.verbose, False,
-                            options.ssh_key_check, options.ssh_key_check, data)
+                            options.ssh_key_check, options.ssh_key_check,
+                            ssh_port, data)
 
 
 @UsesRPC
@@ -238,13 +242,31 @@ def AddNode(opts, args):
 
   """
   cl = GetClient()
+  query_cl = GetClient(query=True)
   node = netutils.GetHostname(name=args[0]).name
   readd = opts.readd
 
+  # Retrieve relevant parameters of the node group.
+  ssh_port = None
   try:
-    output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
-                           use_locking=False)
-    node_exists, sip, is_master = output[0]
+    # Passing [] to QueryGroups means query the default group:
+    node_groups = [opts.nodegroup] if opts.nodegroup is not None else []
+    output = query_cl.QueryGroups(names=node_groups, fields=["ndp/ssh_port"],
+                                  use_locking=False)
+    (ssh_port, ) = output[0]
+  except (errors.OpPrereqError, errors.OpExecError):
+    pass
+
+  try:
+    output = query_cl.QueryNodes(names=[node],
+                                 fields=["name", "sip", "master",
+                                         "ndp/ssh_port"],
+                                 use_locking=False)
+    if len(output) == 0:
+      node_exists = ""
+      sip = None
+    else:
+      node_exists, sip, is_master, ssh_port = output[0]
   except (errors.OpPrereqError, errors.OpExecError):
     node_exists = ""
     sip = None
@@ -276,9 +298,9 @@ def AddNode(opts, args):
              "and grant full intra-cluster ssh root access to/from it\n", node)
 
   if opts.node_setup:
-    _SetupSSH(opts, cluster_name, node)
+    _SetupSSH(opts, cluster_name, node, ssh_port)
 
-  bootstrap.SetupNodeDaemon(opts, cluster_name, node)
+  bootstrap.SetupNodeDaemon(opts, cluster_name, node, ssh_port)
 
   if opts.disk_state:
     disk_state = utils.FlatToDict(opts.disk_state)
@@ -432,8 +454,8 @@ def FailoverNode(opts, args):
   # these fields are static data anyway, so it doesn't matter, but
   # locking=True should be safer
   qcl = GetClient(query=True)
-  result = cl.QueryNodes(names=args, fields=selected_fields,
-                         use_locking=False)
+  result = qcl.QueryNodes(names=args, fields=selected_fields,
+                          use_locking=False)
   qcl.Close()
   node, pinst = result[0]
 
@@ -474,7 +496,7 @@ def MigrateNode(opts, args):
   selected_fields = ["name", "pinst_list"]
 
   qcl = GetClient(query=True)
-  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
+  result = qcl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
   qcl.Close()
   ((node, pinst), ) = result
 
index d17850f..8913305 100644 (file)
@@ -54,11 +54,11 @@ from ganeti.cmdlib.cluster import \
   LUClusterVerify, \
   LUClusterVerifyConfig, \
   LUClusterVerifyGroup, \
-  LUClusterVerifyDisks
+  LUClusterVerifyDisks, \
+  LUClusterRenewCrypto
 from ganeti.cmdlib.group import \
   LUGroupAdd, \
   LUGroupAssignNodes, \
-  LUGroupQuery, \
   LUGroupSetParams, \
   LUGroupRemove, \
   LUGroupRename, \
@@ -71,7 +71,6 @@ from ganeti.cmdlib.node import \
   LUNodeEvacuate, \
   LUNodeMigrate, \
   LUNodeModifyStorage, \
-  LUNodeQuery, \
   LUNodeQueryvols, \
   LUNodeQueryStorage, \
   LUNodeRemove, \
@@ -100,10 +99,8 @@ from ganeti.cmdlib.instance_operation import \
   LUInstanceReboot, \
   LUInstanceConsole
 from ganeti.cmdlib.instance_query import \
-  LUInstanceQuery, \
   LUInstanceQueryData
 from ganeti.cmdlib.backup import \
-  LUBackupQuery, \
   LUBackupPrepare, \
   LUBackupExport, \
   LUBackupRemove
@@ -121,7 +118,6 @@ from ganeti.cmdlib.network import \
   LUNetworkAdd, \
   LUNetworkRemove, \
   LUNetworkSetParams, \
-  LUNetworkQuery, \
   LUNetworkConnect, \
   LUNetworkDisconnect
 from ganeti.cmdlib.misc import \
index 0ca1ad6..f202158 100644 (file)
@@ -38,12 +38,10 @@ from ganeti import constants
 from ganeti import errors
 from ganeti import locking
 from ganeti import masterd
-from ganeti import qlang
-from ganeti import query
 from ganeti import utils
 
-from ganeti.cmdlib.base import QueryBase, NoHooksLU, LogicalUnit
-from ganeti.cmdlib.common import GetWantedNodes, ShareAll, CheckNodeOnline, \
+from ganeti.cmdlib.base import NoHooksLU, LogicalUnit
+from ganeti.cmdlib.common import CheckNodeOnline, \
   ExpandNodeUuidAndName
 from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
   ShutdownInstanceDisks
@@ -51,87 +49,6 @@ from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
   BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance
 
 
-class ExportQuery(QueryBase):
-  FIELDS = query.EXPORT_FIELDS
-
-  #: The node name is not a unique key for this query
-  SORT_FIELD = "node"
-
-  def ExpandNames(self, lu):
-    lu.needed_locks = {}
-
-    # The following variables interact with _QueryBase._GetNames
-    if self.names:
-      (self.wanted, _) = GetWantedNodes(lu, self.names)
-    else:
-      self.wanted = locking.ALL_SET
-
-    self.do_locking = self.use_locking
-
-    if self.do_locking:
-      lu.share_locks = ShareAll()
-      lu.needed_locks = {
-        locking.LEVEL_NODE: self.wanted,
-        }
-
-      if not self.names:
-        lu.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
-
-  def DeclareLocks(self, lu, level):
-    pass
-
-  def _GetQueryData(self, lu):
-    """Computes the list of nodes and their attributes.
-
-    """
-    # Locking is not used
-    # TODO
-    assert not (compat.any(lu.glm.is_owned(level)
-                           for level in locking.LEVELS
-                           if level != locking.LEVEL_CLUSTER) or
-                self.do_locking or self.use_locking)
-
-    node_uuids = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
-
-    result = []
-    for (node_uuid, nres) in lu.rpc.call_export_list(node_uuids).items():
-      node = lu.cfg.GetNodeInfo(node_uuid)
-      if nres.fail_msg:
-        result.append((node.name, None))
-      else:
-        result.extend((node.name, expname) for expname in nres.payload)
-
-    return result
-
-
-class LUBackupQuery(NoHooksLU):
-  """Query the exports list
-
-  """
-  REQ_BGL = False
-
-  def CheckArguments(self):
-    self.expq = ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
-                            ["node", "export"], self.op.use_locking)
-
-  def ExpandNames(self):
-    self.expq.ExpandNames(self)
-
-  def DeclareLocks(self, level):
-    self.expq.DeclareLocks(self, level)
-
-  def Exec(self, feedback_fn):
-    result = {}
-
-    for (node, expname) in self.expq.OldStyleQuery(self):
-      if expname is None:
-        result[node] = False
-      else:
-        result.setdefault(node, []).append(expname)
-
-    return result
-
-
 class LUBackupPrepare(NoHooksLU):
   """Prepares an instance for an export and returns useful information.
 
@@ -350,7 +267,7 @@ class LUBackupExport(LogicalUnit):
     # instance disk type verification
     # TODO: Implement export support for file-based disks
     for disk in self.instance.disks:
-      if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
+      if disk.dev_type in constants.DTS_FILEBASED:
         raise errors.OpPrereqError("Export not supported for instances with"
                                    " file-based disks", errors.ECODE_INVAL)
 
@@ -430,7 +347,8 @@ class LUBackupExport(LogicalUnit):
             raise errors.OpExecError("Could not start instance: %s" % msg)
 
         if self.op.mode == constants.EXPORT_MODE_LOCAL:
-          (fin_resu, dresults) = helper.LocalExport(self.dst_node)
+          (fin_resu, dresults) = helper.LocalExport(self.dst_node,
+                                                    self.op.compress)
         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
 &n