Merge branch 'stable-2.7' into stable-2.8
authorKlaus Aehlig <aehlig@google.com>
Mon, 16 Sep 2013 11:52:45 +0000 (13:52 +0200)
committerKlaus Aehlig <aehlig@google.com>
Mon, 16 Sep 2013 11:58:23 +0000 (13:58 +0200)
* stable-2.7
  Fix incorrect manpage reference to htools

Signed-off-by: Klaus Aehlig <aehlig@google.com>
Reviewed-by: Michele Tartara <mtartara@google.com>

285 files changed:
.gitignore
INSTALL
Makefile.am
NEWS
README
UPGRADE
autotools/build-bash-completion
autotools/check-news
autotools/check-version
configure.ac
daemons/daemon-util.in
devel/build_chroot [new file with mode: 0755]
devel/check-split-query [new file with mode: 0755]
devel/review
devel/upload
doc/admin.rst
doc/design-2.8.rst [new file with mode: 0644]
doc/design-device-uuid-name.rst [new file with mode: 0644]
doc/design-draft.rst
doc/design-hroller.rst [new file with mode: 0644]
doc/design-monitoring-agent.rst
doc/design-partitioned.rst
doc/design-query-splitting.rst
doc/design-reason-trail.rst [new file with mode: 0644]
doc/design-storagetypes.rst [new file with mode: 0644]
doc/devnotes.rst
doc/hooks.rst
doc/iallocator.rst
doc/index.rst
doc/install.rst
doc/monitoring-query-format.rst [new file with mode: 0644]
doc/rapi.rst
doc/security.rst
doc/users/groupmemberships.in [new file with mode: 0644]
doc/users/groups.in [new file with mode: 0644]
doc/users/users.in [new file with mode: 0644]
doc/virtual-cluster.rst
lib/backend.py
lib/bdev.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_group.py
lib/client/gnt_instance.py
lib/client/gnt_network.py
lib/client/gnt_node.py
lib/cmdlib.py [deleted file]
lib/cmdlib/__init__.py [new file with mode: 0644]
lib/cmdlib/backup.py [new file with mode: 0644]
lib/cmdlib/base.py [new file with mode: 0644]
lib/cmdlib/cluster.py [new file with mode: 0644]
lib/cmdlib/common.py [new file with mode: 0644]
lib/cmdlib/group.py [new file with mode: 0644]
lib/cmdlib/instance.py [new file with mode: 0644]
lib/cmdlib/instance_migration.py [new file with mode: 0644]
lib/cmdlib/instance_operation.py [new file with mode: 0644]
lib/cmdlib/instance_query.py [new file with mode: 0644]
lib/cmdlib/instance_storage.py [new file with mode: 0644]
lib/cmdlib/instance_utils.py [new file with mode: 0644]
lib/cmdlib/misc.py [new file with mode: 0644]
lib/cmdlib/network.py [new file with mode: 0644]
lib/cmdlib/node.py [new file with mode: 0644]
lib/cmdlib/operating_system.py [new file with mode: 0644]
lib/cmdlib/query.py [new file with mode: 0644]
lib/cmdlib/tags.py [new file with mode: 0644]
lib/cmdlib/test.py [new file with mode: 0644]
lib/config.py
lib/constants.py
lib/ht.py
lib/http/server.py
lib/hypervisor/hv_base.py
lib/hypervisor/hv_kvm.py
lib/hypervisor/hv_xen.py
lib/jqueue.py
lib/luxi.py
lib/masterd/iallocator.py
lib/netutils.py
lib/objects.py
lib/opcodes.py
lib/outils.py
lib/pathutils.py
lib/query.py
lib/rapi/baserlib.py
lib/rapi/client.py
lib/rapi/connector.py
lib/rapi/rlib2.py
lib/rapi/testutils.py
lib/rpc_defs.py
lib/runtime.py
lib/server/masterd.py
lib/server/noded.py
lib/server/rapi.py
lib/ssh.py
lib/tools/burnin.py
lib/tools/ensure_dirs.py
lib/tools/node_daemon_setup.py
lib/utils/__init__.py
lib/utils/io.py
lib/utils/wrapper.py
lib/vcluster.py
lib/watcher/__init__.py
man/ganeti-confd.rst
man/ganeti-luxid.rst [new file with mode: 0644]
man/ganeti-mond.rst [new file with mode: 0644]
man/ganeti-os-interface.rst
man/ganeti-rapi.rst
man/ganeti.rst
man/gnt-cluster.rst
man/gnt-group.rst
man/gnt-instance.rst
man/gnt-node.rst
man/harep.rst [new file with mode: 0644]
man/hbal.rst
man/hroller.rst
man/htools.rst
pylintrc
qa/ganeti-qa.py
qa/qa-sample.json
qa/qa_cluster.py
qa/qa_config.py
qa/qa_daemon.py
qa/qa_env.py
qa/qa_group.py
qa/qa_instance.py
qa/qa_network.py [new file with mode: 0644]
qa/qa_node.py
qa/qa_os.py
qa/qa_rapi.py
qa/qa_tags.py
qa/qa_utils.py
src/Ganeti/BasicTypes.hs
src/Ganeti/Block/Drbd/Types.hs
src/Ganeti/Common.hs
src/Ganeti/Confd/Client.hs
src/Ganeti/Confd/Server.hs
src/Ganeti/Confd/Types.hs
src/Ganeti/Config.hs
src/Ganeti/ConfigReader.hs [new file with mode: 0644]
src/Ganeti/Curl/Internal.hsc [new file with mode: 0644]
src/Ganeti/Curl/Multi.hs [new file with mode: 0644]
src/Ganeti/Daemon.hs
src/Ganeti/DataCollectors/CLI.hs
src/Ganeti/DataCollectors/Drbd.hs
src/Ganeti/DataCollectors/Types.hs
src/Ganeti/HTools/Backend/IAlloc.hs
src/Ganeti/HTools/Backend/Luxi.hs
src/Ganeti/HTools/Backend/Rapi.hs
src/Ganeti/HTools/Backend/Simu.hs
src/Ganeti/HTools/Backend/Text.hs
src/Ganeti/HTools/CLI.hs
src/Ganeti/HTools/Cluster.hs
src/Ganeti/HTools/Group.hs
src/Ganeti/HTools/Instance.hs
src/Ganeti/HTools/Loader.hs
src/Ganeti/HTools/Nic.hs [new file with mode: 0644]
src/Ganeti/HTools/Node.hs
src/Ganeti/HTools/Program/Harep.hs [new file with mode: 0644]
src/Ganeti/HTools/Program/Hroller.hs
src/Ganeti/HTools/Program/Hscan.hs
src/Ganeti/HTools/Program/Hspace.hs
src/Ganeti/HTools/Program/Main.hs
src/Ganeti/HTools/Types.hs
src/Ganeti/Hypervisor/Xen/Types.hs [new file with mode: 0644]
src/Ganeti/Hypervisor/Xen/XmParser.hs [new file with mode: 0644]
src/Ganeti/JSON.hs
src/Ganeti/Jobs.hs
src/Ganeti/Luxi.hs
src/Ganeti/Monitoring/Server.hs [new file with mode: 0644]
src/Ganeti/Network.hs
src/Ganeti/Objects.hs
src/Ganeti/OpCodes.hs
src/Ganeti/OpParams.hs
src/Ganeti/Query/Common.hs
src/Ganeti/Query/Export.hs [new file with mode: 0644]
src/Ganeti/Query/Filter.hs
src/Ganeti/Query/Group.hs
src/Ganeti/Query/Language.hs
src/Ganeti/Query/Network.hs [new file with mode: 0644]
src/Ganeti/Query/Node.hs
src/Ganeti/Query/Query.hs
src/Ganeti/Query/Server.hs
src/Ganeti/Query/Types.hs
src/Ganeti/Rpc.hs
src/Ganeti/Runtime.hs
src/Ganeti/Ssconf.hs
src/Ganeti/THH.hs
src/Ganeti/Types.hs
src/Ganeti/Utils.hs
src/ganeti-mond.hs [copied from src/hconfd.hs with 74% similarity]
src/hconfd.hs
src/hluxid.hs [copied from src/hconfd.hs with 72% similarity]
src/rpc-test.hs
test/autotools/autotools-check-news.test [new file with mode: 0644]
test/data/NEWS_OK.txt [new file with mode: 0644]
test/data/NEWS_previous_unreleased.txt [new file with mode: 0644]
test/data/cluster_config_2.7.json [new file with mode: 0644]
test/data/cluster_config_downgraded_2.7.json [new file with mode: 0644]
test/data/htools/clean-nonzero-score.data
test/data/htools/common-suffix.data
test/data/htools/empty-cluster.data
test/data/htools/hail-alloc-drbd.json
test/data/htools/hail-alloc-invalid-network.json [new file with mode: 0644]
test/data/htools/hail-alloc-invalid-twodisks.json
test/data/htools/hail-alloc-restricted-network.json [new file with mode: 0644]
test/data/htools/hail-alloc-twodisks.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/hbal-excl-tags.data
test/data/htools/hbal-split-insts.data
test/data/htools/hspace-tiered-dualspec.data [new file with mode: 0644]
test/data/htools/hspace-tiered-ipolicy.data
test/data/htools/hspace-tiered-resourcetypes.data [new file with mode: 0644]
test/data/htools/hspace-tiered.data [new file with mode: 0644]
test/data/htools/invalid-node.data
test/data/htools/missing-resources.data
test/data/htools/multiple-master.data [new file with mode: 0644]
test/data/htools/n1-failure.data
test/data/htools/rapi/groups.json
test/data/htools/rapi/info.json
test/data/htools/unique-reboot-order.data [new file with mode: 0644]
test/data/proc_cpuinfo.txt [new file with mode: 0644]
test/data/proc_meminfo.txt [new file with mode: 0644]
test/data/qa-minimal-nodes-instances-only.json [new file with mode: 0644]
test/data/xen-xm-info-4.0.1.txt [new file with mode: 0644]
test/data/xen-xm-list-4.0.1-dom0-only.txt [new file with mode: 0644]
test/data/xen-xm-list-4.0.1-four-instances.txt [new file with mode: 0644]
test/data/xen-xm-list-long-4.0.1.txt [new file with mode: 0644]
test/data/xen-xm-uptime-4.0.1.txt [new file with mode: 0644]
test/hs/Test/Ganeti/Common.hs
test/hs/Test/Ganeti/HTools/Backend/Text.hs
test/hs/Test/Ganeti/HTools/Cluster.hs
test/hs/Test/Ganeti/HTools/Instance.hs
test/hs/Test/Ganeti/HTools/Node.hs
test/hs/Test/Ganeti/HTools/Types.hs
test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs [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/Network.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Query/Query.hs
test/hs/Test/Ganeti/Rpc.hs
test/hs/Test/Ganeti/Runtime.hs
test/hs/Test/Ganeti/TestCommon.hs
test/hs/Test/Ganeti/TestHTools.hs
test/hs/Test/Ganeti/TestHelper.hs
test/hs/Test/Ganeti/Utils.hs
test/hs/htest.hs
test/hs/shelltests/htools-hail.test
test/hs/shelltests/htools-hroller.test [new file with mode: 0644]
test/hs/shelltests/htools-hspace.test
test/hs/shelltests/htools-invalid.test
test/py/cfgupgrade_unittest.py
test/py/daemon-util_unittest.bash
test/py/ganeti.backend_unittest.py
test/py/ganeti.cli_unittest.py
test/py/ganeti.client.gnt_instance_unittest.py
test/py/ganeti.cmdlib_unittest.py
test/py/ganeti.config_unittest.py
test/py/ganeti.constants_unittest.py
test/py/ganeti.daemon_unittest.py
test/py/ganeti.hypervisor.hv_kvm_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py
test/py/ganeti.hypervisor_unittest.py
test/py/ganeti.objects_unittest.py
test/py/ganeti.outils_unittest.py
test/py/ganeti.query_unittest.py
test/py/ganeti.rapi.baserlib_unittest.py
test/py/ganeti.rapi.client_unittest.py
test/py/ganeti.rapi.rlib2_unittest.py
test/py/ganeti.rapi.testutils_unittest.py
test/py/ganeti.server.rapi_unittest.py
test/py/ganeti.ssh_unittest.py
test/py/ganeti.utils.io_unittest-runasroot.py
test/py/ganeti.utils_unittest.py
test/py/ganeti.vcluster_unittest.py
test/py/mocks.py
test/py/qa.qa_config_unittest.py
tools/cfgupgrade
tools/move-instance
tools/sanitize-config
tools/users-setup.in [deleted file]
tools/vcluster-setup.in

index dec19dd..eb953d3 100644 (file)
@@ -31,6 +31,8 @@
 /config.log
 /config.status
 /configure
+/devel/squeeze-amd64.tar.gz
+/devel/squeeze-amd64.conf
 /epydoc.conf
 /ganeti
 /stamp-srclinks
@@ -60,6 +62,9 @@
 /doc/hs-lint.html
 /doc/manpages-enabled.rst
 /doc/man-*.rst
+/doc/users/groupmemberships
+/doc/users/groups
+/doc/users/users
 
 # doc/examples
 /doc/examples/bash_completion
@@ -86,6 +91,7 @@
 
 # test/hs
 /test/hs/hail
+/test/hs/harep
 /test/hs/hbal
 /test/hs/hcheck
 /test/hs/hinfo
 /src/mon-collector
 /src/htools
 /src/hconfd
+/src/hluxid
 /src/ganeti-confd
+/src/ganeti-luxid
+/src/ganeti-mond
 /src/rpc-test
 
 # automatically-built Haskell files
 /src/Ganeti/Constants.hs
+/src/Ganeti/Curl/Internal.hs
 /src/Ganeti/Version.hs
 /test/hs/Test/Ganeti/TestImports.hs
diff --git a/INSTALL b/INSTALL
index c64ac2e..5abd69c 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -30,7 +30,7 @@ Before installing, please verify that you have the following programs:
 - `iproute2 <http://www.linuxfoundation.org/en/Net:Iproute2>`_
 - `arping <http://www.skbuff.net/iputils/>`_ (part of iputils)
 - `ndisc6 <http://www.remlab.net/ndisc6/>`_ (if using IPv6)
-- `Python <http://www.python.org/>`_, version 2.4 or above, not 3.0
+- `Python <http://www.python.org/>`_, version 2.6 or above, not 3.0
 - `Python OpenSSL bindings <http://pyopenssl.sourceforge.net/>`_
 - `simplejson Python module <http://code.google.com/p/simplejson/>`_
 - `pyparsing Python module <http://pyparsing.wikispaces.com/>`_, version
@@ -48,6 +48,7 @@ Before installing, please verify that you have the following programs:
 - `Python IP address manipulation library
   <http://code.google.com/p/ipaddr-py/>`_
 - `Bitarray Python library <http://pypi.python.org/pypi/bitarray/>`_
+- `GNU Make <http://www.gnu.org/software/make/>`_
 
 These programs are supplied as part of most Linux distributions, so
 usually they can be installed via the standard package manager. Also
@@ -55,7 +56,7 @@ many of them will already be installed on a standard machine. On
 Debian/Ubuntu, you can use this command line to install all required
 packages, except for RBD, DRBD and Xen::
 
-  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping \
+  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping make \
                     ndisc6 python python-pyopenssl openssl \
                     python-pyparsing python-simplejson python-bitarray \
                     python-pyinotify python-pycurl python-ipaddr socat fping
@@ -66,7 +67,7 @@ If bitarray is missing it can be installed from easy-install::
 
 Or on newer distributions (eg. Debian Wheezy) the above becomes::
 
-  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping \
+  $ apt-get install lvm2 ssh bridge-utils iproute iputils-arping make \
                     ndisc6 python python-openssl openssl \
                     python-pyparsing python-simplejson python-bitarray \
                     python-pyinotify python-pycurl python-ipaddr socat fping
@@ -79,16 +80,18 @@ If some of the python packages are not available in your system,
 you can try installing them using ``easy_install`` command.
 For example::
 
-  $ apt-get install python-setuptools
+  $ apt-get install python-setuptools python-dev
   $ cd / && sudo easy_install \
-            affinity
+            affinity \
+            bitarray \
+            ipaddr
 
 
 On Fedora to install all required packages except RBD, DRBD and Xen::
 
-  $ yum install openssh openssh-clients bridge-utils iproute ndisc6 \
+  $ yum install openssh openssh-clients bridge-utils iproute ndisc6 make \
                 pyOpenSSL pyparsing python-simplejson python-inotify \
-                python-lxm socat fping
+                python-lxm socat fping python-bitarray python-ipaddr
 
 For optional packages use the command::
 
@@ -140,19 +143,22 @@ deploy Ganeti on production machines). More specifically:
   `utf8-string <http://hackage.haskell.org/package/utf8-string>`_
   libraries; these usually come with the GHC compiler
 - `deepseq <http://hackage.haskell.org/package/deepseq>`_
+- `curl <http://hackage.haskell.org/package/curl>`_, tested with
+  versions 1.3.4 and above
 
 Some of these are also available as package in Debian/Ubuntu::
 
   $ apt-get install ghc6 libghc6-json-dev libghc6-network-dev \
-                    libghc6-parallel-dev libghc6-deepseq-dev
+                    libghc6-parallel-dev libghc6-deepseq-dev \
+                    libghc6-curl-dev
 
 Or in newer versions of these distributions (using GHC 7.x)::
 
   $ apt-get install ghc libghc-json-dev libghc-network-dev \
                     libghc-parallel-dev libghc-deepseq-dev \
-                    libghc-utf8-string-dev
+                    libghc-utf8-string-dev libghc-curl-dev
 
-In Fedora, they are available via packages as well::
+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
@@ -161,29 +167,21 @@ If using a distribution which does not provide them, first install
 the Haskell platform. You can also install ``cabal`` manually::
 
   $ apt-get install cabal-install
+  $ cabal update
 
-Then install the additional libraries via
-``cabal``::
-
-  $ cabal install json network parallel utf8-string
-
-The compilation of the htools components is automatically enabled when
-the compiler and the requisite libraries are found. You can use the
-``--enable-htools`` configure flag to force the selection (at which
-point ``./configure`` will fail if it doesn't find the prerequisites).
+Then install the additional libraries (only the ones not available in your
+distribution packages) via ``cabal``::
 
+  $ cabal install json network parallel utf8-string curl
 
 Haskell optional features
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Optionally, more functionality can be enabled if your build machine has
-a few more Haskell libraries enabled: RAPI access to remote cluster from
-htools (``--enable-htools-rapi``), the ``ganeti-confd``
-daemon (``--enable-confd``) and the monitoring agent
-(``--enable-monitoring``). The list of extra dependencies for these is:
+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:
 
-- `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)
 - `Crypto <http://hackage.haskell.org/package/Crypto>`_, tested with
@@ -195,25 +193,37 @@ daemon (``--enable-confd``) and the monitoring agent
   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.
 
-These libraries are available in Debian Wheezy (but not in Squeeze, with
-the exception of curl), so you can use either apt::
+These libraries are available in Debian Wheezy (but not in Squeeze), so you
+can use either apt::
 
   $ apt-get install libghc-hslogger-dev libghc-crypto-dev libghc-text-dev \
-                    libghc-hinotify-dev libghc-regex-pcre-dev libghc-curl-dev \
-                    libghc-attoparsec-dev libghc-vector-dev libpcre3-dev
+                    libghc-hinotify-dev libghc-regex-pcre-dev \
+                    libghc-attoparsec-dev libghc-vector-dev \
+                    libghc-snap-server-dev
 
-or ``cabal``::
+or ``cabal``, after installing a required non-Haskell dependency::
 
   $ apt-get install libpcre3-dev libcurl4-openssl-dev
-  $ cabal install hslogger Crypto text hinotify==0.3.2 regex-pcre curl \
-                  attoparsec vector
+  $ cabal install hslogger Crypto text hinotify==0.3.2 regex-pcre \
+                  attoparsec vector snap-server
 
 to install them.
 
-The most recent Fedora doesn't provide ``curl``, ``crypto``,
-``inotify``. So these need to be installed using ``cabal``, if
-desired. The other packages can be installed via ``yum``::
+In case you still use ghc-6.12, note that ``cabal`` would automatically try to
+install newer versions of some of the libraries snap-server depends on, that
+cannot be compiled with ghc-6.12, so you have to install snap-server on its
+own, esplicitly forcing the installation of compatible versions::
+
+  $ cabal install MonadCatchIO-transformers==0.2.2.0 mtl==2.0.1.0 \
+                  hashable==1.1.2.0 case-insensitive==0.3 parsec==3.0.1 \
+                  network==2.3 snap-server==0.8.1
+
+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
index 819ffea..0f8941f 100644 (file)
@@ -40,6 +40,7 @@ SHELL_ENV_INIT = autotools/shell-env-init
 # Note: these are automake-specific variables, and must be named after
 # the directory + 'dir' suffix
 clientdir = $(pkgpythondir)/client
+cmdlibdir = $(pkgpythondir)/cmdlib
 hypervisordir = $(pkgpythondir)/hypervisor
 httpdir = $(pkgpythondir)/http
 masterddir = $(pkgpythondir)/masterd
@@ -64,10 +65,14 @@ HS_DIRS = \
        src/Ganeti/Block \
        src/Ganeti/Block/Drbd \
        src/Ganeti/Confd \
+       src/Ganeti/Curl \
        src/Ganeti/DataCollectors \
        src/Ganeti/HTools \
        src/Ganeti/HTools/Backend \
        src/Ganeti/HTools/Program \
+       src/Ganeti/Hypervisor \
+       src/Ganeti/Hypervisor/Xen \
+       src/Ganeti/Monitoring \
        src/Ganeti/Query \
        test/hs \
        test/hs/Test \
@@ -77,6 +82,8 @@ HS_DIRS = \
        test/hs/Test/Ganeti/Confd \
        test/hs/Test/Ganeti/HTools \
        test/hs/Test/Ganeti/HTools/Backend \
+       test/hs/Test/Ganeti/Hypervisor \
+       test/hs/Test/Ganeti/Hypervisor/Xen \
        test/hs/Test/Ganeti/Query
 
 # Haskell directories without the roots (src, test/hs)
@@ -87,17 +94,21 @@ DIRS = \
        autotools \
        daemons \
        devel \
+       devel/data \
        doc \
        doc/css \
        doc/examples \
        doc/examples/gnt-debug \
        doc/examples/hooks \
+       doc/users \
        test/data/htools \
        test/data/htools/rapi \
        test/hs/shelltests \
+       test/autotools \
        lib \
        lib/build \
        lib/client \
+       lib/cmdlib \
        lib/confd \
        lib/http \
        lib/hypervisor \
@@ -172,11 +183,14 @@ CLEANFILES = \
        $(SHELL_ENV_INIT) \
        daemons/daemon-util \
        daemons/ganeti-cleaner \
+       devel/squeeze-amd64.tar.gz \
+       devel/squeeze-amd64.conf \
        $(mandocrst) \
        doc/manpages-enabled.rst \
        $(BUILT_EXAMPLES) \
        doc/examples/bash_completion \
        doc/examples/bash_completion-debug \
+       $(userspecs) \
        lib/_generated_rpc.py \
        $(man_MANS) \
        $(manhtml) \
@@ -189,6 +203,8 @@ CLEANFILES = \
        $(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
        $(HS_BUILT_TEST_HELPERS) \
        src/ganeti-confd \
+       src/ganeti-luxid \
+       src/ganeti-mond \
        .hpc/*.mix src/*.tix test/hs/*.tix \
        doc/hs-lint.html
 
@@ -201,7 +217,11 @@ HS_GENERATED_FILES =
 if WANT_HTOOLS
 HS_GENERATED_FILES += $(HS_PROGS)
 if ENABLE_CONFD
-HS_GENERATED_FILES += src/hconfd src/ganeti-confd
+HS_GENERATED_FILES += src/hconfd src/ganeti-confd src/hluxid src/ganeti-luxid
+endif
+
+if ENABLE_MOND
+HS_GENERATED_FILES += src/ganeti-mond
 endif
 endif
 
@@ -247,7 +267,6 @@ pkgpython_PYTHON = \
        lib/bdev.py \
        lib/bootstrap.py \
        lib/cli.py \
-       lib/cmdlib.py \
        lib/compat.py \
        lib/config.py \
        lib/constants.py \
@@ -293,6 +312,27 @@ client_PYTHON = \
        lib/client/gnt_os.py \
        lib/client/gnt_storage.py
 
+cmdlib_PYTHON = \
+       lib/cmdlib/__init__.py \
+       lib/cmdlib/backup.py \
+       lib/cmdlib/base.py \
+       lib/cmdlib/cluster.py \
+       lib/cmdlib/common.py \
+       lib/cmdlib/group.py \
+       lib/cmdlib/instance.py \
+       lib/cmdlib/instance_migration.py \
+       lib/cmdlib/instance_operation.py \
+       lib/cmdlib/instance_query.py \
+       lib/cmdlib/instance_storage.py \
+       lib/cmdlib/instance_utils.py \
+       lib/cmdlib/misc.py \
+       lib/cmdlib/network.py \
+       lib/cmdlib/node.py \
+       lib/cmdlib/operating_system.py \
+       lib/cmdlib/query.py \
+       lib/cmdlib/tags.py \
+       lib/cmdlib/test.py
+
 hypervisor_PYTHON = \
        lib/hypervisor/__init__.py \
        lib/hypervisor/hv_base.py \
@@ -377,10 +417,12 @@ docinput = \
        doc/design-2.5.rst \
        doc/design-2.6.rst \
        doc/design-2.7.rst \
+       doc/design-2.8.rst \
        doc/design-autorepair.rst \
        doc/design-bulk-create.rst \
        doc/design-chained-jobs.rst \
        doc/design-cpu-pinning.rst \
+       doc/design-device-uuid-name.rst \
        doc/design-draft.rst \
        doc/design-htools-2.3.rst \
        doc/design-http-server.rst \
@@ -396,12 +438,15 @@ docinput = \
        doc/design-partitioned.rst \
        doc/design-query-splitting.rst \
        doc/design-query2.rst \
+       doc/design-reason-trail.rst \
        doc/design-resource-model.rst \
        doc/design-restricted-commands.rst \
        doc/design-shared-storage.rst \
        doc/design-monitoring-agent.rst \
        doc/design-virtual-clusters.rst \
        doc/design-x509-ca.rst \
+       doc/design-hroller.rst \
+       doc/design-storagetypes.rst \
        doc/devnotes.rst \
        doc/glossary.rst \
        doc/hooks.rst \
@@ -411,6 +456,7 @@ docinput = \
        doc/install.rst \
        doc/locking.rst \
        doc/manpages-disabled.rst \
+       doc/monitoring-query-format.rst \
        doc/move-instance.rst \
        doc/news.rst \
        doc/ovfconverter.rst \
@@ -427,22 +473,23 @@ mandocrst = $(addprefix doc/man-,$(notdir $(manrst)))
 HS_BIN_PROGS=src/htools
 
 # Haskell programs to be installed in the MYEXECLIB dir
-if ENABLE_MONITORING
+if ENABLE_MOND
 HS_MYEXECLIB_PROGS=src/mon-collector
 else
 HS_MYEXECLIB_PROGS=
 endif
 
-# Haskell programs to compiled but not installed automatically
-# Usually they have their own specific installation rules
+# Haskell programs to be compiled by "make really-all"
 HS_COMPILE_PROGS= \
+       src/ganeti-mond \
        src/hconfd \
+       src/hluxid \
        src/rpc-test
 
 # All Haskell non-test programs to be compiled but not automatically installed
 HS_PROGS = $(HS_BIN_PROGS) $(HS_MYEXECLIB_PROGS)
 
-HS_BIN_ROLES = hbal hscan hspace hinfo hcheck hroller
+HS_BIN_ROLES = harep hbal hscan hspace hinfo hcheck hroller
 HS_HTOOLS_PROGS = $(HS_BIN_ROLES) hail
 
 HS_ALL_PROGS = \
@@ -489,6 +536,8 @@ HS_LIB_SRCS = \
        src/Ganeti/Confd/Types.hs \
        src/Ganeti/Confd/Utils.hs \
        src/Ganeti/Config.hs \
+       src/Ganeti/ConfigReader.hs \
+       src/Ganeti/Curl/Multi.hs \
        src/Ganeti/Daemon.hs \
        src/Ganeti/DataCollectors/CLI.hs \
        src/Ganeti/DataCollectors/Drbd.hs \
@@ -508,9 +557,11 @@ HS_LIB_SRCS = \
        src/Ganeti/HTools/Group.hs \
        src/Ganeti/HTools/Instance.hs \
        src/Ganeti/HTools/Loader.hs \
+       src/Ganeti/HTools/Nic.hs \
        src/Ganeti/HTools/Node.hs \
        src/Ganeti/HTools/PeerMap.hs \
        src/Ganeti/HTools/Program/Hail.hs \
+       src/Ganeti/HTools/Program/Harep.hs \
        src/Ganeti/HTools/Program/Hbal.hs \
        src/Ganeti/HTools/Program/Hcheck.hs \
        src/Ganeti/HTools/Program/Hinfo.hs \
@@ -519,22 +570,27 @@ HS_LIB_SRCS = \
        src/Ganeti/HTools/Program/Hroller.hs \
        src/Ganeti/HTools/Program/Main.hs \
        src/Ganeti/HTools/Types.hs \
+       src/Ganeti/Hypervisor/Xen/XmParser.hs \
+       src/Ganeti/Hypervisor/Xen/Types.hs \
        src/Ganeti/Hash.hs \
        src/Ganeti/JQueue.hs \
        src/Ganeti/JSON.hs \
        src/Ganeti/Jobs.hs \
        src/Ganeti/Logging.hs \
        src/Ganeti/Luxi.hs \
+       src/Ganeti/Monitoring/Server.hs \
        src/Ganeti/Network.hs \
        src/Ganeti/Objects.hs \
        src/Ganeti/OpCodes.hs \
        src/Ganeti/OpParams.hs \
        src/Ganeti/Path.hs \
        src/Ganeti/Query/Common.hs \
+       src/Ganeti/Query/Export.hs \
        src/Ganeti/Query/Filter.hs \
        src/Ganeti/Query/Group.hs \
        src/Ganeti/Query/Job.hs \
        src/Ganeti/Query/Language.hs \
+       src/Ganeti/Query/Network.hs \
        src/Ganeti/Query/Node.hs \
        src/Ganeti/Query/Query.hs \
        src/Ganeti/Query/Server.hs \
@@ -567,6 +623,7 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/HTools/Node.hs \
        test/hs/Test/Ganeti/HTools/PeerMap.hs \
        test/hs/Test/Ganeti/HTools/Types.hs \
+       test/hs/Test/Ganeti/Hypervisor/Xen/XmParser.hs \
        test/hs/Test/Ganeti/JSON.hs \
        test/hs/Test/Ganeti/Jobs.hs \
        test/hs/Test/Ganeti/JQueue.hs \
@@ -576,6 +633,7 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/OpCodes.hs \
        test/hs/Test/Ganeti/Query/Filter.hs \
        test/hs/Test/Ganeti/Query/Language.hs \
+       test/hs/Test/Ganeti/Query/Network.hs \
        test/hs/Test/Ganeti/Query/Query.hs \
        test/hs/Test/Ganeti/Rpc.hs \
        test/hs/Test/Ganeti/Runtime.hs \
@@ -592,8 +650,11 @@ HS_LIBTEST_SRCS = $(HS_LIB_SRCS) $(HS_TEST_SRCS)
 HS_BUILT_SRCS = \
        test/hs/Test/Ganeti/TestImports.hs \
        src/Ganeti/Constants.hs \
+       src/Ganeti/Curl/Internal.hs \
        src/Ganeti/Version.hs
-HS_BUILT_SRCS_IN = $(patsubst %,%.in,$(HS_BUILT_SRCS))
+HS_BUILT_SRCS_IN = \
+       $(patsubst %,%.in,$(filter-out src/Ganeti/Curl/Internal.hs,$(HS_BUILT_SRCS))) \
+       src/Ganeti/Curl/Internal.hsc
 
 HS_LIBTESTBUILT_SRCS = $(HS_LIBTEST_SRCS) $(HS_BUILT_SRCS)
 
@@ -611,7 +672,7 @@ doc/man-html/index.html: doc/manpages-enabled.rst $(mandocrst)
 doc/html/index.html doc/man-html/index.html: $(docinput) doc/conf.py \
        configure.ac $(RUN_IN_TEMPDIR) lib/build/sphinx_ext.py \
        lib/build/shell_example_lexer.py lib/opcodes.py lib/ht.py \
-       doc/css/style.css \
+       doc/css/style.css lib/rapi/connector.py lib/rapi/rlib2.py \
        | $(BUILT_PYTHON_SOURCES)
        @test -n "$(SPHINX)" || \
            { echo 'sphinx-build' not found during configure; exit 1; }
@@ -684,17 +745,29 @@ else
        exit 1;
 endif
 
+doc/users/%: doc/users/%.in Makefile $(REPLACE_VARS_SED)
+       cat $< | sed -f $(REPLACE_VARS_SED) | LC_ALL=C sort | uniq | (grep -v '^root' || true) > $@
+
+userspecs = \
+       doc/users/users \
+       doc/users/groups \
+       doc/users/groupmemberships
+
 # Things to build but not to install (add it to EXTRA_DIST if it should be
 # distributed)
 noinst_DATA = \
-       doc/html \
        $(BUILT_EXAMPLES) \
        doc/examples/bash_completion \
        doc/examples/bash_completion-debug \
+       $(userspecs) \
        $(manhtml)
 
+if HAS_SPHINX
 if MANPAGES_IN_DOC
 noinst_DATA += doc/man-html
+else
+noinst_DATA += doc/html
+endif
 endif
 
 gnt_scripts = \
@@ -763,7 +836,7 @@ $(HS_ALL_PROGS): %: %.hs $(HS_LIBTESTBUILT_SRCS) Makefile
        @rm -f $(notdir $@).tix
        $(GHC) --make \
          $(HFLAGS) \
-         $(HS_NOCURL) $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
+         $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
          -osuf $(notdir $@).o -hisuf $(notdir $@).hi \
          $(HEXTRA) $(HEXTRA_INT) $@
        @touch "$@"
@@ -778,7 +851,7 @@ test/hs/hpc-htools: HEXTRA_INT=-fhpc
 test/hs/hpc-mon-collector: HEXTRA_INT=-fhpc
 
 # test dependency
-test/hs/offline-tests.sh: test/hs/hpc-htools test/hs/hpc-mon-collector
+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
@@ -813,7 +886,15 @@ if ENABLE_CONFD
 src/ganeti-confd: src/hconfd
        cp -f $< $@
 
+src/ganeti-luxid: src/hluxid
+       cp -f $< $@
+
 nodist_sbin_SCRIPTS += src/ganeti-confd
+nodist_sbin_SCRIPTS += src/ganeti-luxid
+endif
+
+if ENABLE_MOND
+nodist_sbin_SCRIPTS += src/ganeti-mond
 endif
 
 python_scripts = \
@@ -890,7 +971,6 @@ EXTRA_DIST = \
        devel/upload \
        devel/webserver \
        tools/kvm-ifup.in \
-       tools/users-setup.in \
        tools/vcluster-setup.in \
        $(docinput) \
        doc/html \
@@ -901,6 +981,9 @@ EXTRA_DIST = \
        doc/examples/gnt-debug/README \
        doc/examples/gnt-debug/delay0.json \
        doc/examples/gnt-debug/delay50.json \
+       doc/users/groupmemberships.in \
+       doc/users/groups.in \
+       doc/users/users.in \
        test/py/lockperf.py \
        test/py/testutils.py \
        test/py/mocks.py \
@@ -921,8 +1004,10 @@ EXTRA_DIST = \
 man_MANS = \
        man/ganeti-cleaner.8 \
        man/ganeti-confd.8 \
+       man/ganeti-luxid.8 \
        man/ganeti-listrunner.8 \
        man/ganeti-masterd.8 \
+       man/ganeti-mond.8 \
        man/ganeti-noded.8 \
        man/ganeti-os-interface.7 \
        man/ganeti-extstorage-interface.7 \
@@ -940,6 +1025,7 @@ man_MANS = \
        man/gnt-os.8 \
        man/gnt-storage.8 \
        man/hail.1 \
+       man/harep.1 \
        man/hbal.1 \
        man/hcheck.1 \
        man/hinfo.1 \
@@ -961,11 +1047,14 @@ maninput = \
        $(mangen)
 
 TEST_FILES = \
+       test/autotools/autotools-check-news.test \
        test/data/htools/clean-nonzero-score.data \
        test/data/htools/common-suffix.data \
        test/data/htools/empty-cluster.data \
        test/data/htools/hail-alloc-drbd.json \
+       test/data/htools/hail-alloc-invalid-network.json \
        test/data/htools/hail-alloc-invalid-twodisks.json \
+       test/data/htools/hail-alloc-restricted-network.json \
        test/data/htools/hail-alloc-twodisks.json \
        test/data/htools/hail-change-group.json \
        test/data/htools/hail-invalid-reloc.json \
@@ -973,19 +1062,25 @@ TEST_FILES = \
        test/data/htools/hail-reloc-drbd.json \
        test/data/htools/hbal-excl-tags.data \
        test/data/htools/hbal-split-insts.data \
+       test/data/htools/hspace-tiered-dualspec.data \
        test/data/htools/hspace-tiered-ipolicy.data \
+       test/data/htools/hspace-tiered-resourcetypes.data \
+       test/data/htools/hspace-tiered.data \
        test/data/htools/invalid-node.data \
        test/data/htools/missing-resources.data \
+       test/data/htools/multiple-master.data \
        test/data/htools/n1-failure.data \
        test/data/htools/rapi/groups.json \
        test/data/htools/rapi/info.json \
        test/data/htools/rapi/instances.json \
        test/data/htools/rapi/nodes.json \
+       test/data/htools/unique-reboot-order.data \
        test/hs/shelltests/htools-balancing.test \
        test/hs/shelltests/htools-basic.test \
        test/hs/shelltests/htools-dynutil.test \
        test/hs/shelltests/htools-excl.test \
        test/hs/shelltests/htools-hail.test \
+       test/hs/shelltests/htools-hroller.test \
        test/hs/shelltests/htools-hspace.test \
        test/hs/shelltests/htools-invalid.test \
        test/hs/shelltests/htools-multi-group.test \
@@ -1013,6 +1108,8 @@ TEST_FILES = \
        test/data/bdev-rbd/output_invalid.txt \
        test/data/cert1.pem \
        test/data/cert2.pem \
+       test/data/cluster_config_2.7.json \
+       test/data/cluster_config_downgraded_2.7.json \
        test/data/instance-minor-pairing.txt \
        test/data/ip-addr-show-dummy0.txt \
        test/data/ip-addr-show-lo-ipv4.txt \
@@ -1027,6 +1124,8 @@ TEST_FILES = \
        test/data/kvm_0.9.1_help_boot_test.txt \
        test/data/kvm_1.0_help.txt \
        test/data/kvm_1.1.2_help.txt \
+       test/data/NEWS_OK.txt \
+       test/data/NEWS_previous_unreleased.txt \
        test/data/ovfdata/compr_disk.vmdk.gz \
        test/data/ovfdata/config.ini \
        test/data/ovfdata/corrupted_resources.ovf \
@@ -1058,16 +1157,25 @@ TEST_FILES = \
        test/data/proc_drbd83_sync.txt \
        test/data/proc_drbd83_sync_want.txt \
        test/data/proc_drbd83_sync_krnl2.6.39.txt \
+       test/data/proc_meminfo.txt \
+       test/data/proc_cpuinfo.txt \
+       test/data/qa-minimal-nodes-instances-only.json \
        test/data/sys_drbd_usermode_helper.txt \
        test/data/vgreduce-removemissing-2.02.02.txt \
        test/data/vgreduce-removemissing-2.02.66-fail.txt \
        test/data/vgreduce-removemissing-2.02.66-ok.txt \
        test/data/vgs-missing-pvs-2.02.02.txt \
        test/data/vgs-missing-pvs-2.02.66.txt \
+       test/data/xen-xm-info-4.0.1.txt \
+       test/data/xen-xm-list-4.0.1-dom0-only.txt \
+       test/data/xen-xm-list-4.0.1-four-instances.txt \
+       test/data/xen-xm-list-long-4.0.1.txt \
+       test/data/xen-xm-uptime-4.0.1.txt \
        test/py/ganeti-cli.test \
        test/py/gnt-cli.test \
        test/py/import-export_unittest-helper
 
+
 python_tests = \
        doc/examples/rapi_testutils.py \
        test/py/cfgupgrade_unittest.py \
@@ -1157,8 +1265,11 @@ dist_TESTS = \
        test/py/ganeti-cleaner_unittest.bash \
        test/py/import-export_unittest.bash \
        test/py/cli-test.bash \
-       test/py/bash_completion.bash \
-       $(python_tests)
+       test/py/bash_completion.bash
+
+if PY_UNIT
+dist_TESTS += $(python_tests)
+endif
 
 nodist_TESTS =
 check_SCRIPTS =
@@ -1191,9 +1302,9 @@ all_python_code = \
        $(pkglib_python_scripts) \
        $(nodist_pkglib_python_scripts) \
        $(nodist_tools_python_scripts) \
-       $(python_tests) \
        $(pkgpython_PYTHON) \
        $(client_PYTHON) \
+       $(cmdlib_PYTHON) \
        $(hypervisor_PYTHON) \
        $(rapi_PYTHON) \
        $(server_PYTHON) \
@@ -1207,6 +1318,10 @@ all_python_code = \
        $(noinst_PYTHON) \
        $(qa_scripts)
 
+if PY_UNIT
+all_python_code += $(python_tests)
+endif
+
 srclink_files = \
        man/footer.rst \
        test/py/check-cert-expired_unittest.bash \
@@ -1266,8 +1381,26 @@ tools/kvm-ifup: tools/kvm-ifup.in $(REPLACE_VARS_SED)
        sed -f $(REPLACE_VARS_SED) < $< > $@
        chmod +x $@
 
-tools/users-setup: tools/users-setup.in $(REPLACE_VARS_SED)
-       sed -f $(REPLACE_VARS_SED) < $< > $@
+tools/users-setup: Makefile $(userspecs)
+       set -e; \
+       { echo '#!/bin/sh'; \
+         echo 'if [ "x$$1" != "x--yes-do-it" ];'; \
+         echo 'then echo "This will do the following changes"'; \
+         $(AWK) -- '{print "echo + Will add group ",$$1; count++}\
+                    END {if (count == 0) {print "echo + No groups to add"}}' doc/users/groups; \
+         $(AWK) -- '{if (NF > 1) {print "echo + Will add user",$$1,"with primary group",$$2} \
+                                 else {print "echo + Will add user",$$1}; count++}\
+                    END {if (count == 0) {print "echo + No users to add"}}' doc/users/users; \
+         $(AWK) -- '{print "echo + Will add user",$$1,"to group",$$2}' doc/users/groupmemberships; \
+         echo 'echo'; \
+         echo 'echo "OK? (y/n)"'; \
+         echo 'read confirm'; \
+         echo 'if [ "x$$confirm" != "xy" ]; then exit 0; fi'; \
+         echo 'fi'; \
+         $(AWK) -- '{print "addgroup --system",$$1}' doc/users/groups; \
+         $(AWK) -- '{if (NF > 1) {print "adduser --system --ingroup",$$2,$$1} else {print "adduser --system",$$1}}' doc/users/users; \
+         $(AWK) -- '{print "adduser",$$1,$$2}' doc/users/groupmemberships; \
+       } > $@
        chmod +x $@
 
 tools/vcluster-setup: tools/vcluster-setup.in $(REPLACE_VARS_SED)
@@ -1369,6 +1502,9 @@ src/Ganeti/Constants.hs: src/Ganeti/Constants.hs.in \
          PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CONVERT_CONSTANTS); \
        } > $@
 
+src/Ganeti/Curl/Internal.hs: src/Ganeti/Curl/Internal.hsc | stamp-directories
+       hsc2hs -o $@ $<
+
 test/hs/Test/Ganeti/TestImports.hs: test/hs/Test/Ganeti/TestImports.hs.in \
        $(built_base_sources)
        set -e; \
@@ -1441,8 +1577,12 @@ lib/_autoconf.py: Makefile | stamp-directories
          echo "RAPI_GROUP = '$(RAPI_GROUP)'"; \
          echo "CONFD_USER = '$(CONFD_USER)'"; \
          echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \
+         echo "LUXID_USER = '$(LUXID_USER)'"; \
+         echo "LUXID_GROUP = '$(LUXID_GROUP)'"; \
          echo "NODED_USER = '$(NODED_USER)'"; \
          echo "NODED_GROUP = '$(NODED_GROUP)'"; \
+         echo "MOND_USER = '$(MOND_USER)'"; \
+         echo "MOND_GROUP = '$(MOND_GROUP)'"; \
          echo "DISK_SEPARATOR = '$(DISK_SEPARATOR)'"; \
          echo "QEMUIMG_PATH = '$(QEMUIMG_PATH)'"; \
          echo "HTOOLS = True"; \
@@ -1450,7 +1590,7 @@ lib/_autoconf.py: Makefile | stamp-directories
          echo "XEN_CMD = '$(XEN_CMD)'"; \
          echo "ENABLE_SPLIT_QUERY = $(ENABLE_SPLIT_QUERY)"; \
          echo "ENABLE_RESTRICTED_COMMANDS = $(ENABLE_RESTRICTED_COMMANDS)"; \
-         echo "ENABLE_MONITORING = $(ENABLE_MONITORING)"; \
+         echo "ENABLE_MOND = $(ENABLE_MOND)"; \
 ## Write dictionary with man page name as the key and the section number as the
 ## value
          echo "MAN_PAGES = {"; \
@@ -1520,13 +1660,18 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
          echo 's#@''GNTMASTERUSER@#$(MASTERD_USER)#g'; \
          echo 's#@''GNTRAPIUSER@#$(RAPI_USER)#g'; \
          echo 's#@''GNTCONFDUSER@#$(CONFD_USER)#g'; \
+         echo 's#@''GNTLUXIDUSER@#$(LUXID_USER)#g'; \
          echo 's#@''GNTNODEDUSER@#$(NODED_USER)#g'; \
+         echo 's#@''GNTMONDUSER@#$(MOND_USER)#g'; \
          echo 's#@''GNTRAPIGROUP@#$(RAPI_GROUP)#g'; \
          echo 's#@''GNTADMINGROUP@#$(ADMIN_GROUP)#g'; \
          echo 's#@''GNTCONFDGROUP@#$(CONFD_GROUP)#g'; \
+         echo 's#@''GNTLUXIDGROUP@#$(LUXID_GROUP)#g'; \
          echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
+         echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
          echo 's#@''GNTDAEMONSGROUP@#$(DAEMONS_GROUP)#g'; \
          echo 's#@''CUSTOM_ENABLE_CONFD@#$(ENABLE_CONFD)#g'; \
+         echo 's#@''CUSTOM_ENABLE_MOND@#$(ENABLE_MOND)#g'; \
          echo 's#@''MODULES@#$(strip $(lint_python_code))#g'; \
          echo 's#@''XEN_CONFIG_DIR@#$(XEN_CONFIG_DIR)#g'; \
          echo; \
@@ -1632,28 +1777,30 @@ check-local: check-dirs $(GENERATED_FILES)
        $(CHECK_VERSION) $(VERSION) $(top_srcdir)/NEWS
        RELEASE=$(PACKAGE_VERSION) $(CHECK_NEWS) < $(top_srcdir)/NEWS
        PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CHECK_IMPORTS) . $(standalone_python_modules)
-       @expver=$(VERSION_MAJOR).$(VERSION_MINOR); \
        error= ; \
-       if test "`head -n 1 $(top_srcdir)/README`" != "Ganeti $$expver"; then \
-         echo "Incorrect version in README, expected $$expver" >&2; \
-         error=1; \
-       fi; \
-       for file in doc/iallocator.rst doc/hooks.rst doc/virtual-cluster.rst \
-           doc/security.rst; do \
-         if test "`sed -ne '4 p' $(top_srcdir)/$$file`" != \
-           "Documents Ganeti version $$expver"; then \
-           echo "Incorrect version in $$file, expected $$expver" >&2; \
+       if [ "x`echo $(VERSION_SUFFIX)|grep 'alpha'`" == "x" ]; then \
+         expver=$(VERSION_MAJOR).$(VERSION_MINOR); \
+         if test "`head -n 1 $(top_srcdir)/README`" != "Ganeti $$expver"; then \
+           echo "Incorrect version in README, expected $$expver" >&2; \
+           error=1; \
+         fi; \
+         for file in doc/iallocator.rst doc/hooks.rst doc/virtual-cluster.rst \
+             doc/security.rst; do \
+           if test "`sed -ne '4 p' $(top_srcdir)/$$file`" != \
+             "Documents Ganeti version $$expver"; then \
+             echo "Incorrect version in $$file, expected $$expver" >&2; \
+             error=1; \
+           fi; \
+         done; \
+         if ! test -f $(top_srcdir)/doc/design-$$expver.rst; then \
+           echo "File $(top_srcdir)/doc/design-$$expver.rst not found" >&2; \
+           error=1; \
+         fi; \
+         if test "`sed -ne '5 p' $(top_srcdir)/doc/design-draft.rst`" != \
+           ".. Last updated for Ganeti $$expver"; then \
+           echo "doc/design-draft.rst was not updated for version $$expver" >&2; \
            error=1; \
          fi; \
-       done; \
-       if ! test -f $(top_srcdir)/doc/design-$$expver.rst; then \
-         echo "File $(top_srcdir)/doc/design-$$expver.rst not found" >&2; \
-         error=1; \
-       fi; \
-       if test "`sed -ne '5 p' $(top_srcdir)/doc/design-draft.rst`" != \
-         ".. Last updated for Ganeti $$expver"; then \
-         echo "doc/design-draft.rst was not updated for version $$expver" >&2; \
-         error=1; \
        fi; \
        for file in configure.ac $(HS_LIBTEST_SRCS) $(HS_PROG_SRCS); do \
          if test $$(wc --max-line-length < $(top_srcdir)/$$file) -gt 80; then \
@@ -1835,9 +1982,6 @@ $(APIDOC_HS_DIR)/index.html: $(HS_LIBTESTBUILT_SRCS) Makefile
        set -e ; \
        export LC_ALL=en_US.UTF-8; \
        OPTGHC="--optghc=-isrc --optghc=-itest/hs"; \
-       if [ "$(HS_NOCURL)" ]; \
-       then OPTGHC="$$OPTGHC --optghc=$(HS_NOCURL)"; \
-       fi; \
        if [ "$(HS_PARALLEL3)" ]; \
        then OPTGHC="$$OPTGHC --optghc=$(HS_PARALLEL3)"; \
        fi; \
@@ -1862,7 +2006,7 @@ TAGS: $(GENERATED_FILES)
        rm -f TAGS
        $(GHC) -e ":etags" -v0 \
          $(filter-out -O -Werror,$(HFLAGS)) \
-         $(HS_NOCURL) $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
+         $(HS_PARALLEL3) $(HS_REGEX_PCRE) \
          $(HS_LIBTEST_SRCS)
        find . -path './lib/*.py' -o -path './scripts/gnt-*' -o \
          -path './daemons/ganeti-*' -o -path './tools/*' -o \
@@ -1870,12 +2014,17 @@ TAGS: $(GENERATED_FILES)
          etags -l python -a -
 
 .PHONY: coverage
+
+COVERAGE_TESTS=
 if WANT_HTOOLS
-coverage: py-coverage hs-coverage
-else
-coverage: py-coverage
+COVERAGE_TESTS += hs-coverage
+endif
+if PY_UNIT
+COVERAGE_TESTS += py-coverage
 endif
 
+coverage: $(COVERAGE_TESTS)
+
 .PHONY: py-coverage
 py-coverage: $(GENERATED_FILES) $(python_tests)
        @test -n "$(PYCOVERAGE)" || \
@@ -1916,7 +2065,12 @@ live-test: all
          --srcdir=.. $(HPCEXCL) ; \
        hpc report --srcdir=.. live-test $(HPCEXCL)
 
-commit-check: distcheck lint apidoc
+commit-check: autotools-check distcheck lint apidoc
+
+autotools-check:
+       TESTDATA_DIR=./test/data shelltest $(SHELLTESTARGS) \
+  $(abs_top_srcdir)/test/autotools/*-*.test \
+  -- --hide-successes
 
 .PHONY: gitignore-check
 gitignore-check:
diff --git a/NEWS b/NEWS
index a276ffb..1e29070 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,13 +2,161 @@ News
 ====
 
 
-Version 2.7.2
--------------
+Version 2.8.0 rc3
+-----------------
 
 *(unreleased)*
 
+
+Incompatible/important changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Instance policy can contain multiple instance specs, as described in
+  the “Constrained instance sizes” section of :doc:`Partitioned Ganeti
+  <design-partitioned>`. As a consequence, it's not possible to partially change
+  or override instance specs. Bounding specs (min and max) can be specified as a
+  whole using the new option ``--ipolicy-bounds-specs``, while standard
+  specs use the new option ``--ipolicy-std-specs``.
+- The output of the info command of gnt-cluster, gnt-group, gnt-node,
+  gnt-instance is a valid YAML object.
+- hail now honors network restrictions when allocating nodes. This led to an
+  update of the IAllocator protocol. See the IAllocator documentation for
+  details.
+- confd now only answers static configuration request over the network. luxid
+  was extracted, listens on the local LUXI socket and responds to live queries.
+  This allows finer grained permissions if using separate users.
+
+New features
+~~~~~~~~~~~~
+
+- The :doc:`Remote API <rapi>` daemon now supports a command line flag
+  to always require authentication, ``--require-authentication``. It can
+  be specified in ``$sysconfdir/default/ganeti``.
+- A new cluster attribute 'enabled_disk_templates' is introduced. It will
+  be used to manage the disk templates to be used by instances in the cluster.
+  Initially, it will be set to a list that includes plain, drbd, if they were
+  enabled by specifying a volume group name, and file and sharedfile, if those
+  were enabled at configure time. Additionally, it will include all disk
+  templates that are currently used by instances. The order of disk templates
+  will be based on Ganeti's history of supporting them. In the future, the
+  first entry of the list will be used as a default disk template on instance
+  creation.
+- ``cfgupgrade`` now supports a ``--downgrade`` option to bring the
+  configuration back to the previous stable version.
+- Disk templates in group ipolicy can be restored to the default value.
+- Initial support for diskless instances and virtual clusters in QA.
+- More QA and unit tests for instance policies.
+- Every opcode now contains a reason trail (visible through ``gnt-job info``)
+  describing why the opcode itself was executed.
+- The monitoring daemon is now available. It allows users to query the cluster
+  for obtaining information about the status of the system. The daemon is only
+  responsible for providing the information over the network: the actual data
+  gathering is performed by data collectors (currently, only the DRBD status
+  collector is available).
+- In order to help developers work on Ganeti, a new script
+  (``devel/build_chroot``) is provided, for building a chroot that contains all
+  the required development libraries and tools for compiling Ganeti on a Debian
+  Squeeze system.
+- A new tool, ``harep``, for performing self-repair and recreation of instances
+  in Ganeti has been added.
+- Split queries are enabled for tags, network, exports, cluster info, groups,
+  jobs, nodes.
+- New command ``show-ispecs-cmd`` for ``gnt-cluster`` and ``gnt-group``.
+  It prints the command line to set the current policies, to ease
+  changing them.
+- Add the ``vnet_hdr`` HV parameter for KVM, to control whether the tap
+  devices for KVM virtio-net interfaces will get created with VNET_HDR
+  (IFF_VNET_HDR) support. If set to false, it disables offloading on the
+  virtio-net interfaces, which prevents host kernel tainting and log
+  flooding, when dealing with broken or malicious virtio-net drivers.
+  It's set to true by default.
+- Instance failover now supports a ``--cleanup`` parameter for fixing previous
+  failures.
+- Support 'viridian' parameter in Xen HVM
+- Support DSA SSH keys in bootstrap
+- To simplify the work of packaging frameworks that want to add the needed users
+  and groups in a split-user setup themselves, at build time three files in
+  ``doc/users`` will be generated. The ``groups`` files contains, one per line,
+  the groups to be generated, the ``users`` file contains, one per line, the
+  users to be generated, optionally followed by their primary group, where
+  important. The ``groupmemberships`` file contains, one per line, additional
+  user-group membership relations that need to be established. The syntax of
+  these files will remain stable in all future versions.
+
+
+New dependencies
+~~~~~~~~~~~~~~~~
+The following new dependencies have been added:
+
+For Haskell:
+- The ``curl`` library is not optional anymore for compiling the Haskell code.
+- ``snap-server`` library (if monitoring is enabled).
+
+For Python:
+- The minimum Python version needed to run Ganeti is now 2.6.
+- ``yaml`` library (only for running the QA).
+
+Since 2.8.0 rc2
+~~~~~~~~~~~~~~~
+
+- To simplify the work of packaging frameworks that want to add the needed users
+  and groups in a split-user setup themselves, at build time three files in
+  ``doc/users`` will be generated. The ``groups`` files contains, one per line,
+  the groups to be generated, the ``users`` file contains, one per line, the
+  users to be generated, optionally followed by their primary group, where
+  important. The ``groupmemberships`` file contains, one per line, additional
+  user-group membership relations that need to be established. The syntax of
+  these files will remain stable in all future versions.
+
+
+Version 2.8.0 rc2
+-----------------
+
+*(Released Tue, 27 Aug 2013)*
+
+The second release candidate of the 2.8 series. Since 2.8.0. rc1:
+
+- Support 'viridian' parameter in Xen HVM (Issue 233)
+- Include VCS version in ``gnt-cluster version``
+- Support DSA SSH keys in bootstrap (Issue 338)
+- Fix batch creation of instances
+- Use FQDN to check master node status (Issue 551)
+- Make the DRBD collector more failure-resilient
+
+
+Version 2.8.0 rc1
+-----------------
+
+*(Released Fri, 2 Aug 2013)*
+
+The first release candidate of the 2.8 series. Since 2.8.0 beta1:
+
+- Fix upgrading/downgrading from 2.7
+- Increase maximum RAPI message size
+- Documentation updates
+- Split ``confd`` between ``luxid`` and ``confd``
+- Merge 2.7 series up to the 2.7.1 release
+- Allow the ``modify_etc_hosts`` option to be changed
+- Add better debugging for ``luxid`` queries
+- Expose bulk parameter for GetJobs in RAPI client
+- Expose missing ``network`` fields in RAPI
+- Add some ``cluster verify`` tests
+- Some unittest fixes
+- Fix a malfunction in ``hspace``'s tiered allocation
+- Fix query compatibility between haskell and python implementations
+- Add the ``vnet_hdr`` HV parameter for KVM
+- Add ``--cleanup`` to instance failover
 - Change the connected groups format in ``gnt-network info`` output; it
-  was previously displayed as a raw list by mistake.
+  was previously displayed as a raw list by mistake. (Merged from 2.7)
+
+
+Version 2.8.0 beta1
+-------------------
+
+*(Released Mon, 24 Jun 2013)*
+
+This was the first beta release of the 2.8 series. All important changes
+are listed in the latest 2.8 entry.
 
 
 Version 2.7.1
diff --git a/README b/README
index 20282ee..99799e3 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.7
+Ganeti 2.8
 ==========
 
 For installation instructions, read the INSTALL and the doc/install.rst
diff --git a/UPGRADE b/UPGRADE
index 1ad4311..e193cfb 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -30,6 +30,10 @@ To run commands on all nodes, the `distributed shell (dsh)
 
     $ gnt-job list
 
+#. Pause the watcher for an hour (master node only)::
+
+    $ gnt-cluster watcher pause 1h
+
 #. Stop all daemons on all nodes::
 
     $ /etc/init.d/ganeti stop
@@ -51,6 +55,14 @@ To run commands on all nodes, the `distributed shell (dsh)
 
     $ /usr/lib/ganeti/ensure-dirs --full-run
 
+#. Create the (missing) required users and make users part of the required
+   groups on all nodes::
+
+    $ /usr/lib/ganeti/tools/users-setup
+
+   This will ask for confirmation. To execute directly, add the ``--yes-do-it``
+   option.
+
 #. Restart daemons on all nodes::
 
     $ /etc/init.d/ganeti restart
@@ -69,6 +81,85 @@ To run commands on all nodes, the `distributed shell (dsh)
 
     $ /etc/init.d/ganeti restart
 
+#. Enable the watcher again (master node only)::
+
+    $ gnt-cluster watcher continue
+
+#. Verify cluster (master node only)::
+
+    $ gnt-cluster verify
+
+Reverting an upgrade
+~~~~~~~~~~~~~~~~~~~~
+
+For going back between revisions (e.g. 2.1.1 to 2.1.0) no manual
+intervention is required, as for upgrades.
+
+Starting from version 2.8, ``cfgupgrade`` supports ``--downgrade``
+option to bring the configuration back to the previous stable version.
+This is useful if you upgrade Ganeti and after some time you run into
+problems with the new version. You can downgrade the configuration
+without losing the changes made since the upgrade. Any feature not
+supported by the old version will be removed from the configuration, of
+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.
+
+The procedure is similar to upgrading, but please notice that you have to
+revert the configuration **before** installing the old version.
+
+#. Ensure no jobs are running (master node only)::
+
+    $ gnt-job list
+
+#. Pause the watcher for an hour (master node only)::
+
+    $ gnt-cluster watcher pause 1h
+
+#. Stop all daemons on all nodes::
+
+    $ /etc/init.d/ganeti stop
+
+#. Backup old configuration (master node only)::
+
+    $ tar czf /var/lib/ganeti-$(date +\%FT\%T).tar.gz -C /var/lib ganeti
+
+#. Run cfgupgrade on the master node::
+
+    $ /usr/lib/ganeti/tools/cfgupgrade --verbose --downgrade --dry-run
+    $ /usr/lib/ganeti/tools/cfgupgrade --verbose --downgrade
+
+   You may want to copy all the messages about features that have been
+   removed during the downgrade, in case you want to restore them when
+   upgrading again.
+
+#. Install the old Ganeti version on all nodes
+
+   NB: in Ganeti 2.8, the ``cmdlib.py`` file was split into a series of files
+   contained in the ``cmdlib`` directory. If Ganeti is installed from sources
+   and not from a package, while downgrading Ganeti to a pre-2.8
+   version it is important to remember to remove the ``cmdlib`` directory
+   from the directory containing the Ganeti python files (which usually is
+   ``${PREFIX}/lib/python${VERSION}/dist-packages/ganeti``).
+   A simpler upgrade/downgrade procedure will be made available in future
+   versions of Ganeti.
+
+#. Restart daemons on all nodes::
+
+    $ /etc/init.d/ganeti restart
+
+#. Re-distribute configuration (master node only)::
+
+    $ gnt-cluster redist-conf
+
+#. Restart daemons again on all nodes::
+
+    $ /etc/init.d/ganeti restart
+
+#. Enable the watcher again (master node only)::
+
+    $ gnt-cluster watcher continue
+
 #. Verify cluster (master node only)::
 
     $ gnt-cluster verify
index 49ad9c5..63def12 100755 (executable)
@@ -859,7 +859,7 @@ def main():
                            debug=debug)
 
   # mon-collector, if monitoring is enabled
-  if _autoconf.ENABLE_MONITORING:
+  if _autoconf.ENABLE_MOND:
     WriteHaskellCmdCompletion(sw, "src/mon-collector", debug=debug)
 
   # Reset extglob to original value
index 0ad8d63..1405b58 100755 (executable)
@@ -39,7 +39,7 @@ DASHES_RE = re.compile(r"^\s*-+\s*$")
 RELEASED_RE = re.compile(r"^\*\(Released (?P<day>[A-Z][a-z]{2}),"
                          r" (?P<date>.+)\)\*$")
 UNRELEASED_RE = re.compile(r"^\*\(unreleased\)\*$")
-VERSION_RE = re.compile(r"^Version (\d+(\.\d+)+( (beta|rc)\d+)?)$")
+VERSION_RE = re.compile(r"^Version (\d+(\.\d+)+( (alpha|beta|rc)\d+)?)$")
 
 #: How many days release timestamps may be in the future
 TIMESTAMP_FUTURE_DAYS_MAX = 3
@@ -55,7 +55,7 @@ def Error(msg):
 
 
 def ReqNLines(req, count_empty, lineno, line):
-  """Check if we have N empty lines.
+  """Check if we have N empty lines before the current one.
 
   """
   if count_empty < req:
@@ -68,6 +68,21 @@ def ReqNLines(req, count_empty, lineno, line):
           (lineno, line, req, count_empty))
 
 
+def IsAlphaVersion(version):
+  return "alpha" in version
+
+
+def UpdateAllowUnreleased(allow_unreleased, version_match, release):
+  if not allow_unreleased:
+    return False
+  if IsAlphaVersion(release):
+    return True
+  version = version_match.group(1)
+  if version == release:
+    return False
+  return True
+
+
 def main():
   # Ensure "C" locale is used
   curlocale = locale.getlocale()
@@ -95,8 +110,8 @@ def main():
         Error("Line %s: Duplicate release %s found" %
               (fileinput.filelineno(), version))
       found_versions.add(version)
-      if version == release:
-        allow_unreleased = False
+      allow_unreleased = UpdateAllowUnreleased(allow_unreleased, version_match,
+                                               release)
 
     unreleased_match = UNRELEASED_RE.match(line)
     if unreleased_match and not allow_unreleased:
index be7ecef..9b9c31a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/bash
 #
 
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010,2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -31,8 +31,8 @@ case "$version" in
   # Format "x.y.z"
   $numpat.$numpat.$numpat) : ;;
 
-  # Format "x.y.z~rcN" or "x.y.z~betaN" for N > 0
-  $numpat.$numpat.$numpat~@(rc|beta)[1-9]*([0-9])) : ;;
+  # Format "x.y.z~rcN" or "x.y.z~betaN" or "x.y.z~alphaN" for N > 0
+  $numpat.$numpat.$numpat~@(rc|beta|alpha)[1-9]*([0-9])) : ;;
 
   *)
     echo "Invalid version format: $version" >&2
@@ -42,10 +42,17 @@ esac
 
 readonly newsver="Version ${version/\~/ }"
 
-if ! grep -q -x "$newsver" $newsfile
+# Only alpha versions are allowed not to have their own NEWS section yet
+set +e
+FOUND=x`echo $version | grep "alpha[1-9]*[0-9]$"`
+set -e
+if [ $FOUND == "x" ]
 then
-  echo "Unable to find heading '$newsver' in NEWS" >&2
-  exit 1
+  if ! grep -q -x "$newsver" $newsfile
+  then
+    echo "Unable to find heading '$newsver' in NEWS" >&2
+    exit 1
+  fi
 fi
 
 exit 0
index 0131e05..6ce2935 100644 (file)
@@ -1,8 +1,8 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [7])
-m4_define([gnt_version_revision], [1])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_version_minor], [8])
+m4_define([gnt_version_revision], [0])
+m4_define([gnt_version_suffix], [~rc2])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
@@ -243,15 +243,21 @@ AC_ARG_WITH([user-prefix],
   [user_masterd="${withval}masterd";
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
-   user_noded="$user_default"],
+   user_luxid="${withval}luxid";
+   user_noded="$user_default";
+   user_mond="${withval}mond"],
   [user_masterd="$user_default";
    user_rapi="$user_default";
    user_confd="$user_default";
-   user_noded="$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(LUXID_USER, $user_luxid)
 AC_SUBST(NODED_USER, $user_noded)
+AC_SUBST(MOND_USER, $user_mond)
 
 # --with-group-prefix=...
 AC_ARG_WITH([group-prefix],
@@ -263,26 +269,33 @@ AC_ARG_WITH([group-prefix],
   [group_rapi="${withval}rapi";
    group_admin="${withval}admin";
    group_confd="${withval}confd";
+   group_luxid="${withval}luxid";
    group_masterd="${withval}masterd";
    group_noded="$group_default";
-   group_daemons="${withval}daemons";],
+   group_daemons="${withval}daemons";
+   group_mond="${withval}mond"],
   [group_rapi="$group_default";
    group_admin="$group_default";
    group_confd="$group_default";
+   group_luxid="$group_default";
    group_masterd="$group_default";
    group_noded="$group_default";
-   group_daemons="$group_default"])
+   group_daemons="$group_default";
+   group_mond="$group_default"])
 AC_SUBST(RAPI_GROUP, $group_rapi)
 AC_SUBST(ADMIN_GROUP, $group_admin)
 AC_SUBST(CONFD_GROUP, $group_confd)
+AC_SUBST(LUXID_GROUP, $group_luxid)
 AC_SUBST(MASTERD_GROUP, $group_masterd)
 AC_SUBST(NODED_GROUP, $group_noded)
 AC_SUBST(DAEMONS_GROUP, $group_daemons)
+AC_SUBST(MOND_GROUP, $group_mond)
 
 # Print the config to the user
 AC_MSG_NOTICE([Running ganeti-masterd as $group_masterd:$group_masterd])
 AC_MSG_NOTICE([Running ganeti-rapi as $user_rapi:$group_rapi])
 AC_MSG_NOTICE([Running ganeti-confd as $user_confd:$group_confd])
+AC_MSG_NOTICE([Running ganeti-luxid as $user_luxid:$group_luxid])
 AC_MSG_NOTICE([Group for daemons is $group_daemons])
 AC_MSG_NOTICE([Group for clients is $group_admin])
 
@@ -395,6 +408,7 @@ else
     AC_MSG_ERROR([Sphinx 1.0 or higher is required])
   fi
 fi
+AM_CONDITIONAL([HAS_SPHINX], [test -n "$SPHINX"])
 
 AC_ARG_ENABLE([manpages-in-doc],
   [AS_HELP_STRING([--enable-manpages-in-doc],
@@ -467,14 +481,6 @@ then
   AC_MSG_WARN([qemu-img not found, using ovfconverter will not be possible])
 fi
 
-# --enable-htools-rapi
-HTOOLS_RAPI=
-AC_ARG_ENABLE([htools-rapi],
-        [AS_HELP_STRING([--enable-htools-rapi],
-        [enable use of curl in the Haskell code (default: check)])],
-        [],
-        [enable_htools_rapi=check])
-
 # --enable-confd
 ENABLE_CONFD=
 AC_ARG_ENABLE([confd],
@@ -483,10 +489,10 @@ AC_ARG_ENABLE([confd],
   [],
   [enable_confd=check])
 
-ENABLE_MONITORING=
+ENABLE_MOND=
 AC_ARG_ENABLE([monitoring],
   [AS_HELP_STRING([--enable-monitoring],
-  [enable the ganeti monitoring agent (default: check)])],
+  [enable the ganeti monitoring daemon (default: check)])],
   [],
   [enable_monitoring=check])
 
@@ -517,29 +523,13 @@ fi
 
 # check for modules, first custom/special checks
 AC_MSG_NOTICE([checking for required haskell modules])
-HS_NOCURL=-DNO_CURL
-if test "$enable_htools_rapi" != no; then
-  AC_GHC_PKG_CHECK([curl], [HS_NOCURL=], [])
-  if test -n "$HS_NOCURL"; then
-    if test "$enable_htools_rapi" = check; then
-      AC_MSG_WARN(m4_normalize([The curl library was not found, Haskell
-                                code will be compiled without RAPI support]))
-    else
-      AC_MSG_FAILURE(m4_normalize([The curl library was not found, but it has
-                                   been requested]))
-    fi
-  else
-    AC_MSG_NOTICE([Enabling curl/RAPI/RPC usage in Haskell code])
-  fi
-fi
-AC_SUBST(HS_NOCURL)
-
 HS_PARALLEL3=
 AC_GHC_PKG_CHECK([parallel-3.*], [HS_PARALLEL3=-DPARALLEL3],
                  [AC_GHC_PKG_REQUIRE(parallel)], t)
 AC_SUBST(HS_PARALLEL3)
 
 # and now standard modules
+AC_GHC_PKG_REQUIRE(curl)
 AC_GHC_PKG_REQUIRE(json)
 AC_GHC_PKG_REQUIRE(network)
 AC_GHC_PKG_REQUIRE(mtl)
@@ -576,12 +566,14 @@ fi
 AC_SUBST(ENABLE_CONFD, $has_confd)
 AM_CONDITIONAL([ENABLE_CONFD], [test x$has_confd = xTrue])
 
-#extra modules for monitoring agent functionality
+#extra modules for monitoring daemon functionality
 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"])
   MONITORING_DEP=
   if test "$has_confd" = False; then
     MONITORING_DEP="$MONITORING_DEP confd"
@@ -591,7 +583,7 @@ if test "$enable_monitoring" != no; then
     has_monitoring_pkg=True
   elif test "$enable_monitoring" = check; then
     AC_MSG_WARN(m4_normalize([The required extra libraries for the monitoring
-                              agent were not found ($MONITORING_PKG),
+                              daemon were not found ($MONITORING_PKG),
                               monitoring disabled]))
   else
     AC_MSG_FAILURE(m4_normalize([The monitoring functionality was requested, but
@@ -616,8 +608,8 @@ if test "$has_monitoring_pkg" = True -a "$has_monitoring_dep" = True; then
   has_monitoring=True
   AC_MSG_NOTICE([Enabling the monitoring agent usage])
 fi
-AC_SUBST(ENABLE_MONITORING, $has_monitoring)
-AM_CONDITIONAL([ENABLE_MONITORING], [test "$has_monitoring" = True])
+AC_SUBST(ENABLE_MOND, $has_monitoring)
+AM_CONDITIONAL([ENABLE_MOND], [test "$has_monitoring" = True])
 
 # development modules
 HS_NODEV=
@@ -660,8 +652,8 @@ AC_ARG_ENABLE([split-query],
         ;;
     esac
   ]],
-  [[case "x${has_confd}x${HS_NOCURL}x" in
-     xTruexx)
+  [[case "x${has_confd}x" in
+     xTruex)
        enable_split_query=True
        ;;
      *)
@@ -674,10 +666,6 @@ 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 -a x$HS_NOCURL != x; then
-  AC_MSG_ERROR([Split queries require the htools-rapi feature (curl library)])
-fi
-
 if test x$enable_split_query = xTrue; then
   AC_MSG_NOTICE([Split query functionality enabled])
 fi
@@ -787,7 +775,7 @@ fi
 AC_SUBST(MAN_HAS_WARNINGS)
 
 # Check for Python
-AM_PATH_PYTHON(2.4)
+AM_PATH_PYTHON(2.6)
 
 AC_PYTHON_MODULE(OpenSSL, t)
 AC_PYTHON_MODULE(simplejson, t)
@@ -799,6 +787,22 @@ AC_PYTHON_MODULE(ipaddr, t)
 AC_PYTHON_MODULE(affinity)
 AC_PYTHON_MODULE(paramiko)
 
+# Development-only Python modules
+PY_NODEV=
+AC_PYTHON_MODULE(yaml)
+if test $HAVE_PYMOD_YAML == "no"; then
+  PY_NODEV="$PY_NODEV yaml"
+fi
+
+if test -n "$PY_NODEV"; then
+  AC_MSG_WARN(m4_normalize([Required development modules ($PY_NODEV) were not
+                            found, you won't be able to run Python unittests]))
+else
+  AC_MSG_NOTICE([Python development modules found, unittests enabled])
+fi
+AC_SUBST(PY_NODEV)
+AM_CONDITIONAL([PY_UNIT], [test -n $PY_NODEV])
+
 AC_CONFIG_FILES([ Makefile ])
 
 AC_OUTPUT
index 848a122..44c39a9 100644 (file)
@@ -39,12 +39,23 @@ _confd_enabled() {
 
 if _confd_enabled; then
   DAEMONS+=( ganeti-confd )
+  DAEMONS+=( ganeti-luxid )
+fi
+
+_mond_enabled() {
+  [[ "@CUSTOM_ENABLE_MOND@" == True ]]
+}
+
+if _mond_enabled; then
+  DAEMONS+=( ganeti-mond )
 fi
 
 NODED_ARGS=
 MASTERD_ARGS=
 CONFD_ARGS=
+LUXID_ARGS=
 RAPI_ARGS=
+MOND_ARGS=
 
 # Read defaults file if it exists
 if [[ -s $defaults_file ]]; then
@@ -73,12 +84,18 @@ _daemon_usergroup() {
     confd)
       echo "@GNTCONFDUSER@:@GNTCONFDGROUP@"
       ;;
+    luxid)
+      echo "@GNTLUXIDUSER@:@GNTLUXIDGROUP@"
+      ;;
     rapi)
       echo "@GNTRAPIUSER@:@GNTRAPIGROUP@"
       ;;
     noded)
       echo "@GNTNODEDUSER@:@GNTDAEMONSGROUP@"
       ;;
+    mond)
+      echo "@GNTMONDUSER@:@GNTMONDGROUP@"
+      ;;
     *)
       echo "root:@GNTDAEMONSGROUP@"
       ;;
@@ -216,7 +233,8 @@ start() {
   local usergroup=$(_daemon_usergroup $plain_name)
   local daemonexec=$(_daemon_executable $name)
 
-  if [[ "$name" == ganeti-confd ]] && ! _confd_enabled; then
+  if ( [[ "$name" == ganeti-confd ]] || [[ "$name" == ganeti-luxid ]] ) \
+      && ! _confd_enabled; then
     echo 'ganeti-confd disabled at build time' >&2
     return 1
   fi
@@ -241,11 +259,6 @@ start() {
       $daemonexec $args "$@"
   fi
 
-  # FIXME: This is a workaround for issue 477. Remove this once confd does not
-  # mess up the permissions anymore.
-  if [[ "$name" == ganeti-confd ]]; then
-    @PKGLIBDIR@/ensure-dirs;
-  fi
 }
 
 # Stops a daemon
diff --git a/devel/build_chroot b/devel/build_chroot
new file mode 100755 (executable)
index 0000000..c65b5ff
--- /dev/null
@@ -0,0 +1,231 @@
+#!/bin/bash
+#Configuration
+: ${ARCH:=amd64}
+: ${DIST_RELEASE:=squeeze}
+: ${CONF_DIR:=/etc/schroot/chroot.d}
+: ${CHROOT_DIR:=/srv/chroot}
+: ${ALTERNATIVE_EDITOR:=/usr/bin/vim.basic}
+# The value of DATA_DIR is read as well from the environment.
+
+#Automatically generated variables
+CHROOTNAME=$DIST_RELEASE-$ARCH
+CHNAME=building_$CHROOTNAME
+TEMP_CHROOT_CONF=$CONF_DIR/$CHNAME.conf
+FINAL_CHROOT_CONF=$CHROOTNAME.conf
+ROOT=`pwd`
+CHDIR=$ROOT/$CHNAME
+USER=`whoami`
+COMP_FILENAME=$CHROOTNAME.tar.gz
+COMP_FILEPATH=$ROOT/$COMP_FILENAME
+TEMP_DATA_DIR=`mktemp -d`
+ACTUAL_DATA_DIR=$DATA_DIR
+ACTUAL_DATA_DIR=${ACTUAL_DATA_DIR:-$TEMP_DATA_DIR}
+
+#Runnability checks
+if [ $USER != 'root' ]
+then
+  echo "This script requires root permissions to run"
+  exit
+fi
+
+if [ -f $TEMP_CHROOT_CONF ]
+then
+  echo "The configuration file name for the temporary chroot"
+  echo "  $TEMP_CHROOT_CONF"
+  echo "already exists."
+  echo "Remove it or change the CHNAME value in the script."
+  exit
+fi
+
+#Create configuration dir and files if they do not exist
+if [ ! -d $ACTUAL_DATA_DIR ]
+then
+  mkdir $ACTUAL_DATA_DIR
+  echo "The data directory"
+  echo "  $ACTUAL_DATA_DIR"
+  echo "has been created."
+fi
+
+if [ ! -f $ACTUAL_DATA_DIR/final.schroot.conf.in ]
+then
+  cat <<END >$ACTUAL_DATA_DIR/final.schroot.conf.in
+[${CHROOTNAME}]
+description=Debian ${DIST_RELEASE} ${ARCH}
+groups=src
+source-root-groups=root
+type=file
+file=${CHROOT_DIR}/${COMP_FILENAME}
+END
+  echo "The file"
+  echo " $ACTUAL_DATA_DIR/final.schroot.conf.in"
+  echo "has been created with default configurations."
+fi
+
+if [ ! -f $ACTUAL_DATA_DIR/temp.schroot.conf.in ]
+then
+  cat <<END >$ACTUAL_DATA_DIR/temp.schroot.conf.in
+[${CHNAME}]
+description=Debian ${DIST_RELEASE} ${ARCH}
+directory=${CHDIR}
+groups=src
+users=root
+type=directory
+END
+  echo "The file"
+  echo " $ACTUAL_DATA_DIR/temp.schroot.conf.in"
+  echo "has been created with default configurations."
+fi
+
+#Stop on errors
+set -e
+
+#Cleanup
+rm -rf $CHDIR
+mkdir $CHDIR
+
+#Install tools for building chroots
+apt-get install -y schroot debootstrap
+
+shopt -s expand_aliases
+alias in_chroot='schroot -c $CHNAME -d / '
+function subst_variables {
+  sed \
+    -e "s/\${ARCH}/$ARCH/" \
+    -e "s*\${CHDIR}*$CHDIR*" \
+    -e "s/\${CHNAME}/$CHNAME/" \
+    -e "s/\${CHROOTNAME}/$CHROOTNAME/" \
+    -e "s*\${CHROOT_DIR}*$CHROOT_DIR*" \
+    -e "s/\${COMP_FILENAME}/$COMP_FILENAME/" \
+    -e "s/\${DIST_RELEASE}/$DIST_RELEASE/" $@
+}
+
+#Generate chroot configurations
+cat $ACTUAL_DATA_DIR/temp.schroot.conf.in | subst_variables > $TEMP_CHROOT_CONF
+cat $ACTUAL_DATA_DIR/final.schroot.conf.in | subst_variables > $FINAL_CHROOT_CONF
+
+#Install the base system
+debootstrap --arch $ARCH $DIST_RELEASE $CHDIR
+
+APT_INSTALL="apt-get install -y --no-install-recommends"
+
+echo "deb http://backports.debian.org/debian-backports" \
+     "$DIST_RELEASE-backports main contrib non-free" \
+     > $CHDIR/etc/apt/sources.list.d/backports.list
+
+#Install all the packages
+in_chroot -- \
+  apt-get update
+
+#Install selected packages from backports
+in_chroot -- \
+  apt-get -y --no-install-recommends -t squeeze-backports install \
+    git \
+    git-email \
+    vim
+
+in_chroot -- \
+  $APT_INSTALL python-setuptools build-essential python-dev sudo automake \
+               fakeroot rsync locales less
+
+echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
+
+in_chroot -- \
+  locale-gen
+
+in_chroot -- \
+  $APT_INSTALL lvm2 ssh bridge-utils iproute iputils-arping \
+               ndisc6 python python-pyopenssl openssl \
+               python-pyparsing python-simplejson \
+               python-pyinotify python-pycurl python-yaml socat fping
+
+in_chroot -- \
+  $APT_INSTALL python-paramiko qemu-utils
+
+in_chroot -- \
+  easy_install affinity bitarray ipaddr
+
+#Haskell packages
+in_chroot -- \
+  $APT_INSTALL ghc6 \
+               libghc6-parallel-dev libghc6-deepseq-dev \
+               libghc6-curl-dev
+
+in_chroot -- \
+  $APT_INSTALL cabal-install
+
+in_chroot -- \
+  cabal update
+
+in_chroot -- \
+  $APT_INSTALL libpcre3-dev
+
+in_chroot -- \
+  cabal install --global \
+    network==2.3 \
+    regex-pcre==0.94.2 \
+    hinotify==0.3.2 \
+    hslogger==1.1.4 \
+    attoparsec==0.10.1.1\
+    quickcheck==2.5.1.1 \
+    crypto==4.2.4 \
+    monadcatchio-transformers==0.2.2.0 \
+    mtl==2.0.1.0 \
+    hashable==1.1.2.0 \
+    case-insensitive==0.3 \
+    parsec==3.0.1 \
+    network==2.3 \
+    snap-server==0.8.1 \
+    text==0.11.3.0 \
+    vector==0.9.1 \
+    json==0.4.4
+
+#Python development tools
+in_chroot -- \
+  $APT_INSTALL pandoc python-epydoc graphviz
+
+in_chroot -- \
+  easy_install sphinx==1.1.3 \
+               logilab-common \
+               logilab-astng==0.23.1 \
+               pylint==0.25.1 \
+               pep8==1.2 \
+               coverage
+
+#Haskell development tools
+in_chroot -- \
+  cabal install --global \
+    hunit==1.2.5.2 \
+    happy==1.18.10 \
+    hlint==1.8.34 \
+    hscolour==1.20.3 \
+    temporary==1.1.2.3 \
+    test-framework==0.6.1 \
+    test-framework-hunit==0.2.7 \
+    test-framework-quickcheck2==0.2.12.3
+
+in_chroot -- \
+  cabal install --global shelltestrunner
+
+#Tools for creating debian packages
+in_chroot -- \
+  $APT_INSTALL python-docutils debhelper quilt
+
+#Set default editor
+in_chroot -- \
+  update-alternatives --set editor $ALTERNATIVE_EDITOR
+
+rm -f $COMP_FILEPATH
+echo "Creating compressed schroot image..."
+cd $CHDIR
+tar czf $COMP_FILEPATH ./*
+cd $ROOT
+
+rm -rf $CHDIR
+rm -f $TEMP_CHROOT_CONF
+rm -rf $TEMP_DATA_DIR
+
+echo "Chroot created. In order to run it:"
+echo " * Copy the file $FINAL_CHROOT_CONF to $CONF_DIR/$FINAL_CHROOT_CONF"
+echo " * Copy the file $COMP_FILEPATH to $CHROOT_DIR/$COMP_FILENAME"
+
+echo "Then run \"schroot -c $CHROOTNAME\""
diff --git a/devel/check-split-query b/devel/check-split-query
new file mode 100755 (executable)
index 0000000..506de86
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# Copyright (C) 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# Checks query equivalence between masterd and confd
+#
+# This is not (currently) run automatically during QA, but you can run
+# it manually on a test cluster. It will force all queries known to be
+# converted via both paths and check the difference, via both 'list'
+# and 'list-fields'. For best results, it should be run on a non-empty
+# cluster.
+#
+# Also note that this is not expected to show 100% perfect matches,
+# since the JSON output differs slightly for complex data types
+# (e.g. dictionaries with different sort order for keys, etc.).
+#
+# Current known delta:
+# - all dicts, sort order
+# - ctime is always defined in Haskell as epoch 0 if missing
+
+MA=`mktemp master.XXXXXX`
+CF=`mktemp confd.XXXXXX`
+trap 'rm -f "$MA" "$CF"' EXIT
+trap 'exit 1' SIGINT
+
+RET=0
+SEP="--separator=,"
+ENABLED_QUERIES="node group network backup"
+
+test_cmd() {
+  cmd="$1"
+  desc="$2"
+  FORCE_LUXI_SOCKET=master $cmd > "$MA"
+  FORCE_LUXI_SOCKET=query  $cmd > "$CF"
+  diff -u "$MA" "$CF" || {
+    echo "Mismatch in $desc, see above."
+    RET=1
+  }
+}
+
+for kind in $ENABLED_QUERIES; do
+  all_fields=$(FORCE_LUXI_SOCKET=master gnt-$kind list-fields \
+    --no-headers --separator=,|cut -d, -f1)
+  comma_fields=$(echo $all_fields|tr ' ' ,|sed -e 's/,$//')
+  for op in list list-fields; do
+    test_cmd "gnt-$kind $op $SEP" "$kind $op"
+  done
+  #test_cmd "gnt-$kind list $SEP -o$comma_fields" "$kind list with all fields"
+  for field in $all_fields; do
+    test_cmd "gnt-$kind list $SEP -o$field" "$kind list for field $field"
+  done
+done
+
+exit $RET
index 9f4509f..d3f6594 100755 (executable)
@@ -38,13 +38,15 @@ add_reviewed_by() {
   grep -q '^Reviewed-by: ' "$msgfile" && return
 
   perl -i -e '
+  my $reviewer = $ENV{"REVIEWER"};
+  defined($reviewer) or $reviewer = "";
   my $sob = 0;
   while (<>) {
     if ($sob == 0 and m/^Signed-off-by:/) {
       $sob = 1;
 
     } elsif ($sob == 1 and not m/^Signed-off-by:/) {
-      print "Reviewed-by: \n";
+      print "Reviewed-by: $reviewer\n";
       $sob = -1;
     }
 
@@ -52,7 +54,7 @@ add_reviewed_by() {
   }
 
   if ($sob == 1) {
-    print "Reviewed-by: \n";
+    print "Reviewed-by: $reviewer\n";
   }
   ' "$msgfile"
 }
index 0c26a1a..da2e4df 100755 (executable)
@@ -22,7 +22,7 @@
 
 # Usage: upload node-{1,2,3}
 # it will upload the python libraries to
-# $prefix/lib/python2.4/site-packages/ganeti and the command line utils to
+# $prefix/lib/python2.X/dist-packages/ganeti and the command line utils to
 # $prefix/sbin. It needs passwordless root login to the nodes.
 
 set -e -u
index 6882b02..6859543 100644 (file)
@@ -277,7 +277,7 @@ can give include, among others:
 
 - Arguments for the NICs of the instance; by default, a single-NIC
   instance is created. The IP and/or bridge of the NIC can be changed
-  via ``--nic 0:ip=IP,bridge=BRIDGE``
+  via ``--net 0:ip=IP,link=BRIDGE``
 
 See :manpage:`ganeti-instance(8)` for the detailed option list.
 
@@ -1339,6 +1339,18 @@ SSH changes and log directories:
 Otherwise, if you plan to re-create the cluster, you can just go ahead
 and rerun ``gnt-cluster init``.
 
+Monitoring the cluster
+----------------------
+
+Starting with Ganeti 2.8, a monitoring daemon is available, providing
+information about the status and the performance of the system.
+
+The monitoring daemon runs on every node, listening on TCP port 1815. Each
+instance of the daemon provides information related to the node it is running
+on.
+
+.. include:: monitoring-query-format.rst
+
 Tags handling
 -------------
 
@@ -1398,6 +1410,110 @@ hierarchical kind of way)::
   /cluster foo
   /instances/instance1 owner:bar
 
+Autorepair
+----------
+
+The tool ``harep`` can be used to automatically fix some problems that are
+present in the cluster.
+
+It is mainly meant to be regularly and automatically executed
+as a cron job. This is quite evident by considering that, when executed, it does
+not immediately fix all the issues of the instances of the cluster, but it
+cycles the instances through a series of states, one at every ``harep``
+execution. Every state performs a step towards the resolution of the problem.
+This process goes on until the instance is brought back to the healthy state,
+or the tool realizes that it is not able to fix the instance, and
+therefore marks it as in failure state.
+
+Allowing harep to act on the cluster
+++++++++++++++++++++++++++++++++++++
+
+By default, ``harep`` checks the status of the cluster but it is not allowed to
+perform any modification. Modification must be explicitly allowed by an
+appropriate use of tags. Tagging can be applied at various levels, and can
+enable different kinds of autorepair, as hereafter described.
+
+All the tags that authorize ``harep`` to perform modifications follow this
+syntax::
+
+  ganeti:watcher:autorepair:<type>
+
+where ``<type>`` indicates the kind of intervention that can be performed. Every
+possible value of ``<type>`` includes at least all the authorization of the
+previous one, plus its own. The possible values, in increasing order of
+severity, are:
+
+- ``fix-storage`` allows a disk replacement or another operation that
+  fixes the instance backend storage without affecting the instance
+  itself. This can for example recover from a broken drbd secondary, but
+  risks data loss if something is wrong on the primary but the secondary
+  was somehow recoverable.
+- ``migrate`` allows an instance migration. This can recover from a
+  drained primary, but can cause an instance crash in some cases (bugs).
+- ``failover`` allows instance reboot on the secondary. This can recover
+  from an offline primary, but the instance will lose its running state.
+- ``reinstall`` allows disks to be recreated and an instance to be
+  reinstalled. This can recover from primary&secondary both being
+  offline, or from an offline primary in the case of non-redundant
+  instances. It causes data loss.
+
+These autorepair tags can be applied to a cluster, a nodegroup or an instance,
+and will act where they are applied and to everything in the entities sub-tree
+(e.g. a tag applied to a nodegroup will apply to all the instances contained in
+that nodegroup, but not to the rest of the cluster).
+
+If there are multiple ``ganeti:watcher:autorepair:<type>`` tags in an
+object (cluster, node group or instance), the least destructive tag
+takes precedence. When multiplicity happens across objects, the nearest
+tag wins. For example, if in a cluster with two instances, *I1* and
+*I2*, *I1* has ``failover``, and the cluster itself has both
+``fix-storage`` and ``reinstall``, *I1* will end up with ``failover``
+and *I2* with ``fix-storage``.
+
+Limiting harep
+++++++++++++++
+
+Sometimes it is useful to stop harep from performing its task temporarily,
+and it is useful to be able to do so without distrupting its configuration, that
+is, without removing the authorization tags. In order to do this, suspend tags
+are provided.
+
+Suspend tags can be added to cluster, nodegroup or instances, and act on the
+entire entities sub-tree. No operation will be performed by ``harep`` on the
+instances protected by a suspend tag. Their syntax is as follows::
+
+  ganeti:watcher:autorepair:suspend[:<timestamp>]
+
+If there are multiple suspend tags in an object, the form without timestamp
+takes precedence (permanent suspension); or, if all object tags have a
+timestamp, the one with the highest timestamp.
+
+Tags with a timestamp will be automatically removed when the time indicated by
+the timestamp is passed. Indefinite suspension tags have to be removed manually.
+
+Result reporting
+++++++++++++++++
+
+Harep will report about the result of its actions both through its CLI, and by
+adding tags to the instances it operated on. Such tags will follow the syntax
+hereby described::
+
+  ganeti:watcher:autorepair:result:<type>:<id>:<timestamp>:<result>:<jobs>
+
+If this tag is present a repair of type ``type`` has been performed on
+the instance and has been completed by ``timestamp``. The result is
+either ``success``, ``failure`` or ``enoperm``, and jobs is a
+*+*-separated list of jobs that were executed for this repair.
+
+An ``enoperm`` result is an error state due to permission problems. It
+is returned when the repair cannot proceed because it would require to perform
+an operation that is not allowed by the ``ganeti:watcher:autorepair:<type>`` tag
+that is defining the instance autorepair permissions.
+
+NB: if an instance repair ends up in a failure state, it will not be touched
+again by ``harep`` until it has been manually fixed by the system administrator
+and the ``ganeti:watcher:autorepair:result:failure:*`` tag has been manually
+removed.
 
 Job operations
 --------------
@@ -1580,7 +1696,8 @@ cfgupgrade
 ++++++++++
 
 The ``cfgupgrade`` tools is used to upgrade between major (and minor)
-Ganeti versions. Point-releases are usually transparent for the admin.
+Ganeti versions, and to roll back. Point-releases are usually
+transparent for the admin.
 
 More information about the upgrade procedure is listed on the wiki at
 http://code.google.com/p/ganeti/wiki/UpgradeNotes.
diff --git a/doc/design-2.8.rst b/doc/design-2.8.rst
new file mode 100644 (file)
index 0000000..ef8e6fd
--- /dev/null
@@ -0,0 +1,25 @@
+=================
+Ganeti 2.8 design
+=================
+
+The following design documents have been implemented in Ganeti 2.8:
+
+- :doc:`design-reason-trail`
+- :doc:`design-autorepair`
+- :doc:`design-device-uuid-name`
+
+The following designs have been partially implemented in Ganeti 2.8:
+
+- :doc:`design-storagetypes`
+- :doc:`design-hroller`
+- :doc:`design-query-splitting`: everything except instance queries.
+- :doc:`design-partitioned`: "Constrained instance sizes" implemented.
+- :doc:`design-monitoring-agent`: implementation of all the core functionalities
+  of the monitoring agent. Reason trail implemented as part of the work for the
+  instance status collector.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-device-uuid-name.rst b/doc/design-device-uuid-name.rst
new file mode 100644 (file)
index 0000000..37e4e8d
--- /dev/null
@@ -0,0 +1,88 @@
+==========================================
+Design for adding UUID and name to devices
+==========================================
+
+.. contents:: :depth: 4
+
+This is a design document about adding UUID and name to instance devices
+(Disks/NICs) and the ability to reference them by those identifiers.
+
+
+Current state and shortcomings
+==============================
+
+Currently, the only way to refer to a device (Disk/NIC) is by its index
+inside the VM (e.g. gnt-instance modify --disk 2:remove).
+
+Using indices as identifiers has the drawback that addition/removal of a
+device results in changing the identifiers(indices) of other devices and
+makes the net effect of commands depend on their strict ordering. A
+device reference is not absolute, meaning an external entity controlling
+Ganeti, e.g., over RAPI, cannot keep permanent identifiers for referring
+to devices, nor can it have more than one outstanding commands, since
+their order of execution is not guaranteed.
+
+
+Proposed Changes
+================
+
+To be able to reference a device in a unique way, we propose to extend
+Disks and NICs by assigning to them a UUID and a name. The UUID will be
+assigned by Ganeti upon creation, while the name will be an optional
+user parameter. Renaming a device will also be supported.
+
+Commands (e.g. `gnt-instance modify`) will be able to reference each
+device by its index, UUID, or name. To be able to refer to devices by
+name, we must guarantee that device names are unique. Unlike other
+objects (instances, networks, nodegroups, etc.), NIC and Disk objects
+will not have unique names across the cluster, since they are still not
+independent entities, but rather part of the instance object. This makes
+global uniqueness of names hard to achieve at this point. Instead their
+names will be unique at instance level.
+
+Apart from unique device names, we must also guarantee that a device
+name can not be the UUID of another device. Also, to remove ambiguity
+while supporting both indices and names as identifiers, we forbid purely
+numeric device names.
+
+
+Implementation Details
+======================
+
+Modify OpInstanceSetParams to accept not only indexes, but also device
+names and UUIDs. So, the accepted NIC and disk modifications will have
+the following format:
+
+identifier:action,key=value
+
+where, from now on, identifier can be an index (-1 for the last device),
+UUID, or name and action should be add, modify, or remove.
+
+Configuration Changes
+~~~~~~~~~~~~~~~~~~~~~
+
+Disk and NIC config objects get two extra slots:
+
+- uuid
+- name
+
+Instance Queries
+~~~~~~~~~~~~~~~~~
+
+We will extend the query mechanism to expose names and UUIDs of NICs and
+Disks.
+
+Hook Variables
+~~~~~~~~~~~~~~
+
+We will expose the name of NICs and Disks to the hook environment of
+instance-related operations:
+
+``INSTANCE_NIC%d_NAME``
+``INSTANCE_DISK%d_NAME``
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 581a2d4..8c2eff2 100644 (file)
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.7
+.. Last updated for Ganeti 2.8
 
 .. toctree::
    :maxdepth: 2
@@ -12,9 +12,10 @@ Design document drafts
    design-impexp2.rst
    design-resource-model.rst
    design-query-splitting.rst
-   design-autorepair.rst
    design-partitioned.rst
    design-monitoring-agent.rst
+   design-hroller.rst
+   design-storagetypes.rst
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-hroller.rst b/doc/design-hroller.rst
new file mode 100644 (file)
index 0000000..888f9a9
--- /dev/null
@@ -0,0 +1,162 @@
+============
+HRoller tool
+============
+
+.. contents:: :depth: 4
+
+This is a design document detailing the cluster maintenance scheduler,
+HRoller.
+
+
+Current state and shortcomings
+==============================
+
+To enable automating cluster-wide reboots a new htool, called HRoller,
+was added to Ganeti starting from version 2.7. This tool helps
+parallelizing cluster offline maintenances by calculating which nodes
+are not both primary and secondary for a DRBD instance, and thus can be
+rebooted at the same time, when all instances are down.
+
+The way this is done is documented in the :manpage:`hroller(1)` manpage.
+
+We would now like to perform online maintenance on the cluster by
+rebooting nodes after evacuating their primary instances (rolling
+reboots).
+
+Proposed changes
+================
+
+New options
+-----------
+
+- HRoller should be able to operate on single nodegroups (-G flag) or
+  select its target node through some other mean (eg. via a tag, or a
+  regexp). (Note that individual node selection is already possible via
+  the -O flag, that makes hroller ignore a node altogether).
+- HRoller should handle non redundant instances: currently these are
+  ignored but there should be a way to select its behavior between "it's
+  ok to reboot a node when a non-redundant instance is on it" or "skip
+  nodes with non-redundant instances". This will only be selectable
+  globally, and not per instance.
+- Hroller will make sure to keep any instance which is up in its current
+  state, via live migrations, unless explicitly overridden. The
+  algorithm that will be used calculate the rolling reboot with live
+  migrations is described below, and any override on considering the
+  instance status will only be possible on the whole run, and not
+  per-instance.
+
+
+Calculating rolling maintenances
+--------------------------------
+
+In order to perform rolling maintenance we need to migrate instances off
+the nodes before a reboot. How this can be done depends on the
+instance's disk template and status:
+
+Down instances
+++++++++++++++
+
+If an instance was shutdown when the maintenance started it will be
+considered for avoiding contemporary reboot of its primary and secondary
+nodes, but will *not* be considered as a target for the node evacuation.
+This allows avoiding needlessly moving its primary around, since it
+won't suffer a downtime anyway.
+
+Note that a node with non-redundant instances will only ever be
+considered good for rolling-reboot if these are down (or the checking of
+status is overridden) *and* an explicit option to allow it is set.
+
+DRBD
+++++
+
+Each node must migrate all instances off to their secondaries, and then
+can either be rebooted, or the secondaries can be evacuated as well.
+
+Since currently doing a ``replace-disks`` on DRBD breaks redundancy,
+it's not any safer than temporarily rebooting a node with secondaries on
+them (citation needed). As such we'll implement for now just the
+"migrate+reboot" mode, and focus later on replace-disks as well.
+
+In order to do that we can use the following algorithm:
+
+1) Compute node sets that don't contain both the primary and the
+   secondary for any instance. This can be done already by the current
+   hroller graph coloring algorithm: nodes are in the same set (color)
+   if and only if no edge (instance) exists between them (see the
+   :manpage:`hroller(1)` manpage for more details).
+2) Inside each node set calculate subsets that don't have any secondary
+   node in common (this can be done by creating a graph of nodes that
+   are connected if and only if an instance on both has the same
+   secondary node, and coloring that graph)
+3) It is then possible to migrate in parallel all nodes in a subset
+   created at step 2, and then reboot/perform maintenance on them, and
+   migrate back their original primaries, which allows the computation
+   above to be reused for each following subset without N+1 failures
+   being triggered, if none were present before. See below about the
+   actual execution of the maintenance.
+
+Non-DRBD
+++++++++
+
+All non-DRBD disk templates that can be migrated have no "secondary"
+concept. As such instances can be migrated to any node (in the same
+nodegroup). In order to do the job we can either:
+
+- Perform migrations on one node at a time, perform the maintenance on
+  that node, and proceed (the node will then be targeted again to host
+  instances automatically, as hail chooses targets for the instances
+  between all nodes in a group. Nodes in different nodegroups can be
+  handled in parallel.
+- Perform migrations on one node at a time, but without waiting for the
+  first node to come back before proceeding. This allows us to continue,
+  restricting the cluster, until no more capacity in the nodegroup is
+  available, and then having to wait for some nodes to come back so that
+  capacity is available again for the last few nodes.
+- Pre-Calculate sets of nodes that can be migrated together (probably
+  with a greedy algorithm) and parallelize between them, with the
+  migrate-back approach discussed for DRBD to perform the calculation
+  only once.
+
+Note that for non-DRBD disks that still use local storage (eg. RBD and
+plain) redundancy might break anyway, and nothing except the first
+algorithm might be safe. This perhaps would be a good reason to consider
+managing better RBD pools, if those are implemented on top of nodes
+storage, rather than on dedicated storage machines.
+
+Future work
+===========
+
+Hroller should become able to execute rolling maintenances, rather than
+just calculate them. For this to succeed properly one of the following
+must happen:
+
+- HRoller handles rolling maintenances that happen at the same time as
+  unrelated cluster jobs, and thus recalculates the maintenance at each
+  step
+- HRoller can selectively drain the cluster so it's sure that only the
+  rolling maintenance can be going on
+
+DRBD nodes' ``replace-disks``' functionality should be implemented. Note
+that when we will support a DRBD version that allows multi-secondary
+this can be done safely, without losing replication at any time, by
+adding a temporary secondary and only when the sync is finished dropping
+the previous one.
+
+Non-redundant (plain or file) instances should have a way to be moved
+off as well via plain storage live migration or ``gnt-instance move``
+(which requires downtime).
+
+If/when RBD pools can be managed inside Ganeti, care can be taken so
+that the pool is evacuated as well from a node before it's put into
+maintenance. This is equivalent to evacuating DRBD secondaries.
+
+Master failovers during the maintenance should be performed by hroller.
+This requires RPC/RAPI support for master failover. Hroller should also
+be modified to better support running on the master itself and
+continuing on the new master.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 5109dde..8b5f05c 100644 (file)
@@ -24,7 +24,7 @@ Each Ganeti node should export a status page that can be queried by a
 monitoring system. Such status page will be exported on a network port
 and will be encoded in JSON (simple text) over HTTP.
 
-The choice of json is obvious as we already depend on it in Ganeti and
+The choice of JSON is obvious as we already depend on it in Ganeti and
 thus we don't need to add extra libraries to use it, as opposed to what
 would happen for XML or some other markup format.
 
@@ -48,6 +48,167 @@ The monitoring agent system will report on the following basic information:
 - Node OS resources report (memory, CPU, network interfaces)
 - Information from a plugin system
 
+Format of the report
+--------------------
+
+The report of the will be in JSON format, and it will present an array
+of report objects.
+Each report object will be produced by a specific data collector.
+Each report object includes some mandatory fields, to be provided by all
+the data collectors:
+
+``name``
+  The name of the data collector that produced this part of the report.
+  It is supposed to be unique inside a report.
+
+``version``
+  The version of the data collector that produces this part of the
+  report. Built-in data collectors (as opposed to those implemented as
+  plugins) should have "B" as the version number.
+
+``format_version``
+  The format of what is represented in the "data" field for each data
+  collector might change over time. Every time this happens, the
+  format_version should be changed, so that who reads the report knows
+  what format to expect, and how to correctly interpret it.
+
+``timestamp``
+  The time when the reported data were gathered. It has to be expressed
+  in nanoseconds since the unix epoch (0:00:00 January 01, 1970). If not
+  enough precision is available (or needed) it can be padded with
+  zeroes. If a report object needs multiple timestamps, it can add more
+  and/or override this one inside its own "data" section.
+
+``category``
+  A collector can belong to a given category of collectors (e.g.: storage
+  collectors, daemon collector). This means that it will have to provide a
+  minumum set of prescribed fields, as documented for each category.
+  This field will contain the name of the category the collector belongs to,
+  if any, or just the ``null`` value.
+
+``kind``
+  Two kinds of collectors are possible:
+  `Performance reporting collectors`_ and `Status reporting collectors`_.
+  The respective paragraphs will describe them and the value of this field.
+
+``data``
+  This field contains all the data generated by the specific data collector,
+  in its own independently defined format. The monitoring agent could check
+  this syntactically (according to the JSON specifications) but not
+  semantically.
+
+Here follows a minimal example of a report::
+
+  [
+  {
+      "name" : "TheCollectorIdentifier",
+      "version" : "1.2",
+      "format_version" : 1,
+      "timestamp" : 1351607182000000000,
+      "category" : null,
+      "kind" : 0,
+      "data" : { "plugin_specific_data" : "go_here" }
+  },
+  {
+      "name" : "AnotherDataCollector",
+      "version" : "B",
+      "format_version" : 7,
+      "timestamp" : 1351609526123854000,
+      "category" : "storage",
+      "kind" : 1,
+      "data" : { "status" : { "code" : 1,
+                              "message" : "Error on disk 2"
+                            },
+                 "plugin_specific" : "data",
+                 "some_late_data" : { "timestamp" : 1351609526123942720,
+                                      ...
+                                    }
+               }
+  }
+  ]
+
+Performance reporting collectors
+++++++++++++++++++++++++++++++++
+
+These collectors only provide data about some component of the system, without
+giving any interpretation over their meaning.
+
+The value of the ``kind`` field of the report will be ``0``.
+
+Status reporting collectors
++++++++++++++++++++++++++++
+
+These collectors will provide information about the status of some
+component of ganeti, or managed by ganeti.
+
+The value of their ``kind`` field will be ``1``.
+
+The rationale behind this kind of collectors is that there are some situations
+where exporting data about the underlying subsystems would expose potential
+issues. But if Ganeti itself is able (and going) to fix the problem, conflicts
+might arise between Ganeti and something/somebody else trying to fix the same
+problem.
+Also, some external monitoring systems might not be aware of the internals of a
+particular subsystem (e.g.: DRBD) and might only exploit the high level
+response of its data collector, alerting an administrator if anything is wrong.
+Still, completely hiding the underlying data is not a good idea, as they might
+still be of use in some cases. So status reporting plugins will provide two
+output modes: one just exporting a high level information about the status,
+and one also exporting all the data they gathered.
+The default output mode will be the status-only one. Through a command line
+parameter (for stand-alone data collectors) or through the HTTP request to the
+monitoring agent
+(when collectors are executed as part of it) the verbose output mode providing
+all the data can be selected.
+
+When exporting just the status each status reporting collector will provide,
+in its ``data`` section, at least the following field:
+
+``status``
+  summarizes the status of the component being monitored and consists of two
+  subfields:
+
+  ``code``
+    It assumes a numeric value, encoded in such a way to allow using a bitset
+    to easily distinguish which states are currently present in the whole cluster.
+    If the bitwise OR of all the ``status`` fields is 0, the cluster is
+    completely healty.
+    The status codes are as follows:
+
+    ``0``
+      The collector can determine that everything is working as
+      intended.
+
+    ``1``
+      Something is temporarily wrong but it is being automatically fixed by
+      Ganeti.
+      There is no need of external intervention.
+
+    ``2``
+      The collector has failed to understand whether the status is good or
+      bad. Further analysis is required. Interpret this status as a
+      potentially dangerous situation.
+
+    ``4``
+      The collector can determine that something is wrong and Ganeti has no
+      way to fix it autonomously. External intervention is required.
+
+  ``message``
+    A message to better explain the reason of the status.
+    The exact format of the message string is data collector dependent.
+
+    The field is mandatory, but the content can be an empty string if the
+    ``code`` is ``0`` (working as intended) or ``1`` (being fixed
+    automatically).
+
+    If the status code is ``2``, the message should specify what has gone
+    wrong.
+    If the status code is ``4``, the message shoud explain why it was not
+    possible to determine a proper status.
+
+The ``data`` section will also contain all the fields describing the gathered
+data, according to a collector-specific format.
+
 Instance status
 +++++++++++++++
 
@@ -63,11 +224,9 @@ As such we propose that:
   "reason" attached to it (at opcode level). This can be used for
   example to distinguish an admin request, from a scheduled maintenance
   or an automated tool's work. If this reason is not passed, Ganeti will
-  just use the information it has about the source of the request: for
-  example a cli shutdown operation will have "cli:shutdown" as a reason,
-  a cli failover operation will have "cli:failover". Operations coming
-  from the remote API will use "rapi" instead of "cli". Of course
-  setting a real site-specific reason is still preferred.
+  just use the information it has about the source of the request.
+  This reason information will be structured according to the
+  :doc:`Ganeti reason trail <design-reason-trail>` design document.
 - RPCs that affect the instance status will be changed so that the
   "reason" and the version of the config object they ran on is passed to
   them. They will then export the new expected instance status, together
@@ -75,7 +234,7 @@ As such we propose that:
   system, which then will export those themselves.
 
 Monitoring and auditing systems can then use the reason to understand
-the cause of an instance status, and they can use the object version to
+the cause of an instance status, and they can use the timestamp to
 understand the freshness of their data even in the absence of an atomic
 cross-node reporting: for example if they see an instance "up" on a node
 after seeing it running on a previous one, they can compare these values
@@ -86,73 +245,261 @@ constantly up on more than one), which should be reported and acted
 upon.
 
 The instance status will be on each node, for the instances it is
-primary for and will contain at least:
+primary for, and its ``data`` section of the report will contain a list
+of instances, with at least the following fields for each instance:
+
+``name``
+  The name of the instance.
+
+``uuid``
+  The UUID of the instance (stable on name change).
+
+``admin_state``
+  The status of the instance (up/down/offline) as requested by the admin.
+
+``actual_state``
+  The actual status of the instance. It can be ``up``, ``down``, or
+  ``hung`` if the instance is up but it appears to be completely stuck.
+
+``uptime``
+  The uptime of the instance (if it is up, "null" otherwise).
+
+``mtime``
+  The timestamp of the last known change to the instance state.
 
-- The instance name
-- The instance UUID (stable on name change)
-- The instance running status (up or down)
-- The uptime, as detected by the hypervisor
-- The timestamp of last known change
-- The timestamp of when the status was last checked (see caching, below)
-- The last known reason for change, if any
+``state_reason``
+  The last known reason for state change of the instance, described according
+  to the JSON representation of a reason trail, as detailed in the :doc:`reason trail
+  design document <design-reason-trail>`.
 
-More information about all the fields and their type will be available
-in the "Format of the report" section.
+``status``
+  It represents the status of the instance, and its format is the same as that
+  of the ``status`` field of `Status reporting collectors`_.
+
+Each hypervisor should provide its own instance status data collector, possibly
+with the addition of more, specific, fields.
+The ``category`` field of all of them will be ``instance``.
+The ``kind`` field will be ``1``.
 
 Note that as soon as a node knows it's not the primary anymore for an
 instance it will stop reporting status for it: this means the instance
 will either disappear, if it has been deleted, or appear on another
 node, if it's been moved.
 
-Instance Disk status
-++++++++++++++++++++
+The ``code`` of the ``status`` field of the report of the Instance status data
+collector will be:
 
-As for the instance status Ganeti has now only partial information about
-its instance disks: in particular each node is unaware of the disk to
-instance mapping, that exists only on the master.
+``0``
+  if ``status`` is ``0`` for all the instances it is reporting about.
 
-For this design doc we plan to fix this by changing all RPCs that create
-a backend storage or that put an already existing one in use and passing
-the relevant instance to the node. The node can then export these to the
-status reporting tool.
+``1``
+  otherwise.
+
+Storage status
+++++++++++++++
+
+The storage status collectors will be a series of data collectors
+(drbd, rbd, plain, file) that will gather data about all the storage types
+for the current node (this is right now hardcoded to the enabled storage
+types, and in the future tied to the enabled storage pools for the nodegroup).
+
+The ``name`` of each of these collector will reflect what storage type each of
+them refers to.
+
+The ``category`` field of these collector will be ``storage``.
+
+The ``kind`` field will be ``1`` (`Status reporting collectors`_).
+
+The ``data`` section of the report will provide at least the following fields:
+
+``free``
+  The amount of free space (in KBytes).
+
+``used``
+  The amount of used space (in KBytes).
+
+``total``
+  The total visible space (in KBytes).
+
+Each specific storage type might provide more type-specific fields.
+
+In case of error, the ``message`` subfield of the ``status`` field of the
+report of the instance status collector will disclose the nature of the error
+as a type specific information. Examples of these are "backend pv unavailable"
+for lvm storage, "unreachable" for network based storage or "filesystem error"
+for filesystem based implementations.
+
+DRBD status
+***********
+
+This data collector will run only on nodes where DRBD is actually
+present and it will gather information about DRBD devices.
+
+Its ``kind`` in the report will be ``1`` (`Status reporting collectors`_).
+
+Its ``category`` field in the report will contain the value ``storage``.
+
+When executed in verbose mode, the ``data`` section of the report of this
+collector will provide the following fields:
+
+``versionInfo``
+  Information about the DRBD version number, given by a combination of
+  any (but at least one) of the following fields:
+
+  ``version``
+    The DRBD driver version.
+
+  ``api``
+    The API version number.
+
+  ``proto``
+    The protocol version.
+
+  ``srcversion``
+    The version of the source files.
+
+  ``gitHash``
+    Git hash of the source files.
+
+  ``buildBy``
+    Who built the binary, and, optionally, when.
+
+``device``
+  A list of structures, each describing a DRBD device (a minor) and containing
+  the following fields:
+
+  ``minor``
+    The device minor number.
+
+  ``connectionState``
+    The state of the connection. If it is "Unconfigured", all the following
+    fields are not present.
+
+  ``localRole``
+    The role of the local resource.
+
+  ``remoteRole``
+    The role of the remote resource.
+
+  ``localState``
+    The status of the local disk.
+
+  ``remoteState``
+    The status of the remote disk.
+
+  ``replicationProtocol``
+    The replication protocol being used.
+
+  ``ioFlags``
+    The input/output flags.
+
+  ``perfIndicators``
+    The performance indicators. This field will contain the following
+    sub-fields:
+
+    ``networkSend``
+      KiB of data sent on the network.
+
+    ``networkReceive``
+      KiB of data received from the network.
+
+    ``diskWrite``
+      KiB of data written on local disk.
+
+    ``diskRead``
+      KiB of date read from the local disk.
+
+    ``activityLog``
+      Number of updates of the activity log.
+
+    ``bitMap``
+      Number of updates to the bitmap area of the metadata.
+
+    ``localCount``
+      Number of open requests to the local I/O subsystem.
+
+    ``pending``
+      Number of requests sent to the partner but not yet answered.
+
+    ``unacknowledged``
+      Number of requests received by the partner but still to be answered.
+
+    ``applicationPending``
+      Num of block input/output requests forwarded to DRBD but that have not yet
+      been answered.
+
+    ``epochs``
+      (Optional) Number of epoch objects. Not provided by all DRBD versions.
+
+    ``writeOrder``
+      (Optional) Currently used write ordering method. Not provided by all DRBD
+      versions.
+
+    ``outOfSync``
+      (Optional) KiB of storage currently out of sync. Not provided by all DRBD
+      versions.
+
+  ``syncStatus``
+    (Optional) The status of the synchronization of the disk. This is present
+    only if the disk is being synchronized, and includes the following fields:
+
+    ``percentage``
+      The percentage of synchronized data.
+
+    ``progress``
+      How far the synchronization is. Written as "x/y", where x and y are
+      integer numbers expressed in the measurement unit stated in
+      ``progressUnit``
+
+    ``progressUnit``
+      The measurement unit for the progress indicator.
+
+    ``timeToFinish``
+      The expected time before finishing the synchronization.
+
+    ``speed``
+      The speed of the synchronization.
+
+    ``want``
+      The desiderd speed of the synchronization.
+
+    ``speedUnit``
+      The measurement unit of the ``speed`` and ``want`` values. Expressed
+      as "size/time".
+
+  ``instance``
+    The name of the Ganeti instance this disk is associated to.
 
-While we haven't implemented these RPC changes yet, we'll use confd to
-fetch this information in the data collector.
-
-Since Ganeti supports many type of disks for instances (drbd, rbd,
-plain, file) we will export both a "generic" status which will work for
-any type of disk and will be very opaque (at minimum just an "healthy"
-or "error" state, plus perhaps some human readable comment and a
-"per-type" status which will explain more about the internal details but
-will not be compatible between different storage types (and will for
-example export the drbd connection status, sync, and so on).
-
-Status of storage for instances
-+++++++++++++++++++++++++++++++
-
-The node will also be reporting on all storage types it knows about for
-the current node (this is right now hardcoded to the enabled storage
-types, and in the future tied to the enabled storage pools for the
-nodegroup). For this kind of information also we will report both a
-generic health status (healthy or error) for each type of storage, and
-some more generic statistics (free space, used space, total visible
-space). In addition type specific information can be exported: for
-example, in case of error, the nature of the error can be disclosed as a
-type specific information. Examples of these are "backend pv
-unavailable" for lvm storage, "unreachable" for network based storage or
-"filesystem error" for filesystem based implementations.
 
 Ganeti daemons status
 +++++++++++++++++++++
 
-Ganeti will report what information it has about its own daemons: this
-includes memory usage, uptime, CPU usage. This should allow identifying
-possible problems with the Ganeti system itself: for example memory
-leaks, crashes and high resource utilization should be evident by
-analyzing this information.
+Ganeti will report what information it has about its own daemons.
+This should allow identifying possible problems with the Ganeti system itself:
+for example memory leaks, crashes and high resource utilization should be
+evident by analyzing this information.
+
+The ``kind`` field will be ``1`` (`Status reporting collectors`_).
+
+Each daemon will have its own data collector, and each of them will have
+a ``category`` field valued ``daemon``.
+
+When executed in verbose mode, their data section will include at least:
+
+``memory``
+  The amount of used memory.
 
-Ganeti daemons will also be able to export extra internal information to
-the status reporting, through the plugin system (see below).
+``size_unit``
+  The measurement unit used for the memory.
+
+``uptime``
+  The uptime of the daemon.
+
+``CPU usage``
+  How much cpu the daemon is using (percentage).
+
+Any other daemon-specific information can be included as well in the ``data``
+section.
 
 Hypervisor resources report
 +++++++++++++++++++++++++++
@@ -164,119 +511,97 @@ section we'll report all information we can in a "non hypervisor
 specific" way. Each hypervisor can then add extra specific information
 that is not generic enough be abstracted.
 
+The ``kind`` field will be ``0`` (`Performance reporting collectors`_).
+
+Each of the hypervisor data collectory will be of ``category``: ``hypervisor``.
+
 Node OS resources report
 ++++++++++++++++++++++++
 
 Since Ganeti assumes it's running on Linux, it's useful to export some
-basic information as seen by the host system. This includes number and
-status of CPUs, memory, filesystems and network intefaces as well as the
-version of components Ganeti interacts with (Linux, drbd, hypervisor,
-etc).
+basic information as seen by the host system.
 
-Note that we won't go into any hardware specific details (e.g. querying a
-node RAID is outside the scope of this, and can be implemented as a
-plugin) but we can easily just report the information above, since it's
-standard enough across all systems.
+The ``category`` field of the report will be ``null``.
 
-Plugin system
-+++++++++++++
+The ``kind`` field will be ``0`` (`Performance reporting collectors`_).
 
-The monitoring system will be equipped with a plugin system that can
-export specific local information through it. The plugin system will be
-in the form of either scripts whose output will be inserted in the
-report, plain text files which will be inserted into the report, or
-local unix or network sockets from which the information has to be read.
-This should allow most flexibility for implementing an efficient system,
-while being able to keep it as simple as possible.
+The ``data`` section will include:
 
-The plugin system is expected to be used by local installations to
-export any installation specific information that they want to be
-monitored, about either hardware or software on their systems.
+``cpu_number``
+  The number of available cpus.
 
+``cpus``
+  A list with one element per cpu, showing its average load.
 
-Format of the query
--------------------
+``memory``
+  The current view of memory (free, used, cached, etc.)
 
-The query will be an HTTP GET request on a particular port. At the
-beginning it will only be possible to query the full status report.
+``filesystem``
+  A list with one element per filesystem, showing a summary of the
+  total/available space.
 
+``NICs``
+  A list with one element per network interface, showing the amount of
+  sent/received data, error rate, IP address of the interface, etc.
 
-Format of the report
---------------------
+``versions``
+  A map using the name of a component Ganeti interacts (Linux, drbd,
+  hypervisor, etc) as the key and its version number as the value.
 
-The report of the will be in JSON format, and it will present an array
-of report objects.
-Each report object will be produced by a specific data collector.
-Each report object includes some mandatory fields, to be provided by all
-the data collectors, and a field to contain data collector-specific
-data.
+Note that we won't go into any hardware specific details (e.g. querying a
+node RAID is outside the scope of this, and can be implemented as a
+plugin) but we can easily just report the information above, since it's
+standard enough across all systems.
 
-Here follows a minimal example of a report::
+Format of the query
+-------------------
 
-  [
-  {
-      "name" : "TheCollectorIdentifier",
-      "version" : "1.2",
-      "format_version" : 1,
-      "timestamp" : 1351607182000000000,
-      "data" : { "plugin_specific_data" : "go_here" }
-  },
-  {
-      "name" : "AnotherDataCollector",
-      "version" : "B",
-      "format_version" : 7,
-      "timestamp" : 1351609526123854000,
-      "data" : { "plugin_specific" : "data",
-                 "some_late_data" : { "timestamp" : "SPECIFIC_TIME",
-                                      ... }
-               }
-  }
-  ]
+.. include:: monitoring-query-format.rst
 
-Here is the description of the mandatory fields of each object:
+Instance disk status propagation
+--------------------------------
 
-name
-  the name of the data collector that produced this part of the report.
-  It is supposed to be unique inside a report.
+As for the instance status Ganeti has now only partial information about
+its instance disks: in particular each node is unaware of the disk to
+instance mapping, that exists only on the master.
 
-version
-  the version of the data collector that produces this part of the
-  report. Built-in data collectors (as opposed to those implemented as
-  plugins) should have "B" as the version number.
+For this design doc we plan to fix this by changing all RPCs that create
+a backend storage or that put an already existing one in use and passing
+the relevant instance to the node. The node can then export these to the
+status reporting tool.
 
-format_version
-  the format of what is represented in the "data" field for each data
-  collector might change over time. Every time this happens, the
-  format_version should be changed, so that who reads the report knows
-  what format to expect, and how to correctly interpret it.
+While we haven't implemented these RPC changes yet, we'll use Confd to
+fetch this information in the data collectors.
 
-timestamp
-  the time when the reported data were gathered. Is has to be expressed
-  in nanoseconds since the unix epoch (0:00:00 January 01, 1970). If not
-  enough precision is available (or needed) it can be padded with
-  zeroes. If a report object needs multiple timestamps, it can add more
-  and/or override this one inside its own "data" section.
+Plugin system
+-------------
 
-data
-  this field contains all the data generated by the data collector, in
-  its own independently defined format. The monitoring agent could check
-  this syntactically (according to the JSON specifications) but not
-  semantically.
+The monitoring system will be equipped with a plugin system that can
+export specific local information through it.
+
+The plugin system is expected to be used by local installations to
+export any installation specific information that they want to be
+monitored, about either hardware or software on their systems.
 
+The plugin system will be in the form of either scripts or binaries whose output
+will be inserted in the report.
+
+Eventually support for other kinds of plugins might be added as well, such as
+plain text files which will be inserted into the report, or local unix or
+network sockets from which the information has to be read.  This should allow
+most flexibility for implementing an efficient system, while being able to keep
+it as simple as possible.
 
 Data collectors
 ---------------
 
 In order to ease testing as well as to make it simple to reuse this
 subsystem it will be possible to run just the "data collectors" on each
-node without passing through the agent daemon. Each data collector will
-report specific data about its subsystem and will be documented
-separately.
+node without passing through the agent daemon.
 
 If a data collector is run independently, it should print on stdout its
 report, according to the format corresponding to a single data collector
-report object, as described in the previous paragraph.
-
+report object, as described in the previous paragraphs.
 
 Mode of operation
 -----------------
@@ -297,6 +622,8 @@ try to have defaults, it will be configurable. The first two instead we
 can use adaptively to query a certain resource faster or slower
 depending on those two parameters.
 
+When run as stand-alone binaries, the data collector will not using any
+caching system, and just fetch and return the data immediately.
 
 Implementation place
 --------------------
@@ -309,18 +636,17 @@ impact functionality.
 The libekg library should be looked at for easily providing metrics in
 json format.
 
-
 Implementation order
 --------------------
 
 We will implement the agent system in this order:
 
-- initial example data collectors (eg. for drbd and instance status.
-  Data collector-specific report format TBD).
-- initial daemon for exporting data
+- initial example data collectors (eg. for drbd and instance status).
+- initial daemon for exporting data, integrating the existing collectors
+- plugin system
 - RPC updates for instance status reasons and disk to instance mapping
+- cache layer for the daemon
 - more data collectors
-- cache layer for the daemon (if needed)
 
 
 Future work
index 0c4bc43..c059ccd 100644 (file)
@@ -163,10 +163,12 @@ defined at node-group level.
 Currently it's possible to define an instance policy that limits the
 minimum and maximum value for CPU, memory, and disk usage (and spindles
 and any other resource, when implemented), independently from each other. We
-extend the policy by allowing it to specify more specifications, where
-each specification contains the limits (minimum, maximum, and standard)
-for all the resources. Each specification has a unique priority (an
-integer) associated to it, which is used by ``hspace`` (see below).
+extend the policy by allowing it to contain more occurrences of the
+specifications for both the limits for the instance resources. Each
+specification pair (minimum and maximum) has a unique priority
+associated to it (or in other words, specifications are ordered), which
+is used by ``hspace`` (see below). The standard specification doesn't
+change: there is one for the whole cluster.
 
 For example, a policy could be set up to allow instances with this
 constraints:
@@ -183,12 +185,11 @@ Ganeti will refuse to create (or modify) instances that violate instance
 policy constraints, unless the flag ``--ignore-ipolicy`` is passed.
 
 While the changes needed to check constraint violations are
-straightforward, ``hspace`` behavior needs some adjustments. For both
-standard and tiered allocation, ``hspace`` will start to allocate
-instances using the specification with the highest priority, then it
-will fall back to second highest priority, and so on. For tiered
-allocation, it will try to lower the most constrained resources (without
-breaking the policy) before going to the next specification.
+straightforward, ``hspace`` behavior needs some adjustments for tiered
+allocation. ``hspace`` will start to allocate instances using the
+maximum specification with the highest priority, then it will try to
+lower the most constrained resources (without breaking the policy)
+before moving to the second highest priority, and so on.
 
 For consistent results in capacity calculation, the specifications
 inside a policy should be ordered so that the biggest specifications
index 2992f74..e0718bc 100644 (file)
@@ -77,7 +77,7 @@ the instance console), to a split model:
 - if just masterd is stopped, then other cluster functionality remains
   available: listing instances, connecting to the console of an
   instance, etc.
-- if just "queryd" is stopped, masterd can still process jobs, and one
+- if just "luxid" is stopped, masterd can still process jobs, and one
   can furthermore run queries from other nodes (MCs)
 - only if both are stopped, we end up with the previous state
 
@@ -141,12 +141,12 @@ configuration queries.
 The redirection of Luxi requests can be easily done based on the
 request type, if we have both sockets open, or if we open on demand.
 
-We don't want the masterd to talk to the queryd itself (hidden
+We don't want the masterd to talk to the luxid itself (hidden
 redirection), since we want to be able to run queries while masterd is
 down.
 
 During the 2.7 release cycle, we can test all queries against both
-masterd and queryd in QA, so we know we have exactly the same
+masterd and luxid in QA, so we know we have exactly the same
 interface and it is consistent.
 
 .. vim: set textwidth=72 :
diff --git a/doc/design-reason-trail.rst b/doc/design-reason-trail.rst
new file mode 100644 (file)
index 0000000..5598832
--- /dev/null
@@ -0,0 +1,107 @@
+===================
+Ganeti reason trail
+===================
+
+.. contents:: :depth: 2
+
+This is a design document detailing the implementation of a way for Ganeti to
+track the origin and the reason of every executed command, from its starting
+point (command line, remote API, some htool, etc.) to its actual execution
+time.
+
+Current state and shortcomings
+==============================
+
+There is currently no way to track why a job and all the operations part of it
+were executed, and who or what triggered the execution.
+This is an inconvenience in general, and also it makes impossible to have
+certain information, such as finding the reason why an instance last changed its
+status (i.e.: why it was started/stopped/rebooted/etc.), or distinguishing
+an admin request from a scheduled maintenance or an automated tool's work.
+
+Proposed changes
+================
+
+We propose to introduce a new piece of information, that will be called "reason
+trail", to track the path from the issuing of a command to its execution.
+
+The reason trail will be a list of 3-tuples ``(source, reason, timestamp)``,
+with:
+
+``source``
+  The entity deciding to perform (or forward) a command.
+  It is represented by an arbitrary string, but strings prepended by "gnt:"
+  are reserved for Ganeti components, and they will be refused by the
+  interfaces towards the external world.
+
+``reason``
+  The reason why the entity decided to perform the operation.
+  It is represented by an arbitrary string. The string might possibly be empty,
+  because certain components of the system might just "pass on" the operation
+  (therefore wanting to be recorded in the trail) but without an explicit
+  reason.
+
+``timestamp``
+  The time when the element was added to the reason trail. It has to be
+  expressed in nanoseconds since the unix epoch (0:00:00 January 01, 1970).
+  If not enough precision is available (or needed) it can be padded with
+  zeroes.
+
+The reason trail will be attached at the OpCode level. When it has to be
+serialized externally (such as on the RAPI interface), it will be serialized in
+JSON format. Specifically, it will be serialized as a list of elements.
+Each element will be a list with two strings (for ``source`` and ``reason``)
+and one integer number (the ``timestamp``).
+
+Any component the operation goes through is allowed (but not required) to append
+it's own reason to the list. Other than this, the list shouldn't be modified.
+
+As an example here is the reason trail for a shutdown operation invoked from
+the command line through the gnt-instance tool::
+
+  [("user", "Cleanup of unused instances", 1363088484000000000),
+   ("gnt:client:gnt-instance", "stop", 1363088484020000000),
+   ("gnt:opcode:shutdown", "job=1234;index=0", 1363088484026000000),
+   ("gnt:daemon:noded:shutdown", "", 1363088484135000000)]
+
+where the first 3-tuple is determined by a user-specified message, passed to
+gnt-instance through a command line parameter.
+
+The same operation, launched by an external GUI tool, and executed through the
+remote API, would have a reason trail like::
+
+  [("user", "Cleanup of unused instances", 1363088484000000000),
+   ("other-app:tool-name", "gui:stop", 1363088484000300000),
+   ("gnt:client:rapi:shutdown", "", 1363088484020000000),
+   ("gnt:library:rlib2:shutdown", "", 1363088484023000000),
+   ("gnt:opcode:shutdown", "job=1234;index=0", 1363088484026000000),
+   ("gnt:daemon:noded:shutdown", "", 1363088484135000000)]
+
+Implementation
+==============
+
+The OpCode base class will be modified to include a new parameter, "reason".
+This will receive the reason trail as built by all the previous steps.
+
+When an OpCode is added to a job (in jqueue.py) the job number and the opcode
+index will be recorded as the reason for the existence of that opcode.
+
+From the command line tools down to the opcodes, the implementation of this
+design will be shared by all the components of the system. After the opcodes
+have been enqueued in a job queue and are dispatched for execution, the
+implementation will have to be OpCode specific because of the current
+structure of the ganeti backend.
+
+The implementation of opcode-specific parts will start from the operations that
+affect the instance status (as required by the design document about the
+monitoring daemon, for the instance status data collector). Such opcodes will
+be changed so that the "reason" is passed to them and they will then export
+the reason trail on a file.
+
+The implementation for other opcodes will follow when required.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
diff --git a/doc/design-storagetypes.rst b/doc/design-storagetypes.rst
new file mode 100644 (file)
index 0000000..42cb86a
--- /dev/null
@@ -0,0 +1,283 @@
+=============================================================================
+Management of storage types and disk templates, incl. storage space reporting
+=============================================================================
+
+.. contents:: :depth: 4
+
+Background
+==========
+
+Currently, there is no consistent management of different variants of storage
+in Ganeti. One direct consequence is that storage space reporting is currently
+broken for all storage that is not based on lvm technolgy. This design looks at
+the root causes and proposes a way to fix it.
+
+Proposed changes
+================
+
+We propose to streamline handling of different storage types and disk templates.
+Currently, there is no consistent implementation for dis/enabling of disk
+templates and/or storage types.
+
+Our idea is to introduce a list of enabled disk templates, which can be
+used by instances in the cluster. Based on this list, we want to provide
+storage reporting mechanisms for the available disk templates. Since some
+disk templates share the same underlying storage technology (for example
+``drbd`` and ``plain`` are based on ``lvm``), we map disk templates to storage
+types and implement storage space reporting for each storage type.
+
+Configuration changes
+---------------------
+
+Add a new attribute "enabled_disk_templates" (type: list of strings) to the
+cluster config which holds disk templates, for example, "drbd", "file",
+or "ext". This attribute represents the list of disk templates that are enabled
+cluster-wide for usage by the instances. It will not be possible to create
+instances with a disk template that is not enabled, as well as it will not be
+possible to remove a disk template from the list if there are still instances
+using it.
+
+The list of enabled disk templates can contain any non-empty subset of
+the currently implemented disk templates: ``blockdev``, ``diskless``, ``drbd``,
+``ext``, ``file``, ``plain``, ``rbd``, and ``sharedfile``. See
+``DISK_TEMPLATES`` in ``constants.py``.
+
+Note that the abovementioned list of enabled disk types is just a "mechanism"
+parameter that defines which disk templates the cluster can use. Further
+filtering about what's allowed can go in the ipolicy, which is not covered in
+this design doc. Note that it is possible to force an instance to use a disk
+template that is not allowed by the ipolicy. This is not possible if the
+template is not enabled by the cluster.
+
+The ipolicy also contains a list of enabled disk templates. Since the cluster-
+wide enabled disk templates should be a stronger constraint, the list of
+enabled disk templates in the ipolicy should be a subset of those. In case the
+user tries to create an inconsistent situation here, gnt-cluster should emit
+a warning.
+
+We consider the first disk template in the list to be the default template for
+instance creation and storage reporting. This will remove the need to specify
+the disk template with ``-t`` on instance creation.
+
+Currently, cluster-wide dis/enabling of disk templates is not implemented
+consistently. ``lvm`` based disk templates are enabled by specifying a volume
+group name on cluster initialization and can only be disabled by explicitly
+using the option ``--no-lvm-storage``. This will be replaced by adding/removing
+``drbd`` and ``plain`` from the set of enabled disk templates.
+
+Up till now, file storage and shared file storage could be dis/enabled at
+``./configure`` time. This will also be replaced by adding/removing the
+respective disk templates from the set of enabled disk templates.
+
+There is currently no possibility to dis/enable the disk templates
+``diskless``, ``blockdev``, ``ext``, and ``rdb``. By introducing the set of
+enabled disk templates, we will require these disk templates to be explicitly
+enabled in order to be used. The idea is that the administrator of the cluster
+can tailor the cluster configuration to what is actually needed in the cluster.
+There is hope that this will lead to cleaner code, better performance and fewer
+bugs.
+
+When upgrading the configuration from a version that did not have the list
+of enabled disk templates, we have to decide which disk templates are enabled
+based on the current configuration of the cluster. We propose the following
+update logic to be implemented in the online update of the config in
+the ``Cluster`` class in ``objects.py``:
+- If a ``volume_group_name`` is existing, then enable ``drbd`` and ``plain``.
+(TODO: can we narrow that down further?)
+- If ``file`` or ``sharedfile`` was enabled at configure time, add the
+respective disk template to the list of enabled disk templates.
+- For disk templates ``diskless``, ``blockdev``, ``ext``, and ``rbd``, we
+inspect the current cluster configuration regarding whether or not there
+are instances that use one of those disk templates. We will add only those
+that are currently in use.
+The order in which the list of enabled disk templates is built up will be
+determined by a preference order based on when in the history of Ganeti the
+disk templates were introduced (thus being a heuristic for which are used
+more than others).
+
+The list of enabled disk templates can be specified on cluster initialization
+with ``gnt-cluster init`` using the optional parameter
+``--enabled-disk-templates``. If it is not set, it will be set to a default
+set of enabled disk templates, which includes the following disk templates:
+``drbd`` and ``plain``. The list can be shrunk or extended by
+``gnt-cluster modify`` using the same parameter.
+
+Storage reporting
+-----------------
+
+The storage reporting in ``gnt-node list`` will be the first user of the
+newly introduced list of enabled disk templates. Currently, storage reporting
+works only for lvm-based storage. We want to extend that and report storage
+for the enabled disk templates. The default of ``gnt-node list`` will only
+report on storage of the default disk template (the first in the list of enabled
+disk templates). One can explicitly ask for storage reporting on the other
+enabled disk templates with the ``-o`` option.
+
+Some of the currently implemented disk templates share the same base storage
+technology. Since the storage reporting is based on the underlying technology
+rather than on the user-facing disk templates, we introduce storage types to
+represent the underlying technology. There will be a mapping from disk templates
+to storage types, which will be used by the storage reporting backend to pick
+the right method for estimating the storage for the different disk templates.
+
+The proposed storage types are ``blockdev``, ``diskless``, ``ext``, ``file``,
+``lvm-pv``, ``lvm-vg``, ``rados``.
+
+The mapping from disk templates to storage types will be: ``drbd`` and ``plain``
+to ``lvm-vg``, ``file`` and ``sharedfile`` to ``file``, and all others to their
+obvious counterparts.
+
+Note that there is no disk template mapping to ``lvm-pv``, because this storage
+type is currently only used to enable the user to mark it as (un)allocatable.
+(See ``man gnt-node``.) It is not possible to create an instance on a storage
+unit that is of type ``lvm-pv`` directly, therefore it is not included in the
+mapping.
+
+The storage reporting for file storage will report space on the file storage
+dir, which is currently limited to one directory. In the future, if we'll have
+support for more directories, or for per-nodegroup directories this can be
+changed.
+
+For now, we will implement only the storage reporting for non-shared storage,
+that is disk templates ``file``, ``lvm``, and ``drbd``. For disk template
+``diskless``, there is obviously nothing to report about. When implementing
+storage reporting for file, we can also use it for ``sharedfile``, since it
+uses the same file system mechanisms to determine the free space. In the
+future, we can optimize storage reporting for shared storage by not querying
+all nodes that use a common shared file for the same space information.
+
+In the future, we extend storage reporting for shared storage types like
+``rados`` and ``ext``. Note that it will not make sense to query each node for
+storage reporting on a storage unit that is used by several nodes.
+
+We will not implement storage reporting for the ``blockdev`` disk template,
+because block devices are always adopted after being provided by the system
+administrator, thus coming from outside Ganeti. There is no point in storage
+reporting for block devices, because Ganeti will never try to allocate storage
+inside a block device.
+
+RPC changes
+-----------
+
+The noded RPC call that reports node storage space will be changed to
+accept a list of <disktemplate>,<key> string tuples. For each of them, it will
+report the free amount of storage space found on storage <key> as known
+by the requested disk template. Depending on the disk template, the key would
+be a volume group name, in case of lvm-based disk templates, a directory name
+for the file and shared file storage, and a rados pool name for rados storage.
+
+Masterd will know through the mapping of disk templates to storage types which
+storage type uses which mechanism for storage calculation and invoke only the
+needed ones.
+
+Note that for file and sharedfile the node knows which directories are allowed
+and won't allow any other directory to be queried for security reasons. The
+actual path still needs to be passed to distinguish the two, as the type will
+be the same for both.
+
+These calculations will be implemented in the node storage system
+(currently lib/storage.py) but querying will still happen through the
+``node info`` call, to avoid requiring an extra RPC each time.
+
+Ganeti reporting
+----------------
+
+`gnt-node list`` can be queried for the different disk templates, if they
+are enabled. By default, it will just report information about the default
+disk template. Examples::
+
+  > gnt-node list
+  Node                       DTotal DFree MTotal MNode MFree Pinst Sinst
+  mynode1                      3.6T  3.6T  64.0G 1023M 62.2G     1     0
+  mynode2                      3.6T  3.6T  64.0G 1023M 62.0G     2     1
+  mynode3                      3.6T  3.6T  64.0G 1023M 62.3G     0     2
+
+  > gnt-node list -o dtotal/drbd,dfree/file
+  Node      DTotal (drbd, myvg) DFree (file, mydir)
+  mynode1                 3.6T                    -
+  mynode2                 3.6T                    -
+
+Note that for drbd, we only report the space of the vg and only if it was not
+renamed to something different than the default volume group name. With this
+design, there is also no possibility to ask about the meta volume group. We
+restrict the design here to make the transition to storage pools easier (as it
+is an interim state only). It is the administrator's responsibility to ensure
+that there is enough space for the meta volume group.
+
+When storage pools are implemented, we switch from referencing the disk template
+to referencing the storage pool name. For that, of course, the pool names need
+to be unique over all storage types. For drbd, we will use the default 'drbd'
+storage pool and possibly a second lvm-based storage pool for the metavg. It
+will be possible to rename storage pools (thus also the default lvm storage
+pool). There will be new functionality to ask about what storage pools are
+available and of what type. Storage pools will have a storage pool type which is
+one of the disk templates. There can be more than one storage pool based on the
+same disk template, therefore we will then start referencing the storage pool
+name instead of the disk template.
+
+``gnt-cluster info`` will report which disk templates are enabled, i.e.
+which ones are supported according to the cluster configuration. Example
+output::
+
+  > gnt-cluster info
+  [...]
+  Cluster parameters:
+    - [...]
+    - enabled disk templates: plain, drbd, sharedfile, rados
+    - [...]
+
+``gnt-node list-storage`` will not be affected by any changes, since this design
+is restricted only to free storage reporting for non-shared storage types.
+
+Allocator changes
+-----------------
+
+The iallocator protocol doesn't need to change: since we know which
+disk template an instance has, we'll pass only the "free" value for that
+disk template to the iallocator, when asking for an allocation to be
+made. Note that for DRBD nowadays we ignore the case when vg and metavg
+are different, and we only consider the main volume group. Fixing this is
+outside the scope of this design.
+
+With this design, we ensure forward-compatibility with respect to storage
+pools. For now, we'll report space for all available disk templates that
+are based on non-shared storage types, in the future, for all available
+storage pools.
+
+Rebalancing changes
+-------------------
+
+Hbal will not need changes, as it handles it already. We don't forecast
+any changes needed to it.
+
+Space reporting changes
+-----------------------
+
+Hspace will by default report by assuming the allocation will happen on
+the default disk template for the cluster/nodegroup. An option will be added
+to manually specify a different storage.
+
+Interactions with Partitioned Ganeti
+------------------------------------
+
+Also the design for :doc:`Partitioned Ganeti <design-partitioned>` deals
+with reporting free space. Partitioned Ganeti has a different way to
+report free space for LVM on nodes where the ``exclusive_storage`` flag
+is set. That doesn't interact directly with this design, as the specifics
+of how the free space is computed is not in the scope of this design.
+But the ``node info`` call contains the value of the
+``exclusive_storage`` flag, which is currently only meaningful for the
+LVM storage type. Additional flags like the ``exclusive_storage`` flag
+for lvm might be useful for other disk templates / storage types as well.
+We therefore extend the RPC call with <disktemplate>,<key> to
+<disktemplate>,<key>,<params> to include any disk-template-specific
+(or storage-type specific) parameters in the RPC call.
+
+The reporting of free spindles, also part of Partitioned Ganeti, is not
+concerned with this design doc, as those are seen as a separate resource.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 941e548..d6cfc2c 100644 (file)
@@ -21,6 +21,7 @@ Most dependencies from :doc:`install-quick`, including ``qemu-img``
 - `pylint <http://www.logilab.org/857>`_ and its associated
   dependencies
 - `pep8 <https://github.com/jcrocholl/pep8/>`_
+- `PyYAML <http://pyyaml.org/>`_
 
 For older developement (Ganeti < 2.4) ``docbook`` was used instead
 ``pandoc``.
@@ -44,13 +45,14 @@ To generate unittest coverage reports (``make coverage``), `coverage
 
 Installation of all dependencies listed here::
 
-     $ apt-get install python-setuptools
+     $ apt-get install python-setuptools automake git fakeroot
      $ apt-get install pandoc python-epydoc graphviz
+     $ apt-get install python-yaml
      $ cd / && sudo easy_install \
                sphinx \
-               logilab-astng==0.25.1 \
+               logilab-astng==0.23.1 \
                logilab-common==0.58.0 \
-               pylint==0.23.1 \
+               pylint==0.25.1 \
                pep8==1.2 \
                coverage
 
index 5ccf935..c7fa9fb 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.7
+Documents Ganeti version 2.8
 
 .. contents::
 
index 09b492a..25687ff 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.7
+Documents Ganeti version 2.8
 
 .. contents::
 
@@ -109,6 +109,8 @@ nodegroups
   alloc_policy
     the allocation policy of the node group (consult the semantics of
     this attribute in the :manpage:`gnt-group(8)` manpage)
+  networks
+    the list of network UUID's this node group is connected to
   ipolicy
     the instance policy of the node group
   tags
@@ -380,6 +382,7 @@ time, but not included in further examples below)::
       "f4e06e0d-528a-4963-a5ad-10f3e114232d": {
         "name": "default",
         "alloc_policy": "preferred",
+        "networks": ["net-uuid-1", "net-uuid-2"],
         "ipolicy": {
           "disk-templates": ["drbd", "plain"],
           "minmax": [
index c6963b0..9e8c3ac 100644 (file)
@@ -4,17 +4,85 @@
 Welcome to Ganeti's documentation!
 ==================================
 
-Contents:
+This page is the starting point for browsing the ganeti documentation. It
+contains link to all the sections of the documentation, grouped by topic.
 
+The list of changes between Ganeti versions is provided in the :doc:`news` file.
+
+In order to help understanding the Ganeti terminology, a :doc:`glossary` is
+provided.
+
+Also see the :ref:`search`.
+
+Installing Ganeti
++++++++++++++++++
+
+In order to install Ganeti, follow the instructions contained in the
+:doc:`install`.
+
+If you are an experienced user, the content of the :doc:`install-quick` should
+be enough.
+
+Instructions for upgrading an existing installation to the latest version of
+Ganeti are contained in the :doc:`upgrade`.
+
+Using Ganeti
+++++++++++++
+
+Information about how to manage a Ganeti cluster after it has been installed
+(including management of nodes, instances, info about the tools and the
+monitoring agent) can be found in :doc:`admin`.
+
+A more example-oriended guide is available in :doc:`walkthrough`.
+
+The various tool that are part of Ganeti are described one by one in the
+:doc:`manpages`.
+
+A description of the security model underlying a Ganeti cluster can be found in
+the :doc:`security` document.
+
+Ganeti functionalities can be extended by hooking scripts automatically
+activated when certain events happen. Information on this mechanism is provided
+in the :doc:`hooks` document.
+
+While using Ganeti, the allocation of instances can happen manually or
+automatically, through some external tools making decisions about this. The API
+for such tools is described in :doc:`iallocator`.
+
+Most of the functionalities of Ganeti can be programmatically accessed through
+an API, the :doc:`rapi`.
+
+Compatibility with the standard OVF virtual machine interchange format is
+provided by the :doc:`ovfconverter`.
+
+Mainly for testing reasons, Ganeti also has :doc:`virtual-cluster`.
+
+A few functionalities are explicitly targeted for big installations, where
+multiple clusters are present. A tool for merging two existing clusters
+is provided, and is described in :doc:`cluster-merge`. There is also a document
+describing the procedure for :doc:`move-instance`.
+
+Developing Ganeti
++++++++++++++++++
+
+A few documents useful for who wants to modify Ganeti are available and listed
+in this section.
+
+A description of the locking strategy and, in particular, lock order
+dependencies is presented in :doc:`locking`.
+
+Build dependencies and other useful development-related information are provided
+in the :doc:`devnotes`.
+
+All the features implemented in Ganeti are described in a design document before
+being actually implemented. Designs can be implemented in a released version, or
+be still draft (and therefore either incomplete or not implemented).
+
+Implemented designs
+-------------------
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: 1
 
-   install-quick.rst
-   install.rst
-   upgrade.rst
-   admin.rst
-   walkthrough.rst
-   security.rst
    design-2.0.rst
    design-2.1.rst
    design-2.2.rst
@@ -24,39 +92,54 @@ Contents:
    design-2.5.rst
    design-2.6.rst
    design-2.7.rst
+   design-2.8.rst
+
+Draft designs
+-------------
+.. toctree::
+   :maxdepth: 2
+
    design-draft.rst
-   cluster-merge.rst
-   locking.rst
-   hooks.rst
-   iallocator.rst
-   rapi.rst
-   move-instance.rst
-   virtual-cluster.rst
-   ovfconverter.rst
-   devnotes.rst
-   news.rst
-   manpages.rst
-   glossary.rst
 
 .. toctree::
    :hidden:
 
+   admin.rst
+   cluster-merge.rst
+   design-autorepair.rst
    design-bulk-create.rst
    design-chained-jobs.rst
    design-cpu-pinning.rst
+   design-device-uuid-name.rst
+   design-linuxha.rst
    design-lu-generated-jobs.rst
    design-multi-reloc.rst
+   design-network.rst
    design-node-add.rst
    design-oob.rst
    design-opportunistic-locking.rst
    design-ovf-support.rst
    design-query2.rst
+   design-reason-trail.rst
    design-restricted-commands.rst
    design-shared-storage.rst
    design-virtual-clusters.rst
-   design-network.rst
-   design-linuxha.rst
-
-Also see the :ref:`search`.
+   devnotes.rst
+   glossary.rst
+   hooks.rst
+   iallocator.rst
+   install.rst
+   install-quick.rst
+   locking.rst
+   manpages.rst
+   monitoring-query-format.rst
+   move-instance.rst
+   news.rst
+   ovfconverter.rst
+   rapi.rst
+   security.rst
+   upgrade.rst
+   virtual-cluster.rst
+   walkthrough
 
 .. vim: set textwidth=72 :
index 9ab4646..3be446c 100644 (file)
@@ -561,6 +561,7 @@ You also need to copy the file ``doc/examples/ganeti.initd`` from the
 source archive to ``/etc/init.d/ganeti`` and register it with your
 distribution's startup scripts, for example in Debian::
 
+  $ chmod +x /etc/init.d/ganeti
   $ update-rc.d ganeti defaults 20 80
 
 In order to automatically restart failed instances, you need to setup a
diff --git a/doc/monitoring-query-format.rst b/doc/monitoring-query-format.rst
new file mode 100644 (file)
index 0000000..af65fcb
--- /dev/null
@@ -0,0 +1,50 @@
+The queries to the monitoring agent will be HTTP GET requests on port 1815.
+The answer will be encoded in JSON format and will depend on the specific
+accessed resource.
+
+If a request is sent to a non-existing resource, a 404 error will be returned by
+the HTTP server.
+
+The following paragraphs will present the existing resources supported by the
+current protocol version, that is version 1.
+
+``/``
++++++
+The root resource. It will return the list of the supported protocol version
+numbers.
+
+Currently, this will include only version 1.
+
+``/1``
+++++++
+Not an actual resource per-se, it is the root of all the resources of protocol
+version 1.
+
+If requested through GET, the null JSON value will be returned.
+
+``/1/list/collectors``
+++++++++++++++++++++++
+Returns a list of tuples (kind, category, name) showing all the collectors
+available in the system.
+
+``/1/report/all``
++++++++++++++++++
+A list of the reports of all the data collectors, as a JSON list.
+
+Status reporting collectors will provide their output in non-verbose format.
+The verbose format can be requested by adding the parameter ``verbose=1`` to the
+request.
+
+``/1/report/[category]/[collector_name]``
++++++++++++++++++++++++++++++++++++++++++
+Returns the report of the collector ``[collector_name]`` that belongs to the
+specified ``[category]``.
+
+The ``category`` has to be written in lowercase.
+
+If a collector does not belong to any category, ``default`` will have to be
+used as the value for ``[category]``.
+
+Status reporting collectors will provide their output in non-verbose format.
+The verbose format can be requested by adding the parameter ``verbose=1`` to the
+request.
index 37dbebd..fba7837 100644 (file)
@@ -29,7 +29,21 @@ Lines starting with the hash sign (``#``) are treated as comments. Each
 line consists of two or three fields separated by whitespace. The first
 two fields are for username and password. The third field is optional
 and can be used to specify per-user options (separated by comma without
-spaces). Available options:
+spaces).
+
+Passwords can either be written in clear text or as a hash. Clear text
+passwords may not start with an opening brace (``{``) or they must be
+prefixed with ``{cleartext}``. To use the hashed form, get the MD5 hash
+of the string ``$username:Ganeti Remote API:$password`` (e.g. ``echo -n
+'jack:Ganeti Remote API:abc123' | openssl md5``) [#pwhash]_ and prefix
+it with ``{ha1}``. Using the scheme prefix for all passwords is
+recommended. Scheme prefixes are case insensitive.
+
+Options control a user's access permissions. The section
+:ref:`rapi-access-permissions` lists the permissions required for each
+resource. If the ``--require-authentication`` command line option is
+given to the ``ganeti-rapi`` daemon, all requests require
+authentication. Available options:
 
 .. pyassert::
 
@@ -38,20 +52,26 @@ spaces). Available options:
     rapi.RAPI_ACCESS_READ,
     ])
 
+.. pyassert::
+
+  rlib2.R_2_nodes_name_storage.GET_ACCESS == [rapi.RAPI_ACCESS_WRITE]
+
+.. pyassert::
+
+  rlib2.R_2_jobs_id_wait.GET_ACCESS == [rapi.RAPI_ACCESS_WRITE]
+
 :pyeval:`rapi.RAPI_ACCESS_WRITE`
   Enables the user to execute operations modifying the cluster. Implies
-  :pyeval:`rapi.RAPI_ACCESS_READ` access.
+  :pyeval:`rapi.RAPI_ACCESS_READ` access. Resources blocking other
+  operations for read-only access, such as
+  :ref:`/2/nodes/[node_name]/storage <rapi-res-nodes-node_name-storage+get>`
+  or blocking server-side processes, such as
+  :ref:`/2/jobs/[job_id]/wait <rapi-res-jobs-job_id-wait+get>`, use
+  :pyeval:`rapi.RAPI_ACCESS_WRITE` to control access to their
+  :pyeval:`http.HTTP_GET` method.
 :pyeval:`rapi.RAPI_ACCESS_READ`
   Allow access to operations querying for information.
 
-Passwords can either be written in clear text or as a hash. Clear text
-passwords may not start with an opening brace (``{``) or they must be
-prefixed with ``{cleartext}``. To use the hashed form, get the MD5 hash
-of the string ``$username:Ganeti Remote API:$password`` (e.g. ``echo -n
-'jack:Ganeti Remote API:abc123' | openssl md5``) [#pwhash]_ and prefix
-it with ``{ha1}``. Using the scheme prefix for all passwords is
-recommended. Scheme prefixes are not case sensitive.
-
 Example::
 
   # Give Jack and Fred read-only access
@@ -75,7 +95,8 @@ When using the RAPI, username and password can be sent to the server
 by using the standard HTTP basic access authentication. This means that
 for accessing the protected URL ``https://cluster.example.com/resource``,
 the address ``https://username:password@cluster.example.com/resource`` should
-be used instead. Alternatively, the appropriate parameter of your HTTP client
+be used instead.
+Alternatively, the appropriate parameter of your HTTP client
 (such as ``-u`` for ``curl``) can be used.
 
 .. [#pwhash] Using the MD5 hash of username, realm and password is
@@ -207,8 +228,7 @@ The instance policy specification is a dict with the following fields:
 
 .. pyassert::
 
-  constants.IPOLICY_ALL_KEYS == set([constants.ISPECS_MIN,
-                                     constants.ISPECS_MAX,
+  constants.IPOLICY_ALL_KEYS == set([constants.ISPECS_MINMAX,
                                      constants.ISPECS_STD,
                                      constants.IPOLICY_DTS,
                                      constants.IPOLICY_VCPU_RATIO,
@@ -230,24 +250,30 @@ The instance policy specification is a dict with the following fields:
 .. |ispec-std| replace:: :pyeval:`constants.ISPECS_STD`
 
 
-|ispec-min|, |ispec-max|, |ispec-std|
-  A sub- `dict` with the following fields, which sets the limit and standard
-  values of the instances:
-
-  :pyeval:`constants.ISPEC_MEM_SIZE`
-    The size in MiB of the memory used
-  :pyeval:`constants.ISPEC_DISK_SIZE`
-    The size in MiB of the disk used
-  :pyeval:`constants.ISPEC_DISK_COUNT`
-    The numbers of disks used
-  :pyeval:`constants.ISPEC_CPU_COUNT`
-    The numbers of cpus used
-  :pyeval:`constants.ISPEC_NIC_COUNT`
-    The numbers of nics used
-  :pyeval:`constants.ISPEC_SPINDLE_USE`
-    The numbers of virtual disk spindles used by this instance. They are
-    not real in the sense of actual HDD spindles, but useful for
-    accounting the spindle usage on the residing node
+:pyeval:`constants.ISPECS_MINMAX`
+  A list of dictionaries, each with the following two fields:
+
+  |ispec-min|, |ispec-max|
+    A sub- `dict` with the following fields, which sets the limit of the
+    instances:
+
+    :pyeval:`constants.ISPEC_MEM_SIZE`
+      The size in MiB of the memory used
+    :pyeval:`constants.ISPEC_DISK_SIZE`
+      The size in MiB of the disk used
+    :pyeval:`constants.ISPEC_DISK_COUNT`
+      The numbers of disks used
+    :pyeval:`constants.ISPEC_CPU_COUNT`
+      The numbers of cpus used
+    :pyeval:`constants.ISPEC_NIC_COUNT`
+      The numbers of nics used
+    :pyeval:`constants.ISPEC_SPINDLE_USE`
+      The numbers of virtual disk spindles used by this instance. They
+      are not real in the sense of actual HDD spindles, but useful for
+      accounting the spindle usage on the residing node
+|ispec-std|
+  A sub- `dict` with the same fields as |ispec-min| and |ispec-max| above,
+  which sets the standard values of the instances.
 :pyeval:`constants.IPOLICY_DTS`
   A `list` of disk templates allowed for instances using this policy
 :pyeval:`constants.IPOLICY_VCPU_RATIO`
@@ -341,12 +367,17 @@ method is supported.
 
 Has no function, but for legacy reasons the ``GET`` method is supported.
 
+.. _rapi-res-info:
+
 ``/2/info``
 +++++++++++
 
 Cluster information resource.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/info
+
+
+.. _rapi-res-info+get:
 
 ``GET``
 ~~~~~~~
@@ -386,12 +417,17 @@ Example::
   }
 
 
+.. _rapi-res-redistribute-config:
+
 ``/2/redistribute-config``
 ++++++++++++++++++++++++++
 
 Redistribute configuration to all nodes.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/redistribute-config
+
+
+.. _rapi-res-redistribute-config+put:
 
 ``PUT``
 ~~~~~~~
@@ -403,9 +439,16 @@ Job result:
 .. opcode_result:: OP_CLUSTER_REDIST_CONF
 
 
+.. _rapi-res-features:
+
 ``/2/features``
 +++++++++++++++
 
+.. rapi_resource_details:: /2/features
+
+
+.. _rapi-res-features+get:
+
 ``GET``
 ~~~~~~~
 
@@ -431,12 +474,17 @@ features:
   a new-style result (see resource description)
 
 
+.. _rapi-res-modify:
+
 ``/2/modify``
 ++++++++++++++++++++++++++++++++++++++++
 
 Modifies cluster parameters.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/modify
+
+
+.. _rapi-res-modify+put:
 
 ``PUT``
 ~~~~~~~
@@ -452,12 +500,17 @@ Job result:
 .. opcode_result:: OP_CLUSTER_SET_PARAMS
 
 
+.. _rapi-res-groups:
+
 ``/2/groups``
 +++++++++++++
 
 The groups resource.
 
-It supports the following commands: ``GET``, ``POST``.
+.. rapi_resource_details:: /2/groups
+
+
+.. _rapi-res-groups+get:
 
 ``GET``
 ~~~~~~~
@@ -508,6 +561,9 @@ Example::
       …
     ]
 
+
+.. _rapi-res-groups+post:
+
 ``POST``
 ~~~~~~~~
 
@@ -530,12 +586,17 @@ Job result:
 .. opcode_result:: OP_GROUP_ADD
 
 
+.. _rapi-res-groups-group_name:
+
 ``/2/groups/[group_name]``
 ++++++++++++++++++++++++++
 
 Returns information about a node group.
 
-It supports the following commands: ``GET``, ``DELETE``.
+.. rapi_resource_details:: /2/groups/[group_name]
+
+
+.. _rapi-res-groups-group_name+get:
 
 ``GET``
 ~~~~~~~
@@ -545,6 +606,8 @@ the node group list.
 
 Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.G_FIELDS))`.
 
+.. _rapi-res-groups-group_name+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -557,12 +620,17 @@ Job result:
 .. opcode_result:: OP_GROUP_REMOVE
 
 
+.. _rapi-res-groups-group_name-modify:
+
 ``/2/groups/[group_name]/modify``
 +++++++++++++++++++++++++++++++++
 
 Modifies the parameters of a node group.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/groups/[group_name]/modify
+
+
+.. _rapi-res-groups-group_name-modify+put:
 
 ``PUT``
 ~~~~~~~
@@ -579,12 +647,17 @@ Job result:
 .. opcode_result:: OP_GROUP_SET_PARAMS
 
 
+.. _rapi-res-groups-group_name-rename:
+
 ``/2/groups/[group_name]/rename``
 +++++++++++++++++++++++++++++++++
 
 Renames a node group.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/groups/[group_name]/rename
+
+
+.. _rapi-res-groups-group_name-rename+put:
 
 ``PUT``
 ~~~~~~~
@@ -601,12 +674,16 @@ Job result:
 .. opcode_result:: OP_GROUP_RENAME
 
 
+.. _rapi-res-groups-group_name-assign-nodes:
+
 ``/2/groups/[group_name]/assign-nodes``
 +++++++++++++++++++++++++++++++++++++++
 
 Assigns nodes to a group.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/groups/[group_name]/assign-nodes
+
+.. _rapi-res-groups-group_name-assign-nodes+put:
 
 ``PUT``
 ~~~~~~~
@@ -622,13 +699,17 @@ Job result:
 
 .. opcode_result:: OP_GROUP_ASSIGN_NODES
 
+.. _rapi-res-groups-group_name-tags:
 
 ``/2/groups/[group_name]/tags``
 +++++++++++++++++++++++++++++++
 
 Manages per-nodegroup tags.
 
-Supports the following commands: ``GET``, ``PUT``, ``DELETE``.
+.. rapi_resource_details:: /2/groups/[group_name]/tags
+
+
+.. _rapi-res-groups-group_name-tags+get:
 
 ``GET``
 ~~~~~~~
@@ -639,6 +720,8 @@ Example::
 
     ["tag1", "tag2", "tag3"]
 
+.. _rapi-res-groups-group_name-tags+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -650,6 +733,8 @@ result will be a job id.
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-groups-group_name-tags+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -663,12 +748,17 @@ to URI like::
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-networks:
+
 ``/2/networks``
 +++++++++++++++
 
 The networks resource.
 
-It supports the following commands: ``GET``, ``POST``.
+.. rapi_resource_details:: /2/networks
+
+
+.. _rapi-res-networks+get:
 
 ``GET``
 ~~~~~~~
@@ -716,6 +806,9 @@ Example::
       …
     ]
 
+
+.. _rapi-res-networks+post:
+
 ``POST``
 ~~~~~~~~
 
@@ -735,12 +828,17 @@ Job result:
 .. opcode_result:: OP_NETWORK_ADD
 
 
+.. _rapi-res-networks-network_name:
+
 ``/2/networks/[network_name]``
 ++++++++++++++++++++++++++++++
 
 Returns information about a network.
 
-It supports the following commands: ``GET``, ``DELETE``.
+.. rapi_resource_details:: /2/networks/[network_name]
+
+
+.. _rapi-res-networks-network_name+get:
 
 ``GET``
 ~~~~~~~
@@ -750,6 +848,9 @@ the network list.
 
 Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.NET_FIELDS))`.
 
+
+.. _rapi-res-networks-network_name+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -762,12 +863,17 @@ Job result:
 .. opcode_result:: OP_NETWORK_REMOVE
 
 
+.. _rapi-res-networks-network_name-modify:
+
 ``/2/networks/[network_name]/modify``
 +++++++++++++++++++++++++++++++++++++
 
 Modifies the parameters of a network.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/networks/[network_name]/modify
+
+
+.. _rapi-res-networks-network_name-modify+put:
 
 ``PUT``
 ~~~~~~~
@@ -783,12 +889,17 @@ Job result:
 .. opcode_result:: OP_NETWORK_SET_PARAMS
 
 
+.. _rapi-res-networks-network_name-connect:
+
 ``/2/networks/[network_name]/connect``
 ++++++++++++++++++++++++++++++++++++++
 
 Connects a network to a nodegroup.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/networks/[network_name]/connect
+
+
+.. _rapi-res-networks-network_name-connect+put:
 
 ``PUT``
 ~~~~~~~
@@ -804,12 +915,17 @@ Job result:
 .. opcode_result:: OP_NETWORK_CONNECT
 
 
+.. _rapi-res-networks-network_name-disconnect:
+
 ``/2/networks/[network_name]/disconnect``
 +++++++++++++++++++++++++++++++++++++++++
 
 Disonnects a network from a nodegroup.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/networks/[network_name]/disconnect
+
+
+.. _rapi-res-networks-network_name-disconnect+put:
 
 ``PUT``
 ~~~~~~~
@@ -825,12 +941,17 @@ Job result:
 .. opcode_result:: OP_NETWORK_DISCONNECT
 
 
+.. _rapi-res-networks-network_name-tags:
+
 ``/2/networks/[network_name]/tags``
 +++++++++++++++++++++++++++++++++++
 
 Manages per-network tags.
 
-Supports the following commands: ``GET``, ``PUT``, ``DELETE``.
+.. rapi_resource_details:: /2/networks/[network_name]/tags
+
+
+.. _rapi-res-networks-network_name-tags+get:
 
 ``GET``
 ~~~~~~~
@@ -841,6 +962,9 @@ Example::
 
     ["tag1", "tag2", "tag3"]
 
+
+.. _rapi-res-networks-network_name-tags+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -852,6 +976,8 @@ result will be a job id.
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-networks-network_name-tags+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -865,12 +991,17 @@ to URI like::
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-instances-multi-alloc:
+
 ``/2/instances-multi-alloc``
 ++++++++++++++++++++++++++++
 
 Tries to allocate multiple instances.
 
-It supports the following commands: ``POST``
+.. rapi_resource_details:: /2/instances-multi-alloc
+
+
+.. _rapi-res-instances-multi-alloc+post:
 
 ``POST``
 ~~~~~~~~
@@ -884,12 +1015,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_MULTI_ALLOC
 
 
+.. _rapi-res-instances:
+
 ``/2/instances``
 ++++++++++++++++
 
 The instances resource.
 
-It supports the following commands: ``GET``, ``POST``.
+.. rapi_resource_details:: /2/instances
+
+
+.. _rapi-res-instances+get:
 
 ``GET``
 ~~~~~~~
@@ -946,6 +1082,8 @@ Example::
     ]
 
 
+.. _rapi-res-instances+post:
+
 ``POST``
 ~~~~~~~~
 
@@ -976,12 +1114,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_CREATE
 
 
+.. _rapi-res-instances-instance_name:
+
 ``/2/instances/[instance_name]``
 ++++++++++++++++++++++++++++++++
 
 Instance-specific resource.
 
-It supports the following commands: ``GET``, ``DELETE``.
+.. rapi_resource_details:: /2/instances/[instance_name]
+
+
+.. _rapi-res-instances-instance_name+get:
 
 ``GET``
 ~~~~~~~
@@ -991,6 +1134,9 @@ the instance list.
 
 Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.I_FIELDS))`.
 
+
+.. _rapi-res-instances-instance_name+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -1003,10 +1149,15 @@ Job result:
 .. opcode_result:: OP_INSTANCE_REMOVE
 
 
+.. _rapi-res-instances-instance_name-info:
+
 ``/2/instances/[instance_name]/info``
 +++++++++++++++++++++++++++++++++++++++
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/instances/[instance_name]/info
+
+
+.. _rapi-res-instances-instance_name-info+get:
 
 ``GET``
 ~~~~~~~
@@ -1021,12 +1172,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_QUERY_DATA
 
 
+.. _rapi-res-instances-instance_name-reboot:
+
 ``/2/instances/[instance_name]/reboot``
 +++++++++++++++++++++++++++++++++++++++
 
 Reboots URI for an instance.
 
-It supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/instances/[instance_name]/reboot
+
+
+.. _rapi-res-instances-instance_name-reboot+post:
 
 ``POST``
 ~~~~~~~~
@@ -1053,12 +1209,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_REBOOT
 
 
+.. _rapi-res-instances-instance_name-shutdown:
+
 ``/2/instances/[instance_name]/shutdown``
 +++++++++++++++++++++++++++++++++++++++++
 
 Instance shutdown URI.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/shutdown
+
+
+.. _rapi-res-instances-instance_name-shutdown+put:
 
 ``PUT``
 ~~~~~~~
@@ -1075,12 +1236,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_SHUTDOWN
 
 
+.. _rapi-res-instances-instance_name-startup:
+
 ``/2/instances/[instance_name]/startup``
 ++++++++++++++++++++++++++++++++++++++++
 
 Instance startup URI.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/startup
+
+
+.. _rapi-res-instances-instance_name-startup+put:
 
 ``PUT``
 ~~~~~~~
@@ -1097,12 +1263,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_STARTUP
 
 
+.. _rapi-res-instances-instance_name-reinstall:
+
 ``/2/instances/[instance_name]/reinstall``
 ++++++++++++++++++++++++++++++++++++++++++++++
 
 Installs the operating system again.
 
-It supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/instances/[instance_name]/reinstall
+
+
+.. _rapi-res-instances-instance_name-reinstall+post:
 
 ``POST``
 ~~~~~~~~
@@ -1123,12 +1294,17 @@ parameters ``os`` (OS template name) and ``nostartup`` (bool). New
 clients should use the body parameters.
 
 
+.. _rapi-res-instances-instance_name-replace-disks:
+
 ``/2/instances/[instance_name]/replace-disks``
 ++++++++++++++++++++++++++++++++++++++++++++++
 
 Replaces disks on an instance.
 
-It supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/instances/[instance_name]/replace-disks
+
+
+.. _rapi-res-instances-instance_name-replace-disks+post:
 
 ``POST``
 ~~~~~~~~
@@ -1148,12 +1324,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_REPLACE_DISKS
 
 
+.. _rapi-res-instances-instance_name-activate-disks:
+
 ``/2/instances/[instance_name]/activate-disks``
 +++++++++++++++++++++++++++++++++++++++++++++++
 
 Activate disks on an instance.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/activate-disks
+
+
+.. _rapi-res-instances-instance_name-activate-disks+put:
 
 ``PUT``
 ~~~~~~~
@@ -1166,12 +1347,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_ACTIVATE_DISKS
 
 
+.. _rapi-res-instances-instance_name-deactivate-disks:
+
 ``/2/instances/[instance_name]/deactivate-disks``
 +++++++++++++++++++++++++++++++++++++++++++++++++
 
 Deactivate disks on an instance.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/deactivate-disks
+
+
+.. _rapi-res-instances-instance_name-deactivate-disks+put:
 
 ``PUT``
 ~~~~~~~
@@ -1183,11 +1369,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_DEACTIVATE_DISKS
 
 
+.. _rapi-res-instances-instance_name-recreate-disks:
+
 ``/2/instances/[instance_name]/recreate-disks``
 +++++++++++++++++++++++++++++++++++++++++++++++++
 
-Recreate disks of an instance. Supports the following commands:
-``POST``.
+Recreate disks of an instance.
+
+.. rapi_resource_details:: /2/instances/[instance_name]/recreate-disks
+
+
+.. _rapi-res-instances-instance_name-recreate-disks+post:
 
 ``POST``
 ~~~~~~~~
@@ -1204,12 +1396,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_RECREATE_DISKS
 
 
+.. _rapi-res-instances-instance_name-disk-disk_index-grow:
+
 ``/2/instances/[instance_name]/disk/[disk_index]/grow``
 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 Grows one disk of an instance.
 
-Supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/instances/[instance_name]/disk/[disk_index]/grow
+
+
+.. _rapi-res-instances-instance_name-disk-disk_index-grow+post:
 
 ``POST``
 ~~~~~~~~
@@ -1226,12 +1423,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_GROW_DISK
 
 
+.. _rapi-res-instances-instance_name-prepare-export:
+
 ``/2/instances/[instance_name]/prepare-export``
 +++++++++++++++++++++++++++++++++++++++++++++++++
 
 Prepares an export of an instance.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/prepare-export
+
+
+.. _rapi-res-instances-instance_name-prepare-export+put:
 
 ``PUT``
 ~~~~~~~
@@ -1243,12 +1445,17 @@ Job result:
 .. opcode_result:: OP_BACKUP_PREPARE
 
 
+.. _rapi-res-instances-instance_name-export:
+
 ``/2/instances/[instance_name]/export``
 +++++++++++++++++++++++++++++++++++++++++++++++++
 
 Exports an instance.
 
-It supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/export
+
+
+.. _rapi-res-instances-instance_name-export+put:
 
 ``PUT``
 ~~~~~~~
@@ -1266,12 +1473,17 @@ Job result:
 .. opcode_result:: OP_BACKUP_EXPORT
 
 
+.. _rapi-res-instances-instance_name-migrate:
+
 ``/2/instances/[instance_name]/migrate``
 ++++++++++++++++++++++++++++++++++++++++
 
 Migrates an instance.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/migrate
+
+
+.. _rapi-res-instances-instance_name-migrate+put:
 
 ``PUT``
 ~~~~~~~
@@ -1288,12 +1500,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_MIGRATE
 
 
+.. _rapi-res-instances-instance_name-failover:
+
 ``/2/instances/[instance_name]/failover``
 +++++++++++++++++++++++++++++++++++++++++
 
 Does a failover of an instance.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/failover
+
+
+.. _rapi-res-instances-instance_name-failover+put:
 
 ``PUT``
 ~~~~~~~
@@ -1310,12 +1527,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_FAILOVER
 
 
+.. _rapi-res-instances-instance_name-rename:
+
 ``/2/instances/[instance_name]/rename``
 ++++++++++++++++++++++++++++++++++++++++
 
 Renames an instance.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/rename
+
+
+.. _rapi-res-instances-instance_name-rename+put:
 
 ``PUT``
 ~~~~~~~
@@ -1332,12 +1554,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_RENAME
 
 
+.. _rapi-res-instances-instance_name-modify:
+
 ``/2/instances/[instance_name]/modify``
 ++++++++++++++++++++++++++++++++++++++++
 
 Modifies an instance.
 
-Supports the following commands: ``PUT``.
+.. rapi_resource_details:: /2/instances/[instance_name]/modify
+
+
+.. _rapi-res-instances-instance_name-modify+put:
 
 ``PUT``
 ~~~~~~~
@@ -1354,20 +1581,17 @@ Job result:
 .. opcode_result:: OP_INSTANCE_SET_PARAMS
 
 
+.. _rapi-res-instances-instance_name-console:
+
 ``/2/instances/[instance_name]/console``
 ++++++++++++++++++++++++++++++++++++++++
 
 Request information for connecting to instance's console.
 
-.. pyassert::
+.. rapi_resource_details:: /2/instances/[instance_name]/console
 
-  not (hasattr(rlib2.R_2_instances_name_console, "PUT") or
-       hasattr(rlib2.R_2_instances_name_console, "POST") or
-       hasattr(rlib2.R_2_instances_name_console, "DELETE"))
 
-Supports the following commands: ``GET``. Requires authentication with
-one of the following options:
-:pyeval:`utils.CommaJoin(rlib2.R_2_instances_name_console.GET_ACCESS)`.
+.. _rapi-res-instances-instance_name-console+get:
 
 ``GET``
 ~~~~~~~
@@ -1384,6 +1608,20 @@ console. Contained keys:
      constants.CONS_SPICE,
      ])
 
+.. pyassert::
+
+  frozenset(objects.InstanceConsole.GetAllSlots()) == frozenset([
+    "command",
+    "display",
+    "host",
+    "instance",
+    "kind",
+    "message",
+    "port",
+    "user",
+    ])
+
+
 ``instance``
   Instance name
 ``kind``
@@ -1406,12 +1644,17 @@ console. Contained keys:
   VNC display number (:pyeval:`constants.CONS_VNC` only)
 
 
+.. _rapi-res-instances-instance_name-tags:
+
 ``/2/instances/[instance_name]/tags``
 +++++++++++++++++++++++++++++++++++++
 
 Manages per-instance tags.
 
-It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
+.. rapi_resource_details:: /2/instances/[instance_name]/tags
+
+
+.. _rapi-res-instances-instance_name-tags+get:
 
 ``GET``
 ~~~~~~~
@@ -1422,6 +1665,9 @@ Example::
 
     ["tag1", "tag2", "tag3"]
 
+
+.. _rapi-res-instances-instance_name-tags+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1433,6 +1679,8 @@ result will be a job id.
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-instances-instance_name-tags+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -1446,12 +1694,17 @@ to URI like::
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-jobs:
+
 ``/2/jobs``
 +++++++++++
 
 The ``/2/jobs`` resource.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/jobs
+
+
+.. _rapi-res-jobs+get:
 
 ``GET``
 ~~~~~~~
@@ -1468,13 +1721,18 @@ Returned fields for bulk requests (unlike other bulk requests, these
 fields are not the same as for per-job requests):
 :pyeval:`utils.CommaJoin(sorted(rlib2.J_FIELDS_BULK))`.
 
+
+.. _rapi-res-jobs-job_id:
+
 ``/2/jobs/[job_id]``
 ++++++++++++++++++++
 
-
 Individual job URI.
 
-It supports the following commands: ``GET``, ``DELETE``.
+.. rapi_resource_details:: /2/jobs/[job_id]
+
+
+.. _rapi-res-jobs-job_id+get:
 
 ``GET``
 ~~~~~~~
@@ -1562,15 +1820,24 @@ Note that in the above list, by entity we refer to a node or instance,
 while by a resource we refer to an instance's disk, or NIC, etc.
 
 
+.. _rapi-res-jobs-job_id+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
 Cancel a not-yet-started job.
 
 
+.. _rapi-res-jobs-job_id-wait:
+
 ``/2/jobs/[job_id]/wait``
 +++++++++++++++++++++++++
 
+.. rapi_resource_details:: /2/jobs/[job_id]/wait
+
+
+.. _rapi-res-jobs-job_id-wait+get:
+
 ``GET``
 ~~~~~~~
 
@@ -1591,12 +1858,17 @@ Returns None if no changes have been detected and a dict with two keys,
 ``job_info`` and ``log_entries`` otherwise.
 
 
+.. _rapi-res-nodes:
+
 ``/2/nodes``
 ++++++++++++
 
 Nodes resource.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/nodes
+
+
+.. _rapi-res-nodes+get:
 
 ``GET``
 ~~~~~~~
@@ -1641,19 +1913,37 @@ Example::
       …
     ]
 
+
+.. _rapi-res-nodes-node_name:
+
 ``/2/nodes/[node_name]``
 +++++++++++++++++++++++++++++++++
 
 Returns information about a node.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/nodes/[node_name]
+
+
+.. _rapi-res-nodes-node_name+get:
+
+``GET``
+~~~~~~~
 
 Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.N_FIELDS))`.
 
+
+
+.. _rapi-res-nodes-node_name-powercycle:
+
 ``/2/nodes/[node_name]/powercycle``
 +++++++++++++++++++++++++++++++++++
 
-Powercycles a node. Supports the following commands: ``POST``.
+Powercycles a node.
+
+.. rapi_resource_details:: /2/nodes/[node_name]/powercycle
+
+
+.. _rapi-res-nodes-node_name-powercycle+post:
 
 ``POST``
 ~~~~~~~~
@@ -1665,12 +1955,17 @@ Job result:
 .. opcode_result:: OP_NODE_POWERCYCLE
 
 
+.. _rapi-res-nodes-node_name-evacuate:
+
 ``/2/nodes/[node_name]/evacuate``
 +++++++++++++++++++++++++++++++++
 
 Evacuates instances off a node.
 
-It supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/nodes/[node_name]/evacuate
+
+
+.. _rapi-res-nodes-node_name-evacuate+post:
 
 ``POST``
 ~~~~~~~~
@@ -1692,12 +1987,17 @@ Job result:
 .. opcode_result:: OP_NODE_EVACUATE
 
 
+.. _rapi-res-nodes-node_name-migrate:
+
 ``/2/nodes/[node_name]/migrate``
 +++++++++++++++++++++++++++++++++
 
 Migrates all primary instances from a node.
 
-It supports the following commands: ``POST``.
+.. rapi_resource_details:: /2/nodes/[node_name]/migrate
+
+
+.. _rapi-res-nodes-node_name-migrate+post:
 
 ``POST``
 ~~~~~~~~
@@ -1717,12 +2017,14 @@ Job result:
 .. opcode_result:: OP_NODE_MIGRATE
 
 
+.. _rapi-res-nodes-node_name-role:
+
 ``/2/nodes/[node_name]/role``
 +++++++++++++++++++++++++++++
 
 Manages node role.
 
-It supports the following commands: ``GET``, ``PUT``.
+.. rapi_resource_details:: /2/nodes/[node_name]/role
 
 The role is always one of the following:
 
@@ -1735,6 +2037,9 @@ Note that the 'master' role is a special, and currently it can't be
 modified via RAPI, only via the command line (``gnt-cluster
 master-failover``).
 
+
+.. _rapi-res-nodes-node_name-role+get:
+
 ``GET``
 ~~~~~~~
 
@@ -1744,6 +2049,9 @@ Example::
 
     "master-candidate"
 
+
+.. _rapi-res-nodes-node_name-role+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1759,11 +2067,17 @@ Job result:
 .. opcode_result:: OP_NODE_SET_PARAMS
 
 
+.. _rapi-res-nodes-node_name-modify:
+
 ``/2/nodes/[node_name]/modify``
 +++++++++++++++++++++++++++++++
 
-Modifies the parameters of a node. Supports the following commands:
-``POST``.
+Modifies the parameters of a node.
+
+.. rapi_resource_details:: /2/nodes/[node_name]/modify
+
+
+.. _rapi-res-nodes-node_name-modify+post:
 
 ``POST``
 ~~~~~~~~
@@ -1780,15 +2094,23 @@ Job result:
 .. opcode_result:: OP_NODE_SET_PARAMS
 
 
+.. _rapi-res-nodes-node_name-storage:
+
 ``/2/nodes/[node_name]/storage``
 ++++++++++++++++++++++++++++++++
 
 Manages storage units on the node.
 
+.. rapi_resource_details:: /2/nodes/[node_name]/storage
+
+
+.. _rapi-res-nodes-node_name-storage+get:
+
 ``GET``
 ~~~~~~~
 
-.. pyassert::
+FIXME: enable ".. pyassert::" again when all storage types are
+implemented::
 
    constants.VALID_STORAGE_TYPES == set([constants.ST_FILE,
                                          constants.ST_LVM_PV,
@@ -1800,11 +2122,19 @@ Requests a list of storage units on a node. Requires the parameters
 ``output_fields``. The result will be a job id, using which the result
 can be retrieved.
 
+
+.. _rapi-res-nodes-node_name-storage-modify:
+
 ``/2/nodes/[node_name]/storage/modify``
 +++++++++++++++++++++++++++++++++++++++
 
 Modifies storage units on the node.
 
+.. rapi_resource_details:: /2/nodes/[node_name]/storage/modify
+
+
+.. _rapi-res-nodes-node_name-storage-modify+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1820,11 +2150,18 @@ Job result:
 .. opcode_result:: OP_NODE_MODIFY_STORAGE
 
 
+.. _rapi-res-nodes-node_name-storage-repair:
+
 ``/2/nodes/[node_name]/storage/repair``
 +++++++++++++++++++++++++++++++++++++++
 
 Repairs a storage unit on the node.
 
+.. rapi_resource_details:: /2/nodes/[node_name]/storage/repair
+
+
+.. _rapi-res-nodes-node_name-storage-repair+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1844,12 +2181,17 @@ Job result:
 .. opcode_result:: OP_REPAIR_NODE_STORAGE
 
 
+.. _rapi-res-nodes-node_name-tags:
+
 ``/2/nodes/[node_name]/tags``
 +++++++++++++++++++++++++++++
 
 Manages per-node tags.
 
-It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
+.. rapi_resource_details:: /2/nodes/[node_name]/tags
+
+
+.. _rapi-res-nodes-node_name-tags+get:
 
 ``GET``
 ~~~~~~~
@@ -1860,6 +2202,9 @@ Example::
 
     ["tag1", "tag2", "tag3"]
 
+
+.. _rapi-res-nodes-node_name-tags+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1870,6 +2215,9 @@ will be a job id.
 
 It supports the ``dry-run`` argument.
 
+
+.. _rapi-res-nodes-node_name-tags+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -1883,6 +2231,8 @@ to URI like::
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-query-resource:
+
 ``/2/query/[resource]``
 +++++++++++++++++++++++
 
@@ -1891,15 +2241,10 @@ pages and using ``/2/query/[resource]/fields``. The resource is one of
 :pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the :doc:`query2
 design document <design-query2>` for more details.
 
-.. pyassert::
+.. rapi_resource_details:: /2/query/[resource]
 
-  (rlib2.R_2_query.GET_ACCESS == rlib2.R_2_query.PUT_ACCESS and
-   not (hasattr(rlib2.R_2_query, "POST") or
-        hasattr(rlib2.R_2_query, "DELETE")))
 
-Supports the following commands: ``GET``, ``PUT``. Requires
-authentication with one of the following options:
-:pyeval:`utils.CommaJoin(rlib2.R_2_query.GET_ACCESS)`.
+.. _rapi-res-query-resource+get:
 
 ``GET``
 ~~~~~~~
@@ -1908,6 +2253,9 @@ Returns list of included fields and actual data. Takes a query parameter
 named "fields", containing a comma-separated list of field names. Does
 not support filtering.
 
+
+.. _rapi-res-query-resource+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1918,6 +2266,8 @@ be given and must be either ``null`` or a list containing filter
 operators.
 
 
+.. _rapi-res-query-resource-fields:
+
 ``/2/query/[resource]/fields``
 ++++++++++++++++++++++++++++++
 
@@ -1925,7 +2275,10 @@ Request list of available fields for a resource. The resource is one of
 :pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the
 :doc:`query2 design document <design-query2>` for more details.
 
-Supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/query/[resource]/fields
+
+
+.. _rapi-res-query-resource-fields+get:
 
 ``GET``
 ~~~~~~~
@@ -1935,12 +2288,17 @@ optional query parameter named "fields", containing a comma-separated
 list of field names.
 
 
+.. _rapi-res-os:
+
 ``/2/os``
 +++++++++
 
 OS resource.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /2/os
+
+
+.. _rapi-res-os+get:
 
 ``GET``
 ~~~~~~~
@@ -1954,12 +2312,18 @@ Example::
 
     ["debian-etch"]
 
+
+.. _rapi-res-tags:
+
 ``/2/tags``
 +++++++++++
 
 Manages cluster tags.
 
-It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
+.. rapi_resource_details:: /2/tags
+
+
+.. _rapi-res-tags+get:
 
 ``GET``
 ~~~~~~~
@@ -1970,6 +2334,9 @@ Example::
 
     ["tag1", "tag2", "tag3"]
 
+
+.. _rapi-res-tags+put:
+
 ``PUT``
 ~~~~~~~
 
@@ -1981,6 +2348,8 @@ will be a job id.
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-tags+delete:
+
 ``DELETE``
 ~~~~~~~~~~
 
@@ -1994,6 +2363,8 @@ to URI like::
 It supports the ``dry-run`` argument.
 
 
+.. _rapi-res-version:
+
 ``/version``
 ++++++++++++
 
@@ -2002,7 +2373,10 @@ The version resource.
 This resource should be used to determine the remote API version and to
 adapt clients accordingly.
 
-It supports the following commands: ``GET``.
+.. rapi_resource_details:: /version
+
+
+.. _rapi-res-version+get:
 
 ``GET``
 ~~~~~~~
@@ -2010,6 +2384,18 @@ It supports the following commands: ``GET``.
 Returns the remote API version. Ganeti 1.2 returned ``1`` and Ganeti 2.0
 returns ``2``.
 
+
+.. _rapi-access-permissions:
+
+Access permissions
+------------------
+
+The following list describes the access permissions required for each
+resource. See :ref:`rapi-users` for more details.
+
+.. rapi_access_table::
+
+
 .. vim: set textwidth=72 :
 .. Local Variables:
 .. mode: rst
index 4b4e976..87c2f24 100644 (file)
@@ -1,17 +1,19 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.7
+Documents Ganeti version 2.8
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
 
 Up to version 2.3 all Ganeti code ran as root. Since version 2.4 it is
-possible to run all daemons except the node daemon as non-root users by
-specifying user names and groups at build time. The node daemon
-continues to require root privileges to create logical volumes, DRBD
-devices, start instances, etc. Cluster commands can be run as root or by
-users in a group specified at build time.
+possible to run all daemons except the node daemon and the monitoring daemon
+as non-root users by specifying user names and groups at build time.
+The node daemon continues to require root privileges to create logical volumes,
+DRBD devices, start instances, etc. Cluster commands can be run as root or by
+users in a group specified at build time. The monitoring daemon requires root
+privileges in order to be able to access and present information that are only
+avilable to root (such as the output of the ``xm`` command of Xen).
 
 Host issues
 -----------
@@ -122,24 +124,50 @@ before serving requests. This permission-based protection is documented
 and works on Linux, but is not-portable; however, Ganeti doesn't work on
 non-Linux system at the moment.
 
+Luxi daemon
+-----------
+
+The ``luxid`` daemon (automatically enabled if ``confd`` is enabled at
+build time) serves local (UNIX socket) queries about the run-time
+configuration. Answering these means talking to other cluster nodes,
+exactly as ``masterd`` does. See the notes for ``masterd`` regarding
+permission-based protection.
+
 Conf daemon
 -----------
 
-In Ganeti 2.7, the ``confd`` daemon (if enabled at build time), serves
-both network-originated queries (about the static configuration) and
-local (UNIX socket) queries (about the run-time configuration; answering
-these means talking to other cluster nodes, which makes use of the
-internal RPC SSL certificate). This makes it a bit more sensitive to
-bugs (a remote attacker could get direct access to the intra-cluster
-RPC), so to harden security it's recommended to:
-
-- disable confd at build time if it's not needed in your setup
-- otherwise, configure Ganeti (at build time) to use separate users, so
-  that the confd daemon doesn't also have access to the server SSL/TLS
-  certificates
-
-It is planned to split the two functionalities (local/remote querying)
-of confd into two separate daemons in a future Ganeti version.
+In Ganeti 2.8, the ``confd`` daemon (if enabled at build time), serves
+network-originated queries about parts of the static cluster
+configuration.
+
+If Ganeti is not configured (at build time) to use separate users,
+``confd`` has access to all Ganeti related files (including internal RPC
+SSL certificates). This makes it a bit more sensitive to bugs (a remote
+attacker could get direct access to the intra-cluster RPC), so to harden
+security it's recommended to:
+
+- disable confd at build time if it (and ``luxid``) is not needed in
+  your setup.
+- configure Ganeti (at build time) to use separate users, so that the
+  confd daemon doesn't also have access to the server SSL/TLS
+  certificates.
+- add firewall rules to protect the ``confd`` port or bind it to a
+  trusted address. Make sure that all nodes can access the daemon, as
+  the monitoring daemon requires it.
+
+Monitoring daemon
+-----------------
+
+The monitoring daemon provides information about the status and the
+performance of the cluster over HTTP.
+It is currently unencrypted and non-authenticated, therefore it is strongly
+advised to set proper firewalling rules to prevent unwanted access.
+
+The monitoring daemon runs as root, because it needs to be able to access
+privileged information (such as the state of the instances as provided by
+the Xen hypervisor). Nevertheless, the security implications are mitigated
+by the fact that the agent only provides reporting functionalities,
+without the ability to actually modify the state of the cluster.
 
 Remote API
 ----------
diff --git a/doc/users/groupmemberships.in b/doc/users/groupmemberships.in
new file mode 100644 (file)
index 0000000..07be505
--- /dev/null
@@ -0,0 +1,11 @@
+@GNTMASTERUSER@ @GNTDAEMONSGROUP@
+@GNTCONFDUSER@ @GNTDAEMONSGROUP@
+@GNTLUXIDUSER@ @GNTDAEMONSGROUP@
+@GNTRAPIUSER@ @GNTDAEMONSGROUP@
+@GNTMONDUSER@ @GNTDAEMONSGROUP@
+@GNTMASTERUSER@ @GNTADMINGROUP@
+@GNTRAPIUSER@ @GNTADMINGROUP@
+@GNTMASTERUSER@ @GNTCONFDGROUP@
+@GNTMONDUSER@ @GNTMASTERDGROUP@
+@GNTLUXIDUSER@ @GNTMASTERDGROUP@
+@GNTLUXIDUSER@ @GNTCONFDGROUP@
diff --git a/doc/users/groups.in b/doc/users/groups.in
new file mode 100644 (file)
index 0000000..09306b5
--- /dev/null
@@ -0,0 +1,7 @@
+@GNTDAEMONSGROUP@
+@GNTADMINGROUP@
+@GNTMASTERUSER@
+@GNTRAPIUSER@
+@GNTCONFDUSER@
+@GNTLUXIDUSER@
+@GNTMONDUSER@
diff --git a/doc/users/users.in b/doc/users/users.in
new file mode 100644 (file)
index 0000000..83b6d32
--- /dev/null
@@ -0,0 +1,6 @@
+@GNTMASTERUSER@ @GNTMASTERDGROUP@
+@GNTRAPIUSER@ @GNTRAPIGROUP@
+@GNTCONFDUSER@ @GNTCONFDGROUP@
+@GNTLUXIDUSER@ @GNTLUXIDGROUP@
+@GNTMONDUSER@ @GNTMONDGROUP@
+@GNTNODEDUSER@
index 54abd04..c460a8d 100644 (file)
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.7
+Documents Ganeti version 2.8
 
 .. contents::
 
index a035146..a75432b 100644 (file)
@@ -110,6 +110,34 @@ class RPCFail(Exception):
   """
 
 
+def _GetInstReasonFilename(instance_name):
+  """Path of the file containing the reason of the instance status change.
+
+  @type instance_name: string
+  @param instance_name: The name of the instance
+  @rtype: string
+  @return: The path of the file
+
+  """
+  return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)
+
+
+def _StoreInstReasonTrail(instance_name, trail):
+  """Serialize a reason trail related to an instance change of state to file.
+
+  The exact location of the file depends on the name of the instance and on
+  the configuration of the Ganeti cluster defined at deploy time.
+
+  @type instance_name: string
+  @param instance_name: The name of the instance
+  @rtype: None
+
+  """
+  json = serializer.DumpJson(trail)
+  filename = _GetInstReasonFilename(instance_name)
+  utils.WriteFile(filename, data=json)
+
+
 def _Fail(msg, *args, **kwargs):
   """Log an error and the raise an RPCFail exception.
 
@@ -1300,13 +1328,17 @@ def _GatherAndLinkBlockDevs(instance):
   return block_devices
 
 
-def StartInstance(instance, startup_paused):
+def StartInstance(instance, startup_paused, reason, store_reason=True):
   """Start an instance.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
   @type startup_paused: bool
   @param instance: pause instance at startup?
+  @type reason: list of reasons
+  @param reason: the reason trail for this startup
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
@@ -1320,6 +1352,8 @@ def StartInstance(instance, startup_paused):
     block_devices = _GatherAndLinkBlockDevs(instance)
     hyper = hypervisor.GetHypervisor(instance.hypervisor)
     hyper.StartInstance(instance, block_devices, startup_paused)
+    if store_reason:
+      _StoreInstReasonTrail(instance.name, reason)
   except errors.BlockDeviceError, err:
     _Fail("Block device error: %s", err, exc=True)
   except errors.HypervisorError, err:
@@ -1327,7 +1361,7 @@ def StartInstance(instance, startup_paused):
     _Fail("Hypervisor error: %s", err, exc=True)
 
 
-def InstanceShutdown(instance, timeout):
+def InstanceShutdown(instance, timeout, reason, store_reason=True):
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
@@ -1336,6 +1370,10 @@ def InstanceShutdown(instance, timeout):
   @param instance: the instance object
   @type timeout: integer
   @param timeout: maximum timeout for soft shutdown
+  @type reason: list of reasons
+  @param reason: the reason trail for this shutdown
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
@@ -1357,6 +1395,8 @@ def InstanceShutdown(instance, timeout):
 
       try:
         hyper.StopInstance(instance, retry=self.tried_once)
+        if store_reason:
+          _StoreInstReasonTrail(instance.name, reason)
       except errors.HypervisorError, err:
         if iname not in hyper.ListInstances():
           # if the instance is no longer existing, consider this a
@@ -1396,7 +1436,7 @@ def InstanceShutdown(instance, timeout):
   _RemoveBlockDevLinks(iname, instance.disks)
 
 
-def InstanceReboot(instance, reboot_type, shutdown_timeout):
+def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   """Reboot an instance.
 
   @type instance: L{objects.Instance}
@@ -1414,6 +1454,8 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout):
         instance (instead of a call_instance_reboot RPC)
   @type shutdown_timeout: integer
   @param shutdown_timeout: maximum timeout for soft shutdown
+  @type reason: list of reasons
+  @param reason: the reason trail for this reboot
   @rtype: None
 
   """
@@ -1430,8 +1472,10 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout):
       _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
-      InstanceShutdown(instance, shutdown_timeout)
-      return StartInstance(instance, False)
+      InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
+      result = StartInstance(instance, False, reason, store_reason=False)
+      _StoreInstReasonTrail(instance.name, reason)
+      return result
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
   else:
@@ -2479,6 +2523,9 @@ def OSEnvironment(instance, inst_os, debug=0):
     real_disk = _OpenRealBD(disk)
     result["DISK_%d_PATH" % idx] = real_disk.dev_path
     result["DISK_%d_ACCESS" % idx] = disk.mode
+    result["DISK_%d_UUID" % idx] = disk.uuid
+    if disk.name:
+      result["DISK_%d_NAME" % idx] = disk.name
     if constants.HV_DISK_TYPE in instance.hvparams:
       result["DISK_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_DISK_TYPE]
@@ -2491,6 +2538,9 @@ def OSEnvironment(instance, inst_os, debug=0):
   # NICs
   for idx, nic in enumerate(instance.nics):
     result["NIC_%d_MAC" % idx] = nic.mac
+    result["NIC_%d_UUID" % idx] = nic.uuid
+    if nic.name:
+      result["NIC_%d_NAME" % idx] = nic.name
     if nic.ip:
       result["NIC_%d_IP" % idx] = nic.ip
     result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
index 1b486e7..b1e1ef9 100644 (file)
@@ -644,7 +644,7 @@ class LogicalVolume(BlockDev):
 
   @staticmethod
   def _GetVolumeInfo(lvm_cmd, fields):
-    """Returns LVM Volumen infos using lvm_cmd
+    """Returns LVM Volume infos using lvm_cmd
 
     @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
     @param fields: Fields to return
index 620de88..cc62303 100644 (file)
@@ -256,6 +256,27 @@ def _WaitForMasterDaemon():
                              " %s seconds" % _DAEMON_READY_TIMEOUT)
 
 
+def _WaitForSshDaemon(hostname, port, family):
+  """Wait for SSH daemon to become responsive.
+
+  """
+  hostip = netutils.GetHostname(name=hostname, family=family).ip
+
+  def _CheckSshDaemon():
+    if netutils.TcpPing(hostip, port, timeout=1.0, live_port_needed=True):
+      logging.debug("SSH daemon on %s:%s (IP address %s) has become"
+                    " responsive", hostname, port, hostip)
+    else:
+      raise utils.RetryAgain()
+
+  try:
+    utils.Retry(_CheckSshDaemon, 1.0, _DAEMON_READY_TIMEOUT)
+  except utils.RetryTimeout:
+    raise errors.OpExecError("SSH daemon on %s:%s (IP address %s) didn't"
+                             " become responsive within %s seconds" %
+                             (hostname, port, hostip, _DAEMON_READY_TIMEOUT))
+
+
 def RunNodeSetupCmd(cluster_name, node, basecmd, debug, verbose,
                     use_cluster_key, ask_key, strict_host_check, data):
   """Runs a command to configure something on a remote machine.
@@ -310,6 +331,8 @@ 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)
+
 
 def _InitFileStorage(file_storage_dir):
   """Initialize if needed the file storage.
@@ -349,11 +372,14 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                 maintain_node_health=False, drbd_helper=None, uid_pool=None,
                 default_iallocator=None, primary_ip_version=None, ipolicy=None,
                 prealloc_wipe_disks=False, use_external_mip_script=False,
-                hv_state=None, disk_state=None):
+                hv_state=None, disk_state=None, enabled_disk_templates=None):
   """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
 
   """
   # TODO: complete the docstring
@@ -370,6 +396,16 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                                " entries: %s" % invalid_hvs,
                                errors.ECODE_INVAL)
 
+  if not enabled_disk_templates:
+    raise errors.OpPrereqError("Enabled disk templates list must contain at"
+                               " least one member", errors.ECODE_INVAL)
+  invalid_disk_templates = \
+    set(enabled_disk_templates) - constants.DISK_TEMPLATES
+  if invalid_disk_templates:
+    raise errors.OpPrereqError("Enabled disk templates list contains invalid"
+                               " entries: %s" % invalid_disk_templates,
+                               errors.ECODE_INVAL)
+
   try:
     ipcls = netutils.IPAddress.GetClassFromIpVersion(primary_ip_version)
   except errors.ProgrammerError:
@@ -541,8 +577,17 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                                errors.ECODE_INVAL)
 
   # set up ssh config and /etc/hosts
-  sshline = utils.ReadFile(pathutils.SSH_HOST_RSA_PUB)
-  sshkey = sshline.split(" ")[1]
+  rsa_sshkey = ""
+  dsa_sshkey = ""
+  if os.path.isfile(pathutils.SSH_HOST_RSA_PUB):
+    sshline = utils.ReadFile(pathutils.SSH_HOST_RSA_PUB)
+    rsa_sshkey = sshline.split(" ")[1]
+  if os.path.isfile(pathutils.SSH_HOST_DSA_PUB):
+    sshline = utils.ReadFile(pathutils.SSH_HOST_DSA_PUB)
+    dsa_sshkey = sshline.split(" ")[1]
+  if not rsa_sshkey and not dsa_sshkey:
+    raise errors.OpPrereqError("Failed to find SSH public keys",
+                               errors.ECODE_ENVIRON)
 
   if modify_etc_hosts:
     utils.AddHostToEtcHosts(hostname.name, hostname.ip)
@@ -570,7 +615,8 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   # init of cluster config file
   cluster_config = objects.Cluster(
     serial_no=1,
-    rsahostkeypub=sshkey,
+    rsahostkeypub=rsa_sshkey,
+    dsahostkeypub=dsa_sshkey,
     highest_used_port=(constants.FIRST_DRBD_PORT - 1),
     mac_prefix=mac_prefix,
     volume_group_name=vg_name,
@@ -603,6 +649,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     ipolicy=full_ipolicy,
     hv_state_static=hv_state,
     disk_state_static=disk_state,
+    enabled_disk_templates=enabled_disk_templates,
     )
   master_node_config = objects.Node(name=hostname.name,
                                     primary_ip=hostname.ip,
index 8b6c98d..55e67cb 100644 (file)
@@ -54,14 +54,21 @@ from ganeti import opcodes
 from ganeti import ht
 from ganeti import rapi
 from ganeti import luxi
+from ganeti import objects
+from ganeti import http
 from ganeti import _autoconf
 
 import ganeti.rapi.rlib2 # pylint: disable=W0611
+import ganeti.rapi.connector # pylint: disable=W0611
 
 
 #: Regular expression for man page names
 _MAN_RE = re.compile(r"^(?P<name>[-\w_]+)\((?P<section>\d+)\)$")
 
+_TAB_WIDTH = 2
+
+RAPI_URI_ENCODE_RE = re.compile("[^_a-z0-9]+", re.I)
+
 
 class ReSTError(Exception):
   """Custom class for generating errors in Sphinx.
@@ -85,7 +92,8 @@ 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)
+               rlib2=rapi.rlib2, luxi=luxi, rapi=rapi, objects=objects,
+               http=http)
 
 # Constants documentation for man pages
 CV_ECODES_DOC = "ecodes"
@@ -213,12 +221,11 @@ class OpcodeParams(s_compat.Directive):
     exclude = self.options.get("exclude", None)
     alias = self.options.get("alias", {})
 
-    tab_width = 2
     path = op_id
     include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
 
     # Inject into state machine
-    include_lines = docutils.statemachine.string2lines(include_text, tab_width,
+    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
                                                        convert_whitespace=1)
     self.state_machine.insert_input(include_lines, path)
 
@@ -239,12 +246,11 @@ class OpcodeResult(s_compat.Directive):
   def run(self):
     op_id = self.arguments[0]
 
-    tab_width = 2
     path = op_id
     include_text = _BuildOpcodeResult(op_id)
 
     # Inject into state machine
-    include_lines = docutils.statemachine.string2lines(include_text, tab_width,
+    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
                                                        convert_whitespace=1)
     self.state_machine.insert_input(include_lines, path)
 
@@ -424,6 +430,189 @@ def _ManPageRole(typ, rawtext, text, lineno, inliner, # pylint: disable=W0102
                            options=options, content=content)
 
 
+def _EncodeRapiResourceLink(method, uri):
+  """Encodes a RAPI resource URI for use as a link target.
+
+  """
+  parts = [RAPI_URI_ENCODE_RE.sub("-", uri.lower()).strip("-")]
+
+  if method is not None:
+    parts.append(method.lower())
+
+  return "rapi-res-%s" % "+".join(filter(None, parts))
+
+
+def _MakeRapiResourceLink(method, uri):
+  """Generates link target name for RAPI resource.
+
+  """
+  if uri in ["/", "/2"]:
+    # Don't link these
+    return None
+
+  elif uri == "/version":
+    return _EncodeRapiResourceLink(method, uri)
+
+  elif uri.startswith("/2/"):
+    return _EncodeRapiResourceLink(method, uri[len("/2/"):])
+
+  else:
+    raise ReSTError("Unhandled URI '%s'" % uri)
+
+
+def _GetHandlerMethods(handler):
+  """Returns list of HTTP methods supported by handler class.
+
+  @type handler: L{rapi.baserlib.ResourceBase}
+  @param handler: Handler class
+  @rtype: list of strings
+
+  """
+  return sorted(method
+                for (method, op_attr, _, _) in rapi.baserlib.OPCODE_ATTRS
+                # Only if handler supports method
+                if hasattr(handler, method) or hasattr(handler, op_attr))
+
+
+def _DescribeHandlerAccess(handler, method):
+  """Returns textual description of required RAPI permissions.
+
+  @type handler: L{rapi.baserlib.ResourceBase}
+  @param handler: Handler class
+  @type method: string
+  @param method: HTTP method (e.g. L{http.HTTP_GET})
+  @rtype: string
+
+  """
+  access = rapi.baserlib.GetHandlerAccess(handler, method)
+
+  if access:
+    return utils.CommaJoin(sorted(access))
+  else:
+    return "*(none)*"
+
+
+class _RapiHandlersForDocsHelper(object):
+  @classmethod
+  def Build(cls):
+    """Returns dictionary of resource handlers.
+
+    """
+    resources = \
+      rapi.connector.GetHandlers("[node_name]", "[instance_name]",
+                                 "[group_name]", "[network_name]", "[job_id]",
+                                 "[disk_index]", "[resource]",
+                                 translate=cls._TranslateResourceUri)
+
+    return resources
+
+  @classmethod
+  def _TranslateResourceUri(cls, *args):
+    """Translates a resource URI for use in documentation.
+
+    @see: L{rapi.connector.GetHandlers}
+
+    """
+    return "".join(map(cls._UriPatternToString, args))
+
+  @staticmethod
+  def _UriPatternToString(value):
+    """Converts L{rapi.connector.UriPattern} to strings.
+
+    """
+    if isinstance(value, rapi.connector.UriPattern):
+      return value.content
+    else:
+      return value
+
+
+_RAPI_RESOURCES_FOR_DOCS = _RapiHandlersForDocsHelper.Build()
+
+
+def _BuildRapiAccessTable(res):
+  """Build a table with access permissions needed for all RAPI resources.
+
+  """
+  for (uri, handler) in utils.NiceSort(res.items(), key=compat.fst):
+    reslink = _MakeRapiResourceLink(None, uri)
+    if not reslink:
+      # No link was generated
+      continue
+
+    yield ":ref:`%s <%s>`" % (uri, reslink)
+
+    for method in _GetHandlerMethods(handler):
+      yield ("  | :ref:`%s <%s>`: %s" %
+             (method, _MakeRapiResourceLink(method, uri),
+              _DescribeHandlerAccess(handler, method)))
+
+
+class RapiAccessTable(s_compat.Directive):
+  """Custom directive to generate table of all RAPI resources.
+
+  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
+
+  """
+  has_content = False
+  required_arguments = 0
+  optional_arguments = 0
+  final_argument_whitespace = False
+  option_spec = {}
+
+  def run(self):
+    include_text = "\n".join(_BuildRapiAccessTable(_RAPI_RESOURCES_FOR_DOCS))
+
+    # Inject into state machine
+    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
+                                                       convert_whitespace=1)
+    self.state_machine.insert_input(include_lines, self.__class__.__name__)
+
+    return []
+
+
+class RapiResourceDetails(s_compat.Directive):
+  """Custom directive for RAPI resource details.
+
+  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
+
+  """
+  has_content = False
+  required_arguments = 1
+  optional_arguments = 0
+  final_argument_whitespace = False
+
+  def run(self):
+    uri = self.arguments[0]
+
+    try:
+      handler = _RAPI_RESOURCES_FOR_DOCS[uri]
+    except KeyError:
+      raise self.error("Unknown resource URI '%s'" % uri)
+
+    lines = [
+      ".. list-table::",
+      "   :widths: 1 4",
+      "   :header-rows: 1",
+      "",
+      "   * - Method",
+      "     - :ref:`Required permissions <rapi-users>`",
+      ]
+
+    for method in _GetHandlerMethods(handler):
+      lines.extend([
+        "   * - :ref:`%s <%s>`" % (method, _MakeRapiResourceLink(method, uri)),
+        "     - %s" % _DescribeHandlerAccess(handler, method),
+        ])
+
+    # Inject into state machine
+    include_lines = \
+      docutils.statemachine.string2lines("\n".join(lines), _TAB_WIDTH,
+                                         convert_whitespace=1)
+    self.state_machine.insert_input(include_lines, self.__class__.__name__)
+
+    return []
+
+
 def setup(app):
   """Sphinx extension callback.
 
@@ -433,6 +622,8 @@ def setup(app):
   app.add_directive("opcode_result", OpcodeResult)
   app.add_directive("pyassert", PythonAssert)
   app.add_role("pyeval", PythonEvalRole)
+  app.add_directive("rapi_access_table", RapiAccessTable)
+  app.add_directive("rapi_resource_details", RapiResourceDetails)
 
   app.add_config_value("enable_manpages", False, True)
   app.add_role("manpage", _ManPageRole)
index c82de33..a265c26 100644 (file)
@@ -81,6 +81,7 @@ __all__ = [
   "DST_NODE_OPT",
   "EARLY_RELEASE_OPT",
   "ENABLED_HV_OPT",
+  "ENABLED_DISK_TEMPLATES_OPT",
   "ERROR_CODES_OPT",
   "FAILURE_ONLY_OPT",
   "FIELDS_OPT",
@@ -107,6 +108,7 @@ __all__ = [
   "IGNORE_REMOVE_FAILURES_OPT",
   "IGNORE_SECONDARIES_OPT",
   "IGNORE_SIZE_OPT",
+  "INCLUDEDEFAULTS_OPT",
   "INTERVAL_OPT",
   "MAC_PREFIX_OPT",
   "MAINTAIN_NODE_HEALTH_OPT",
@@ -114,6 +116,7 @@ __all__ = [
   "MASTER_NETMASK_OPT",
   "MC_OPT",
   "MIGRATION_MODE_OPT",
+  "MODIFY_ETCHOSTS_OPT",
   "NET_OPT",
   "NETWORK_OPT",
   "NETWORK6_OPT",
@@ -121,6 +124,7 @@ __all__ = [
   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
   "NEW_CONFD_HMAC_KEY_OPT",
   "NEW_RAPI_CERT_OPT",
+  "NEW_PRIMARY_OPT",
   "NEW_SECONDARY_OPT",
   "NEW_SPICE_CERT_OPT",
   "NIC_PARAMS_OPT",
@@ -165,6 +169,7 @@ __all__ = [
   "PRIORITY_OPT",
   "RAPI_CERT_OPT",
   "READD_OPT",
+  "REASON_OPT",
   "REBOOT_TYPE_OPT",
   "REMOVE_INSTANCE_OPT",
   "REMOVE_RESERVED_IPS_OPT",
@@ -185,6 +190,8 @@ __all__ = [
   "SPECS_DISK_SIZE_OPT",
   "SPECS_MEM_SIZE_OPT",
   "SPECS_NIC_COUNT_OPT",
+  "SPLIT_ISPECS_OPTS",
+  "IPOLICY_STD_SPECS_OPT",
   "IPOLICY_DISK_TEMPLATES",
   "IPOLICY_VCPU_RATIO",
   "SPICE_CACERT_OPT",
@@ -231,7 +238,10 @@ __all__ = [
   "ToStderr", "ToStdout",
   "FormatError",
   "FormatQueryResult",
-  "FormatParameterDict",
+  "FormatParamsDictInfo",
+  "FormatPolicyInfo",
+  "PrintIPolicyCommand",
+  "PrintGenericInfo",
   "GenerateTable",
   "AskUser",
   "FormatTimestamp",
@@ -558,12 +568,13 @@ def check_unit(option, opt, value): # pylint: disable=W0613
     raise OptionValueError("option %s: %s" % (opt, err))
 
 
-def _SplitKeyVal(opt, data):
+def _SplitKeyVal(opt, data, parse_prefixes):
   """Convert a KeyVal string into a dict.
 
   This function will convert a key=val[,...] string into a dict. Empty
   values will be converted specially: keys which have the prefix 'no_'
-  will have the value=False and the prefix stripped, the others will
+  will have the value=False and the prefix stripped, keys with the prefix
+  "-" will have value=None and the prefix stripped, and the others will
   have value=True.
 
   @type opt: string
@@ -571,6 +582,8 @@ def _SplitKeyVal(opt, data):
       data, used in building error messages
   @type data: string
   @param data: a string of the format key=val,key=val,...
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially
   @rtype: dict
   @return: {key=val, key=val}
   @raises errors.ParameterError: if there are duplicate keys
@@ -581,13 +594,16 @@ def _SplitKeyVal(opt, data):
     for elem in utils.UnescapeAndSplit(data, sep=","):
       if "=" in elem:
         key, val = elem.split("=", 1)
-      else:
+      elif parse_prefixes:
         if elem.startswith(NO_PREFIX):
           key, val = elem[len(NO_PREFIX):], False
         elif elem.startswith(UN_PREFIX):
           key, val = elem[len(UN_PREFIX):], None
         else:
           key, val = elem, True
+      else:
+        raise errors.ParameterError("Missing value for key '%s' in option %s" %
+                                    (elem, opt))
       if key in kv_dict:
         raise errors.ParameterError("Duplicate key '%s' in option %s" %
                                     (key, opt))
@@ -595,11 +611,19 @@ def _SplitKeyVal(opt, data):
   return kv_dict
 
 
-def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
-  """Custom parser for ident:key=val,key=val options.
+def _SplitIdentKeyVal(opt, value, parse_prefixes):
+  """Helper function to parse "ident:key=val,key=val" options.
 
-  This will store the parsed values as a tuple (ident, {key: val}). As such,
-  multiple uses of this option via action=append is possible.
+  @type opt: string
+  @param opt: option name, used in error messages
+  @type value: string
+  @param value: expected to be in the format "ident:key=val,key=val,..."
+  @type parse_prefixes: bool
+  @param parse_prefixes: whether to handle prefixes specially (see
+      L{_SplitKeyVal})
+  @rtype: tuple
+  @return: (ident, {key=val, key=val})
+  @raises errors.ParameterError: in case of duplicates or other parsing errors
 
   """
   if ":" not in value:
@@ -607,31 +631,67 @@ def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
   else:
     ident, rest = value.split(":", 1)
 
-  if ident.startswith(NO_PREFIX):
+  if parse_prefixes and ident.startswith(NO_PREFIX):
     if rest:
       msg = "Cannot pass options when removing parameter groups: %s" % value
       raise errors.ParameterError(msg)
     retval = (ident[len(NO_PREFIX):], False)
-  elif (ident.startswith(UN_PREFIX) and
-        (len(ident) <= len(UN_PREFIX) or
-         not ident[len(UN_PREFIX)][0].isdigit())):
+  elif (parse_prefixes and ident.startswith(UN_PREFIX) and
+        (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
     if rest:
       msg = "Cannot pass options when removing parameter groups: %s" % value
       raise errors.ParameterError(msg)
     retval = (ident[len(UN_PREFIX):], None)
   else:
-    kv_dict = _SplitKeyVal(opt, rest)
+    kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
     retval = (ident, kv_dict)
   return retval
 
 
+def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
+  """Custom parser for ident:key=val,key=val options.
+
+  This will store the parsed values as a tuple (ident, {key: val}). As such,
+  multiple uses of this option via action=append is possible.
+
+  """
+  return _SplitIdentKeyVal(opt, value, True)
+
+
 def check_key_val(option, opt, value):  # pylint: disable=W0613
   """Custom parser class for key=val,key=val options.
 
   This will store the parsed values as a dict {key: val}.
 
   """
-  return _SplitKeyVal(opt, value)
+  return _SplitKeyVal(opt, value, True)
+
+
+def _SplitListKeyVal(opt, value):
+  retval = {}
+  for elem in value.split("/"):
+    if not elem:
+      raise errors.ParameterError("Empty section in option '%s'" % opt)
+    (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
+    if ident in retval:
+      msg = ("Duplicated parameter '%s' in parsing %s: %s" %
+             (ident, opt, elem))
+      raise errors.ParameterError(msg)
+    retval[ident] = valdict
+  return retval
+
+
+def check_multilist_ident_key_val(_, opt, value):
+  """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options.
+
+  @rtype: list of dictionary
+  @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}]
+
+  """
+  retval = []
+  for line in value.split("//"):
+    retval.append(_SplitListKeyVal(opt, line))
+  return retval
 
 
 def check_bool(option, opt, value): # pylint: disable=W0613
@@ -706,6 +766,7 @@ class CliOption(Option):
     "completion_suggest",
     ]
   TYPES = Option.TYPES + (
+    "multilistidentkeyval",
     "identkeyval",
     "keyval",
     "unit",
@@ -714,6 +775,7 @@ class CliOption(Option):
     "maybefloat",
     )
   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
+  TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
   TYPE_CHECKER["identkeyval"] = check_ident_key_val
   TYPE_CHECKER["keyval"] = check_key_val
   TYPE_CHECKER["unit"] = check_unit
@@ -824,7 +886,7 @@ FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
 
 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
                                   help="Driver to use for image files",
-                                  default="loop", metavar="<DRIVER>",
+                                  default=None, metavar="<DRIVER>",
                                   choices=list(constants.FILE_DRIVER))
 
 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
@@ -903,6 +965,18 @@ SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
                                  help="NIC count specs: list of key=value,"
                                  " where key is one of min, max, std")
 
+IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs"
+IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR,
+                                      dest="ipolicy_bounds_specs",
+                                      type="multilistidentkeyval", default=None,
+                                      help="Complete instance specs limits")
+
+IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
+IPOLICY_STD_SPECS_OPT = cli_option(IPOLICY_STD_SPECS_STR,
+                                   dest="ipolicy_std_specs",
+                                   type="keyval", default=None,
+                                   help="Complte standard instance specs")
+
 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
                                     dest="ipolicy_disk_templates",
                                     type="list", default=None,
@@ -1014,12 +1088,12 @@ SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
 
 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
                          default=False, action="store_true",
-                         help="Instead of performing the migration, try to"
-                         " recover from a failed cleanup. This is safe"
+                         help="Instead of performing the migration/failover,"
+                         " try to recover from a failed cleanup. This is safe"
                          " to run even if the instance is healthy, but it"
                          " will create extra replication traffic and "
                          " disrupt briefly the replication (like during the"
-                         " migration")
+                         " migration/failover")
 
 STATIC_OPT = cli_option("-s", "--static", dest="static",
                         action="store_true", default=False,
@@ -1063,6 +1137,11 @@ NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
                                metavar="NODE", default=None,
                                completion_suggest=OPT_COMPL_ONE_NODE)
 
+NEW_PRIMARY_OPT = cli_option("--new-primary", dest="new_primary_node",
+                             help="Specifies the new primary node",
+                             metavar="<node>", default=None,
+                             completion_suggest=OPT_COMPL_ONE_NODE)
+
 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
                             default=False, action="store_true",
                             help="Replace the disk(s) on the primary"
@@ -1155,6 +1234,12 @@ ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
                             help="Comma-separated list of hypervisors",
                             type="string", default=None)
 
+ENABLED_DISK_TEMPLATES_OPT = cli_option("--enabled-disk-templates",
+                                        dest="enabled_disk_templates",
+                                        help="Comma-separated list of "
+                                             "disk templates",
+                                        type="string", default=None)
+
 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
                             type="keyval", default={},
                             help="NIC parameters")
@@ -1222,6 +1307,12 @@ NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
                                    help="Don't modify %s" % pathutils.ETC_HOSTS,
                                    action="store_false", default=True)
 
+MODIFY_ETCHOSTS_OPT = \
+ cli_option("--modify-etc-hosts", dest="modify_etc_hosts", metavar=_YORNO,
+            default=None, type="bool",
+            help="Defines whether the cluster should autonomously modify"
+            " and keep in sync the /etc/hosts file of the nodes")
+
 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
                                     help="Don't initialize SSH keys",
                                     action="store_false", default=True)
@@ -1389,6 +1480,9 @@ FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
                               help=("Hide successful results and show failures"
                                     " only (determined by the exit code)"))
 
+REASON_OPT = cli_option("--reason", default=None,
+                        help="The reason for executing the command")
+
 
 def _PriorityOptionCb(option, _, value, parser):
   """Callback for processing C{--priority} option.
@@ -1540,8 +1634,12 @@ NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
                                   action="store_false",
                                   help="Don't check for conflicting IPs")
 
+INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
+                                 default=False, action="store_true",
+                                 help="Include default values")
+
 #: Options provided by all commands
-COMMON_OPTS = [DEBUG_OPT]
+COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
 # common options for creating instances. add and import then add their own
 # specific ones.
@@ -1570,14 +1668,19 @@ COMMON_CREATE_OPTS = [
 
 # common instance policy options
 INSTANCE_POLICY_OPTS = [
+  IPOLICY_BOUNDS_SPECS_OPT,
+  IPOLICY_DISK_TEMPLATES,
+  IPOLICY_VCPU_RATIO,
+  IPOLICY_SPINDLE_RATIO,
+  ]
+
+# instance policy split specs options
+SPLIT_ISPECS_OPTS = [
   SPECS_CPU_COUNT_OPT,
   SPECS_DISK_COUNT_OPT,
   SPECS_DISK_SIZE_OPT,
   SPECS_MEM_SIZE_OPT,
   SPECS_NIC_COUNT_OPT,
-  IPOLICY_DISK_TEMPLATES,
-  IPOLICY_VCPU_RATIO,
-  IPOLICY_SPINDLE_RATIO,
   ]
 
 
@@ -2180,6 +2283,31 @@ def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
 
 
+def _InitReasonTrail(op, opts):
+  """Builds the first part of the reason trail
+
+  Builds the initial part of the reason trail, adding the user provided reason
+  (if it exists) and the name of the command starting the operation.
+
+  @param op: the opcode the reason trail will be added to
+  @param opts: the command line options selected by the user
+
+  """
+  assert len(sys.argv) >= 2
+  trail = []
+
+  if opts.reason:
+    trail.append((constants.OPCODE_REASON_SRC_USER,
+                  opts.reason,
+                  utils.EpochNano()))
+
+  binary = os.path.basename(sys.argv[0])
+  source = "%s:%s" % (constants.OPCODE_REASON_SRC_CLIENT, binary)
+  command = sys.argv[1]
+  trail.append((source, command, utils.EpochNano()))
+  op.reason = trail
+
+
 def SetGenericOpcodeOpts(opcode_list, options):
   """Processor for generic options.
 
@@ -2199,6 +2327,7 @@ def SetGenericOpcodeOpts(opcode_list, options):
       op.dry_run = options.dry_run
     if getattr(options, "priority", None) is not None:
       op.priority = options.priority
+    _InitReasonTrail(op, options)
 
 
 def GetClient(query=False):
@@ -2211,7 +2340,15 @@ def GetClient(query=False):
       connected to the query socket instead of the masterd socket
 
   """
-  if query and constants.ENABLE_SPLIT_QUERY:
+  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
@@ -2298,10 +2435,12 @@ def FormatError(err):
     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
   elif isinstance(err, luxi.NoMasterError):
     if err.args[0] == pathutils.MASTER_SOCKET:
-      daemon = "master"
+      daemon = "the master daemon"
+    elif err.args[0] == pathutils.QUERY_SOCKET:
+      daemon = "the config daemon"
     else:
-      daemon = "config"
-    obuf.write("Cannot communicate with the %s daemon.\nIs it running"
+      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):
     obuf.write("Timeout while talking to the master daemon. Jobs might have"
@@ -3561,31 +3700,127 @@ class JobExecutor(object):
       return [row[1:3] for row in self.jobs]
 
 
-def FormatParameterDict(buf, param_dict, actual, level=1):
+def FormatParamsDictInfo(param_dict, actual):
   """Formats a parameter dictionary.
 
-  @type buf: L{StringIO}
-  @param buf: the buffer into which to write
   @type param_dict: dict
   @param param_dict: the own parameters
   @type actual: dict
   @param actual: the current parameter set (including defaults)
-  @param level: Level of indent
+  @rtype: dict
+  @return: dictionary where the value of each parameter is either a fully
+      formatted string or a dictionary containing formatted strings
 
   """
-  indent = "  " * level
-
-  for key in sorted(actual):
-    data = actual[key]
-    buf.write("%s- %s:" % (indent, key))
-
+  ret = {}
+  for (key, data) in actual.items():
     if isinstance(data, dict) and data:
-      buf.write("\n")
-      FormatParameterDict(buf, param_dict.get(key, {}), data,
-                          level=level + 1)
+      ret[key] = FormatParamsDictInfo(param_dict.get(key, {}), data)
     else:
-      val = param_dict.get(key, "default (%s)" % data)
-      buf.write(" %s\n" % val)
+      ret[key] = str(param_dict.get(key, "default (%s)" % data))
+  return ret
+
+
+def _FormatListInfoDefault(data, def_data):
+  if data is not None:
+    ret = utils.CommaJoin(data)
+  else:
+    ret = "default (%s)" % utils.CommaJoin(def_data)
+  return ret
+
+
+def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster):
+  """Formats an instance policy.
+
+  @type custom_ipolicy: dict
+  @param custom_ipolicy: own policy
+  @type eff_ipolicy: dict
+  @param eff_ipolicy: effective policy (including defaults); ignored for
+      cluster
+  @type iscluster: bool
+  @param iscluster: the policy is at cluster level
+  @rtype: list of pairs
+  @return: formatted data, suitable for L{PrintGenericInfo}
+
+  """
+  if iscluster:
+    eff_ipolicy = custom_ipolicy
+
+  minmax_out = []
+  custom_minmax = custom_ipolicy.get(constants.ISPECS_MINMAX)
+  if custom_minmax:
+    for (k, minmax) in enumerate(custom_minmax):
+      minmax_out.append([
+        ("%s/%s" % (key, k),
+         FormatParamsDictInfo(minmax[key], minmax[key]))
+        for key in constants.ISPECS_MINMAX_KEYS
+        ])
+  else:
+    for (k, minmax) in enumerate(eff_ipolicy[constants.ISPECS_MINMAX]):
+      minmax_out.append([
+        ("%s/%s" % (key, k),
+         FormatParamsDictInfo({}, minmax[key]))
+        for key in constants.ISPECS_MINMAX_KEYS
+        ])
+  ret = [("bounds specs", minmax_out)]
+
+  if iscluster:
+    stdspecs = custom_ipolicy[constants.ISPECS_STD]
+    ret.append(
+      (constants.ISPECS_STD,
+       FormatParamsDictInfo(stdspecs, stdspecs))
+      )
+
+  ret.append(
+    ("allowed disk templates",
+     _FormatListInfoDefault(custom_ipolicy.get(constants.IPOLICY_DTS),
+                            eff_ipolicy[constants.IPOLICY_DTS]))
+    )
+  ret.extend([
+    (key, str(custom_ipolicy.get(key, "default (%s)" % eff_ipolicy[key])))
+    for key in constants.IPOLICY_PARAMETERS
+    ])
+  return ret
+
+
+def _PrintSpecsParameters(buf, specs):
+  values = ("%s=%s" % (par, val) for (par, val) in sorted(specs.items()))
+  buf.write(",".join(values))
+
+
+def PrintIPolicyCommand(buf, ipolicy, isgroup):
+  """Print the command option used to generate the given instance policy.
+
+  Currently only the parts dealing with specs are supported.
+
+  @type buf: StringIO
+  @param buf: stream to write into
+  @type ipolicy: dict
+  @param ipolicy: instance policy
+  @type isgroup: bool
+  @param isgroup: whether the policy is at group level
+
+  """
+  if not isgroup:
+    stdspecs = ipolicy.get("std")
+    if stdspecs:
+      buf.write(" %s " % IPOLICY_STD_SPECS_STR)
+      _PrintSpecsParameters(buf, stdspecs)
+  minmaxes = ipolicy.get("minmax", [])
+  first = True
+  for minmax in minmaxes:
+    minspecs = minmax.get("min")
+    maxspecs = minmax.get("max")
+    if minspecs and maxspecs:
+      if first:
+        buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
+        first = False
+      else:
+        buf.write("//")
+      buf.write("min:")
+      _PrintSpecsParameters(buf, minspecs)
+      buf.write("/max:")
+      _PrintSpecsParameters(buf, maxspecs)
 
 
 def ConfirmOperation(names, list_type, text, extra=""):
@@ -3640,24 +3875,9 @@ def _MaybeParseUnit(elements):
   return parsed
 
 
-def CreateIPolicyFromOpts(ispecs_mem_size=None,
-                          ispecs_cpu_count=None,
-                          ispecs_disk_count=None,
-                          ispecs_disk_size=None,
-                          ispecs_nic_count=None,
-                          ipolicy_disk_templates=None,
-                          ipolicy_vcpu_ratio=None,
-                          ipolicy_spindle_ratio=None,
-                          group_ipolicy=False,
-                          allowed_values=None,
-                          fill_all=False):
-  """Creation of instance policy based on command line options.
-
-  @param fill_all: whether for cluster policies we should ensure that
-    all values are filled
-
-
-  """
+def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
+                             ispecs_disk_count, ispecs_disk_size,
+                             ispecs_nic_count, group_ipolicy, fill_all):
   try:
     if ispecs_mem_size:
       ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
@@ -3670,7 +3890,7 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
                                errors.ECODE_INVAL)
 
   # prepare ipolicy dict
-  ipolicy_transposed = {
+  ispecs_transposed = {
     constants.ISPEC_MEM_SIZE: ispecs_mem_size,
     constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
     constants.ISPEC_DISK_COUNT: ispecs_disk_count,
@@ -3683,29 +3903,136 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
     forced_type = TISPECS_GROUP_TYPES
   else:
     forced_type = TISPECS_CLUSTER_TYPES
-
-  for specs in ipolicy_transposed.values():
-    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
+  for specs in ispecs_transposed.values():
+    assert type(specs) is dict
+    utils.ForceDictType(specs, forced_type)
 
   # then transpose
-  ipolicy_out = objects.MakeEmptyIPolicy()
-  for name, specs in ipolicy_transposed.iteritems():
+  ispecs = {
+    constants.ISPECS_MIN: {},
+    constants.ISPECS_MAX: {},
+    constants.ISPECS_STD: {},
+    }
+  for (name, specs) in ispecs_transposed.iteritems():
     assert name in constants.ISPECS_PARAMETERS
     for key, val in specs.items(): # {min: .. ,max: .., std: ..}
-      ipolicy_out[key][name] = val
+      assert key in ispecs
+      ispecs[key][name] = val
+  minmax_out = {}
+  for key in constants.ISPECS_MINMAX_KEYS:
+    if fill_all:
+      minmax_out[key] = \
+        objects.FillDict(constants.ISPECS_MINMAX_DEFAULTS[key], ispecs[key])
+    else:
+      minmax_out[key] = ispecs[key]
+  ipolicy[constants.ISPECS_MINMAX] = [minmax_out]
+  if fill_all:
+    ipolicy[constants.ISPECS_STD] = \
+        objects.FillDict(constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
+                         ispecs[constants.ISPECS_STD])
+  else:
+    ipolicy[constants.ISPECS_STD] = ispecs[constants.ISPECS_STD]
+
+
+def _ParseSpecUnit(spec, keyname):
+  ret = spec.copy()
+  for k in [constants.ISPEC_DISK_SIZE, constants.ISPEC_MEM_SIZE]:
+    if k in ret:
+      try:
+        ret[k] = utils.ParseUnit(ret[k])
+      except (TypeError, ValueError, errors.UnitParseError), err:
+        raise errors.OpPrereqError(("Invalid parameter %s (%s) in %s instance"
+                                    " specs: %s" % (k, ret[k], keyname, err)),
+                                   errors.ECODE_INVAL)
+  return ret
+
+
+def _ParseISpec(spec, keyname, required):
+  ret = _ParseSpecUnit(spec, keyname)
+  utils.ForceDictType(ret, constants.ISPECS_PARAMETER_TYPES)
+  missing = constants.ISPECS_PARAMETERS - frozenset(ret.keys())
+  if required and missing:
+    raise errors.OpPrereqError("Missing parameters in ipolicy spec %s: %s" %
+                               (keyname, utils.CommaJoin(missing)),
+                               errors.ECODE_INVAL)
+  return ret
+
+
+def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values):
+  ret = None
+  if (minmax_ispecs and allowed_values and len(minmax_ispecs) == 1 and
+      len(minmax_ispecs[0]) == 1):
+    for (key, spec) in minmax_ispecs[0].items():
+      # This loop is executed exactly once
+      if key in allowed_values and not spec:
+        ret = key
+  return ret
+
+
+def _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
+                            group_ipolicy, allowed_values):
+  found_allowed = _GetISpecsInAllowedValues(minmax_ispecs, allowed_values)
+  if found_allowed is not None:
+    ipolicy_out[constants.ISPECS_MINMAX] = found_allowed
+  elif minmax_ispecs is not None:
+    minmax_out = []
+    for mmpair in minmax_ispecs:
+      mmpair_out = {}
+      for (key, spec) in mmpair.items():
+        if key not in constants.ISPECS_MINMAX_KEYS:
+          msg = "Invalid key in bounds instance specifications: %s" % key
+          raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
+        mmpair_out[key] = _ParseISpec(spec, key, True)
+      minmax_out.append(mmpair_out)
+    ipolicy_out[constants.ISPECS_MINMAX] = minmax_out
+  if std_ispecs is not None:
+    assert not group_ipolicy # This is not an option for gnt-group
+    ipolicy_out[constants.ISPECS_STD] = _ParseISpec(std_ispecs, "std", False)
+
+
+def CreateIPolicyFromOpts(ispecs_mem_size=None,
+                          ispecs_cpu_count=None,
+                          ispecs_disk_count=None,
+                          ispecs_disk_size=None,
+                          ispecs_nic_count=None,
+                          minmax_ispecs=None,
+                          std_ispecs=None,
+                          ipolicy_disk_templates=None,
+                          ipolicy_vcpu_ratio=None,
+                          ipolicy_spindle_ratio=None,
+                          group_ipolicy=False,
+                          allowed_values=None,
+                          fill_all=False):
+  """Creation of instance policy based on command line options.
+
+  @param fill_all: whether for cluster policies we should ensure that
+    all values are filled
+
+  """
+  assert not (fill_all and allowed_values)
+
+  split_specs = (ispecs_mem_size or ispecs_cpu_count or ispecs_disk_count or
+                 ispecs_disk_size or ispecs_nic_count)
+  if (split_specs and (minmax_ispecs is not None or std_ispecs is not None)):
+    raise errors.OpPrereqError("A --specs-xxx option cannot be specified"
+                               " together with any --ipolicy-xxx-specs option",
+                               errors.ECODE_INVAL)
+
+  ipolicy_out = objects.MakeEmptyIPolicy()
+  if split_specs:
+    assert fill_all
+    _InitISpecsFromSplitOpts(ipolicy_out, ispecs_mem_size, ispecs_cpu_count,
+                             ispecs_disk_count, ispecs_disk_size,
+                             ispecs_nic_count, group_ipolicy, fill_all)
+  elif (minmax_ispecs is not None or std_ispecs is not None):
+    _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
+                            group_ipolicy, allowed_values)
 
-  # no filldict for non-dicts
-  if not group_ipolicy and fill_all:
-    if ipolicy_disk_templates is None:
-      ipolicy_disk_templates = constants.DISK_TEMPLATES
-    if ipolicy_vcpu_ratio is None:
-      ipolicy_vcpu_ratio = \
-        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
-    if ipolicy_spindle_ratio is None:
-      ipolicy_spindle_ratio = \
-        constants.IPOLICY_DEFAULTS[constants.IPOLICY_SPINDLE_RATIO]
   if ipolicy_disk_templates is not None:
-    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
+    if allowed_values and ipolicy_disk_templates in allowed_values:
+      ipolicy_out[constants.IPOLICY_DTS] = ipolicy_disk_templates
+    else:
+      ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
   if ipolicy_vcpu_ratio is not None:
     ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
   if ipolicy_spindle_ratio is not None:
@@ -3713,4 +4040,97 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
 
   assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
 
+  if not group_ipolicy and fill_all:
+    ipolicy_out = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, ipolicy_out)
+
   return ipolicy_out
+
+
+def _SerializeGenericInfo(buf, data, level, afterkey=False):
+  """Formatting core of L{PrintGenericInfo}.
+
+  @param buf: (string) stream to accumulate the result into
+  @param data: data to format
+  @type level: int
+  @param level: depth in the data hierarchy, used for indenting
+  @type afterkey: bool
+  @param afterkey: True when we are in the middle of a line after a key (used
+      to properly add newlines or indentation)
+
+  """
+  baseind = "  "
+  if isinstance(data, dict):
+    if not data:
+      buf.write("\n")
+    else:
+      if afterkey:
+        buf.write("\n")
+        doindent = True
+      else:
+        doindent = False
+      for key in sorted(data):
+        if doindent:
+          buf.write(baseind * level)
+        else:
+          doindent = True
+        buf.write(key)
+        buf.write(": ")
+        _SerializeGenericInfo(buf, data[key], level + 1, afterkey=True)
+  elif isinstance(data, list) and len(data) > 0 and isinstance(data[0], tuple):
+    # list of tuples (an ordered dictionary)
+    if afterkey:
+      buf.write("\n")
+      doindent = True
+    else:
+      doindent = False
+    for (key, val) in data:
+      if doindent:
+        buf.write(baseind * level)
+      else:
+        doindent = True
+      buf.write(key)
+      buf.write(": ")
+      _SerializeGenericInfo(buf, val, level + 1, afterkey=True)
+  elif isinstance(data, list):
+    if not data:
+      buf.write("\n")
+    else:
+      if afterkey:
+        buf.write("\n")
+        doindent = True
+      else:
+        doindent = False
+      for item in data:
+        if doindent:
+          buf.write(baseind * level)
+        else:
+          doindent = True
+        buf.write("-")
+        buf.write(baseind[1:])
+        _SerializeGenericInfo(buf, item, level + 1)
+  else:
+    # This branch should be only taken for strings, but it's practically
+    # impossible to guarantee that no other types are produced somewhere
+    buf.write(str(data))
+    buf.write("\n")
+
+
+def PrintGenericInfo(data):
+  """Print information formatted according to the hierarchy.
+
+  The output is a valid YAML string.
+
+  @param data: the data to print. It's a hierarchical structure whose elements
+      can be:
+        - dictionaries, where keys are strings and values are of any of the
+          types listed here
+        - lists of pairs (key, value), where key is a string and value is of
+          any of the types listed here; it's a way to encode ordered
+          dictionaries
+        - lists of any of the types listed here
+        - strings
+
+  """
+  buf = StringIO()
+  _SerializeGenericInfo(buf, data, 0)
+  ToStdout(buf.getvalue().rstrip("\n"))
index edc8b44..4a18593 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -50,9 +50,11 @@ def PrintExportList(opts, args):
 
   qfilter = qlang.MakeSimpleFilter("node", opts.nodes)
 
+  cl = GetClient(query=True)
+
   return GenericList(constants.QR_EXPORT, selected_fields, None, opts.units,
                      opts.separator, not opts.no_headers,
-                     verbose=opts.verbose, qfilter=qfilter)
+                     verbose=opts.verbose, qfilter=qfilter, cl=cl)
 
 
 def ListExportFields(opts, args):
@@ -65,8 +67,10 @@ def ListExportFields(opts, args):
   @return: the desired exit code
 
   """
+  cl = GetClient(query=True)
+
   return GenericListFields(constants.QR_EXPORT, args, opts.separator,
-                           not opts.no_headers)
+                           not opts.no_headers, cl=cl)
 
 
 def ExportInstance(opts, args):
index 77d8a6f..e70361b 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-cluster
 
+from cStringIO import StringIO
 import os.path
 import time
 import OpenSSL
@@ -144,17 +145,18 @@ def InitCluster(opts, args):
     utils.ForceDictType(diskparams[templ], constants.DISK_DT_TYPES)
 
   # prepare ipolicy dict
-  ipolicy_raw = CreateIPolicyFromOpts(
+  ipolicy = CreateIPolicyFromOpts(
     ispecs_mem_size=opts.ispecs_mem_size,
     ispecs_cpu_count=opts.ispecs_cpu_count,
     ispecs_disk_count=opts.ispecs_disk_count,
     ispecs_disk_size=opts.ispecs_disk_size,
     ispecs_nic_count=opts.ispecs_nic_count,
+    minmax_ispecs=opts.ipolicy_bounds_specs,
+    std_ispecs=opts.ipolicy_std_specs,
     ipolicy_disk_templates=opts.ipolicy_disk_templates,
     ipolicy_vcpu_ratio=opts.ipolicy_vcpu_ratio,
     ipolicy_spindle_ratio=opts.ipolicy_spindle_ratio,
     fill_all=True)
-  ipolicy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, ipolicy_raw)
 
   if opts.candidate_pool_size is None:
     opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
@@ -194,6 +196,12 @@ def InitCluster(opts, args):
 
   hv_state = dict(opts.hv_state)
 
+  enabled_disk_templates = opts.enabled_disk_templates
+  if enabled_disk_templates:
+    enabled_disk_templates = enabled_disk_templates.split(",")
+  else:
+    enabled_disk_templates = list(constants.DEFAULT_ENABLED_DISK_TEMPLATES)
+
   bootstrap.InitCluster(cluster_name=args[0],
                         secondary_ip=opts.secondary_ip,
                         vg_name=vg_name,
@@ -221,6 +229,7 @@ def InitCluster(opts, args):
                         use_external_mip_script=external_ip_setup_script,
                         hv_state=hv_state,
                         disk_state=disk_state,
+                        enabled_disk_templates=enabled_disk_templates,
                         )
   op = opcodes.OpClusterPostInit()
   SubmitOpCode(op, opts=opts)
@@ -342,6 +351,7 @@ def ShowClusterVersion(opts, args):
   ToStdout("Configuration format: %s", result["config_version"])
   ToStdout("OS api version: %s", result["os_api_version"])
   ToStdout("Export interface: %s", result["export_version"])
+  ToStdout("VCS version: %s", result["vcs_version"])
   return 0
 
 
@@ -360,24 +370,24 @@ def ShowClusterMaster(opts, args):
   return 0
 
 
-def _PrintGroupedParams(paramsdict, level=1, roman=False):
-  """Print Grouped parameters (be, nic, disk) by group.
+def _FormatGroupedParams(paramsdict, roman=False):
+  """Format Grouped parameters (be, nic, disk) by group.
 
   @type paramsdict: dict of dicts
   @param paramsdict: {group: {param: value, ...}, ...}
-  @type level: int
-  @param level: Level of indention
+  @rtype: dict of dicts
+  @return: copy of the input dictionaries with strings as values
 
   """
-  indent = "  " * level
-  for item, val in sorted(paramsdict.items()):
+  ret = {}
+  for (item, val) in paramsdict.items():
     if isinstance(val, dict):
-      ToStdout("%s- %s:", indent, item)
-      _PrintGroupedParams(val, level=level + 1, roman=roman)
+      ret[item] = _FormatGroupedParams(val, roman=roman)
     elif roman and isinstance(val, int):
-      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
+      ret[item] = compat.TryToRoman(val)
     else:
-      ToStdout("%s  %s: %s", indent, item, val)
+      ret[item] = str(val)
+  return ret
 
 
 def ShowClusterConfig(opts, args):
@@ -393,89 +403,88 @@ def ShowClusterConfig(opts, args):
   cl = GetClient(query=True)
   result = cl.QueryClusterInfo()
 
-  ToStdout("Cluster name: %s", result["name"])
-  ToStdout("Cluster UUID: %s", result["uuid"])
-
-  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
-  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
-
-  ToStdout("Master node: %s", result["master"])
-
-  ToStdout("Architecture (this node): %s (%s)",
-           result["architecture"][0], result["architecture"][1])
-
   if result["tags"]:
     tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
   else:
     tags = "(none)"
+  if result["reserved_lvs"]:
+    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
+  else:
+    reserved_lvs = "(none)"
 
-  ToStdout("Tags: %s", tags)
+  enabled_hv = result["enabled_hypervisors"]
+  hvparams = dict((k, v) for k, v in result["hvparams"].iteritems()
+                  if k in enabled_hv)
 
-  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
-  ToStdout("Enabled hypervisors: %s",
-           utils.CommaJoin(result["enabled_hypervisors"]))
+  info = [
+    ("Cluster name", result["name"]),
+    ("Cluster UUID", result["uuid"]),
 
-  ToStdout("Hypervisor parameters:")
-  _PrintGroupedParams(result["hvparams"])
+    ("Creation time", utils.FormatTime(result["ctime"])),
+    ("Modification time", utils.FormatTime(result["mtime"])),
 
-  ToStdout("OS-specific hypervisor parameters:")
-  _PrintGroupedParams(result["os_hvp"])
+    ("Master node", result["master"]),
 
-  ToStdout("OS parameters:")
-  _PrintGroupedParams(result["osparams"])
+    ("Architecture (this node)",
+     "%s (%s)" % (result["architecture"][0], result["architecture"][1])),
 
-  ToStdout("Hidden OSes: %s", utils.CommaJoin(result["hidden_os"]))
-  ToStdout("Blacklisted OSes: %s", utils.CommaJoin(result["blacklisted_os"]))
+    ("Tags", tags),
 
-  ToStdout("Cluster parameters:")
-  ToStdout("  - candidate pool size: %s",
-            compat.TryToRoman(result["candidate_pool_size"],
-                              convert=opts.roman_integers))
-  ToStdout("  - master netdev: %s", result["master_netdev"])
-  ToStdout("  - master netmask: %s", result["master_netmask"])
-  ToStdout("  - use external master IP address setup script: %s",
-           result["use_external_mip_script"])
-  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
-  if result["reserved_lvs"]:
-    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
-  else:
-    reserved_lvs = "(none)"
-  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
-  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
-  ToStdout("  - file storage path: %s", result["file_storage_dir"])
-  ToStdout("  - shared file storage path: %s",
-           result["shared_file_storage_dir"])
-  ToStdout("  - maintenance of node health: %s",
-           result["maintain_node_health"])
-  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
-  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
-  ToStdout("  - primary ip version: %d", result["primary_ip_version"])
-  ToStdout("  - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
-  ToStdout("  - OS search path: %s", utils.CommaJoin(pathutils.OS_SEARCH_PATH))
-  ToStdout("  - ExtStorage Providers search path: %s",
-           utils.CommaJoin(pathutils.ES_SEARCH_PATH))
-
-  ToStdout("Default node parameters:")
-  _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
-
-  ToStdout("Default instance parameters:")
-  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
-
-  ToStdout("Default nic parameters:")
-  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
-
-  ToStdout("Default disk parameters:")
-  _PrintGroupedParams(result["diskparams"], roman=opts.roman_integers)
-
-  ToStdout("Instance policy - limits for instances:")
-  for key in constants.IPOLICY_ISPECS:
-    ToStdout("  - %s", key)
-    _PrintGroupedParams(result["ipolicy"][key], roman=opts.roman_integers)
-  ToStdout("  - enabled disk templates: %s",
-           utils.CommaJoin(result["ipolicy"][constants.IPOLICY_DTS]))
-  for key in constants.IPOLICY_PARAMETERS:
-    ToStdout("  - %s: %s", key, result["ipolicy"][key])
+    ("Default hypervisor", result["default_hypervisor"]),
+    ("Enabled hypervisors", utils.CommaJoin(enabled_hv)),
+
+    ("Hypervisor parameters", _FormatGroupedParams(hvparams)),
+
+    ("OS-specific hypervisor parameters",
+     _FormatGroupedParams(result["os_hvp"])),
+
+    ("OS parameters", _FormatGroupedParams(result["osparams"])),
+
+    ("Hidden OSes", utils.CommaJoin(result["hidden_os"])),
+    ("Blacklisted OSes", utils.CommaJoin(result["blacklisted_os"])),
+
+    ("Cluster parameters", [
+      ("candidate pool size",
+       compat.TryToRoman(result["candidate_pool_size"],
+                         convert=opts.roman_integers)),
+      ("master netdev", result["master_netdev"]),
+      ("master netmask", result["master_netmask"]),
+      ("use external master IP address setup script",
+       result["use_external_mip_script"]),
+      ("lvm volume group", result["volume_group_name"]),
+      ("lvm reserved volumes", reserved_lvs),
+      ("drbd usermode helper", result["drbd_usermode_helper"]),
+      ("file storage path", result["file_storage_dir"]),
+      ("shared file storage path", result["shared_file_storage_dir"]),
+      ("maintenance of node health", result["maintain_node_health"]),
+      ("uid pool", uidpool.FormatUidPool(result["uid_pool"])),
+      ("default instance allocator", result["default_iallocator"]),
+      ("primary ip version", result["primary_ip_version"]),
+      ("preallocation wipe disks", result["prealloc_wipe_disks"]),
+      ("OS search path", utils.CommaJoin(pathutils.OS_SEARCH_PATH)),
+      ("ExtStorage Providers search path",
+       utils.CommaJoin(pathutils.ES_SEARCH_PATH)),
+      ("enabled disk templates",
+       utils.CommaJoin(result["enabled_disk_templates"])),
+      ]),
+
+    ("Default node parameters",
+     _FormatGroupedParams(result["ndparams"], roman=opts.roman_integers)),
 
+    ("Default instance parameters",
+     _FormatGroupedParams(result["beparams"], roman=opts.roman_integers)),
+
+    ("Default nic parameters",
+     _FormatGroupedParams(result["nicparams"], roman=opts.roman_integers)),
+
+    ("Default disk parameters",
+     _FormatGroupedParams(result["diskparams"], roman=opts.roman_integers)),
+
+    ("Instance policy - limits for instances",
+     FormatPolicyInfo(result["ipolicy"], None, True)),
+    ]
+
+  PrintGenericInfo(info)
   return 0
 
 
@@ -960,15 +969,14 @@ def SetClusterParams(opts, args):
           opts.use_external_mip_script is not None or
           opts.prealloc_wipe_disks is not None or
           opts.hv_state or
+          opts.enabled_disk_templates or
           opts.disk_state or
-          opts.ispecs_mem_size or
-          opts.ispecs_cpu_count or
-          opts.ispecs_disk_count or
-          opts.ispecs_disk_size or
-          opts.ispecs_nic_count or
+          opts.ipolicy_bounds_specs is not None or
+          opts.ipolicy_std_specs is not None or
           opts.ipolicy_disk_templates is not None or
           opts.ipolicy_vcpu_ratio is not None or
-          opts.ipolicy_spindle_ratio is not None):
+          opts.ipolicy_spindle_ratio is not None or
+          opts.modify_etc_hosts is not None):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
@@ -992,6 +1000,10 @@ def SetClusterParams(opts, args):
   if hvlist is not None:
     hvlist = hvlist.split(",")
 
+  enabled_disk_templates = opts.enabled_disk_templates
+  if enabled_disk_templates:
+    enabled_disk_templates = enabled_disk_templates.split(",")
+
   # a list of (name, dict) we can pass directly to dict() (or [])
   hvparams = dict(opts.hvparams)
   for hv_params in hvparams.values():
@@ -1013,11 +1025,8 @@ def SetClusterParams(opts, args):
     utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
 
   ipolicy = CreateIPolicyFromOpts(
-    ispecs_mem_size=opts.ispecs_mem_size,
-    ispecs_cpu_count=opts.ispecs_cpu_count,
-    ispecs_disk_count=opts.ispecs_disk_count,
-    ispecs_disk_size=opts.ispecs_disk_size,
-    ispecs_nic_count=opts.ispecs_nic_count,
+    minmax_ispecs=opts.ipolicy_bounds_specs,
+    std_ispecs=opts.ipolicy_std_specs,
     ipolicy_disk_templates=opts.ipolicy_disk_templates,
     ipolicy_vcpu_ratio=opts.ipolicy_vcpu_ratio,
     ipolicy_spindle_ratio=opts.ipolicy_spindle_ratio,
@@ -1059,30 +1068,34 @@ def SetClusterParams(opts, args):
 
   hv_state = dict(opts.hv_state)
 
-  op = opcodes.OpClusterSetParams(vg_name=vg_name,
-                                  drbd_helper=drbd_helper,
-                                  enabled_hypervisors=hvlist,
-                                  hvparams=hvparams,
-                                  os_hvp=None,
-                                  beparams=beparams,
-                                  nicparams=nicparams,
-                                  ndparams=ndparams,
-                                  diskparams=diskparams,
-                                  ipolicy=ipolicy,
-                                  candidate_pool_size=opts.candidate_pool_size,
-                                  maintain_node_health=mnh,
-                                  uid_pool=uid_pool,
-                                  add_uids=add_uids,
-                                  remove_uids=remove_uids,
-                                  default_iallocator=opts.default_iallocator,
-                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
-                                  master_netdev=opts.master_netdev,
-                                  master_netmask=opts.master_netmask,
-                                  reserved_lvs=opts.reserved_lvs,
-                                  use_external_mip_script=ext_ip_script,
-                                  hv_state=hv_state,
-                                  disk_state=disk_state,
-                                  )
+  op = opcodes.OpClusterSetParams(
+    vg_name=vg_name,
+    drbd_helper=drbd_helper,
+    enabled_hypervisors=hvlist,
+    hvparams=hvparams,
+    os_hvp=None,
+    beparams=beparams,
+    nicparams=nicparams,
+    ndparams=ndparams,
+    diskparams=diskparams,
+    ipolicy=ipolicy,
+    candidate_pool_size=opts.candidate_pool_size,
+    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,
+    prealloc_wipe_disks=opts.prealloc_wipe_disks,
+    master_netdev=opts.master_netdev,
+    master_netmask=opts.master_netmask,
+    reserved_lvs=opts.reserved_lvs,
+    use_external_mip_script=ext_ip_script,
+    hv_state=hv_state,
+    disk_state=disk_state,
+    enabled_disk_templates=enabled_disk_templates,
+    force=opts.force,
+    )
   SubmitOrSend(op, opts)
   return 0
 
@@ -1476,6 +1489,26 @@ def Epo(opts, args, cl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
     return _off_fn(opts, node_list, inst_map)
 
 
+def _GetCreateCommand(info):
+  buf = StringIO()
+  buf.write("gnt-cluster init")
+  PrintIPolicyCommand(buf, info["ipolicy"], False)
+  buf.write(" ")
+  buf.write(info["name"])
+  return buf.getvalue()
+
+
+def ShowCreateCommand(opts, args):
+  """Shows the command that can be used to re-create the cluster.
+
+  Currently it works only for ipolicy specs.
+
+  """
+  cl = GetClient(query=True)
+  result = cl.QueryClusterInfo()
+  ToStdout(_GetCreateCommand(result))
+
+
 commands = {
   "init": (
     InitCluster, [ArgHost(min=1, max=1)],
@@ -1486,7 +1519,8 @@ commands = {
      MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_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] + INSTANCE_POLICY_OPTS,
+     DISK_PARAMS_OPT, HV_STATE_OPT, DISK_STATE_OPT, ENABLED_DISK_TEMPLATES_OPT,
+     IPOLICY_STD_SPECS_OPT] + INSTANCE_POLICY_OPTS + SPLIT_ISPECS_OPTS,
     "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
   "destroy": (
     DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
@@ -1558,14 +1592,15 @@ commands = {
     "{pause <timespec>|continue|info}", "Change watcher properties"),
   "modify": (
     SetClusterParams, ARGS_NONE,
-    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
+    [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, NODRBD_STORAGE_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_OPT] +
-    INSTANCE_POLICY_OPTS,
+     DISK_STATE_OPT, SUBMIT_OPT, ENABLED_DISK_TEMPLATES_OPT,
+     IPOLICY_STD_SPECS_OPT, MODIFY_ETCHOSTS_OPT] + INSTANCE_POLICY_OPTS,
     "[opts...]",
     "Alters the parameters of the cluster"),
   "renew-crypto": (
@@ -1587,6 +1622,9 @@ commands = {
   "deactivate-master-ip": (
     DeactivateMasterIp, ARGS_NONE, [CONFIRM_OPT], "",
     "Deactivates the master IP"),
+  "show-ispecs-cmd": (
+    ShowCreateCommand, ARGS_NONE, [], "",
+    "Show the command line to re-create the cluster"),
   }
 
 
index e05f982..5bef440 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2010, 2011, 2012, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # W0401: Wildcard import ganeti.cli
 # W0614: Unused import %s from wildcard import (since we need cli)
 
+from cStringIO import StringIO
+
 from ganeti.cli import *
 from ganeti import constants
 from ganeti import opcodes
 from ganeti import utils
 from ganeti import compat
-from cStringIO import StringIO
 
 
 #: default list of fields for L{ListGroups}
@@ -49,11 +50,7 @@ def AddGroup(opts, args):
 
   """
   ipolicy = CreateIPolicyFromOpts(
-    ispecs_mem_size=opts.ispecs_mem_size,
-    ispecs_cpu_count=opts.ispecs_cpu_count,
-    ispecs_disk_count=opts.ispecs_disk_count,
-    ispecs_disk_size=opts.ispecs_disk_size,
-    ispecs_nic_count=opts.ispecs_nic_count,
+    minmax_ispecs=opts.ipolicy_bounds_specs,
     ipolicy_vcpu_ratio=opts.ipolicy_vcpu_ratio,
     ipolicy_spindle_ratio=opts.ipolicy_spindle_ratio,
     group_ipolicy=True)
@@ -160,10 +157,9 @@ def SetGroupParams(opts, args):
 
   """
   allmods = [opts.ndparams, opts.alloc_policy, opts.diskparams, opts.hv_state,
-             opts.disk_state, opts.ispecs_mem_size, opts.ispecs_cpu_count,
-             opts.ispecs_disk_count, opts.ispecs_disk_size,
-             opts.ispecs_nic_count, opts.ipolicy_vcpu_ratio,
-             opts.ipolicy_spindle_ratio, opts.diskparams]
+             opts.disk_state, opts.ipolicy_bounds_specs,
+             opts.ipolicy_vcpu_ratio, opts.ipolicy_spindle_ratio,
+             opts.diskparams]
   if allmods.count(None) == len(allmods):
     ToStderr("Please give at least one of the parameters.")
     return 1
@@ -177,26 +173,9 @@ def SetGroupParams(opts, args):
 
   diskparams = dict(opts.diskparams)
 
-  # set the default values
-  to_ipolicy = [
-    opts.ispecs_mem_size,
-    opts.ispecs_cpu_count,
-    opts.ispecs_disk_count,
-    opts.ispecs_disk_size,
-    opts.ispecs_nic_count,
-    ]
-  for ispec in to_ipolicy:
-    for param in ispec:
-      if isinstance(ispec[param], basestring):
-        if ispec[param].lower() == "default":
-          ispec[param] = constants.VALUE_DEFAULT
 &nb