Merge branch 'stable-2.8' into master
authorThomas Thrainer <thomasth@google.com>
Wed, 26 Jun 2013 07:57:25 +0000 (09:57 +0200)
committerThomas Thrainer <thomasth@google.com>
Wed, 26 Jun 2013 09:18:32 +0000 (11:18 +0200)
* stable-2.8:
  gnt-cluster info (py): add enabled disk templates
  Version bump to 2.8.0~beta1
  Change version numbers in documentation
  Fix issue with python coverage tests
  Merge branch 'stable-2.7' into stable-2.8
  Disable python test if required libraries are missing
  Better specify what packages to install
  Improve install guide
  Fix typo in the documentation index
  Fix typos in the documentation index
  Update security document wrt confd access to SSL cert
  Add tools for building deb packages to build_chroot
  Improve the final message of build_chroot
  Make build_chroot self-contained

Conflicts:
configure.ac (trivial)
lib/cmdlib/instance_storage.py (trivial)

The version numbers changed in 'Change version numbers in documentation'
and 'Version bump to 2.8.0~beta1' were adapted to 2.9.

Signed-off-by: Thomas Thrainer <thomasth@google.com>
Reviewed-by: Michele Tartara <mtartara@google.com>

220 files changed:
INSTALL
Makefile.am
NEWS
README
configure.ac
devel/build_chroot
doc/design-draft.rst
doc/design-glusterfs-ganeti-support.rst [new file with mode: 0644]
doc/design-hroller.rst
doc/design-internal-shutdown.rst [new file with mode: 0644]
doc/design-monitoring-agent.rst
doc/design-partitioned.rst
doc/design-storagetypes.rst
doc/devnotes.rst
doc/hooks.rst
doc/iallocator.rst
doc/security.rst
doc/virtual-cluster.rst
lib/backend.py
lib/bdev.py [deleted file]
lib/bootstrap.py
lib/cli.py
lib/client/gnt_backup.py
lib/client/gnt_cluster.py
lib/client/gnt_debug.py
lib/client/gnt_group.py
lib/client/gnt_instance.py
lib/client/gnt_job.py
lib/client/gnt_network.py
lib/client/gnt_node.py
lib/client/gnt_os.py
lib/cmdlib/backup.py
lib/cmdlib/base.py
lib/cmdlib/cluster.py
lib/cmdlib/common.py
lib/cmdlib/group.py
lib/cmdlib/instance.py
lib/cmdlib/instance_migration.py
lib/cmdlib/instance_operation.py
lib/cmdlib/instance_query.py
lib/cmdlib/instance_storage.py
lib/cmdlib/instance_utils.py
lib/cmdlib/misc.py
lib/cmdlib/network.py
lib/cmdlib/node.py
lib/cmdlib/operating_system.py
lib/cmdlib/tags.py
lib/cmdlib/test.py
lib/config.py
lib/constants.py
lib/daemon.py
lib/hooksmaster.py
lib/http/server.py
lib/hypervisor/hv_base.py
lib/hypervisor/hv_chroot.py
lib/hypervisor/hv_fake.py
lib/hypervisor/hv_kvm.py
lib/hypervisor/hv_lxc.py
lib/hypervisor/hv_xen.py
lib/locking.py
lib/masterd/iallocator.py
lib/masterd/instance.py
lib/objects.py
lib/opcodes.py
lib/query.py
lib/rapi/rlib2.py
lib/rpc.py
lib/rpc_defs.py
lib/server/masterd.py
lib/server/noded.py
lib/server/rapi.py
lib/ssconf.py
lib/storage/__init__.py [new file with mode: 0644]
lib/storage/base.py [new file with mode: 0644]
lib/storage/bdev.py [new file with mode: 0644]
lib/storage/container.py [moved from lib/storage.py with 100% similarity]
lib/storage/drbd.py [new file with mode: 0644]
lib/storage/drbd_cmdgen.py [new file with mode: 0644]
lib/storage/drbd_info.py [new file with mode: 0644]
lib/storage/filestorage.py [new file with mode: 0644]
lib/tools/burnin.py
lib/utils/__init__.py
lib/utils/storage.py [new file with mode: 0644]
lib/watcher/nodemaint.py
man/ganeti-noded.rst
man/ganeti-rapi.rst
man/ganeti.rst
man/gnt-backup.rst
man/gnt-cluster.rst
man/gnt-group.rst
man/gnt-instance.rst
man/gnt-job.rst
man/gnt-network.rst
man/gnt-node.rst
man/gnt-os.rst
man/hroller.rst
man/htools.rst
man/mon-collector.rst
qa/ganeti-qa.py
qa/qa-sample.json
qa/qa_cluster.py
qa/qa_config.py
qa/qa_instance.py
qa/qa_instance_utils.py [new file with mode: 0644]
qa/qa_monitoring.py [new file with mode: 0644]
qa/qa_node.py
qa/qa_rapi.py
src/Ganeti/Confd/Client.hs
src/Ganeti/Confd/Server.hs
src/Ganeti/Config.hs
src/Ganeti/Daemon.hs
src/Ganeti/DataCollectors/CLI.hs
src/Ganeti/DataCollectors/Diskstats.hs [new file with mode: 0644]
src/Ganeti/DataCollectors/Drbd.hs
src/Ganeti/DataCollectors/InstStatus.hs [new file with mode: 0644]
src/Ganeti/DataCollectors/InstStatusTypes.hs [new file with mode: 0644]
src/Ganeti/DataCollectors/Program.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/Instance.hs
src/Ganeti/HTools/Loader.hs
src/Ganeti/HTools/Node.hs
src/Ganeti/HTools/Program/Harep.hs
src/Ganeti/HTools/Program/Hroller.hs
src/Ganeti/HTools/Program/Hspace.hs
src/Ganeti/HTools/Types.hs
src/Ganeti/Hypervisor/Xen.hs [new file with mode: 0644]
src/Ganeti/JSON.hs
src/Ganeti/Monitoring/Server.hs
src/Ganeti/Objects.hs
src/Ganeti/OpCodes.hs
src/Ganeti/OpParams.hs
src/Ganeti/Path.hs
src/Ganeti/Query/Cluster.hs [new file with mode: 0644]
src/Ganeti/Query/Node.hs
src/Ganeti/Query/Server.hs
src/Ganeti/Rpc.hs
src/Ganeti/Storage/Diskstats/Parser.hs [new file with mode: 0644]
src/Ganeti/Storage/Diskstats/Types.hs [new file with mode: 0644]
src/Ganeti/Storage/Drbd/Parser.hs [moved from src/Ganeti/Block/Drbd/Parser.hs with 99% similarity]
src/Ganeti/Storage/Drbd/Types.hs [moved from src/Ganeti/Block/Drbd/Types.hs with 99% similarity]
src/Ganeti/Types.hs
src/Ganeti/Utils.hs
test/data/bdev-drbd-8.4.txt [new file with mode: 0644]
test/data/cluster_config_2.8.json [moved from test/data/cluster_config_downgraded_2.7.json with 87% similarity]
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-spindles.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/hroller-nodegroups.data [new file with mode: 0644]
test/data/htools/hroller-nonredundant.data [new file with mode: 0644]
test/data/htools/hroller-online.data [new file with mode: 0644]
test/data/htools/hspace-tiered-dualspec-exclusive.data [new file with mode: 0644]
test/data/htools/hspace-tiered-exclusive.data [new file with mode: 0644]
test/data/htools/hspace-tiered-mixed.data [new file with mode: 0644]
test/data/htools/multiple-tags.data [new file with mode: 0644]
test/data/htools/rapi/instances.json
test/data/htools/rapi/nodes.json
test/data/htools/unique-reboot-order.data
test/data/proc_diskstats.txt [new file with mode: 0644]
test/data/proc_drbd80-emptyline.txt
test/data/proc_drbd80-emptyversion.txt [new file with mode: 0644]
test/data/proc_drbd84.txt [new file with mode: 0644]
test/data/proc_drbd84_sync.txt [new file with mode: 0644]
test/hs/Test/Ganeti/HTools/Backend/Text.hs
test/hs/Test/Ganeti/HTools/CLI.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/JSON.hs
test/hs/Test/Ganeti/Objects.hs
test/hs/Test/Ganeti/OpCodes.hs
test/hs/Test/Ganeti/Rpc.hs
test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs [new file with mode: 0644]
test/hs/Test/Ganeti/Storage/Drbd/Parser.hs [moved from test/hs/Test/Ganeti/Block/Drbd/Parser.hs with 75% similarity]
test/hs/Test/Ganeti/Storage/Drbd/Types.hs [moved from test/hs/Test/Ganeti/Block/Drbd/Types.hs with 98% similarity]
test/hs/Test/Ganeti/TestCommon.hs
test/hs/Test/Ganeti/TestHTools.hs
test/hs/htest.hs
test/hs/offline-test.sh
test/hs/shelltests/htools-hail.test
test/hs/shelltests/htools-hroller.test
test/hs/shelltests/htools-hspace.test
test/hs/shelltests/htools-mon-collector.test
test/py/cfgupgrade_unittest.py
test/py/ganeti.backend_unittest.py
test/py/ganeti.bdev_unittest.py [deleted file]
test/py/ganeti.cmdlib_unittest.py
test/py/ganeti.config_unittest.py
test/py/ganeti.hooks_unittest.py
test/py/ganeti.hypervisor.hv_chroot_unittest.py
test/py/ganeti.hypervisor.hv_fake_unittest.py
test/py/ganeti.hypervisor.hv_kvm_unittest.py
test/py/ganeti.hypervisor.hv_lxc_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py
test/py/ganeti.objects_unittest.py
test/py/ganeti.query_unittest.py
test/py/ganeti.rpc_unittest.py
test/py/ganeti.ssconf_unittest.py
test/py/ganeti.storage.bdev_unittest.py [new file with mode: 0755]
test/py/ganeti.storage.container_unittest.py [moved from test/py/ganeti.storage_unittest.py with 94% similarity]
test/py/ganeti.storage.drbd_unittest.py [new file with mode: 0755]
test/py/ganeti.storage.filestorage_unittest.py [new file with mode: 0755]
test/py/ganeti.utils.storage_unittest.py [new file with mode: 0755]
test/py/mocks.py
test/py/testutils.py
tools/cfgupgrade
tools/move-instance
tools/vcluster-setup.in

diff --git a/INSTALL b/INSTALL
index 30c75be..231950d 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -194,6 +194,8 @@ a few more Haskell libraries enabled: the ``ganeti-confd`` daemon
 - `vector <http://hackage.haskell.org/package/vector>`_
 - `snap-server` <http://hackage.haskell.org/package/snap-server>`_, version
   0.8.1 and above.
+- `process <http://hackage.haskell.org/package/process>`_, version 1.0.1.1 and
+  above
 
 These libraries are available in Debian Wheezy (but not in Squeeze), so you
 can use either apt::
index a99c84a..abf8f17 100644 (file)
@@ -42,6 +42,7 @@ SHELL_ENV_INIT = autotools/shell-env-init
 clientdir = $(pkgpythondir)/client
 cmdlibdir = $(pkgpythondir)/cmdlib
 hypervisordir = $(pkgpythondir)/hypervisor
+storagedir = $(pkgpythondir)/storage
 httpdir = $(pkgpythondir)/http
 masterddir = $(pkgpythondir)/masterd
 confddir = $(pkgpythondir)/confd
@@ -62,8 +63,6 @@ myexeclibdir = $(pkglibdir)
 HS_DIRS = \
        src \
        src/Ganeti \
-       src/Ganeti/Block \
-       src/Ganeti/Block/Drbd \
        src/Ganeti/Confd \
        src/Ganeti/Curl \
        src/Ganeti/DataCollectors \
@@ -74,11 +73,15 @@ HS_DIRS = \
        src/Ganeti/Hypervisor/Xen \
        src/Ganeti/Monitoring \
        src/Ganeti/Query \
+       src/Ganeti/Storage \
+       src/Ganeti/Storage/Diskstats \
+       src/Ganeti/Storage/Drbd \
        test/hs \
        test/hs/Test \
        test/hs/Test/Ganeti \
-       test/hs/Test/Ganeti/Block \
-       test/hs/Test/Ganeti/Block/Drbd \
+       test/hs/Test/Ganeti/Storage \
+       test/hs/Test/Ganeti/Storage/Diskstats \
+       test/hs/Test/Ganeti/Storage/Drbd \
        test/hs/Test/Ganeti/Confd \
        test/hs/Test/Ganeti/HTools \
        test/hs/Test/Ganeti/HTools/Backend \
@@ -115,6 +118,7 @@ DIRS = \
        lib/masterd \
        lib/rapi \
        lib/server \
+       lib/storage \
        lib/tools \
        lib/utils \
        lib/watcher \
@@ -260,7 +264,6 @@ pkgpython_PYTHON = \
        lib/__init__.py \
        lib/asyncnotifier.py \
        lib/backend.py \
-       lib/bdev.py \
        lib/bootstrap.py \
        lib/cli.py \
        lib/compat.py \
@@ -289,7 +292,6 @@ pkgpython_PYTHON = \
        lib/serializer.py \
        lib/ssconf.py \
        lib/ssh.py \
-       lib/storage.py \
        lib/uidpool.py \
        lib/vcluster.py \
        lib/network.py \
@@ -338,6 +340,16 @@ hypervisor_PYTHON = \
        lib/hypervisor/hv_lxc.py \
        lib/hypervisor/hv_xen.py
 
+storage_PYTHON = \
+       lib/storage/__init__.py \
+       lib/storage/bdev.py \
+       lib/storage/base.py \
+       lib/storage/container.py \
+       lib/storage/drbd.py \
+       lib/storage/drbd_info.py \
+       lib/storage/drbd_cmdgen.py \
+       lib/storage/filestorage.py
+
 rapi_PYTHON = \
        lib/rapi/__init__.py \
        lib/rapi/baserlib.py \
@@ -396,6 +408,7 @@ utils_PYTHON = \
        lib/utils/nodesetup.py \
        lib/utils/process.py \
        lib/utils/retry.py \
+       lib/utils/storage.py \
        lib/utils/text.py \
        lib/utils/wrapper.py \
        lib/utils/x509.py
@@ -423,6 +436,7 @@ docinput = \
        doc/design-htools-2.3.rst \
        doc/design-http-server.rst \
        doc/design-impexp2.rst \
+       doc/design-internal-shutdown.rst \
        doc/design-lu-generated-jobs.rst \
        doc/design-linuxha.rst \
        doc/design-multi-reloc.rst \
@@ -520,8 +534,6 @@ HPCEXCL = --exclude Main \
        $(patsubst src.%,--exclude Test.%,$(subst /,.,$(patsubst %.hs,%, $(HS_LIB_SRCS))))
 
 HS_LIB_SRCS = \
-       src/Ganeti/Block/Drbd/Types.hs \
-       src/Ganeti/Block/Drbd/Parser.hs \
        src/Ganeti/BasicTypes.hs \
        src/Ganeti/Common.hs \
        src/Ganeti/Compat.hs \
@@ -533,7 +545,10 @@ HS_LIB_SRCS = \
        src/Ganeti/Curl/Multi.hs \
        src/Ganeti/Daemon.hs \
        src/Ganeti/DataCollectors/CLI.hs \
+       src/Ganeti/DataCollectors/Diskstats.hs \
        src/Ganeti/DataCollectors/Drbd.hs \
+       src/Ganeti/DataCollectors/InstStatus.hs \
+       src/Ganeti/DataCollectors/InstStatusTypes.hs \
        src/Ganeti/DataCollectors/Program.hs \
        src/Ganeti/DataCollectors/Types.hs \
        src/Ganeti/Errors.hs \
@@ -563,6 +578,7 @@ HS_LIB_SRCS = \
        src/Ganeti/HTools/Program/Hroller.hs \
        src/Ganeti/HTools/Program/Main.hs \
        src/Ganeti/HTools/Types.hs \
+       src/Ganeti/Hypervisor/Xen.hs \
        src/Ganeti/Hypervisor/Xen/XmParser.hs \
        src/Ganeti/Hypervisor/Xen/Types.hs \
        src/Ganeti/Hash.hs \
@@ -577,6 +593,7 @@ HS_LIB_SRCS = \
        src/Ganeti/OpCodes.hs \
        src/Ganeti/OpParams.hs \
        src/Ganeti/Path.hs \
+       src/Ganeti/Query/Cluster.hs \
        src/Ganeti/Query/Common.hs \
        src/Ganeti/Query/Export.hs \
        src/Ganeti/Query/Filter.hs \
@@ -591,6 +608,10 @@ HS_LIB_SRCS = \
        src/Ganeti/Rpc.hs \
        src/Ganeti/Runtime.hs \
        src/Ganeti/Ssconf.hs \
+       src/Ganeti/Storage/Diskstats/Parser.hs \
+       src/Ganeti/Storage/Diskstats/Types.hs \
+       src/Ganeti/Storage/Drbd/Parser.hs \
+       src/Ganeti/Storage/Drbd/Types.hs \
        src/Ganeti/THH.hs \
        src/Ganeti/Types.hs \
        src/Ganeti/Utils.hs
@@ -598,8 +619,6 @@ HS_LIB_SRCS = \
 HS_TEST_SRCS = \
        test/hs/Test/Ganeti/Attoparsec.hs \
        test/hs/Test/Ganeti/BasicTypes.hs \
-       test/hs/Test/Ganeti/Block/Drbd/Parser.hs \
-       test/hs/Test/Ganeti/Block/Drbd/Types.hs \
        test/hs/Test/Ganeti/Common.hs \
        test/hs/Test/Ganeti/Confd/Types.hs \
        test/hs/Test/Ganeti/Confd/Utils.hs \
@@ -631,6 +650,9 @@ HS_TEST_SRCS = \
        test/hs/Test/Ganeti/Rpc.hs \
        test/hs/Test/Ganeti/Runtime.hs \
        test/hs/Test/Ganeti/Ssconf.hs \
+       test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs \
+       test/hs/Test/Ganeti/Storage/Drbd/Parser.hs \
+       test/hs/Test/Ganeti/Storage/Drbd/Types.hs \
        test/hs/Test/Ganeti/THH.hs \
        test/hs/Test/Ganeti/TestCommon.hs \
        test/hs/Test/Ganeti/TestHTools.hs \
@@ -788,7 +810,9 @@ qa_scripts = \
        qa/qa_error.py \
        qa/qa_group.py \
        qa/qa_instance.py \
+       qa/qa_instance_utils.py \
        qa/qa_job.py \
+       qa/qa_monitoring.py \
        qa/qa_node.py \
        qa/qa_os.py \
        qa/qa_rapi.py \
@@ -1029,6 +1053,7 @@ TEST_FILES = \
        test/data/htools/hail-alloc-invalid-network.json \
        test/data/htools/hail-alloc-invalid-twodisks.json \
        test/data/htools/hail-alloc-restricted-network.json \
+       test/data/htools/hail-alloc-spindles.json \
        test/data/htools/hail-alloc-twodisks.json \
        test/data/htools/hail-change-group.json \
        test/data/htools/hail-invalid-reloc.json \
@@ -1036,18 +1061,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-exclusive.data \
        test/data/htools/hspace-tiered-dualspec.data \
+       test/data/htools/hspace-tiered-exclusive.data \
        test/data/htools/hspace-tiered-ipolicy.data \
+       test/data/htools/hspace-tiered-mixed.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/multiple-tags.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/hroller-nodegroups.data \
+       test/data/htools/hroller-nonredundant.data \
+       test/data/htools/hroller-online.data \
        test/data/htools/unique-reboot-order.data \
        test/hs/shelltests/htools-balancing.test \
        test/hs/shelltests/htools-basic.test \
@@ -1065,6 +1097,7 @@ TEST_FILES = \
        test/hs/shelltests/htools-mon-collector.test \
        test/data/bdev-drbd-8.0.txt \
        test/data/bdev-drbd-8.3.txt \
+       test/data/bdev-drbd-8.4.txt \
        test/data/bdev-drbd-disk.txt \
        test/data/bdev-drbd-net-ip4.txt \
        test/data/bdev-drbd-net-ip6.txt \
@@ -1083,7 +1116,7 @@ TEST_FILES = \
        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/cluster_config_2.8.json \
        test/data/instance-minor-pairing.txt \
        test/data/ip-addr-show-dummy0.txt \
        test/data/ip-addr-show-lo-ipv4.txt \
@@ -1125,12 +1158,16 @@ TEST_FILES = \
        test/data/ovfdata/wrong_manifest.ovf \
        test/data/ovfdata/wrong_ova.ova \
        test/data/ovfdata/wrong_xml.ovf \
+       test/data/proc_diskstats.txt \
        test/data/proc_drbd8.txt \
        test/data/proc_drbd80-emptyline.txt \
+       test/data/proc_drbd80-emptyversion.txt \
        test/data/proc_drbd83.txt \
        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_drbd84.txt \
+       test/data/proc_drbd84_sync.txt \
        test/data/qa-minimal-nodes-instances-only.json \
        test/data/sys_drbd_usermode_helper.txt \
        test/data/vgreduce-removemissing-2.02.02.txt \
@@ -1155,7 +1192,6 @@ python_tests = \
        test/py/ganeti.asyncnotifier_unittest.py \
        test/py/ganeti.backend_unittest-runasroot.py \
        test/py/ganeti.backend_unittest.py \
-       test/py/ganeti.bdev_unittest.py \
        test/py/ganeti.cli_unittest.py \
        test/py/ganeti.client.gnt_cluster_unittest.py \
        test/py/ganeti.client.gnt_instance_unittest.py \
@@ -1202,7 +1238,10 @@ python_tests = \
        test/py/ganeti.server.rapi_unittest.py \
        test/py/ganeti.ssconf_unittest.py \
        test/py/ganeti.ssh_unittest.py \
-       test/py/ganeti.storage_unittest.py \
+       test/py/ganeti.storage.bdev_unittest.py \
+       test/py/ganeti.storage.container_unittest.py \
+       test/py/ganeti.storage.drbd_unittest.py \
+       test/py/ganeti.storage.filestorage_unittest.py \
        test/py/ganeti.tools.burnin_unittest.py \
        test/py/ganeti.tools.ensure_dirs_unittest.py \
        test/py/ganeti.tools.node_daemon_setup_unittest.py \
@@ -1219,6 +1258,7 @@ python_tests = \
        test/py/ganeti.utils.nodesetup_unittest.py \
        test/py/ganeti.utils.process_unittest.py \
        test/py/ganeti.utils.retry_unittest.py \
+       test/py/ganeti.utils.storage_unittest.py \
        test/py/ganeti.utils.text_unittest.py \
        test/py/ganeti.utils.wrapper_unittest.py \
        test/py/ganeti.utils.x509_unittest.py \
@@ -1278,6 +1318,7 @@ all_python_code = \
        $(client_PYTHON) \
        $(cmdlib_PYTHON) \
        $(hypervisor_PYTHON) \
+       $(storage_PYTHON) \
        $(rapi_PYTHON) \
        $(server_PYTHON) \
        $(pytools_PYTHON) \
diff --git a/NEWS b/NEWS
index 3c3527b..78cb945 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,25 @@ News
 ====
 
 
+Version 2.9.0 beta1
+-------------------
+
+*(unreleased)*
+
+- DRBD 8.4 support. Depending on the installed DRBD version, Ganeti now uses
+  the correct command syntax. It is possible to use different DRBD versions
+  on different nodes as long as they are compatible to each other. This
+  enables rolling upgrades of DRBD with no downtime. As permanent operation
+  of different DRBD versions within a node group is discouraged,
+  ``gnt-cluster verify`` will emit a warning if it detects such a situation.
+- hroller now also plans for capacity to move non-redundant instances off
+  any node to be rebooted; the old behavior of completely ignoring any
+  non-redundant instances can be restored by adding the --ignore-non-redundant
+  option.
+- The cluster option '--no-lvm-storage' was removed in favor of the new option
+  '--enabled-disk-templates'.
+
+
 Version 2.8.0 beta1
 -------------------
 
diff --git a/README b/README
index 99799e3..4266b70 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.8
+Ganeti 2.9
 ==========
 
 For installation instructions, read the INSTALL and the doc/install.rst
index 3d0f096..65fb83c 100644 (file)
@@ -1,8 +1,8 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [8])
+m4_define([gnt_version_minor], [9])
 m4_define([gnt_version_revision], [0])
-m4_define([gnt_version_suffix], [~beta1])
+m4_define([gnt_version_suffix], [~alpha1])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
@@ -119,19 +119,6 @@ AC_ARG_WITH([xen-initrd],
   [xen_initrd="/boot/initrd-3-xenU"])
 AC_SUBST(XEN_INITRD, $xen_initrd)
 
-# --with-xen-cmd=...
-AC_ARG_WITH([xen-cmd],
-  [AS_HELP_STRING([--with-xen-cmd=CMD],
-    [Sets the xen cli interface command (default is xm)]
-  )],
-  [xen_cmd="$withval"],
-  [xen_cmd=xm])
-AC_SUBST(XEN_CMD, $xen_cmd)
-
-if ! test "$XEN_CMD" = xl -o "$XEN_CMD" = xm; then
-  AC_MSG_ERROR([Unsupported xen command specified])
-fi
-
 # --with-kvm-kernel=...
 AC_ARG_WITH([kvm-kernel],
   [AS_HELP_STRING([--with-kvm-kernel=PATH],
@@ -244,7 +231,7 @@ AC_ARG_WITH([user-prefix],
    user_rapi="${withval}rapi";
    user_confd="${withval}confd";
    user_noded="$user_default";
-   user_mond="${withval}mond"],
+   user_mond="$user_default"],
   [user_masterd="$user_default";
    user_rapi="$user_default";
    user_confd="$user_default";
@@ -269,7 +256,7 @@ AC_ARG_WITH([group-prefix],
    group_masterd="${withval}masterd";
    group_noded="$group_default";
    group_daemons="${withval}daemons";
-   group_mond="${withval}mond"],
+   group_mond="$group_default"],
   [group_rapi="$group_default";
    group_admin="$group_default";
    group_confd="$group_default";
@@ -566,6 +553,8 @@ if test "$enable_monitoring" != no; then
                    [MONITORING_PKG="$MONITORING_PKG attoparsec"])
   AC_GHC_PKG_CHECK([snap-server], [],
                    [MONITORING_PKG="$MONITORING_PKG snap-server"])
+  AC_GHC_PKG_CHECK([process], [],
+                   [MONITORING_PKG="$MONITORING_PKG process"])
   MONITORING_DEP=
   if test "$has_confd" = False; then
     MONITORING_DEP="$MONITORING_DEP confd"
@@ -615,6 +604,8 @@ AC_GHC_PKG_CHECK([temporary], [], [HS_NODEV=1])
 #        of the checks.
 AC_GHC_PKG_CHECK([attoparsec], [], [HS_NODEV=1])
 AC_GHC_PKG_CHECK([vector], [], [HS_NODEV=1])
+AC_GHC_PKG_CHECK([process], [],
+                 [MONITORING_PKG="$MONITORING_PKG process"])
 if test -n "$HS_NODEV"; then
    AC_MSG_WARN(m4_normalize([Required development modules were not found,
                              you won't be able to run Haskell unittests]))
@@ -776,6 +767,7 @@ AC_PYTHON_MODULE(pyinotify, t)
 AC_PYTHON_MODULE(pycurl, t)
 AC_PYTHON_MODULE(bitarray, t)
 AC_PYTHON_MODULE(ipaddr, t)
+AC_PYTHON_MODULE(mock)
 AC_PYTHON_MODULE(affinity)
 AC_PYTHON_MODULE(paramiko)
 
index 38f27a6..77e78e0 100755 (executable)
@@ -133,7 +133,8 @@ 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
+               python-pyinotify python-pycurl python-yaml python-mock \
+               socat fping
 
 in_chroot -- \
   $APT_INSTALL python-paramiko qemu-utils
@@ -174,7 +175,8 @@ in_chroot -- \
     snap-server==0.8.1 \
     text==0.11.3.0 \
     vector==0.9.1 \
-    json==0.4.4
+    json==0.4.4 \
+    process==1.0.1.2
 
 #Python development tools
 in_chroot -- \
index e715086..4bfe52a 100644 (file)
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.8
+.. Last updated for Ganeti 2.9
 
 .. toctree::
    :maxdepth: 2
@@ -17,6 +17,8 @@ Design document drafts
    design-hroller.rst
    design-storagetypes.rst
    design-device-uuid-name.rst
+   design-internal-shutdown.rst
+   design-glusterfs-ganeti-support.rst
 
 .. vim: set textwidth=72 :
 .. Local Variables:
diff --git a/doc/design-glusterfs-ganeti-support.rst b/doc/design-glusterfs-ganeti-support.rst
new file mode 100644 (file)
index 0000000..53bfb7a
--- /dev/null
@@ -0,0 +1,88 @@
+========================
+GlusterFS Ganeti support
+========================
+
+This document describes the plan for adding GlusterFS support inside Ganeti.
+
+.. contents:: :depth: 4
+.. highlight:: shell-example
+
+Objective
+=========
+
+The aim is to let Ganeti support GlusterFS as one of its backend storage.
+This includes three aspects to finish:
+
+- Add Gluster as a storage backend.
+- Make sure Ganeti VMs can use GlusterFS backends in userspace mode (for
+  newer QEMU/KVM which has this support) and otherwise, if possible, through
+  some kernel exported block device.
+- Make sure Ganeti can configure GlusterFS by itself, by just joining
+  storage space on new nodes to a GlusterFS nodes pool. Note that this
+  may need another design document that explains how it interacts with
+  storage pools, and that the node might or might not host VMs as well.
+
+Background
+==========
+
+There are two possible ways to implement "GlusterFS Ganeti Support". One is
+GlusterFS as one of external backend storage, the other one is realizing
+GlusterFS inside Ganeti, that is, as a new disk type for Ganeti. The benefit
+of the latter one is that it would not be opaque but fully supported and
+integrated in Ganeti, which would not need to add infrastructures for
+testing/QAing and such. Having it internal we can also provide a monitoring
+agent for it and more visibility into what's going on. For these reasons,
+GlusterFS support will be added directly inside Ganeti.
+
+Implementation Plan
+===================
+
+Ganeti Side
+-----------
+
+To realize an internal storage backend for Ganeti, one should realize
+BlockDev class in `ganeti/lib/storage/base.py` that is a specific
+class including create, remove and such. These functions should be
+realized in `ganeti/lib/storage/bdev.py`. Actually, the differences
+between implementing inside and outside (external) Ganeti are how to
+finish these functions in BlockDev class and how to combine with Ganeti
+itself. The internal implementation is not based on external scripts
+and combines with Ganeti in a more compact way. RBD patches may be a
+good reference here. Adding a backend storage steps are as follows:
+
+- Implement the BlockDev interface in bdev.py.
+- Add the logic in cmdlib (eg, migration, verify).
+- Add the new storage type name to constants.
+- Modify objects.Disk to support GlusterFS storage type.
+- The implementation will be performed similarly to the RBD one (see
+  commit 7181fba).
+
+GlusterFS side
+--------------
+
+GlusterFS is a distributed file system implemented in user space.
+The way to access GlusterFS namespace is via FUSE based Gluster native
+client except NFS and CIFS. The efficiency of this way is lower because
+the data would be pass the kernel space and then come to user space.
+Now, there are two specific enhancements:
+
+- A new library called libgfapi is now available as part of GlusterFS
+  that provides POSIX-like C APIs for accessing Gluster volumes.
+  libgfapi support will be available from GlusterFS-3.4 release.
+- QEMU/KVM (starting from QEMU-1.3) will have GlusterFS block driver that
+  uses libgfapi and hence there is no FUSE overhead any longer when QEMU/KVM
+  works with VM images on Gluster volumes.
+
+There are two possible ways to implement "GlusterFS Ganeti Support" inside
+Ganeti. One is based on libgfapi, which call APIs by libgfapi to realize
+GlusterFS interfaces in bdev.py. The other way is based on QEMU/KVM. Since
+QEMU/KVM has supported for GlusterFS and Ganeti could support for GlusterFS
+by QEMU/KVM. However, the latter way can just let VMs of QEMU/KVM use GlusterFS
+backend storage but other VMs like XEN and such. So the first way is more
+suitable for us.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 888f9a9..fade880 100644 (file)
@@ -80,18 +80,18 @@ them (citation needed). As such we'll implement for now just the
 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
+   secondary of any instance, and also don't contain the primary
+   nodes of two instances that have the same node as secondary. These
+   can be obtained by computing a coloring of the graph with nodes
+   as vertexes and an edge between two nodes, if either condition
+   prevents simultaneous maintenance. (This is the current algorithm of
+   :manpage:`hroller(1)` with the extension that the graph to be colored
+   has additional edges between the primary nodes of two instances sharing
+   their secondary node.)
+2) It is then possible to migrate in parallel all nodes in a set
+   created at step 1, 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
+   above to be reused for each following set without N+1 failures
    being triggered, if none were present before. See below about the
    actual execution of the maintenance.
 
diff --git a/doc/design-internal-shutdown.rst b/doc/design-internal-shutdown.rst
new file mode 100644 (file)
index 0000000..e1cc864
--- /dev/null
@@ -0,0 +1,128 @@
+============================================================
+Detection of user-initiated shutdown from inside an instance
+============================================================
+
+.. contents:: :depth: 2
+
+This is a design document detailing the implementation of a way for Ganeti to
+detect whether a machine marked as up but not running was shutdown gracefully
+by the user from inside the machine itself.
+
+Current state and shortcomings
+==============================
+
+Ganeti keeps track of the desired status of instances in order to be able to
+take proper actions (e.g.: reboot) on the ones that happen to crash.
+Currently, the only way to properly shut down a machine is through Ganeti's own
+commands, that will mark an instance as ``ADMIN_down``.
+If a user shuts down an instance from inside, through the proper command of the
+operating system it is running, the instance will be shutdown gracefully, but
+Ganeti is not aware of that: the desired status of the instance will still be
+marked as ``running``, so when the watcher realises that the instance is down,
+it will restart it. This behaviour is usually not what the user expects.
+
+Proposed changes
+================
+
+We propose to modify Ganeti in such a way that it will detect when an instance
+was shutdown because of an explicit user request. When such a situation is
+detected, instead of presenting an error as it happens now, either the state
+of the instance will be set to ADMIN_down, or the instance will be
+automatically rebooted, depending on a instance-specific configuration value.
+The default behavior in case no such parameter is found will be to follow
+the apparent will of the user, and setting to ADMIN_down an instance that
+was shut down correctly from inside.
+
+This design document applies to the Xen backend of Ganeti, because it uses
+features specific of such hypervisor. Initial analysis suggests that a similar
+approach might be used for KVM as well, so this design document will be later
+extended to add more details about it.
+
+Implementation
+==============
+
+Xen knows why a domain is being shut down (a crash or an explicit shutdown
+or poweroff request), but such information is not usually readily available
+externally, because all such cases lead to the virtual machine being destroyed
+immediately after the event is detected.
+
+Still, Xen allows the instance configuration file to define what action to be
+taken in all those cases through the ``on_poweroff``, ``on_shutdown`` and
+``on_crash`` variables. By setting them to ``preserve``, Xen will avoid
+destroying the domains automatically.
+
+When the domain is not destroyed, it can be viewed by using ``xm list`` (or ``xl
+list`` in newer Xen versions), and the ``State`` field of the output will
+provide useful information.
+
+If the state is ``----c-`` it means the instance has crashed.
+
+If the state is ``---s--`` it means the instance was properly shutdown.
+
+If the instance was properly shutdown and it is still marked as ``running`` by
+Ganeti, it means that it was shutdown from inside by the user, and the ganeti
+status of the instance needs to be changed to ``ADMIN_down``.
+
+This will be done at regular intervals by the group watcher, just before
+deciding which instances to reboot.
+
+On top of that, at the same times, the watcher will also need to issue ``xm
+destroy`` commands for all the domains that are in crashed or shutdown state,
+since this will not be done automatically by Xen anymore because of the
+``preserve`` setting in their config files.
+
+This behavior will be limited to the domains shut down from inside, because it
+will actually keep the resources of the domain busy until the watcher will do
+the cleaning job (that, with the default setting, is up to every 5 minutes).
+Still, this is considered acceptable, because it is not frequent for a domain
+to be shut down this way. The cleanup function will be also run
+automatically just before performing any job that requires resources to be
+available (such as when creating a new instance), in order to ensure that the
+new resource allocation happens starting from a clean state. Functionalities
+that only query the state of instances will not run the cleanup function.
+
+The cleanup operation includes both node-specific operations (the actual
+destruction of the stopped domains) and configuration changes, to be performed
+on the master node (marking as offline an instance that was shut down
+internally). The watcher, on the master node, will fetch the list of instances
+that have been shutdown from inside (recognizable by their ``oper_state``
+as described below). It will then submit a series of ``InstanceShutdown`` jobs
+that will mark such instances as ``ADMIN_down`` and clean them up (after
+the functionality of ``InstanceShutdown`` will have been extended as specified
+in the rest of this design document).
+
+LUs performing operations other than an explicit cleanup will have to be
+modified to perform the cleanup as well, either by submitting a job to perform
+the cleanup (to be completed before actually performing the task at hand) or by
+explicitly performing the cleanup themselves through the RPC calls.
+
+Other required changes
+++++++++++++++++++++++
+
+The implementation of this design document will require some commands to be
+changed in order to cope with the new shutdown procedure.
+
+With the default shutdown action in Xen set to ``preserve``, the Ganeti
+command for shutting down instances would leave them in a shutdown but
+preserved state. Therefore, it will have to be changed in such a way to
+immediately perform the cleanup of the instance after verifying its correct
+shutdown. Also, it will correctly deal with instances that have been shutdown
+from inside but are still active according to Ganeti, by detecting this
+situation, destroying the instance and carrying out the rest of the Ganeti
+shutdown procedure as usual.
+
+The ``gnt-instance list`` command will need to be able to handle the situation
+where an instance was shutdown internally but not yet cleaned up.
+The ``admin_state`` field will maintain the current meaning unchanged. The
+``oper_state`` field will get a new possible state, ``S``, meaning that the
+instance was shutdown internally.
+
+The ``gnt-instance info`` command ``State`` field, in such case, will show a
+message stating that the instance was supposed to be run but was shut down
+internally.
+
+.. vim: set textwidth=72 :
+.. Local Variables:
+.. mode: rst
+.. fill-column: 72
+.. End:
index 8b5f05c..b485ce0 100644 (file)
@@ -246,7 +246,8 @@ upon.
 
 The instance status will be on each node, for the instances it is
 primary for, and its ``data`` section of the report will contain a list
-of instances, with at least the following fields for each instance:
+of instances, named ``instances``, with at least the following fields for
+each instance:
 
 ``name``
   The name of the instance.
@@ -269,8 +270,8 @@ of instances, with at least the following fields for each instance:
 
 ``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>`.
+  to the JSON representation of a reason trail, as detailed in the :doc:`reason
+  trail design document <design-reason-trail>`.
 
 ``status``
   It represents the status of the instance, and its format is the same as that
@@ -295,33 +296,24 @@ collector will be:
 ``1``
   otherwise.
 
-Storage status
-++++++++++++++
+Storage collectors
+++++++++++++++++++
 
-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 storage collectors will be a series of data collectors
+that will gather data about storage for the current node. The collection
+will be performed at different granularity and abstraction levels, from
+the physical disks, to partitions, logical volumes and to the specific
+storage types used by Ganeti itself (drbd, rbd, plain, file).
 
 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).
+The ``kind`` field will depend on the specific collector.
 
-``total``
-  The total visible space (in KBytes).
-
-Each specific storage type might provide more type-specific fields.
+Each ``storage`` collector's ``data`` section will provide collector-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
@@ -329,6 +321,78 @@ 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.
 
+Diskstats collector
+*******************
+
+This storage data collector will gather information about the status of the
+disks installed in the system, as listed in the /proc/diskstats file. This means
+that not only physical hard drives, but also ramdisks and loopback devices will
+be listed.
+
+Its ``kind`` in the report will be ``0`` (`Performance 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 be a list of items, each representing one disk, each providing
+the following fields:
+
+``major``
+  The major number of the device.
+
+``minor``
+  The minor number of the device.
+
+``name``
+  The name of the device.
+
+``readsNum``
+  This is the total number of reads completed successfully.
+
+``mergedReads``
+  Reads which are adjacent to each other may be merged for efficiency. Thus
+  two 4K reads may become one 8K read before it is ultimately handed to the
+  disk, and so it will be counted (and queued) as only one I/O. This field
+  specifies how often this was done.
+
+``secRead``
+  This is the total number of sectors read successfully.
+
+``timeRead``
+  This is the total number of milliseconds spent by all reads.
+
+``writes``
+  This is the total number of writes completed successfully.
+
+``mergedWrites``
+  Writes which are adjacent to each other may be merged for efficiency. Thus
+  two 4K writes may become one 8K read before it is ultimately handed to the
+  disk, and so it will be counted (and queued) as only one I/O. This field
+  specifies how often this was done.
+
+``secWritten``
+  This is the total number of sectors written successfully.
+
+``timeWrite``
+  This is the total number of milliseconds spent by all writes
+
+``ios``
+  The number of I/Os currently in progress.
+  The only field that should go to zero, it is incremented as requests are
+  given to appropriate struct request_queue and decremented as they finish.
+
+``timeIO``
+  The number of milliseconds spent doing I/Os. This field increases so long
+  as field ``IOs`` is nonzero.
+
+``wIOmillis``
+  The weighted number of milliseconds spent doing I/Os.
+  This field is incremented at each I/O start, I/O completion, I/O merge,
+  or read of these stats by the number of I/Os in progress (field ``IOs``)
+  times the number of milliseconds spent doing I/O since the last update of
+  this field. This can provide an easy measure of both I/O completion time
+  and the backlog that may be accumulating.
+
 DRBD status
 ***********
 
index c059ccd..2af77a2 100644 (file)
@@ -86,12 +86,22 @@ accounted separately in allocation and balancing.
 There is already a concept of spindles in Ganeti. It's not related to
 any actual spindle or volume count, but it's used in ``spindle_use`` to
 measure the pressure of an instance on the storage system and in
-``spindle_ratio`` to balance the I/O load on the nodes. These two
-parameters will be renamed to ``storage_io_use`` and
-``storage_io_ratio`` to reflect better their meaning. When
-``exclusive_storage`` is enabled, such parameters are ignored, as
-balancing the use of storage I/O is already addressed by the exclusive
-assignment of PVs.
+``spindle_ratio`` to balance the I/O load on the nodes. When
+``exclusive_storage`` is enabled, these parameters as currently defined
+won't make any sense, so their meaning will be changed in this way:
+
+- ``spindle_use`` refers to the resource, hence to the actual spindles
+  (PVs in LVM), used by an instance. The values specified in the instance
+  policy specifications are compared to the run-time numbers of spindle
+  used by an instance. The ``spindle_use`` back-end parameter will be
+  ignored.
+- ``spindle_ratio`` in instance policies and ``spindle_count`` in node
+  parameters are ignored, as the exclusive assignment of PVs already
+  implies a value of 1.0 for the first, and the second is replaced by
+  the actual number of spindles.
+
+When ``exclusive_storage`` is disabled, the existing spindle parameters
+behave as before.
 
 Dedicated CPUs
 --------------
@@ -125,7 +135,7 @@ Under KVM or LXC memory is fully shared between the host system and all
 the guests, and instances can even be swapped out by the host OS.
 
 It's not clear if the problem can be solved by limiting the size of the
-instances, so that there is plenty of room for the host OS. 
+instances, so that there is plenty of room for the host OS.
 
 We could implement segregation using cgroups to limit the memory used by
 the host OS. This requires finishing the implementation of the memory
@@ -172,8 +182,9 @@ change: there is one for the whole cluster.
 
 For example, a policy could be set up to allow instances with this
 constraints:
+
 - between 1 and 2 CPUs, 2 GB of RAM, and between 10 GB and 400 GB of
-disk space;
+  disk space;
 - 4 CPUs, 4 GB of RAM, and between 10 GB and 800 GB of disk space.
 
 Then, an instance using 1 CPU, 2 GB of RAM and 50 GB of disk would be
index 42cb86a..b56fb10 100644 (file)
@@ -160,15 +160,15 @@ 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
+accept a list of <storage_type>,<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.
+by the requested storage_type. Depending on the storage_type, the key would
+be a volume group name in case of lvm, a directory name for the file-based
+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.
+Masterd will know through the mapping of storage types to storage calculation
+functions 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
@@ -269,8 +269,8 @@ 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
+We therefore extend the RPC call with <storage_type>,<key> to
+<storage_type>,<key>,[<param>] 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
index d6cfc2c..82bad09 100644 (file)
@@ -16,6 +16,7 @@ Most dependencies from :doc:`install-quick`, including ``qemu-img``
 - `python-epydoc <http://epydoc.sourceforge.net/>`_
 - `python-sphinx <http://sphinx.pocoo.org/>`_
   (tested with version 1.1.3)
+- `python-mock <http://www.voidspace.org.uk/python/mock/>`_
 - `graphviz <http://www.graphviz.org/>`_
 - the `en_US.UTF-8` locale must be enabled on the system
 - `pylint <http://www.logilab.org/857>`_ and its associated
@@ -47,7 +48,7 @@ Installation of all dependencies listed here::
 
      $ apt-get install python-setuptools automake git fakeroot
      $ apt-get install pandoc python-epydoc graphviz
-     $ apt-get install python-yaml
+     $ apt-get install python-yaml python-mock
      $ cd / && sudo easy_install \
                sphinx \
                logilab-astng==0.23.1 \
index c7fa9fb..a60e4ac 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
 
 .. contents::
 
index 25687ff..15254cc 100644 (file)
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
 
 .. contents::
 
index 8bfe88a..f81c0c7 100644 (file)
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
index c460a8d..41e4dba 100644 (file)
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
 
 .. contents::
 
index a75432b..a89c55a 100644 (file)
@@ -54,7 +54,9 @@ from ganeti import utils
 from ganeti import ssh
 from ganeti import hypervisor
 from ganeti import constants
-from ganeti import bdev
+from ganeti.storage import bdev
+from ganeti.storage import drbd
+from ganeti.storage import filestorage
 from ganeti import objects
 from ganeti import ssconf
 from ganeti import serializer
@@ -64,6 +66,8 @@ from ganeti import compat
 from ganeti import pathutils
 from ganeti import vcluster
 from ganeti import ht
+from ganeti.storage.base import BlockDev
+from ganeti.storage.drbd import DRBD8
 from ganeti import hooksmaster
 
 
@@ -583,13 +587,39 @@ def _GetVgInfo(name, excl_stor):
     vg_size = None
 
   return {
+    "type": constants.ST_LVM_VG,
     "name": name,
-    "vg_free": vg_free,
-    "vg_size": vg_size,
+    "storage_free": vg_free,
+    "storage_size": vg_size,
     }
 
 
-def _GetHvInfo(name):
+def _GetVgSpindlesInfo(name, excl_stor):
+  """Retrieves information about spindles in an LVM volume group.
+
+  @type name: string
+  @param name: VG name
+  @type excl_stor: bool
+  @param excl_stor: exclusive storage
+  @rtype: dict
+  @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name,
+      free spindles, total spindles respectively
+
+  """
+  if excl_stor:
+    (vg_free, vg_size) = bdev.LogicalVolume.GetVgSpindlesInfo(name)
+  else:
+    vg_free = 0
+    vg_size = 0
+  return {
+    "type": constants.ST_LVM_PV,
+    "name": name,
+    "storage_free": vg_free,
+    "storage_size": vg_size,
+    }
+
+
+def _GetHvInfo(name, hvparams, get_hv_fn=hypervisor.GetHypervisor):
   """Retrieves node information from a hypervisor.
 
   The information returned depends on the hypervisor. Common items:
@@ -601,8 +631,29 @@ def _GetHvInfo(name):
     - memory_total is the total number of ram in MiB
     - hv_version: the hypervisor version, if available
 
+  @type hvparams: dict of string
+  @param hvparams: the hypervisor's hvparams
+
+  """
+  return get_hv_fn(name).GetNodeInfo(hvparams=hvparams)
+
+
+def _GetHvInfoAll(hv_specs, get_hv_fn=hypervisor.GetHypervisor):
+  """Retrieves node information for all hypervisors.
+
+  See C{_GetHvInfo} for information on the output.
+
+  @type hv_specs: list of pairs (string, dict of strings)
+  @param hv_specs: list of pairs of a hypervisor's name and its hvparams
+
   """
-  return hypervisor.GetHypervisor(name).GetNodeInfo()
+  if hv_specs is None:
+    return None
+
+  result = []
+  for hvname, hvparams in hv_specs:
+    result.append(_GetHvInfo(hvname, hvparams, get_hv_fn))
+  return result
 
 
 def _GetNamedNodeInfo(names, fn):
@@ -617,13 +668,15 @@ def _GetNamedNodeInfo(names, fn):
     return map(fn, names)
 
 
-def GetNodeInfo(vg_names, hv_names, excl_stor):
+def GetNodeInfo(storage_units, hv_specs, excl_stor):
   """Gives back a hash with different information about the node.
 
-  @type vg_names: list of string
-  @param vg_names: Names of the volume groups to ask for disk space information
-  @type hv_names: list of string
-  @param hv_names: Names of the hypervisors to ask for node information
+  @type storage_units: list of pairs (string, string)
+  @param storage_units: List of pairs (storage unit, identifier) to ask for disk
+                        space information. In case of lvm-vg, the identifier is
+                        the VG name.
+  @type hv_specs: list of pairs (string, dict of strings)
+  @param hv_specs: list of pairs of a hypervisor's name and its hvparams
   @type excl_stor: boolean
   @param excl_stor: Whether exclusive_storage is active
   @rtype: tuple; (string, None/dict, None/dict)
@@ -632,10 +685,66 @@ def GetNodeInfo(vg_names, hv_names, excl_stor):
 
   """
   bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
-  vg_info = _GetNamedNodeInfo(vg_names, (lambda vg: _GetVgInfo(vg, excl_stor)))
-  hv_info = _GetNamedNodeInfo(hv_names, _GetHvInfo)
+  storage_info = _GetNamedNodeInfo(
+    storage_units,
+    (lambda storage_unit: _ApplyStorageInfoFunction(storage_unit[0],
+                                                    storage_unit[1],
+                                                    excl_stor)))
+  hv_info = _GetHvInfoAll(hv_specs)
+  return (bootid, storage_info, hv_info)
+
+
+# pylint: disable=W0613
+def _GetFileStorageSpaceInfo(path, *args):
+  """Wrapper around filestorage.GetSpaceInfo.
+
+  The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo
+  and ignore the *args parameter to not leak it into the filestorage
+  module's code.
 
-  return (bootid, vg_info, hv_info)
+  @see: C{filestorage.GetFileStorageSpaceInfo} for description of the
+    parameters.
+
+  """
+  return filestorage.GetFileStorageSpaceInfo(path)
+
+
+# FIXME: implement storage reporting for all missing storage types.
+_STORAGE_TYPE_INFO_FN = {
+  constants.ST_BLOCK: None,
+  constants.ST_DISKLESS: None,
+  constants.ST_EXT: None,
+  constants.ST_FILE: _GetFileStorageSpaceInfo,
+  constants.ST_LVM_PV: _GetVgSpindlesInfo,
+  constants.ST_LVM_VG: _GetVgInfo,
+  constants.ST_RADOS: None,
+}
+
+
+def _ApplyStorageInfoFunction(storage_type, storage_key, *args):
+  """Looks up and applies the correct function to calculate free and total
+  storage for the given storage type.
+
+  @type storage_type: string
+  @param storage_type: the storage type for which the storage shall be reported.
+  @type storage_key: string
+  @param storage_key: identifier of a storage unit, e.g. the volume group name
+    of an LVM storage unit
+  @type args: any
+  @param args: various parameters that can be used for storage reporting. These
+    parameters and their semantics vary from storage type to storage type and
+    are just propagated in this function.
+  @return: the results of the application of the storage space function (see
+    _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that
+    storage type
+  @raises NotImplementedError: for storage types who don't support space
+    reporting yet
+  """
+  fn = _STORAGE_TYPE_INFO_FN[storage_type]
+  if fn is not None:
+    return fn(storage_key, *args)
+  else:
+    raise NotImplementedError
 
 
 def _CheckExclusivePvs(pvi_list):
@@ -655,7 +764,111 @@ def _CheckExclusivePvs(pvi_list):
   return res
 
 
-def VerifyNode(what, cluster_name):
+def _VerifyHypervisors(what, vm_capable, result, all_hvparams,
+                       get_hv_fn=hypervisor.GetHypervisor):
+  """Verifies the hypervisor. Appends the results to the 'results' list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+  @type get_hv_fn: function
+  @param get_hv_fn: function to retrieve the hypervisor, to improve testability
+
+  """
+  if not vm_capable:
+    return
+
+  if constants.NV_HYPERVISOR in what:
+    result[constants.NV_HYPERVISOR] = {}
+    for hv_name in what[constants.NV_HYPERVISOR]:
+      hvparams = all_hvparams[hv_name]
+      try:
+        val = get_hv_fn(hv_name).Verify(hvparams=hvparams)
+      except errors.HypervisorError, err:
+        val = "Error while checking hypervisor: %s" % str(err)
+      result[constants.NV_HYPERVISOR][hv_name] = val
+
+
+def _VerifyHvparams(what, vm_capable, result,
+                    get_hv_fn=hypervisor.GetHypervisor):
+  """Verifies the hvparams. Appends the results to the 'results' list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type get_hv_fn: function
+  @param get_hv_fn: function to retrieve the hypervisor, to improve testability
+
+  """
+  if not vm_capable:
+    return
+
+  if constants.NV_HVPARAMS in what:
+    result[constants.NV_HVPARAMS] = []
+    for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
+      try:
+        logging.info("Validating hv %s, %s", hv_name, hvparms)
+        get_hv_fn(hv_name).ValidateParameters(hvparms)
+      except errors.HypervisorError, err:
+        result[constants.NV_HVPARAMS].append((source, hv_name, str(err)))
+
+
+def _VerifyInstanceList(what, vm_capable, result, all_hvparams):
+  """Verifies the instance list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+
+  """
+  if constants.NV_INSTANCELIST in what and vm_capable:
+    # GetInstanceList can fail
+    try:
+      val = GetInstanceList(what[constants.NV_INSTANCELIST],
+                            all_hvparams=all_hvparams)
+    except RPCFail, err:
+      val = str(err)
+    result[constants.NV_INSTANCELIST] = val
+
+
+def _VerifyNodeInfo(what, vm_capable, result, all_hvparams):
+  """Verifies the node info.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+
+  """
+  if constants.NV_HVINFO in what and vm_capable:
+    hvname = what[constants.NV_HVINFO]
+    hyper = hypervisor.GetHypervisor(hvname)
+    hvparams = all_hvparams[hvname]
+    result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)
+
+
+def VerifyNode(what, cluster_name, all_hvparams):
   """Verify the status of the local node.
 
   Based on the input L{what} parameter, various checks are done on the
@@ -679,6 +892,10 @@ def VerifyNode(what, cluster_name):
       - node-net-test: list of nodes we should check node daemon port
         connectivity with
       - hypervisor: list with hypervisors to run the verify for
+  @type cluster_name: string
+  @param cluster_name: the cluster's name
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: a dictionary mapping hypervisor names to hvparams
   @rtype: dict
   @return: a dictionary with the same keys as the input dict, and
       values representing the result of the checks
@@ -689,23 +906,8 @@ def VerifyNode(what, cluster_name):
   port = netutils.GetDaemonPort(constants.NODED)
   vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
 
-  if constants.NV_HYPERVISOR in what and vm_capable:
-    result[constants.NV_HYPERVISOR] = tmp = {}
-    for hv_name in what[constants.NV_HYPERVISOR]:
-      try:
-        val = hypervisor.GetHypervisor(hv_name).Verify()
-      except errors.HypervisorError, err:
-        val = "Error while checking hypervisor: %s" % str(err)
-      tmp[hv_name] = val
-
-  if constants.NV_HVPARAMS in what and vm_capable:
-    result[constants.NV_HVPARAMS] = tmp = []
-    for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
-      try:
-        logging.info("Validating hv %s, %s", hv_name, hvparms)
-        hypervisor.GetHypervisor(hv_name).ValidateParameters(hvparms)
-      except errors.HypervisorError, err:
-        tmp.append((source, hv_name, str(err)))
+  _VerifyHypervisors(what, vm_capable, result, all_hvparams)
+  _VerifyHvparams(what, vm_capable, result)
 
   if constants.NV_FILELIST in what:
     fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath,
@@ -797,13 +999,7 @@ def VerifyNode(what, cluster_name):
       val = str(err)
     result[constants.NV_LVLIST] = val
 
-  if constants.NV_INSTANCELIST in what and vm_capable:
-    # GetInstanceList can fail
-    try:
-      val = GetInstanceList(what[constants.NV_INSTANCELIST])
-    except RPCFail, err:
-      val = str(err)
-    result[constants.NV_INSTANCELIST] = val
+  _VerifyInstanceList(what, vm_capable, result, all_hvparams)
 
   if constants.NV_VGLIST in what and vm_capable:
     result[constants.NV_VGLIST] = utils.ListVolumeGroups()
@@ -824,13 +1020,19 @@ def VerifyNode(what, cluster_name):
     result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
                                     constants.RELEASE_VERSION)
 
-  if constants.NV_HVINFO in what and vm_capable:
-    hyper = hypervisor.GetHypervisor(what[constants.NV_HVINFO])
-    result[constants.NV_HVINFO] = hyper.GetNodeInfo()
+  _VerifyNodeInfo(what, vm_capable, result, all_hvparams)
+
+  if constants.NV_DRBDVERSION in what and vm_capable:
+    try:
+      drbd_version = DRBD8.GetProcInfo().GetVersionString()
+    except errors.BlockDeviceError, err:
+      logging.warning("Can't get DRBD version", exc_info=True)
+      drbd_version = str(err)
+    result[constants.NV_DRBDVERSION] = drbd_version
 
   if constants.NV_DRBDLIST in what and vm_capable:
     try:
-      used_minors = bdev.DRBD8.GetUsedDevs().keys()
+      used_minors = drbd.DRBD8.GetUsedDevs()
     except errors.BlockDeviceError, err:
       logging.warning("Can't get used minors list", exc_info=True)
       used_minors = str(err)
@@ -839,7 +1041,7 @@ def VerifyNode(what, cluster_name):
   if constants.NV_DRBDHELPER in what and vm_capable:
     status = True
     try:
-      payload = bdev.BaseDRBD.GetUsermodeHelper()
+      payload = drbd.DRBD8.GetUsermodeHelper()
     except errors.BlockDeviceError, err:
       logging.error("Can't get DRBD usermode helper: %s", str(err))
       status = False
@@ -1033,11 +1235,47 @@ def BridgesExist(bridges_list):
     _Fail("Missing bridges %s", utils.CommaJoin(missing))
 
 
-def GetInstanceList(hypervisor_list):
+def GetInstanceListForHypervisor(hname, hvparams=None,
+                                 get_hv_fn=hypervisor.GetHypervisor):
+  """Provides a list of instances of the given hypervisor.
+
+  @type hname: string
+  @param hname: name of the hypervisor
+  @type hvparams: dict of strings
+  @param hvparams: hypervisor parameters for the given hypervisor
+  @type get_hv_fn: function
+  @param get_hv_fn: function that returns a hypervisor for the given hypervisor
+    name; optional parameter to increase testability
+
+  @rtype: list
+  @return: a list of all running instances on the current node
+    - instance1.example.com
+    - instance2.example.com
+
+  """
+  results = []
+  try:
+    hv = get_hv_fn(hname)
+    names = hv.ListInstances(hvparams=hvparams)
+    results.extend(names)
+  except errors.HypervisorError, err:
+    _Fail("Error enumerating instances (hypervisor %s): %s",
+          hname, err, exc=True)
+  return results
+
+
+def GetInstanceList(hypervisor_list, all_hvparams=None,
+                    get_hv_fn=hypervisor.GetHypervisor):
   """Provides a list of instances.
 
   @type hypervisor_list: list
   @param hypervisor_list: the list of hypervisors to query information
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: a dictionary mapping hypervisor types to respective
+    cluster-wide hypervisor parameters
+  @type get_hv_fn: function
+  @param get_hv_fn: function that returns a hypervisor for the given hypervisor
+    name; optional parameter to increase testability
 
   @rtype: list
   @return: a list of all running instances on the current node
@@ -1047,23 +1285,21 @@ def GetInstanceList(hypervisor_list):
   """
   results = []
   for hname in hypervisor_list:
-    try:
-      names = hypervisor.GetHypervisor(hname).ListInstances()
-      results.extend(names)
-    except errors.HypervisorError, err:
-      _Fail("Error enumerating instances (hypervisor %s): %s",
-            hname, err, exc=True)
-
+    hvparams = all_hvparams[hname]
+    results.extend(GetInstanceListForHypervisor(hname, hvparams=hvparams,
+                                                get_hv_fn=get_hv_fn))
   return results
 
 
-def GetInstanceInfo(instance, hname):
+def GetInstanceInfo(instance, hname, hvparams=None):
   """Gives back the information about an instance as a dictionary.
 
   @type instance: string
   @param instance: the instance name
   @type hname: string
   @param hname: the hypervisor type of the instance
+  @type hvparams: dict of strings
+  @param hvparams: the instance's hvparams
 
   @rtype: dict
   @return: dictionary with the following keys:
@@ -1075,7 +1311,8 @@ def GetInstanceInfo(instance, hname):
   """
   output = {}
 
-  iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance)
+  iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance,
+                                                          hvparams=hvparams)
   if iinfo is not None:
     output["memory"] = iinfo[2]
     output["vcpus"] = iinfo[3]
@@ -1086,7 +1323,7 @@ def GetInstanceInfo(instance, hname):
 
 
 def GetInstanceMigratable(instance):
-  """Gives whether an instance can be migrated.
+  """Computes whether an instance can be migrated.
 
   @type instance: L{objects.Instance}
   @param instance: object representing the instance to be checked.
@@ -1099,7 +1336,7 @@ def GetInstanceMigratable(instance):
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
   iname = instance.name
-  if iname not in hyper.ListInstances():
+  if iname not in hyper.ListInstances(instance.hvparams):
     _Fail("Instance %s is not running", iname)
 
   for idx in range(len(instance.disks)):
@@ -1109,7 +1346,7 @@ def GetInstanceMigratable(instance):
                       iname, link_name, idx)
 
 
-def GetAllInstancesInfo(hypervisor_list):
+def GetAllInstancesInfo(hypervisor_list, all_hvparams):
   """Gather data about all instances.
 
   This is the equivalent of L{GetInstanceInfo}, except that it
@@ -1118,6 +1355,8 @@ def GetAllInstancesInfo(hypervisor_list):
 
   @type hypervisor_list: list
   @param hypervisor_list: list of hypervisors to query for instance data
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: mapping of hypervisor names to hvparams
 
   @rtype: dict
   @return: dictionary of instance: data, with data having the following keys:
@@ -1130,7 +1369,8 @@ def GetAllInstancesInfo(hypervisor_list):
   output = {}
 
   for hname in hypervisor_list:
-    iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo()
+    hvparams = all_hvparams[hname]
+    iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
     if iinfo:
       for name, _, memory, vcpus, state, times in iinfo:
         value = {
@@ -1342,7 +1582,8 @@ def StartInstance(instance, startup_paused, reason, store_reason=True):
   @rtype: None
 
   """
-  running_instances = GetInstanceList([instance.hypervisor])
+  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
+                                                   instance.hvparams)
 
   if instance.name in running_instances:
     logging.info("Instance %s already running, not starting", instance.name)
@@ -1381,7 +1622,7 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
   hyper = hypervisor.GetHypervisor(hv_name)
   iname = instance.name
 
-  if instance.name not in hyper.ListInstances():
+  if instance.name not in hyper.ListInstances(instance.hvparams):
     logging.info("Instance %s not running, doing nothing", iname)
     return
 
@@ -1390,7 +1631,7 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
       self.tried_once = False
 
     def __call__(self):
-      if iname not in hyper.ListInstances():
+      if iname not in hyper.ListInstances(instance.hvparams):
         return
 
       try:
@@ -1398,7 +1639,7 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
         if store_reason:
           _StoreInstReasonTrail(instance.name, reason)
       except errors.HypervisorError, err:
-        if iname not in hyper.ListInstances():
+        if iname not in hyper.ListInstances(instance.hvparams):
           # if the instance is no longer existing, consider this a
           # success and go to cleanup
           return
@@ -1418,14 +1659,14 @@ def InstanceShutdown(instance, timeout, reason, store_reason=True):
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
-      if iname in hyper.ListInstances():
+      if iname in hyper.ListInstances(instance.hvparams):
         # only raise an error if the instance still exists, otherwise
         # the error could simply be "instance ... unknown"!
         _Fail("Failed to force stop instance %s: %s", iname, err)
 
     time.sleep(1)
 
-    if iname in hyper.ListInstances():
+    if iname in hyper.ListInstances(instance.hvparams):
       _Fail("Could not shutdown instance %s even by destroy", iname)
 
   try:
@@ -1459,7 +1700,8 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   @rtype: None
 
   """
-  running_instances = GetInstanceList([instance.hypervisor])
+  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
+                                                   instance.hvparams)
 
   if instance.name not in running_instances:
     _Fail("Cannot reboot instance %s that is not running", instance.name)
@@ -1493,7 +1735,7 @@ def InstanceBalloonMemory(instance, memory):
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
-  running = hyper.ListInstances()
+  running = hyper.ListInstances(instance.hvparams)
   if instance.name not in running:
     logging.info("Instance %s is not running, cannot balloon", instance.name)
     return
@@ -1565,9 +1807,11 @@ def FinalizeMigrationDst(instance, info, success):
     _Fail("Failed to finalize migration on the target node: %s", err, exc=True)
 
 
-def MigrateInstance(instance, target, live):
+def MigrateInstance(cluster_name, instance, target, live):
   """Migrates an instance to another node.
 
+  @type cluster_name: string
+  @param cluster_name: name of the cluster
   @type instance: L{objects.Instance}
   @param instance: the instance definition
   @type target: string
@@ -1581,7 +1825,7 @@ def MigrateInstance(instance, target, live):
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   try:
-    hyper.MigrateInstance(instance, target, live)
+    hyper.MigrateInstance(cluster_name, instance, target, live)
   except errors.HypervisorError, err:
     _Fail("Failed to migrate instance: %s", err, exc=True)
 
@@ -1884,7 +2128,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx):
   """
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
-    if isinstance(result, bdev.BlockDev):
+    if isinstance(result, BlockDev):
       # pylint: disable=E1103
       result = result.dev_path
       if as_primary:
@@ -2095,7 +2339,7 @@ def BlockdevFind(disk):
   return rbd.GetSyncStatus()
 
 
-def BlockdevGetsize(disks):
+def BlockdevGetdimensions(disks):
   """Computes the size of the given disks.
 
   If a disk is not found, returns None instead.
@@ -2104,7 +2348,8 @@ def BlockdevGetsize(disks):
   @param disks: the list of disk to compute the size for
   @rtype: list
   @return: list with elements None if the disk cannot be found,
-      otherwise the size
+      otherwise the pair (size, spindles), where spindles is None if the
+      device doesn't support that
 
   """
   result = []
@@ -2117,7 +2362,7 @@ def BlockdevGetsize(disks):
     if rbd is None:
       result.append(None)
     else:
-      result.append(rbd.GetActualSize())
+      result.append(rbd.GetActualDimensions())
   return result
 
 
@@ -3556,14 +3801,13 @@ def CleanupImportExport(name):
   shutil.rmtree(status_dir, ignore_errors=True)
 
 
-def _FindDisks(nodes_ip, disks):
+def _FindDisks(target_node_uuid, nodes_ip, disks):
   """Sets the physical ID on disks and returns the block devices.
 
   """
   # set the correct physical ID
-  my_name = netutils.Hostname.GetSysName()
   for cf in disks:
-    cf.SetPhysicalID(my_name, nodes_ip)
+    cf.SetPhysicalID(target_node_uuid, nodes_ip)
 
   bdevs = []
 
@@ -3575,11 +3819,11 @@ def _FindDisks(nodes_ip, disks):
   return bdevs
 
 
-def DrbdDisconnectNet(nodes_ip, disks):
+def DrbdDisconnectNet(target_node_uuid, nodes_ip, disks):
   """Disconnects the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
 
   # disconnect disks
   for rd in bdevs:
@@ -3590,11 +3834,12 @@ def DrbdDisconnectNet(nodes_ip, disks):
             err, exc=True)
 
 
-def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
+def DrbdAttachNet(target_node_uuid, nodes_ip, disks, instance_name,
+                  multimaster):
   """Attaches the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
@@ -3652,7 +3897,7 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
         _Fail("Can't change to primary mode: %s", err)
 
 
-def DrbdWaitSync(nodes_ip, disks):
+def DrbdWaitSync(target_node_uuid, nodes_ip, disks):
   """Wait until DRBDs have synchronized.
 
   """
@@ -3662,7 +3907,7 @@ def DrbdWaitSync(nodes_ip, disks):
       raise utils.RetryAgain()
     return stats
 
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
 
   min_resync = 100
   alldone = True
@@ -3687,12 +3932,12 @@ def GetDrbdUsermodeHelper():
 
   """
   try:
-    return bdev.BaseDRBD.GetUsermodeHelper()
+    return drbd.DRBD8.GetUsermodeHelper()
   except errors.BlockDeviceError, err:
     _Fail(str(err))
 
 
-def PowercycleNode(hypervisor_type):
+def PowercycleNode(hypervisor_type, hvparams=None):
   """Hard-powercycle the node.
 
   Because we need to return first, and schedule the powercycle in the
@@ -3713,7 +3958,7 @@ def PowercycleNode(hypervisor_type):
   except Exception: # pylint: disable=W0703
     pass
   time.sleep(5)
-  hyper.PowercycleNode()
+  hyper.PowercycleNode(hvparams=hvparams)
 
 
 def _VerifyRestrictedCmdName(cmd):
diff --git a/lib/bdev.py b/lib/bdev.py
deleted file mode 100644 (file)
index b1e1ef9..0000000
+++ /dev/null
@@ -1,3446 +0,0 @@
-#
-#
-
-# 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
-# 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.
-
-
-"""Block device abstraction"""
-
-import re
-import time
-import errno
-import shlex
-import stat
-import pyparsing as pyp
-import os
-import logging
-import math
-
-from ganeti import utils
-from ganeti import errors
-from ganeti import constants
-from ganeti import objects
-from ganeti import compat
-from ganeti import netutils
-from ganeti import pathutils
-from ganeti import serializer
-
-
-# Size of reads in _CanReadDevice
-_DEVICE_READ_SIZE = 128 * 1024
-
-
-class RbdShowmappedJsonError(Exception):
-  """`rbd showmmapped' JSON formatting error Exception class.
-
-  """
-  pass
-
-
-def _IgnoreError(fn, *args, **kwargs):
-  """Executes the given function, ignoring BlockDeviceErrors.
-
-  This is used in order to simplify the execution of cleanup or
-  rollback functions.
-
-  @rtype: boolean
-  @return: True when fn didn't raise an exception, False otherwise
-
-  """
-  try:
-    fn(*args, **kwargs)
-    return True
-  except errors.BlockDeviceError, err:
-    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
-    return False
-
-
-def _ThrowError(msg, *args):
-  """Log an error to the node daemon and the raise an exception.
-
-  @type msg: string
-  @param msg: the text of the exception
-  @raise errors.BlockDeviceError
-
-  """
-  if args:
-    msg = msg % args
-  logging.error(msg)
-  raise errors.BlockDeviceError(msg)
-
-
-def _CheckResult(result):
-  """Throws an error if the given result is a failed one.
-
-  @param result: result from RunCmd
-
-  """
-  if result.failed:
-    _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
-                result.output)
-
-
-def _CanReadDevice(path):
-  """Check if we can read from the given device.
-
-  This tries to read the first 128k of the device.
-
-  """
-  try:
-    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
-    return True
-  except EnvironmentError:
-    logging.warning("Can't read from device %s", path, exc_info=True)
-    return False
-
-
-def _GetForbiddenFileStoragePaths():
-  """Builds a list of path prefixes which shouldn't be used for file storage.
-
-  @rtype: frozenset
-
-  """
-  paths = set([
-    "/boot",
-    "/dev",
-    "/etc",
-    "/home",
-    "/proc",
-    "/root",
-    "/sys",
-    ])
-
-  for prefix in ["", "/usr", "/usr/local"]:
-    paths.update(map(lambda s: "%s/%s" % (prefix, s),
-                     ["bin", "lib", "lib32", "lib64", "sbin"]))
-
-  return compat.UniqueFrozenset(map(os.path.normpath, paths))
-
-
-def _ComputeWrongFileStoragePaths(paths,
-                                  _forbidden=_GetForbiddenFileStoragePaths()):
-  """Cross-checks a list of paths for prefixes considered bad.
-
-  Some paths, e.g. "/bin", should not be used for file storage.
-
-  @type paths: list
-  @param paths: List of paths to be checked
-  @rtype: list
-  @return: Sorted list of paths for which the user should be warned
-
-  """
-  def _Check(path):
-    return (not os.path.isabs(path) or
-            path in _forbidden or
-            filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
-
-  return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
-
-
-def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
-  """Returns a list of file storage paths whose prefix is considered bad.
-
-  See L{_ComputeWrongFileStoragePaths}.
-
-  """
-  return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
-
-
-def _CheckFileStoragePath(path, allowed):
-  """Checks if a path is in a list of allowed paths for file storage.
-
-  @type path: string
-  @param path: Path to check
-  @type allowed: list
-  @param allowed: List of allowed paths
-  @raise errors.FileStoragePathError: If the path is not allowed
-
-  """
-  if not os.path.isabs(path):
-    raise errors.FileStoragePathError("File storage path must be absolute,"
-                                      " got '%s'" % path)
-
-  for i in allowed:
-    if not os.path.isabs(i):
-      logging.info("Ignoring relative path '%s' for file storage", i)
-      continue
-
-    if utils.IsBelowDir(i, path):
-      break
-  else:
-    raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
-                                      " storage. A possible fix might be to add"
-                                      " it to /etc/ganeti/file-storage-paths"
-                                      " on all nodes." % path)
-
-
-def _LoadAllowedFileStoragePaths(filename):
-  """Loads file containing allowed file storage paths.
-
-  @rtype: list
-  @return: List of allowed paths (can be an empty list)
-
-  """
-  try:
-    contents = utils.ReadFile(filename)
-  except EnvironmentError:
-    return []
-  else:
-    return utils.FilterEmptyLinesAndComments(contents)
-
-
-def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
-  """Checks if a path is allowed for file storage.
-
-  @type path: string
-  @param path: Path to check
-  @raise errors.FileStoragePathError: If the path is not allowed
-
-  """
-  allowed = _LoadAllowedFileStoragePaths(_filename)
-
-  if _ComputeWrongFileStoragePaths([path]):
-    raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
-                                      path)
-
-  _CheckFileStoragePath(path, allowed)
-
-
-class BlockDev(object):
-  """Block device abstract class.
-
-  A block device can be in the following states:
-    - not existing on the system, and by `Create()` it goes into:
-    - existing but not setup/not active, and by `Assemble()` goes into:
-    - active read-write and by `Open()` it goes into
-    - online (=used, or ready for use)
-
-  A device can also be online but read-only, however we are not using
-  the readonly state (LV has it, if needed in the future) and we are
-  usually looking at this like at a stack, so it's easier to
-  conceptualise the transition from not-existing to online and back
-  like a linear one.
-
-  The many different states of the device are due to the fact that we
-  need to cover many device types:
-    - logical volumes are created, lvchange -a y $lv, and used
-    - drbd devices are attached to a local disk/remote peer and made primary
-
-  A block device is identified by three items:
-    - the /dev path of the device (dynamic)
-    - a unique ID of the device (static)
-    - it's major/minor pair (dynamic)
-
-  Not all devices implement both the first two as distinct items. LVM
-  logical volumes have their unique ID (the pair volume group, logical
-  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
-  the /dev path is again dynamic and the unique id is the pair (host1,
-  dev1), (host2, dev2).
-
-  You can get to a device in two ways:
-    - creating the (real) device, which returns you
-      an attached instance (lvcreate)
-    - attaching of a python instance to an existing (real) device
-
-  The second point, the attachment to a device, is different
-  depending on whether the device is assembled or not. At init() time,
-  we search for a device with the same unique_id as us. If found,
-  good. It also means that the device is already assembled. If not,
-  after assembly we'll have our correct major/minor.
-
-  """
-  def __init__(self, unique_id, children, size, params):
-    self._children = children
-    self.dev_path = None
-    self.unique_id = unique_id
-    self.major = None
-    self.minor = None
-    self.attached = False
-    self.size = size
-    self.params = params
-
-  def Assemble(self):
-    """Assemble the device from its components.
-
-    Implementations of this method by child classes must ensure that:
-      - after the device has been assembled, it knows its major/minor
-        numbers; this allows other devices (usually parents) to probe
-        correctly for their children
-      - calling this method on an existing, in-use device is safe
-      - if the device is already configured (and in an OK state),
-        this method is idempotent
-
-    """
-    pass
-
-  def Attach(self):
-    """Find a device which matches our config and attach to it.
-
-    """
-    raise NotImplementedError
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    """
-    raise NotImplementedError
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create the device.
-
-    If the device cannot be created, it will return None
-    instead. Error messages go to the logging system.
-
-    Note that for some devices, the unique_id is used, and for other,
-    the children. The idea is that these two, taken together, are
-    enough for both creation and assembly (later).
-
-    """
-    raise NotImplementedError
-
-  def Remove(self):
-    """Remove this device.
-
-    This makes sense only for some of the device types: LV and file
-    storage. Also note that if the device can't attach, the removal
-    can't be completed.
-
-    """
-    raise NotImplementedError
-
-  def Rename(self, new_id):
-    """Rename this device.
-
-    This may or may not make sense for a given device type.
-
-    """
-    raise NotImplementedError
-
-  def Open(self, force=False):
-    """Make the device ready for use.
-
-    This makes the device ready for I/O. For now, just the DRBD
-    devices need this.
-
-    The force parameter signifies that if the device has any kind of
-    --force thing, it should be used, we know what we are doing.
-
-    """
-    raise NotImplementedError
-
-  def Shutdown(self):
-    """Shut down the device, freeing its children.
-
-    This undoes the `Assemble()` work, except for the child
-    assembling; as such, the children on the device are still
-    assembled after this call.
-
-    """
-    raise NotImplementedError
-
-  def SetSyncParams(self, params):
-    """Adjust the synchronization parameters of the mirror.
-
-    In case this is not a mirroring device, this is no-op.
-
-    @param params: dictionary of LD level disk parameters related to the
-    synchronization.
-    @rtype: list
-    @return: a list of error messages, emitted both by the current node and by
-    children. An empty list means no errors.
-
-    """
-    result = []
-    if self._children:
-      for child in self._children:
-        result.extend(child.SetSyncParams(params))
-    return result
-
-  def PauseResumeSync(self, pause):
-    """Pause/Resume the sync of the mirror.
-
-    In case this is not a mirroring device, this is no-op.
-
-    @param pause: Whether to pause or resume
-
-    """
-    result = True
-    if self._children:
-      for child in self._children:
-        result = result and child.PauseResumeSync(pause)
-    return result
-
-  def GetSyncStatus(self):
-    """Returns the sync status of the device.
-
-    If this device is a mirroring device, this function returns the
-    status of the mirror.
-
-    If sync_percent is None, it means the device is not syncing.
-
-    If estimated_time is None, it means we can't estimate
-    the time needed, otherwise it's the time left in seconds.
-
-    If is_degraded is True, it means the device is missing
-    redundancy. This is usually a sign that something went wrong in
-    the device setup, if sync_percent is None.
-
-    The ldisk parameter represents the degradation of the local
-    data. This is only valid for some devices, the rest will always
-    return False (not degraded).
-
-    @rtype: objects.BlockDevStatus
-
-    """
-    return objects.BlockDevStatus(dev_path=self.dev_path,
-                                  major=self.major,
-                                  minor=self.minor,
-                                  sync_percent=None,
-                                  estimated_time=None,
-                                  is_degraded=False,
-                                  ldisk_status=constants.LDS_OKAY)
-
-  def CombinedSyncStatus(self):
-    """Calculate the mirror status recursively for our children.
-
-    The return value is the same as for `GetSyncStatus()` except the
-    minimum percent and maximum time are calculated across our
-    children.
-
-    @rtype: objects.BlockDevStatus
-
-    """
-    status = self.GetSyncStatus()
-
-    min_percent = status.sync_percent
-    max_time = status.estimated_time
-    is_degraded = status.is_degraded
-    ldisk_status = status.ldisk_status
-
-    if self._children:
-      for child in self._children:
-        child_status = child.GetSyncStatus()
-
-        if min_percent is None:
-          min_percent = child_status.sync_percent
-        elif child_status.sync_percent is not None:
-          min_percent = min(min_percent, child_status.sync_percent)
-
-        if max_time is None:
-          max_time = child_status.estimated_time
-        elif child_status.estimated_time is not None:
-          max_time = max(max_time, child_status.estimated_time)
-
-        is_degraded = is_degraded or child_status.is_degraded
-
-        if ldisk_status is None:
-          ldisk_status = child_status.ldisk_status
-        elif child_status.ldisk_status is not None:
-          ldisk_status = max(ldisk_status, child_status.ldisk_status)
-
-    return objects.BlockDevStatus(dev_path=self.dev_path,
-                                  major=self.major,
-                                  minor=self.minor,
-                                  sync_percent=min_percent,
-                                  estimated_time=max_time,
-                                  is_degraded=is_degraded,
-                                  ldisk_status=ldisk_status)
-
-  def SetInfo(self, text):
-    """Update metadata with info text.
-
-    Only supported for some device types.
-
-    """
-    for child in self._children:
-      child.SetInfo(text)
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the block device.
-
-    @type amount: integer
-    @param amount: the amount (in mebibytes) to grow with
-    @type dryrun: boolean
-    @param dryrun: whether to execute the operation in simulation mode
-        only, without actually increasing the size
-    @param backingstore: whether to execute the operation on backing storage
-        only, or on "logical" storage only; e.g. DRBD is logical storage,
-        whereas LVM, file, RBD are backing storage
-
-    """
-    raise NotImplementedError
-
-  def GetActualSize(self):
-    """Return the actual disk size.
-
-    @note: the device needs to be active when this is called
-
-    """
-    assert self.attached, "BlockDevice not attached in GetActualSize()"
-    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
-    if result.failed:
-      _ThrowError("blockdev failed (%s): %s",
-                  result.fail_reason, result.output)
-    try:
-      sz = int(result.output.strip())
-    except (ValueError, TypeError), err:
-      _ThrowError("Failed to parse blockdev output: %s", str(err))
-    return sz
-
-  def __repr__(self):
-    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
-            (self.__class__, self.unique_id, self._children,
-             self.major, self.minor, self.dev_path))
-
-
-class LogicalVolume(BlockDev):
-  """Logical Volume block device.
-
-  """
-  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
-  _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
-  _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
-
-  def __init__(self, unique_id, children, size, params):
-    """Attaches to a LV device.
-
-    The unique_id is a tuple (vg_name, lv_name)
-
-    """
-    super(LogicalVolume, self).__init__(unique_id, children, size, params)
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-    self._vg_name, self._lv_name = unique_id
-    self._ValidateName(self._vg_name)
-    self._ValidateName(self._lv_name)
-    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
-    self._degraded = True
-    self.major = self.minor = self.pe_size = self.stripe_count = None
-    self.Attach()
-
-  @staticmethod
-  def _GetStdPvSize(pvs_info):
-    """Return the the standard PV size (used with exclusive storage).
-
-    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
-    @rtype: float
-    @return: size in MiB
-
-    """
-    assert len(pvs_info) > 0
-    smallest = min([pv.size for pv in pvs_info])
-    return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
-
-  @staticmethod
-  def _ComputeNumPvs(size, pvs_info):
-    """Compute the number of PVs needed for an LV (with exclusive storage).
-
-    @type size: float
-    @param size: LV size in MiB
-    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
-    @rtype: integer
-    @return: number of PVs needed
-    """
-    assert len(pvs_info) > 0
-    pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
-    return int(math.ceil(float(size) / pv_size))
-
-  @staticmethod
-  def _GetEmptyPvNames(pvs_info, max_pvs=None):
-    """Return a list of empty PVs, by name.
-
-    """
-    empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
-    if max_pvs is not None:
-      empty_pvs = empty_pvs[:max_pvs]
-    return map((lambda pv: pv.name), empty_pvs)
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new logical volume.
-
-    """
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise errors.ProgrammerError("Invalid configuration data %s" %
-                                   str(unique_id))
-    vg_name, lv_name = unique_id
-    cls._ValidateName(vg_name)
-    cls._ValidateName(lv_name)
-    pvs_info = cls.GetPVInfo([vg_name])
-    if not pvs_info:
-      if excl_stor:
-        msg = "No (empty) PVs found"
-      else:
-        msg = "Can't compute PV info for vg %s" % vg_name
-      _ThrowError(msg)
-    pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
-
-    pvlist = [pv.name for pv in pvs_info]
-    if compat.any(":" in v for v in pvlist):
-      _ThrowError("Some of your PVs have the invalid character ':' in their"
-                  " name, this is not supported - please filter them out"
-                  " in lvm.conf using either 'filter' or 'preferred_names'")
-
-    current_pvs = len(pvlist)
-    desired_stripes = params[constants.LDP_STRIPES]
-    stripes = min(current_pvs, desired_stripes)
-
-    if excl_stor:
-      (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
-      if err_msgs:
-        for m in err_msgs:
-          logging.warning(m)
-      req_pvs = cls._ComputeNumPvs(size, pvs_info)
-      pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
-      current_pvs = len(pvlist)
-      if current_pvs < req_pvs:
-        _ThrowError("Not enough empty PVs to create a disk of %d MB:"
-                    " %d available, %d needed", size, current_pvs, req_pvs)
-      assert current_pvs == len(pvlist)
-      if stripes > current_pvs:
-        # No warning issued for this, as it's no surprise
-        stripes = current_pvs
-
-    else:
-      if stripes < desired_stripes:
-        logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
-                        " available.", desired_stripes, vg_name, current_pvs)
-      free_size = sum([pv.free for pv in pvs_info])
-      # The size constraint should have been checked from the master before
-      # calling the create function.
-      if free_size < size:
-        _ThrowError("Not enough free space: required %s,"
-                    " available %s", size, free_size)
-
-    # If the free space is not well distributed, we won't be able to
-    # create an optimally-striped volume; in that case, we want to try
-    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
-    # stripes
-    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
-    for stripes_arg in range(stripes, 0, -1):
-      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
-      if not result.failed:
-        break
-    if result.failed:
-      _ThrowError("LV create failed (%s): %s",
-                  result.fail_reason, result.output)
-    return LogicalVolume(unique_id, children, size, params)
-
-  @staticmethod
-  def _GetVolumeInfo(lvm_cmd, fields):
-    """Returns LVM Volume infos using lvm_cmd
-
-    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
-    @param fields: Fields to return
-    @return: A list of dicts each with the parsed fields
-
-    """
-    if not fields:
-      raise errors.ProgrammerError("No fields specified")
-
-    sep = "|"
-    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
-           "--separator=%s" % sep, "-o%s" % ",".join(fields)]
-
-    result = utils.RunCmd(cmd)
-    if result.failed:
-      raise errors.CommandError("Can't get the volume information: %s - %s" %
-                                (result.fail_reason, result.output))
-
-    data = []
-    for line in result.stdout.splitlines():
-      splitted_fields = line.strip().split(sep)
-
-      if len(fields) != len(splitted_fields):
-        raise errors.CommandError("Can't parse %s output: line '%s'" %
-                                  (lvm_cmd, line))
-
-      data.append(splitted_fields)
-
-    return data
-
-  @classmethod
-  def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
-    """Get the free space info for PVs in a volume group.
-
-    @param vg_names: list of volume group names, if empty all will be returned
-    @param filter_allocatable: whether to skip over unallocatable PVs
-    @param include_lvs: whether to include a list of LVs hosted on each PV
-
-    @rtype: list
-    @return: list of objects.LvmPvInfo objects
-
-    """
-    # We request "lv_name" field only if we care about LVs, so we don't get
-    # a long list of entries with many duplicates unless we really have to.
-    # The duplicate "pv_name" field will be ignored.
-    if include_lvs:
-      lvfield = "lv_name"
-    else:
-      lvfield = "pv_name"
-    try:
-      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
-                                        "pv_attr", "pv_size", lvfield])
-    except errors.GenericError, err:
-      logging.error("Can't get PV information: %s", err)
-      return None
-
-    # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
-    # pair. We sort entries by PV name and then LV name, so it's easy to weed
-    # out duplicates.
-    if include_lvs:
-      info.sort(key=(lambda i: (i[0], i[5])))
-    data = []
-    lastpvi = None
-    for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
-      # (possibly) skip over pvs which are not allocatable
-      if filter_allocatable and pv_attr[0] != "a":
-        continue
-      # (possibly) skip over pvs which are not in the right volume group(s)
-      if vg_names and vg_name not in vg_names:
-        continue
-      # Beware of duplicates (check before inserting)
-      if lastpvi and lastpvi.name == pv_name:
-        if include_lvs and lv_name:
-          if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
-            lastpvi.lv_list.append(lv_name)
-      else:
-        if include_lvs and lv_name:
-          lvl = [lv_name]
-        else:
-          lvl = []
-        lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
-                                    size=float(pv_size), free=float(pv_free),
-                                    attributes=pv_attr, lv_list=lvl)
-        data.append(lastpvi)
-
-    return data
-
-  @classmethod
-  def _GetExclusiveStorageVgFree(cls, vg_name):
-    """Return the free disk space in the given VG, in exclusive storage mode.
-
-    @type vg_name: string
-    @param vg_name: VG name
-    @rtype: float
-    @return: free space in MiB
-    """
-    pvs_info = cls.GetPVInfo([vg_name])
-    if not pvs_info:
-      return 0.0
-    pv_size = cls._GetStdPvSize(pvs_info)
-    num_pvs = len(cls._GetEmptyPvNames(pvs_info))
-    return pv_size * num_pvs
-
-  @classmethod
-  def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
-    """Get the free space info for specific VGs.
-
-    @param vg_names: list of volume group names, if empty all will be returned
-    @param excl_stor: whether exclusive_storage is enabled
-    @param filter_readonly: whether to skip over readonly VGs
-
-    @rtype: list
-    @return: list of tuples (free_space, total_size, name) with free_space in
-             MiB
-
-    """
-    try:
-      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
-                                        "vg_size"])
-    except errors.GenericError, err:
-      logging.error("Can't get VG information: %s", err)
-      return None
-
-    data = []
-    for vg_name, vg_free, vg_attr, vg_size in info:
-      # (possibly) skip over vgs which are not writable
-      if filter_readonly and vg_attr[0] == "r":
-        continue
-      # (possibly) skip over vgs which are not in the right volume group(s)
-      if vg_names and vg_name not in vg_names:
-        continue
-      # Exclusive storage needs a different concept of free space
-      if excl_stor:
-        es_free = cls._GetExclusiveStorageVgFree(vg_name)
-        assert es_free <= vg_free
-        vg_free = es_free
-      data.append((float(vg_free), float(vg_size), vg_name))
-
-    return data
-
-  @classmethod
-  def _ValidateName(cls, name):
-    """Validates that a given name is valid as VG or LV name.
-
-    The list of valid characters and restricted names is taken out of
-    the lvm(8) manpage, with the simplification that we enforce both
-    VG and LV restrictions on the names.
-
-    """
-    if (not cls._VALID_NAME_RE.match(name) or
-        name in cls._INVALID_NAMES or
-        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
-      _ThrowError("Invalid LVM name '%s'", name)
-
-  def Remove(self):
-    """Remove this logical volume.
-
-    """
-    if not self.minor and not self.Attach():
-      # the LV does not exist
-      return
-    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
-                           (self._vg_name, self._lv_name)])
-    if result.failed:
-      _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
-
-  def Rename(self, new_id):
-    """Rename this logical volume.
-
-    """
-    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
-      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
-    new_vg, new_name = new_id
-    if new_vg != self._vg_name:
-      raise errors.ProgrammerError("Can't move a logical volume across"
-                                   " volume groups (from %s to to %s)" %
-                                   (self._vg_name, new_vg))
-    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
-    if result.failed:
-      _ThrowError("Failed to rename the logical volume: %s", result.output)
-    self._lv_name = new_name
-    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
-
-  def Attach(self):
-    """Attach to an existing LV.
-
-    This method will try to see if an existing and active LV exists
-    which matches our name. If so, its major/minor will be
-    recorded.
-
-    """
-    self.attached = False
-    result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
-                           "--units=k", "--nosuffix",
-                           "-olv_attr,lv_kernel_major,lv_kernel_minor,"
-                           "vg_extent_size,stripes", self.dev_path])
-    if result.failed:
-      logging.error("Can't find LV %s: %s, %s",
-                    self.dev_path, result.fail_reason, result.output)
-      return False
-    # the output can (and will) have multiple lines for multi-segment
-    # LVs, as the 'stripes' parameter is a segment one, so we take
-    # only the last entry, which is the one we're interested in; note
-    # that with LVM2 anyway the 'stripes' value must be constant
-    # across segments, so this is a no-op actually
-    out = result.stdout.splitlines()
-    if not out: # totally empty result? splitlines() returns at least
-                # one line for any non-empty string
-      logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
-      return False
-    out = out[-1].strip().rstrip(",")
-    out = out.split(",")
-    if len(out) != 5:
-      logging.error("Can't parse LVS output, len(%s) != 5", str(out))
-      return False
-
-    status, major, minor, pe_size, stripes = out
-    if len(status) < 6:
-      logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
-      return False
-
-    try:
-      major = int(major)
-      minor = int(minor)
-    except (TypeError, ValueError), err:
-      logging.error("lvs major/minor cannot be parsed: %s", str(err))
-
-    try:
-      pe_size = int(float(pe_size))
-    except (TypeError, ValueError), err:
-      logging.error("Can't parse vg extent size: %s", err)
-      return False
-
-    try:
-      stripes = int(stripes)
-    except (TypeError, ValueError), err:
-      logging.error("Can't parse the number of stripes: %s", err)
-      return False
-
-    self.major = major
-    self.minor = minor
-    self.pe_size = pe_size
-    self.stripe_count = stripes
-    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
-                                      # storage
-    self.attached = True
-    return True
-
-  def Assemble(self):
-    """Assemble the device.
-
-    We always run `lvchange -ay` on the LV to ensure it's active before
-    use, as there were cases when xenvg was not active after boot
-    (also possibly after disk issues).
-
-    """
-    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
-    if result.failed:
-      _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
-
-  def Shutdown(self):
-    """Shutdown the device.
-
-    This is a no-op for the LV device type, as we don't deactivate the
-    volumes on shutdown.
-
-    """
-    pass
-
-  def GetSyncStatus(self):
-    """Returns the sync status of the device.
-
-    If this device is a mirroring device, this function returns the
-    status of the mirror.
-
-    For logical volumes, sync_percent and estimated_time are always
-    None (no recovery in progress, as we don't handle the mirrored LV
-    case). The is_degraded parameter is the inverse of the ldisk
-    parameter.
-
-    For the ldisk parameter, we check if the logical volume has the
-    'virtual' type, which means it's not backed by existing storage
-    anymore (read from it return I/O error). This happens after a
-    physical disk failure and subsequent 'vgreduce --removemissing' on
-    the volume group.
-
-    The status was already read in Attach, so we just return it.
-
-    @rtype: objects.BlockDevStatus
-
-    """
-    if self._degraded:
-      ldisk_status = constants.LDS_FAULTY
-    else:
-      ldisk_status = constants.LDS_OKAY
-
-    return objects.BlockDevStatus(dev_path=self.dev_path,
-                                  major=self.major,
-                                  minor=self.minor,
-                                  sync_percent=None,
-                                  estimated_time=None,
-                                  is_degraded=self._degraded,
-                                  ldisk_status=ldisk_status)
-
-  def Open(self, force=False):
-    """Make the device ready for I/O.
-
-    This is a no-op for the LV device type.
-
-    """
-    pass
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    This is a no-op for the LV device type.
-
-    """
-    pass
-
-  def Snapshot(self, size):
-    """Create a snapshot copy of an lvm block device.
-
-    @returns: tuple (vg, lv)
-
-    """
-    snap_name = self._lv_name + ".snap"
-
-    # remove existing snapshot if found
-    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
-    _IgnoreError(snap.Remove)
-
-    vg_info = self.GetVGInfo([self._vg_name], False)
-    if not vg_info:
-      _ThrowError("Can't compute VG info for vg %s", self._vg_name)
-    free_size, _, _ = vg_info[0]
-    if free_size < size:
-      _ThrowError("Not enough free space: required %s,"
-                  " available %s", size, free_size)
-
-    _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
-                               "-n%s" % snap_name, self.dev_path]))
-
-    return (self._vg_name, snap_name)
-
-  def _RemoveOldInfo(self):
-    """Try to remove old tags from the lv.
-
-    """
-    result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
-                           self.dev_path])
-    _CheckResult(result)
-
-    raw_tags = result.stdout.strip()
-    if raw_tags:
-      for tag in raw_tags.split(","):
-        _CheckResult(utils.RunCmd(["lvchange", "--deltag",
-                                   tag.strip(), self.dev_path]))
-
-  def SetInfo(self, text):
-    """Update metadata with info text.
-
-    """
-    BlockDev.SetInfo(self, text)
-
-    self._RemoveOldInfo()
-
-    # Replace invalid characters
-    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
-    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
-
-    # Only up to 128 characters are allowed
-    text = text[:128]
-
-    _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the logical volume.
-
-    """
-    if not backingstore:
-      return
-    if self.pe_size is None or self.stripe_count is None:
-      if not self.Attach():
-        _ThrowError("Can't attach to LV during Grow()")
-    full_stripe_size = self.pe_size * self.stripe_count
-    # pe_size is in KB
-    amount *= 1024
-    rest = amount % full_stripe_size
-    if rest != 0:
-      amount += full_stripe_size - rest
-    cmd = ["lvextend", "-L", "+%dk" % amount]
-    if dryrun:
-      cmd.append("--test")
-    # we try multiple algorithms since the 'best' ones might not have
-    # space available in the right place, but later ones might (since
-    # they have less constraints); also note that only recent LVM
-    # supports 'cling'
-    for alloc_policy in "contiguous", "cling", "normal":
-      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
-      if not result.failed:
-        return
-    _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
-
-
-class DRBD8Status(object):
-  """A DRBD status representation class.
-
-  Note that this doesn't support unconfigured devices (cs:Unconfigured).
-
-  """
-  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
-  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
-                       "\s+ds:([^/]+)/(\S+)\s+.*$")
-  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
-                       # Due to a bug in drbd in the kernel, introduced in
-                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
-                       "(?:\s|M)"
-                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
-
-  CS_UNCONFIGURED = "Unconfigured"
-  CS_STANDALONE = "StandAlone"
-  CS_WFCONNECTION = "WFConnection"
-  CS_WFREPORTPARAMS = "WFReportParams"
-  CS_CONNECTED = "Connected"
-  CS_STARTINGSYNCS = "StartingSyncS"
-  CS_STARTINGSYNCT = "StartingSyncT"
-  CS_WFBITMAPS = "WFBitMapS"
-  CS_WFBITMAPT = "WFBitMapT"
-  CS_WFSYNCUUID = "WFSyncUUID"
-  CS_SYNCSOURCE = "SyncSource"
-  CS_SYNCTARGET = "SyncTarget"
-  CS_PAUSEDSYNCS = "PausedSyncS"
-  CS_PAUSEDSYNCT = "PausedSyncT"
-  CSET_SYNC = compat.UniqueFrozenset([
-    CS_WFREPORTPARAMS,
-    CS_STARTINGSYNCS,
-    CS_STARTINGSYNCT,
-    CS_WFBITMAPS,
-    CS_WFBITMAPT,
-    CS_WFSYNCUUID,
-    CS_SYNCSOURCE,
-    CS_SYNCTARGET,
-    CS_PAUSEDSYNCS,
-    CS_PAUSEDSYNCT,
-    ])
-
-  DS_DISKLESS = "Diskless"
-  DS_ATTACHING = "Attaching" # transient state
-  DS_FAILED = "Failed" # transient state, next: diskless
-  DS_NEGOTIATING = "Negotiating" # transient state
-  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
-  DS_OUTDATED = "Outdated"
-  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
-  DS_CONSISTENT = "Consistent"
-  DS_UPTODATE = "UpToDate" # normal state
-
-  RO_PRIMARY = "Primary"
-  RO_SECONDARY = "Secondary"
-  RO_UNKNOWN = "Unknown"
-
-  def __init__(self, procline):
-    u = self.UNCONF_RE.match(procline)
-    if u:
-      self.cstatus = self.CS_UNCONFIGURED
-      self.lrole = self.rrole = self.ldisk = self.rdisk = None
-    else:
-      m = self.LINE_RE.match(procline)
-      if not m:
-        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
-      self.cstatus = m.group(1)
-      self.lrole = m.group(2)
-      self.rrole = m.group(3)
-      self.ldisk = m.group(4)
-      self.rdisk = m.group(5)
-
-    # end reading of data from the LINE_RE or UNCONF_RE
-
-    self.is_standalone = self.cstatus == self.CS_STANDALONE
-    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
-    self.is_connected = self.cstatus == self.CS_CONNECTED
-    self.is_primary = self.lrole == self.RO_PRIMARY
-    self.is_secondary = self.lrole == self.RO_SECONDARY
-    self.peer_primary = self.rrole == self.RO_PRIMARY
-    self.peer_secondary = self.rrole == self.RO_SECONDARY
-    self.both_primary = self.is_primary and self.peer_primary
-    self.both_secondary = self.is_secondary and self.peer_secondary
-
-    self.is_diskless = self.ldisk == self.DS_DISKLESS
-    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
-
-    self.is_in_resync = self.cstatus in self.CSET_SYNC
-    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
-
-    m = self.SYNC_RE.match(procline)
-    if m:
-      self.sync_percent = float(m.group(1))
-      hours = int(m.group(2))
-      minutes = int(m.group(3))
-      seconds = int(m.group(4))
-      self.est_time = hours * 3600 + minutes * 60 + seconds
-    else:
-      # we have (in this if branch) no percent information, but if
-      # we're resyncing we need to 'fake' a sync percent information,
-      # as this is how cmdlib determines if it makes sense to wait for
-      # resyncing or not
-      if self.is_in_resync:
-        self.sync_percent = 0
-      else:
-        self.sync_percent = None
-      self.est_time = None
-
-
-class BaseDRBD(BlockDev): # pylint: disable=W0223
-  """Base DRBD class.
-
-  This class contains a few bits of common functionality between the
-  0.7 and 8.x versions of DRBD.
-
-  """
-  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
-                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
-  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
-  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
-
-  _DRBD_MAJOR = 147
-  _ST_UNCONFIGURED = "Unconfigured"
-  _ST_WFCONNECTION = "WFConnection"
-  _ST_CONNECTED = "Connected"
-
-  _STATUS_FILE = constants.DRBD_STATUS_FILE
-  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
-
-  @staticmethod
-  def _GetProcData(filename=_STATUS_FILE):
-    """Return data from /proc/drbd.
-
-    """
-    try:
-      data = utils.ReadFile(filename).splitlines()
-    except EnvironmentError, err:
-      if err.errno == errno.ENOENT:
-        _ThrowError("The file %s cannot be opened, check if the module"
-                    " is loaded (%s)", filename, str(err))
-      else:
-        _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
-    if not data:
-      _ThrowError("Can't read any data from %s", filename)
-    return data
-
-  @classmethod
-  def _MassageProcData(cls, data):
-    """Transform the output of _GetProdData into a nicer form.
-
-    @return: a dictionary of minor: joined lines from /proc/drbd
-        for that minor
-
-    """
-    results = {}
-    old_minor = old_line = None
-    for line in data:
-      if not line: # completely empty lines, as can be returned by drbd8.0+
-        continue
-      lresult = cls._VALID_LINE_RE.match(line)
-      if lresult is not None:
-        if old_minor is not None:
-          results[old_minor] = old_line
-        old_minor = int(lresult.group(1))
-        old_line = line
-      else:
-        if old_minor is not None:
-          old_line += " " + line.strip()
-    # add last line
-    if old_minor is not None:
-      results[old_minor] = old_line
-    return results
-
-  @classmethod
-  def _GetVersion(cls, proc_data):
-    """Return the DRBD version.
-
-    This will return a dict with keys:
-      - k_major
-      - k_minor
-      - k_point
-      - api
-      - proto
-      - proto2 (only on drbd > 8.2.X)
-
-    """
-    first_line = proc_data[0].strip()
-    version = cls._VERSION_RE.match(first_line)
-    if not version:
-      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
-                                    first_line)
-
-    values = version.groups()
-    retval = {
-      "k_major": int(values[0]),
-      "k_minor": int(values[1]),
-      "k_point": int(values[2]),
-      "api": int(values[3]),
-      "proto": int(values[4]),
-      }
-    if values[5] is not None:
-      retval["proto2"] = values[5]
-
-    return retval
-
-  @staticmethod
-  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
-    """Returns DRBD usermode_helper currently set.
-
-    """
-    try:
-      helper = utils.ReadFile(filename).splitlines()[0]
-    except EnvironmentError, err:
-      if err.errno == errno.ENOENT:
-        _ThrowError("The file %s cannot be opened, check if the module"
-                    " is loaded (%s)", filename, str(err))
-      else:
-        _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
-    if not helper:
-      _ThrowError("Can't read any data from %s", filename)
-    return helper
-
-  @staticmethod
-  def _DevPath(minor):
-    """Return the path to a drbd device for a given minor.
-
-    """
-    return "/dev/drbd%d" % minor
-
-  @classmethod
-  def GetUsedDevs(cls):
-    """Compute the list of used DRBD devices.
-
-    """
-    data = cls._GetProcData()
-
-    used_devs = {}
-    for line in data:
-      match = cls._VALID_LINE_RE.match(line)
-      if not match:
-        continue
-      minor = int(match.group(1))
-      state = match.group(2)
-      if state == cls._ST_UNCONFIGURED:
-        continue
-      used_devs[minor] = state, line
-
-    return used_devs
-
-  def _SetFromMinor(self, minor):
-    """Set our parameters based on the given minor.
-
-    This sets our minor variable and our dev_path.
-
-    """
-    if minor is None:
-      self.minor = self.dev_path = None
-      self.attached = False
-    else:
-      self.minor = minor
-      self.dev_path = self._DevPath(minor)
-      self.attached = True
-
-  @staticmethod
-  def _CheckMetaSize(meta_device):
-    """Check if the given meta device looks like a valid one.
-
-    This currently only checks the size, which must be around
-    128MiB.
-
-    """
-    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
-    if result.failed:
-      _ThrowError("Failed to get device size: %s - %s",
-                  result.fail_reason, result.output)
-    try:
-      sectors = int(result.stdout)
-    except (TypeError, ValueError):
-      _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
-    num_bytes = sectors * 512
-    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
-      _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
-    # the maximum *valid* size of the meta device when living on top
-    # of LVM is hard to compute: it depends on the number of stripes
-    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
-    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
-    # size meta device; as such, we restrict it to 1GB (a little bit
-    # too generous, but making assumptions about PE size is hard)
-    if num_bytes > 1024 * 1024 * 1024:
-      _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
-
-  def Rename(self, new_id):
-    """Rename a device.
-
-    This is not supported for drbd devices.
-
-    """
-    raise errors.ProgrammerError("Can't rename a drbd device")
-
-
-class DRBD8(BaseDRBD):
-  """DRBD v8.x block device.
-
-  This implements the local host part of the DRBD device, i.e. it
-  doesn't do anything to the supposed peer. If you need a fully
-  connected DRBD pair, you need to use this class on both hosts.
-
-  The unique_id for the drbd device is a (local_ip, local_port,
-  remote_ip, remote_port, local_minor, secret) tuple, and it must have
-  two children: the data device and the meta_device. The meta device
-  is checked for valid size and is zeroed on create.
-
-  """
-  _MAX_MINORS = 255
-  _PARSE_SHOW = None
-
-  # timeout constants
-  _NET_RECONFIG_TIMEOUT = 60
-
-  # command line options for barriers
-  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
-  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
-  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
-  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
-
-  def __init__(self, unique_id, children, size, params):
-    if children and children.count(None) > 0:
-      children = []
-    if len(children) not in (0, 2):
-      raise ValueError("Invalid configuration data %s" % str(children))
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-    (self._lhost, self._lport,
-     self._rhost, self._rport,
-     self._aminor, self._secret) = unique_id
-    if children:
-      if not _CanReadDevice(children[1].dev_path):
-        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
-        children = []
-    super(DRBD8, self).__init__(unique_id, children, size, params)
-    self.major = self._DRBD_MAJOR
-    version = self._GetVersion(self._GetProcData())
-    if version["k_major"] != 8:
-      _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
-                  " usage: kernel is %s.%s, ganeti wants 8.x",
-                  version["k_major"], version["k_minor"])
-
-    if (self._lhost is not None and self._lhost == self._rhost and
-        self._lport == self._rport):
-      raise ValueError("Invalid configuration data, same local/remote %s" %
-                       (unique_id,))
-    self.Attach()
-
-  @classmethod
-  def _InitMeta(cls, minor, dev_path):
-    """Initialize a meta device.
-
-    This will not work if the given minor is in use.
-
-    """
-    # Zero the metadata first, in order to make sure drbdmeta doesn't
-    # try to auto-detect existing filesystems or similar (see
-    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
-    # care about the first 128MB of data in the device, even though it
-    # can be bigger
-    result = utils.RunCmd([constants.DD_CMD,
-                           "if=/dev/zero", "of=%s" % dev_path,
-                           "bs=1048576", "count=128", "oflag=direct"])
-    if result.failed:
-      _ThrowError("Can't wipe the meta device: %s", result.output)
-
-    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
-                           "v08", dev_path, "0", "create-md"])
-    if result.failed:
-      _ThrowError("Can't initialize meta device: %s", result.output)
-
-  @classmethod
-  def _FindUnusedMinor(cls):
-    """Find an unused DRBD device.
-
-    This is specific to 8.x as the minors are allocated dynamically,
-    so non-existing numbers up to a max minor count are actually free.
-
-    """
-    data = cls._GetProcData()
-
-    highest = None
-    for line in data:
-      match = cls._UNUSED_LINE_RE.match(line)
-      if match:
-        return int(match.group(1))
-      match = cls._VALID_LINE_RE.match(line)
-      if match:
-        minor = int(match.group(1))
-        highest = max(highest, minor)
-    if highest is None: # there are no minors in use at all
-      return 0
-    if highest >= cls._MAX_MINORS:
-      logging.error("Error: no free drbd minors!")
-      raise errors.BlockDeviceError("Can't find a free DRBD minor")
-    return highest + 1
-
-  @classmethod
-  def _GetShowParser(cls):
-    """Return a parser for `drbd show` output.
-
-    This will either create or return an already-created parser for the
-    output of the command `drbd show`.
-
-    """
-    if cls._PARSE_SHOW is not None:
-      return cls._PARSE_SHOW
-
-    # pyparsing setup
-    lbrace = pyp.Literal("{").suppress()
-    rbrace = pyp.Literal("}").suppress()
-    lbracket = pyp.Literal("[").suppress()
-    rbracket = pyp.Literal("]").suppress()
-    semi = pyp.Literal(";").suppress()
-    colon = pyp.Literal(":").suppress()
-    # this also converts the value to an int
-    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
-
-    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
-    defa = pyp.Literal("_is_default").suppress()
-    dbl_quote = pyp.Literal('"').suppress()
-
-    keyword = pyp.Word(pyp.alphanums + "-")
-
-    # value types
-    value = pyp.Word(pyp.alphanums + "_-/.:")
-    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
-    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
-                 pyp.Word(pyp.nums + ".") + colon + number)
-    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
-                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
-                 pyp.Optional(rbracket) + colon + number)
-    # meta device, extended syntax
-    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
-    # device name, extended syntax
-    device_value = pyp.Literal("minor").suppress() + number
-
-    # a statement
-    stmt = (~rbrace + keyword + ~lbrace +
-            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
-                         device_value) +
-            pyp.Optional(defa) + semi +
-            pyp.Optional(pyp.restOfLine).suppress())
-
-    # an entire section
-    section_name = pyp.Word(pyp.alphas + "_")
-    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
-
-    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
-    bnf.ignore(comment)
-
-    cls._PARSE_SHOW = bnf
-
-    return bnf
-
-  @classmethod
-  def _GetShowData(cls, minor):
-    """Return the `drbdsetup show` data for a minor.
-
-    """
-    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
-    if result.failed:
-      logging.error("Can't display the drbd config: %s - %s",
-                    result.fail_reason, result.output)
-      return None
-    return result.stdout
-
-  @classmethod
-  def _GetDevInfo(cls, out):
-    """Parse details about a given DRBD minor.
-
-    This return, if available, the local backing device (as a path)
-    and the local and remote (ip, port) information from a string
-    containing the output of the `drbdsetup show` command as returned
-    by _GetShowData.
-
-    """
-    data = {}
-    if not out:
-      return data
-
-    bnf = cls._GetShowParser()
-    # run pyparse
-
-    try:
-      results = bnf.parseString(out)
-    except pyp.ParseException, err:
-      _ThrowError("Can't parse drbdsetup show output: %s", str(err))
-
-    # and massage the results into our desired format
-    for section in results:
-      sname = section[0]
-      if sname == "_this_host":
-        for lst in section[1:]:
-          if lst[0] == "disk":
-            data["local_dev"] = lst[1]
-          elif lst[0] == "meta-disk":
-            data["meta_dev"] = lst[1]
-            data["meta_index"] = lst[2]
-          elif lst[0] == "address":
-            data["local_addr"] = tuple(lst[1:])
-      elif sname == "_remote_host":
-        for lst in section[1:]:
-          if lst[0] == "address":
-            data["remote_addr"] = tuple(lst[1:])
-    return data
-
-  def _MatchesLocal(self, info):
-    """Test if our local config matches with an existing device.
-
-    The parameter should be as returned from `_GetDevInfo()`. This
-    method tests if our local backing device is the same as the one in
-    the info parameter, in effect testing if we look like the given
-    device.
-
-    """
-    if self._children:
-      backend, meta = self._children
-    else:
-      backend = meta = None
-
-    if backend is not None:
-      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
-    else:
-      retval = ("local_dev" not in info)
-
-    if meta is not None:
-      retval = retval and ("meta_dev" in info and
-                           info["meta_dev"] == meta.dev_path)
-      retval = retval and ("meta_index" in info and
-                           info["meta_index"] == 0)
-    else:
-      retval = retval and ("meta_dev" not in info and
-                           "meta_index" not in info)
-    return retval
-
-  def _MatchesNet(self, info):
-    """Test if our network config matches with an existing device.
-
-    The parameter should be as returned from `_GetDevInfo()`. This
-    method tests if our network configuration is the same as the one
-    in the info parameter, in effect testing if we look like the given
-    device.
-
-    """
-    if (((self._lhost is None and not ("local_addr" in info)) and
-         (self._rhost is None and not ("remote_addr" in info)))):
-      return True
-
-    if self._lhost is None:
-      return False
-
-    if not ("local_addr" in info and
-            "remote_addr" in info):
-      return False
-
-    retval = (info["local_addr"] == (self._lhost, self._lport))
-    retval = (retval and
-              info["remote_addr"] == (self._rhost, self._rport))
-    return retval
-
-  def _AssembleLocal(self, minor, backend, meta, size):
-    """Configure the local part of a DRBD device.
-
-    """
-    args = ["drbdsetup", self._DevPath(minor), "disk",
-            backend, meta, "0",
-            "-e", "detach",
-            "--create-device"]
-    if size:
-      args.extend(["-d", "%sm" % size])
-
-    version = self._GetVersion(self._GetProcData())
-    vmaj = version["k_major"]
-    vmin = version["k_minor"]
-    vrel = version["k_point"]
-
-    barrier_args = \
-      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
-                                   self.params[constants.LDP_BARRIERS],
-                                   self.params[constants.LDP_NO_META_FLUSH])
-    args.extend(barrier_args)
-
-    if self.params[constants.LDP_DISK_CUSTOM]:
-      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
-
-    result = utils.RunCmd(args)
-    if result.failed:
-      _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
-
-  @classmethod
-  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
-                              disable_meta_flush):
-    """Compute the DRBD command line parameters for disk barriers
-
-    Returns a list of the disk barrier parameters as requested via the
-    disabled_barriers and disable_meta_flush arguments, and according to the
-    supported ones in the DRBD version vmaj.vmin.vrel
-
-    If the desired option is unsupported, raises errors.BlockDeviceError.
-
-    """
-    disabled_barriers_set = frozenset(disabled_barriers)
-    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
-      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
-                                    " barriers" % disabled_barriers)
-
-    args = []
-
-    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
-    # does not exist)
-    if not vmaj == 8 and vmin in (0, 2, 3):
-      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
-                                    (vmaj, vmin, vrel))
-
-    def _AppendOrRaise(option, min_version):
-      """Helper for DRBD options"""
-      if min_version is not None and vrel >= min_version:
-        args.append(option)
-      else:
-        raise errors.BlockDeviceError("Could not use the option %s as the"
-                                      " DRBD version %d.%d.%d does not support"
-                                      " it." % (option, vmaj, vmin, vrel))
-
-    # the minimum version for each feature is encoded via pairs of (minor
-    # version -> x) where x is version in which support for the option was
-    # introduced.
-    meta_flush_supported = disk_flush_supported = {
-      0: 12,
-      2: 7,
-      3: 0,
-      }
-
-    disk_drain_supported = {
-      2: 7,
-      3: 0,
-      }
-
-    disk_barriers_supported = {
-      3: 0,
-      }
-
-    # meta flushes
-    if disable_meta_flush:
-      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
-                     meta_flush_supported.get(vmin, None))
-
-    # disk flushes
-    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
-      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
-                     disk_flush_supported.get(vmin, None))
-
-    # disk drain
-    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
-      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
-                     disk_drain_supported.get(vmin, None))
-
-    # disk barriers
-    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
-      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
-                     disk_barriers_supported.get(vmin, None))
-
-    return args
-
-  def _AssembleNet(self, minor, net_info, protocol,
-                   dual_pri=False, hmac=None, secret=None):
-    """Configure the network part of the device.
-
-    """
-    lhost, lport, rhost, rport = net_info
-    if None in net_info:
-      # we don't want network connection and actually want to make
-      # sure its shutdown
-      self._ShutdownNet(minor)
-      return
-
-    # Workaround for a race condition. When DRBD is doing its dance to
-    # establish a connection with its peer, it also sends the
-    # synchronization speed over the wire. In some cases setting the
-    # sync speed only after setting up both sides can race with DRBD
-    # connecting, hence we set it here before telling DRBD anything
-    # about its peer.
-    sync_errors = self._SetMinorSyncParams(minor, self.params)
-    if sync_errors:
-      _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
-                  (minor, utils.CommaJoin(sync_errors)))
-
-    if netutils.IP6Address.IsValid(lhost):
-      if not netutils.IP6Address.IsValid(rhost):
-        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
-                    (minor, lhost, rhost))
-      family = "ipv6"
-    elif netutils.IP4Address.IsValid(lhost):
-      if not netutils.IP4Address.IsValid(rhost):
-        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
-                    (minor, lhost, rhost))
-      family = "ipv4"
-    else:
-      _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
-
-    args = ["drbdsetup", self._DevPath(minor), "net",
-            "%s:%s:%s" % (family, lhost, lport),
-            "%s:%s:%s" % (family, rhost, rport), protocol,
-            "-A", "discard-zero-changes",
-            "-B", "consensus",
-            "--create-device",
-            ]
-    if dual_pri:
-      args.append("-m")
-    if hmac and secret:
-      args.extend(["-a", hmac, "-x", secret])
-
-    if self.params[constants.LDP_NET_CUSTOM]:
-      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
-
-    result = utils.RunCmd(args)
-    if result.failed:
-      _ThrowError("drbd%d: can't setup network: %s - %s",
-                  minor, result.fail_reason, result.output)
-
-    def _CheckNetworkConfig():
-      info = self._GetDevInfo(self._GetShowData(minor))
-      if not "local_addr" in info or not "remote_addr" in info:
-        raise utils.RetryAgain()
-
-      if (info["local_addr"] != (lhost, lport) or
-          info["remote_addr"] != (rhost, rport)):
-        raise utils.RetryAgain()
-
-    try:
-      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
-    except utils.RetryTimeout:
-      _ThrowError("drbd%d: timeout while configuring network", minor)
-
-  def AddChildren(self, devices):
-    """Add a disk to the DRBD device.
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
-                  self._aminor)
-    if len(devices) != 2:
-      _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
-    info = self._GetDevInfo(self._GetShowData(self.minor))
-    if "local_dev" in info:
-      _ThrowError("drbd%d: already attached to a local disk", self.minor)
-    backend, meta = devices
-    if backend.dev_path is None or meta.dev_path is None:
-      _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
-    backend.Open()
-    meta.Open()
-    self._CheckMetaSize(meta.dev_path)
-    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
-
-    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
-    self._children = devices
-
-  def RemoveChildren(self, devices):
-    """Detach the drbd device from local storage.
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
-                  self._aminor)
-    # early return if we don't actually have backing storage
-    info = self._GetDevInfo(self._GetShowData(self.minor))
-    if "local_dev" not in info:
-      return
-    if len(self._children) != 2:
-      _ThrowError("drbd%d: we don't have two children: %s", self.minor,
-                  self._children)
-    if self._children.count(None) == 2: # we don't actually have children :)
-      logging.warning("drbd%d: requested detach while detached", self.minor)
-      return
-    if len(devices) != 2:
-      _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
-    for child, dev in zip(self._children, devices):
-      if dev != child.dev_path:
-        _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
-                    " RemoveChildren", self.minor, dev, child.dev_path)
-
-    self._ShutdownLocal(self.minor)
-    self._children = []
-
-  @classmethod
-  def _SetMinorSyncParams(cls, minor, params):
-    """Set the parameters of the DRBD syncer.
-
-    This is the low-level implementation.
-
-    @type minor: int
-    @param minor: the drbd minor whose settings we change
-    @type params: dict
-    @param params: LD level disk parameters related to the synchronization
-    @rtype: list
-    @return: a list of error messages
-
-    """
-
-    args = ["drbdsetup", cls._DevPath(minor), "syncer"]
-    if params[constants.LDP_DYNAMIC_RESYNC]:
-      version = cls._GetVersion(cls._GetProcData())
-      vmin = version["k_minor"]
-      vrel = version["k_point"]
-
-      # By definition we are using 8.x, so just check the rest of the version
-      # number
-      if vmin != 3 or vrel < 9:
-        msg = ("The current DRBD version (8.%d.%d) does not support the "
-               "dynamic resync speed controller" % (vmin, vrel))
-        logging.error(msg)
-        return [msg]
-
-      if params[constants.LDP_PLAN_AHEAD] == 0:
-        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
-               " controller at DRBD level. If you want to disable it, please"
-               " set the dynamic-resync disk parameter to False.")
-        logging.error(msg)
-        return [msg]
-
-      # add the c-* parameters to args
-      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
-                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
-                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
-                   "--c-max-rate", params[constants.LDP_MAX_RATE],
-                   "--c-min-rate", params[constants.LDP_MIN_RATE],
-                   ])
-
-    else:
-      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
-
-    args.append("--create-device")
-    result = utils.RunCmd(args)
-    if result.failed:
-      msg = ("Can't change syncer rate: %s - %s" %
-             (result.fail_reason, result.output))
-      logging.error(msg)
-      return [msg]
-
-    return []
-
-  def SetSyncParams(self, params):
-    """Set the synchronization parameters of the DRBD syncer.
-
-    @type params: dict
-    @param params: LD level disk parameters related to the synchronization
-    @rtype: list
-    @return: a list of error messages, emitted both by the current node and by
-    children. An empty list means no errors
-
-    """
-    if self.minor is None:
-      err = "Not attached during SetSyncParams"
-      logging.info(err)
-      return [err]
-
-    children_result = super(DRBD8, self).SetSyncParams(params)
-    children_result.extend(self._SetMinorSyncParams(self.minor, params))
-    return children_result
-
-  def PauseResumeSync(self, pause):
-    """Pauses or resumes the sync of a DRBD device.
-
-    @param pause: Wether to pause or resume
-    @return: the success of the operation
-
-    """
-    if self.minor is None:
-      logging.info("Not attached during PauseSync")
-      return False
-
-    children_result = super(DRBD8, self).PauseResumeSync(pause)
-
-    if pause:
-      cmd = "pause-sync"
-    else:
-      cmd = "resume-sync"
-
-    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
-    if result.failed:
-      logging.error("Can't %s: %s - %s", cmd,
-                    result.fail_reason, result.output)
-    return not result.failed and children_result
-
-  def GetProcStatus(self):
-    """Return device data from /proc.
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
-    proc_info = self._MassageProcData(self._GetProcData())
-    if self.minor not in proc_info:
-      _ThrowError("drbd%d: can't find myself in /proc", self.minor)
-    return DRBD8Status(proc_info[self.minor])
-
-  def GetSyncStatus(self):
-    """Returns the sync status of the device.
-
-
-    If sync_percent is None, it means all is ok
-    If estimated_time is None, it means we can't estimate
-    the time needed, otherwise it's the time left in seconds.
-
-
-    We set the is_degraded parameter to True on two conditions:
-    network not connected or local disk missing.
-
-    We compute the ldisk parameter based on whether we have a local
-    disk or not.
-
-    @rtype: objects.BlockDevStatus
-
-    """
-    if self.minor is None and not self.Attach():
-      _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
-
-    stats = self.GetProcStatus()
-    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
-
-    if stats.is_disk_uptodate:
-      ldisk_status = constants.LDS_OKAY
-    elif stats.is_diskless:
-      ldisk_status = constants.LDS_FAULTY
-    else:
-      ldisk_status = constants.LDS_UNKNOWN
-
-    return objects.BlockDevStatus(dev_path=self.dev_path,
-                                  major=self.major,
-                                  minor=self.minor,
-                                  sync_percent=stats.sync_percent,
-                                  estimated_time=stats.est_time,
-                                  is_degraded=is_degraded,
-                                  ldisk_status=ldisk_status)
-
-  def Open(self, force=False):
-    """Make the local state primary.
-
-    If the 'force' parameter is given, the '-o' option is passed to
-    drbdsetup. Since this is a potentially dangerous operation, the
-    force flag should be only given after creation, when it actually
-    is mandatory.
-
-    """
-    if self.minor is None and not self.Attach():
-      logging.error("DRBD cannot attach to a device during open")
-      return False
-    cmd = ["drbdsetup", self.dev_path, "primary"]
-    if force:
-      cmd.append("-o")
-    result = utils.RunCmd(cmd)
-    if result.failed:
-      _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
-                  result.output)
-
-  def Close(self):
-    """Make the local state secondary.
-
-    This will, of course, fail if the device is in use.
-
-    """
-    if self.minor is None and not self.Attach():
-      _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
-    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
-    if result.failed:
-      _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
-                  self.minor, result.output)
-
-  def DisconnectNet(self):
-    """Removes network configuration.
-
-    This method shutdowns the network side of the device.
-
-    The method will wait up to a hardcoded timeout for the device to
-    go into standalone after the 'disconnect' command before
-    re-configuring it, as sometimes it takes a while for the
-    disconnect to actually propagate and thus we might issue a 'net'
-    command while the device is still connected. If the device will
-    still be attached to the network and we time out, we raise an
-    exception.
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
-
-    if None in (self._lhost, self._lport, self._rhost, self._rport):
-      _ThrowError("drbd%d: DRBD disk missing network info in"
-                  " DisconnectNet()", self.minor)
-
-    class _DisconnectStatus:
-      def __init__(self, ever_disconnected):
-        self.ever_disconnected = ever_disconnected
-
-    dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
-
-    def _WaitForDisconnect():
-      if self.GetProcStatus().is_standalone:
-        return
-
-      # retry the disconnect, it seems possible that due to a well-time
-      # disconnect on the peer, my disconnect command might be ignored and
-      # forgotten
-      dstatus.ever_disconnected = \
-        _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
-
-      raise utils.RetryAgain()
-
-    # Keep start time
-    start_time = time.time()
-
-    try:
-      # Start delay at 100 milliseconds and grow up to 2 seconds
-      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
-                  self._NET_RECONFIG_TIMEOUT)
-    except utils.RetryTimeout:
-      if dstatus.ever_disconnected:
-        msg = ("drbd%d: device did not react to the"
-               " 'disconnect' command in a timely manner")
-      else:
-        msg = "drbd%d: can't shutdown network, even after multiple retries"
-
-      _ThrowError(msg, self.minor)
-
-    reconfig_time = time.time() - start_time
-    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
-      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
-                   self.minor, reconfig_time)
-
-  def AttachNet(self, multimaster):
-    """Reconnects the network.
-
-    This method connects the network side of the device with a
-    specified multi-master flag. The device needs to be 'Standalone'
-    but have valid network configuration data.
-
-    Args:
-      - multimaster: init the network in dual-primary mode
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
-
-    if None in (self._lhost, self._lport, self._rhost, self._rport):
-      _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
-
-    status = self.GetProcStatus()
-
-    if not status.is_standalone:
-      _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
-
-    self._AssembleNet(self.minor,
-                      (self._lhost, self._lport, self._rhost, self._rport),
-                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
-                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
-
-  def Attach(self):
-    """Check if our minor is configured.
-
-    This doesn't do any device configurations - it only checks if the
-    minor is in a state different from Unconfigured.
-
-    Note that this function will not change the state of the system in
-    any way (except in case of side-effects caused by reading from
-    /proc).
-
-    """
-    used_devs = self.GetUsedDevs()
-    if self._aminor in used_devs:
-      minor = self._aminor
-    else:
-      minor = None
-
-    self._SetFromMinor(minor)
-    return minor is not None
-
-  def Assemble(self):
-    """Assemble the drbd.
-
-    Method:
-      - if we have a configured device, we try to ensure that it matches
-        our config
-      - if not, we create it from zero
-      - anyway, set the device parameters
-
-    """
-    super(DRBD8, self).Assemble()
-
-    self.Attach()
-    if self.minor is None:
-      # local device completely unconfigured
-      self._FastAssemble()
-    else:
-      # we have to recheck the local and network status and try to fix
-      # the device
-      self._SlowAssemble()
-
-    sync_errors = self.SetSyncParams(self.params)
-    if sync_errors:
-      _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
-                  (self.minor, utils.CommaJoin(sync_errors)))
-
-  def _SlowAssemble(self):
-    """Assembles the DRBD device from a (partially) configured device.
-
-    In case of partially attached (local device matches but no network
-    setup), we perform the network attach. If successful, we re-test
-    the attach if can return success.
-
-    """
-    # TODO: Rewrite to not use a for loop just because there is 'break'
-    # pylint: disable=W0631
-    net_data = (self._lhost, self._lport, self._rhost, self._rport)
-    for minor in (self._aminor,):
-      info = self._GetDevInfo(self._GetShowData(minor))
-      match_l = self._MatchesLocal(info)
-      match_r = self._MatchesNet(info)
-
-      if match_l and match_r:
-        # everything matches
-        break
-
-      if match_l and not match_r and "local_addr" not in info:
-        # disk matches, but not attached to network, attach and recheck
-        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
-                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
-        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
-          break
-        else:
-          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
-                      " show' disagrees", minor)
-
-      if match_r and "local_dev" not in info:
-        # no local disk, but network attached and it matches
-        self._AssembleLocal(minor, self._children[0].dev_path,
-                            self._children[1].dev_path, self.size)
-        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
-          break
-        else:
-          _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
-                      " show' disagrees", minor)
-
-      # this case must be considered only if we actually have local
-      # storage, i.e. not in diskless mode, because all diskless
-      # devices are equal from the point of view of local
-      # configuration
-      if (match_l and "local_dev" in info and
-          not match_r and "local_addr" in info):
-        # strange case - the device network part points to somewhere
-        # else, even though its local storage is ours; as we own the
-        # drbd space, we try to disconnect from the remote peer and
-        # reconnect to our correct one
-        try:
-          self._ShutdownNet(minor)
-        except errors.BlockDeviceError, err:
-          _ThrowError("drbd%d: device has correct local storage, wrong"
-                      " remote peer and is unable to disconnect in order"
-                      " to attach to the correct peer: %s", minor, str(err))
-        # note: _AssembleNet also handles the case when we don't want
-        # local storage (i.e. one or more of the _[lr](host|port) is
-        # None)
-        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
-                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
-        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
-          break
-        else:
-          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
-                      " show' disagrees", minor)
-
-    else:
-      minor = None
-
-    self._SetFromMinor(minor)
-    if minor is None:
-      _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
-                  self._aminor)
-
-  def _FastAssemble(self):
-    """Assemble the drbd device from zero.
-
-    This is run when in Assemble we detect our minor is unused.
-
-    """
-    minor = self._aminor
-    if self._children and self._children[0] and self._children[1]:
-      self._AssembleLocal(minor, self._children[0].dev_path,
-                          self._children[1].dev_path, self.size)
-    if self._lhost and self._lport and self._rhost and self._rport:
-      self._AssembleNet(minor,
-                        (self._lhost, self._lport, self._rhost, self._rport),
-                        constants.DRBD_NET_PROTOCOL,
-                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
-    self._SetFromMinor(minor)
-
-  @classmethod
-  def _ShutdownLocal(cls, minor):
-    """Detach from the local device.
-
-    I/Os will continue to be served from the remote device. If we
-    don't have a remote device, this operation will fail.
-
-    """
-    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
-    if result.failed:
-      _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
-
-  @classmethod
-  def _ShutdownNet(cls, minor):
-    """Disconnect from the remote peer.
-
-    This fails if we don't have a local device.
-
-    """
-    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
-    if result.failed:
-      _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
-
-  @classmethod
-  def _ShutdownAll(cls, minor):
-    """Deactivate the device.
-
-    This will, of course, fail if the device is in use.
-
-    """
-    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
-    if result.failed:
-      _ThrowError("drbd%d: can't shutdown drbd device: %s",
-                  minor, result.output)
-
-  def Shutdown(self):
-    """Shutdown the DRBD device.
-
-    """
-    if self.minor is None and not self.Attach():
-      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
-      return
-    minor = self.minor
-    self.minor = None
-    self.dev_path = None
-    self._ShutdownAll(minor)
-
-  def Remove(self):
-    """Stub remove for DRBD devices.
-
-    """
-    self.Shutdown()
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new DRBD8 device.
-
-    Since DRBD devices are not created per se, just assembled, this
-    function only initializes the metadata.
-
-    """
-    if len(children) != 2:
-      raise errors.ProgrammerError("Invalid setup for the drbd device")
-    if excl_stor:
-      raise errors.ProgrammerError("DRBD device requested with"
-                                   " exclusive_storage")
-    # check that the minor is unused
-    aminor = unique_id[4]
-    proc_info = cls._MassageProcData(cls._GetProcData())
-    if aminor in proc_info:
-      status = DRBD8Status(proc_info[aminor])
-      in_use = status.is_in_use
-    else:
-      in_use = False
-    if in_use:
-      _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
-    meta = children[1]
-    meta.Assemble()
-    if not meta.Attach():
-      _ThrowError("drbd%d: can't attach to meta device '%s'",
-                  aminor, meta)
-    cls._CheckMetaSize(meta.dev_path)
-    cls._InitMeta(aminor, meta.dev_path)
-    return cls(unique_id, children, size, params)
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Resize the DRBD device and its backing storage.
-
-    """
-    if self.minor is None:
-      _ThrowError("drbd%d: Grow called while not attached", self._aminor)
-    if len(self._children) != 2 or None in self._children:
-      _ThrowError("drbd%d: cannot grow diskless device", self.minor)
-    self._children[0].Grow(amount, dryrun, backingstore)
-    if dryrun or backingstore:
-      # DRBD does not support dry-run mode and is not backing storage,
-      # so we'll return here
-      return
-    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
-                           "%dm" % (self.size + amount)])
-    if result.failed:
-      _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
-
-
-class FileStorage(BlockDev):
-  """File device.
-
-  This class represents the a file storage backend device.
-
-  The unique_id for the file device is a (file_driver, file_path) tuple.
-
-  """
-  def __init__(self, unique_id, children, size, params):
-    """Initalizes a file device backend.
-
-    """
-    if children:
-      raise errors.BlockDeviceError("Invalid setup for file device")
-    super(FileStorage, self).__init__(unique_id, children, size, params)
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-    self.driver = unique_id[0]
-    self.dev_path = unique_id[1]
-
-    CheckFileStoragePath(self.dev_path)
-
-    self.Attach()
-
-  def Assemble(self):
-    """Assemble the device.
-
-    Checks whether the file device exists, raises BlockDeviceError otherwise.
-
-    """
-    if not os.path.exists(self.dev_path):
-      _ThrowError("File device '%s' does not exist" % self.dev_path)
-
-  def Shutdown(self):
-    """Shutdown the device.
-
-    This is a no-op for the file type, as we don't deactivate
-    the file on shutdown.
-
-    """
-    pass
-
-  def Open(self, force=False):
-    """Make the device ready for I/O.
-
-    This is a no-op for the file type.
-
-    """
-    pass
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    This is a no-op for the file type.
-
-    """
-    pass
-
-  def Remove(self):
-    """Remove the file backing the block device.
-
-    @rtype: boolean
-    @return: True if the removal was successful
-
-    """
-    try:
-      os.remove(self.dev_path)
-    except OSError, err:
-      if err.errno != errno.ENOENT:
-        _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
-
-  def Rename(self, new_id):
-    """Renames the file.
-
-    """
-    # TODO: implement rename for file-based storage
-    _ThrowError("Rename is not supported for file-based storage")
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the file
-
-    @param amount: the amount (in mebibytes) to grow with
-
-    """
-    if not backingstore:
-      return
-    # Check that the file exists
-    self.Assemble()
-    current_size = self.GetActualSize()
-    new_size = current_size + amount * 1024 * 1024
-    assert new_size > current_size, "Cannot Grow with a negative amount"
-    # We can't really simulate the growth
-    if dryrun:
-      return
-    try:
-      f = open(self.dev_path, "a+")
-      f.truncate(new_size)
-      f.close()
-    except EnvironmentError, err:
-      _ThrowError("Error in file growth: %", str(err))
-
-  def Attach(self):
-    """Attach to an existing file.
-
-    Check if this file already exists.
-
-    @rtype: boolean
-    @return: True if file exists
-
-    """
-    self.attached = os.path.exists(self.dev_path)
-    return self.attached
-
-  def GetActualSize(self):
-    """Return the actual disk size.
-
-    @note: the device needs to be active when this is called
-
-    """
-    assert self.attached, "BlockDevice not attached in GetActualSize()"
-    try:
-      st = os.stat(self.dev_path)
-      return st.st_size
-    except OSError, err:
-      _ThrowError("Can't stat %s: %s", self.dev_path, err)
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new file.
-
-    @param size: the size of file in MiB
-
-    @rtype: L{bdev.FileStorage}
-    @return: an instance of FileStorage
-
-    """
-    if excl_stor:
-      raise errors.ProgrammerError("FileStorage device requested with"
-                                   " exclusive_storage")
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-
-    dev_path = unique_id[1]
-
-    CheckFileStoragePath(dev_path)
-
-    try:
-      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
-      f = os.fdopen(fd, "w")
-      f.truncate(size * 1024 * 1024)
-      f.close()
-    except EnvironmentError, err:
-      if err.errno == errno.EEXIST:
-        _ThrowError("File already existing: %s", dev_path)
-      _ThrowError("Error in file creation: %", str(err))
-
-    return FileStorage(unique_id, children, size, params)
-
-
-class PersistentBlockDevice(BlockDev):
-  """A block device with persistent node
-
-  May be either directly attached, or exposed through DM (e.g. dm-multipath).
-  udev helpers are probably required to give persistent, human-friendly
-  names.
-
-  For the time being, pathnames are required to lie under /dev.
-
-  """
-  def __init__(self, unique_id, children, size, params):
-    """Attaches to a static block device.
-
-    The unique_id is a path under /dev.
-
-    """
-    super(PersistentBlockDevice, self).__init__(unique_id, children, size,
-                                                params)
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-    self.dev_path = unique_id[1]
-    if not os.path.realpath(self.dev_path).startswith("/dev/"):
-      raise ValueError("Full path '%s' lies outside /dev" %
-                              os.path.realpath(self.dev_path))
-    # TODO: this is just a safety guard checking that we only deal with devices
-    # we know how to handle. In the future this will be integrated with
-    # external storage backends and possible values will probably be collected
-    # from the cluster configuration.
-    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
-      raise ValueError("Got persistent block device of invalid type: %s" %
-                       unique_id[0])
-
-    self.major = self.minor = None
-    self.Attach()
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new device
-
-    This is a noop, we only return a PersistentBlockDevice instance
-
-    """
-    if excl_stor:
-      raise errors.ProgrammerError("Persistent block device requested with"
-                                   " exclusive_storage")
-    return PersistentBlockDevice(unique_id, children, 0, params)
-
-  def Remove(self):
-    """Remove a device
-
-    This is a noop
-
-    """
-    pass
-
-  def Rename(self, new_id):
-    """Rename this device.
-
-    """
-    _ThrowError("Rename is not supported for PersistentBlockDev storage")
-
-  def Attach(self):
-    """Attach to an existing block device.
-
-
-    """
-    self.attached = False
-    try:
-      st = os.stat(self.dev_path)
-    except OSError, err:
-      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
-      return False
-
-    if not stat.S_ISBLK(st.st_mode):
-      logging.error("%s is not a block device", self.dev_path)
-      return False
-
-    self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
-    self.attached = True
-
-    return True
-
-  def Assemble(self):
-    """Assemble the device.
-
-    """
-    pass
-
-  def Shutdown(self):
-    """Shutdown the device.
-
-    """
-    pass
-
-  def Open(self, force=False):
-    """Make the device ready for I/O.
-
-    """
-    pass
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    """
-    pass
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the logical volume.
-
-    """
-    _ThrowError("Grow is not supported for PersistentBlockDev storage")
-
-
-class RADOSBlockDevice(BlockDev):
-  """A RADOS Block Device (rbd).
-
-  This class implements the RADOS Block Device for the backend. You need
-  the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
-  this to be functional.
-
-  """
-  def __init__(self, unique_id, children, size, params):
-    """Attaches to an rbd device.
-
-    """
-    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-
-    self.driver, self.rbd_name = unique_id
-
-    self.major = self.minor = None
-    self.Attach()
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new rbd device.
-
-    Provision a new rbd volume inside a RADOS pool.
-
-    """
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise errors.ProgrammerError("Invalid configuration data %s" %
-                                   str(unique_id))
-    if excl_stor:
-      raise errors.ProgrammerError("RBD device requested with"
-                                   " exclusive_storage")
-    rbd_pool = params[constants.LDP_POOL]
-    rbd_name = unique_id[1]
-
-    # Provision a new rbd volume (Image) inside the RADOS cluster.
-    cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
-           rbd_name, "--size", "%s" % size]
-    result = utils.RunCmd(cmd)
-    if result.failed:
-      _ThrowError("rbd creation failed (%s): %s",
-                  result.fail_reason, result.output)
-
-    return RADOSBlockDevice(unique_id, children, size, params)
-
-  def Remove(self):
-    """Remove the rbd device.
-
-    """
-    rbd_pool = self.params[constants.LDP_POOL]
-    rbd_name = self.unique_id[1]
-
-    if not self.minor and not self.Attach():
-      # The rbd device doesn't exist.
-      return
-
-    # First shutdown the device (remove mappings).
-    self.Shutdown()
-
-    # Remove the actual Volume (Image) from the RADOS cluster.
-    cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
-    result = utils.RunCmd(cmd)
-    if result.failed:
-      _ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
-                  result.fail_reason, result.output)
-
-  def Rename(self, new_id):
-    """Rename this device.
-
-    """
-    pass
-
-  def Attach(self):
-    """Attach to an existing rbd device.
-
-    This method maps the rbd volume that matches our name with
-    an rbd device and then attaches to this device.
-
-    """
-    self.attached = False
-
-    # Map the rbd volume to a block device under /dev
-    self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
-
-    try:
-      st = os.stat(self.dev_path)
-    except OSError, err:
-      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
-      return False
-
-    if not stat.S_ISBLK(st.st_mode):
-      logging.error("%s is not a block device", self.dev_path)
-      return False
-
-    self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
-    self.attached = True
-
-    return True
-
-  def _MapVolumeToBlockdev(self, unique_id):
-    """Maps existing rbd volumes to block devices.
-
-    This method should be idempotent if the mapping already exists.
-
-    @rtype: string
-    @return: the block device path that corresponds to the volume
-
-    """
-    pool = self.params[constants.LDP_POOL]
-    name = unique_id[1]
-
-    # Check if the mapping already exists.
-    rbd_dev = self._VolumeToBlockdev(pool, name)
-    if rbd_dev:
-      # The mapping exists. Return it.
-      return rbd_dev
-
-    # The mapping doesn't exist. Create it.
-    map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
-    result = utils.RunCmd(map_cmd)
-    if result.failed:
-      _ThrowError("rbd map failed (%s): %s",
-                  result.fail_reason, result.output)
-
-    # Find the corresponding rbd device.
-    rbd_dev = self._VolumeToBlockdev(pool, name)
-    if not rbd_dev:
-      _ThrowError("rbd map succeeded, but could not find the rbd block"
-                  " device in output of showmapped, for volume: %s", name)
-
-    # The device was successfully mapped. Return it.
-    return rbd_dev
-
-  @classmethod
-  def _VolumeToBlockdev(cls, pool, volume_name):
-    """Do the 'volume name'-to-'rbd block device' resolving.
-
-    @type pool: string
-    @param pool: RADOS pool to use
-    @type volume_name: string
-    @param volume_name: the name of the volume whose device we search for
-    @rtype: string or None
-    @return: block device path if the volume is mapped, else None
-
-    """
-    try:
-      # Newer versions of the rbd tool support json output formatting. Use it
-      # if available.
-      showmap_cmd = [
-        constants.RBD_CMD,
-        "showmapped",
-        "-p",
-        pool,
-        "--format",
-        "json"
-        ]
-      result = utils.RunCmd(showmap_cmd)
-      if result.failed:
-        logging.error("rbd JSON output formatting returned error (%s): %s,"
-                      "falling back to plain output parsing",
-                      result.fail_reason, result.output)
-        raise RbdShowmappedJsonError
-
-      return cls._ParseRbdShowmappedJson(result.output, volume_name)
-    except RbdShowmappedJsonError:
-      # For older versions of rbd, we have to parse the plain / text output
-      # manually.
-      showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
-      result = utils.RunCmd(showmap_cmd)
-      if result.failed:
-        _ThrowError("rbd showmapped failed (%s): %s",
-                    result.fail_reason, result.output)
-
-      return cls._ParseRbdShowmappedPlain(result.output, volume_name)
-
-  @staticmethod
-  def _ParseRbdShowmappedJson(output, volume_name):
-    """Parse the json output of `rbd showmapped'.
-
-    This method parses the json output of `rbd showmapped' and returns the rbd
-    block device path (e.g. /dev/rbd0) that matches the given rbd volume.
-
-    @type output: string
-    @param output: the json output of `rbd showmapped'
-    @type volume_name: string
-    @param volume_name: the name of the volume whose device we search for
-    @rtype: string or None
-    @return: block device path if the volume is mapped, else None
-
-    """
-    try:
-      devices = serializer.LoadJson(output)
-    except ValueError, err:
-      _ThrowError("Unable to parse JSON data: %s" % err)
-
-    rbd_dev = None
-    for d in devices.values(): # pylint: disable=E1103
-      try:
-        name = d["name"]
-      except KeyError:
-        _ThrowError("'name' key missing from json object %s", devices)
-
-      if name == volume_name:
-        if rbd_dev is not None:
-          _ThrowError("rbd volume %s is mapped more than once", volume_name)
-
-        rbd_dev = d["device"]
-
-    return rbd_dev
-
-  @staticmethod
-  def _ParseRbdShowmappedPlain(output, volume_name):
-    """Parse the (plain / text) output of `rbd showmapped'.
-
-    This method parses the output of `rbd showmapped' and returns
-    the rbd block device path (e.g. /dev/rbd0) that matches the
-    given rbd volume.
-
-    @type output: string
-    @param output: the plain text output of `rbd showmapped'
-    @type volume_name: string
-    @param volume_name: the name of the volume whose device we search for
-    @rtype: string or None
-    @return: block device path if the volume is mapped, else None
-
-    """
-    allfields = 5
-    volumefield = 2
-    devicefield = 4
-
-    lines = output.splitlines()
-
-    # Try parsing the new output format (ceph >= 0.55).
-    splitted_lines = map(lambda l: l.split(), lines)
-
-    # Check for empty output.
-    if not splitted_lines:
-      return None
-
-    # Check showmapped output, to determine number of fields.
-    field_cnt = len(splitted_lines[0])
-    if field_cnt != allfields:
-      # Parsing the new format failed. Fallback to parsing the old output
-      # format (< 0.55).
-      splitted_lines = map(lambda l: l.split("\t"), lines)
-      if field_cnt != allfields:
-        _ThrowError("Cannot parse rbd showmapped output expected %s fields,"
-                    " found %s", allfields, field_cnt)
-
-    matched_lines = \
-      filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
-             splitted_lines)
-
-    if len(matched_lines) > 1:
-      _ThrowError("rbd volume %s mapped more than once", volume_name)
-
-    if matched_lines:
-      # rbd block device found. Return it.
-      rbd_dev = matched_lines[0][devicefield]
-      return rbd_dev
-
-    # The given volume is not mapped.
-    return None
-
-  def Assemble(self):
-    """Assemble the device.
-
-    """
-    pass
-
-  def Shutdown(self):
-    """Shutdown the device.
-
-    """
-    if not self.minor and not self.Attach():
-      # The rbd device doesn't exist.
-      return
-
-    # Unmap the block device from the Volume.
-    self._UnmapVolumeFromBlockdev(self.unique_id)
-
-    self.minor = None
-    self.dev_path = None
-
-  def _UnmapVolumeFromBlockdev(self, unique_id):
-    """Unmaps the rbd device from the Volume it is mapped.
-
-    Unmaps the rbd device from the Volume it was previously mapped to.
-    This method should be idempotent if the Volume isn't mapped.
-
-    """
-    pool = self.params[constants.LDP_POOL]
-    name = unique_id[1]
-
-    # Check if the mapping already exists.
-    rbd_dev = self._VolumeToBlockdev(pool, name)
-
-    if rbd_dev:
-      # The mapping exists. Unmap the rbd device.
-      unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
-      result = utils.RunCmd(unmap_cmd)
-      if result.failed:
-        _ThrowError("rbd unmap failed (%s): %s",
-                    result.fail_reason, result.output)
-
-  def Open(self, force=False):
-    """Make the device ready for I/O.
-
-    """
-    pass
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    """
-    pass
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the Volume.
-
-    @type amount: integer
-    @param amount: the amount (in mebibytes) to grow with
-    @type dryrun: boolean
-    @param dryrun: whether to execute the operation in simulation mode
-        only, without actually increasing the size
-
-    """
-    if not backingstore:
-      return
-    if not self.Attach():
-      _ThrowError("Can't attach to rbd device during Grow()")
-
-    if dryrun:
-      # the rbd tool does not support dry runs of resize operations.
-      # Since rbd volumes are thinly provisioned, we assume
-      # there is always enough free space for the operation.
-      return
-
-    rbd_pool = self.params[constants.LDP_POOL]
-    rbd_name = self.unique_id[1]
-    new_size = self.size + amount
-
-    # Resize the rbd volume (Image) inside the RADOS cluster.
-    cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
-           rbd_name, "--size", "%s" % new_size]
-    result = utils.RunCmd(cmd)
-    if result.failed:
-      _ThrowError("rbd resize failed (%s): %s",
-                  result.fail_reason, result.output)
-
-
-class ExtStorageDevice(BlockDev):
-  """A block device provided by an ExtStorage Provider.
-
-  This class implements the External Storage Interface, which means
-  handling of the externally provided block devices.
-
-  """
-  def __init__(self, unique_id, children, size, params):
-    """Attaches to an extstorage block device.
-
-    """
-    super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise ValueError("Invalid configuration data %s" % str(unique_id))
-
-    self.driver, self.vol_name = unique_id
-    self.ext_params = params
-
-    self.major = self.minor = None
-    self.Attach()
-
-  @classmethod
-  def Create(cls, unique_id, children, size, params, excl_stor):
-    """Create a new extstorage device.
-
-    Provision a new volume using an extstorage provider, which will
-    then be mapped to a block device.
-
-    """
-    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
-      raise errors.ProgrammerError("Invalid configuration data %s" %
-                                   str(unique_id))
-    if excl_stor:
-      raise errors.ProgrammerError("extstorage device requested with"
-                                   " exclusive_storage")
-
-    # Call the External Storage's create script,
-    # to provision a new Volume inside the External Storage
-    _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
-                      params, str(size))
-
-    return ExtStorageDevice(unique_id, children, size, params)
-
-  def Remove(self):
-    """Remove the extstorage device.
-
-    """
-    if not self.minor and not self.Attach():
-      # The extstorage device doesn't exist.
-      return
-
-    # First shutdown the device (remove mappings).
-    self.Shutdown()
-
-    # Call the External Storage's remove script,
-    # to remove the Volume from the External Storage
-    _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id,
-                      self.ext_params)
-
-  def Rename(self, new_id):
-    """Rename this device.
-
-    """
-    pass
-
-  def Attach(self):
-    """Attach to an existing extstorage device.
-
-    This method maps the extstorage volume that matches our name with
-    a corresponding block device and then attaches to this device.
-
-    """
-    self.attached = False
-
-    # Call the External Storage's attach script,
-    # to attach an existing Volume to a block device under /dev
-    self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
-                                      self.unique_id, self.ext_params)
-
-    try:
-      st = os.stat(self.dev_path)
-    except OSError, err:
-      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
-      return False
-
-    if not stat.S_ISBLK(st.st_mode):
-      logging.error("%s is not a block device", self.dev_path)
-      return False
-
-    self.major = os.major(st.st_rdev)
-    self.minor = os.minor(st.st_rdev)
-    self.attached = True
-
-    return True
-
-  def Assemble(self):
-    """Assemble the device.
-
-    """
-    pass
-
-  def Shutdown(self):
-    """Shutdown the device.
-
-    """
-    if not self.minor and not self.Attach():
-      # The extstorage device doesn't exist.
-      return
-
-    # Call the External Storage's detach script,
-    # to detach an existing Volume from it's block device under /dev
-    _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
-                      self.ext_params)
-
-    self.minor = None
-    self.dev_path = None
-
-  def Open(self, force=False):
-    """Make the device ready for I/O.
-
-    """
-    pass
-
-  def Close(self):
-    """Notifies that the device will no longer be used for I/O.
-
-    """
-    pass
-
-  def Grow(self, amount, dryrun, backingstore):
-    """Grow the Volume.
-
-    @type amount: integer
-    @param amount: the amount (in mebibytes) to grow with
-    @type dryrun: boolean
-    @param dryrun: whether to execute the operation in simulation mode
-        only, without actually increasing the size
-
-    """
-    if not backingstore:
-      return
-    if not self.Attach():
-      _ThrowError("Can't attach to extstorage device during Grow()")
-
-    if dryrun:
-      # we do not support dry runs of resize operations for now.
-      return
-
-    new_size = self.size + amount
-
-    # Call the External Storage's grow script,
-    # to grow an existing Volume inside the External Storage
-    _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
-                      self.ext_params, str(self.size), grow=str(new_size))
-
-  def SetInfo(self, text):
-    """Update metadata with info text.
-
-    """
-    # Replace invalid characters
-    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
-    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
-
-    # Only up to 128 characters are allowed
-    text = text[:128]
-
-    # Call the External Storage's setinfo script,
-    # to set metadata for an existing Volume inside the External Storage
-    _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id,
-                      self.ext_params, metadata=text)
-
-
-def _ExtStorageAction(action, unique_id, ext_params,
-                      size=None, grow=None, metadata=None):
-  """Take an External Storage action.
-
-  Take an External Storage action concerning or affecting
-  a specific Volume inside the External Storage.
-
-  @type action: string
-  @param action: which action to perform. One of:
-                 create / remove / grow / attach / detach
-  @type unique_id: tuple (driver, vol_name)
-  @param unique_id: a tuple containing the type of ExtStorage (driver)
-                    and the Volume name
-  @type ext_params: dict
-  @param ext_params: ExtStorage parameters
-  @type size: integer
-  @param size: the size of the Volume in mebibytes
-  @type grow: integer
-  @param grow: the new size in mebibytes (after grow)
-  @type metadata: string
-  @param metadata: metadata info of the Volume, for use by the provider
-  @rtype: None or a block device path (during attach)
-
-  """
-  driver, vol_name = unique_id
-
-  # Create an External Storage instance of type `driver'
-  status, inst_es = ExtStorageFromDisk(driver)
-  if not status:
-    _ThrowError("%s" % inst_es)
-
-  # Create the basic environment for the driver's scripts
-  create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
-                                      grow, metadata)
-
-  # Do not use log file for action `attach' as we need
-  # to get the output from RunResult
-  # TODO: find a way to have a log file for attach too
-  logfile = None
-  if action is not constants.ES_ACTION_ATTACH:
-    logfile = _VolumeLogName(action, driver, vol_name)
-
-  # Make sure the given action results in a valid script
-  if action not in constants.ES_SCRIPTS:
-    _ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
-                action)
-
-  # Find out which external script to run according the given action
-  script_name = action + "_script"
-  script = getattr(inst_es, script_name)
-
-  # Run the external script
-  result = utils.RunCmd([script], env=create_env,
-                        cwd=inst_es.path, output=logfile,)
-  if result.failed:
-    logging.error("External storage's %s command '%s' returned"
-                  " error: %s, logfile: %s, output: %s",
-                  action, result.cmd, result.fail_reason,
-                  logfile, result.output)
-
-    # If logfile is 'None' (during attach), it breaks TailFile
-    # TODO: have a log file for attach too
-    if action is not constants.ES_ACTION_ATTACH:
-      lines = [utils.SafeEncode(val)
-               for val in utils.TailFile(logfile, lines=20)]
-    else:
-      lines = result.output[-20:]
-
-    _ThrowError("External storage's %s script failed (%s), last"
-                " lines of output:\n%s",
-                action, result.fail_reason, "\n".join(lines))
-
-  if action == constants.ES_ACTION_ATTACH:
-    return result.stdout
-
-
-def ExtStorageFromDisk(name, base_dir=None):
-  """Create an ExtStorage instance from disk.
-
-  This function will return an ExtStorage instance
-  if the given name is a valid ExtStorage name.
-
-  @type base_dir: string
-  @keyword base_dir: Base directory containing ExtStorage installations.
-                     Defaults to a search in all the ES_SEARCH_PATH dirs.
-  @rtype: tuple
-  @return: True and the ExtStorage instance if we find a valid one, or
-      False and the diagnose message on error
-
-  """
-  if base_dir is None:
-    es_base_dir = pathutils.ES_SEARCH_PATH
-  else:
-    es_base_dir = [base_dir]
-
-  es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
-
-  if es_dir is None:
-    return False, ("Directory for External Storage Provider %s not"
-                   " found in search path" % name)
-
-  # ES Files dictionary, we will populate it with the absolute path
-  # names; if the value is True, then it is a required file, otherwise
-  # an optional one
-  es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
-
-  es_files[constants.ES_PARAMETERS_FILE] = True
-
-  for (filename, _) in es_files.items():
-    es_files[filename] = utils.PathJoin(es_dir, filename)
-
-    try:
-      st = os.stat(es_files[filename])
-    except EnvironmentError, err:
-      return False, ("File '%s' under path '%s' is missing (%s)" %
-                     (filename, es_dir, utils.ErrnoOrStr(err)))
-
-    if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
-      return False, ("File '%s' under path '%s' is not a regular file" %
-                     (filename, es_dir))
-
-    if filename in constants.ES_SCRIPTS:
-      if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
-        return False, ("File '%s' under path '%s' is not executable" %
-                       (filename, es_dir))
-
-  parameters = []
-  if constants.ES_PARAMETERS_FILE in es_files:
-    parameters_file = es_files[constants.ES_PARAMETERS_FILE]
-    try:
-      parameters = utils.ReadFile(parameters_file).splitlines()
-    except EnvironmentError, err:
-      return False, ("Error while reading the EXT parameters file at %s: %s" %
-                     (parameters_file, utils.ErrnoOrStr(err)))
-    parameters = [v.split(None, 1) for v in parameters]
-
-  es_obj = \
-    objects.ExtStorage(name=name, path=es_dir,
-                       create_script=es_files[constants.ES_SCRIPT_CREATE],
-                       remove_script=es_files[constants.ES_SCRIPT_REMOVE],
-                       grow_script=es_files[constants.ES_SCRIPT_GROW],
-                       attach_script=es_files[constants.ES_SCRIPT_ATTACH],
-                       detach_script=es_files[constants.ES_SCRIPT_DETACH],
-                       setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
-                       verify_script=es_files[constants.ES_SCRIPT_VERIFY],
-                       supported_parameters=parameters)
-  return True, es_obj
-
-
-def _ExtStorageEnvironment(unique_id, ext_params,
-                           size=None, grow=None, metadata=None):
-  """Calculate the environment for an External Storage script.
-
-  @type unique_id: tuple (driver, vol_name)
-  @param unique_id: ExtStorage pool and name of the Volume
-  @type ext_params: dict
-  @param ext_params: the EXT parameters
-  @type size: string
-  @param size: size of the Volume (in mebibytes)
-  @type grow: string
-  @param grow: new size of Volume after grow (in mebibytes)
-  @type metadata: string
-  @param metadata: metadata info of the Volume
-  @rtype: dict
-  @return: dict of environment variables
-
-  """
-  vol_name = unique_id[1]
-
-  result = {}
-  result["VOL_NAME"] = vol_name
-
-  # EXT params
-  for pname, pvalue in ext_params.items():
-    result["EXTP_%s" % pname.upper()] = str(pvalue)
-
-  if size is not None:
-    result["VOL_SIZE"] = size
-
-  if grow is not None:
-    result["VOL_NEW_SIZE"] = grow
-
-  if metadata is not None:
-    result["VOL_METADATA"] = metadata
-
-  return result
-
-
-def _VolumeLogName(kind, es_name, volume):
-  """Compute the ExtStorage log filename for a given Volume and operation.
-
-  @type kind: string
-  @param kind: the operation type (e.g. create, remove etc.)
-  @type es_name: string
-  @param es_name: the ExtStorage name
-  @type volume: string
-  @param volume: the name of the Volume inside the External Storage
-
-  """
-  # Check if the extstorage log dir is a valid dir
-  if not os.path.isdir(pathutils.LOG_ES_DIR):
-    _ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
-
-  # TODO: Use tempfile.mkstemp to create unique filename
-  base = ("%s-%s-%s-%s.log" %
-          (kind, es_name, volume, utils.TimestampForFilename()))
-  return utils.PathJoin(pathutils.LOG_ES_DIR, base)
-
-
-DEV_MAP = {
-  constants.LD_LV: LogicalVolume,
-  constants.LD_DRBD8: DRBD8,
-  constants.LD_BLOCKDEV: PersistentBlockDevice,
-  constants.LD_RBD: RADOSBlockDevice,
-  constants.LD_EXT: ExtStorageDevice,
-  }
-
-if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
-  DEV_MAP[constants.LD_FILE] = FileStorage
-
-
-def _VerifyDiskType(dev_type):
-  if dev_type not in DEV_MAP:
-    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
-
-
-def _VerifyDiskParams(disk):
-  """Verifies if all disk parameters are set.
-
-  """
-  missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
-  if missing:
-    raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
-                                 missing)
-
-
-def FindDevice(disk, children):
-  """Search for an existing, assembled device.
-
-  This will succeed only if the device exists and is assembled, but it
-  does not do any actions in order to activate the device.
-
-  @type disk: L{objects.Disk}
-  @param disk: the disk object to find
-  @type children: list of L{bdev.BlockDev}
-  @param children: the list of block devices that are children of the device
-                  represented by the disk parameter
-
-  """
-  _VerifyDiskType(disk.dev_type)
-  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
-                                  disk.params)
-  if not device.attached:
-    return None
-  return device
-
-
-def Assemble(disk, children):
-  """Try to attach or assemble an existing device.
-
-  This will attach to assemble the device, as needed, to bring it
-  fully up. It must be safe to run on already-assembled devices.
-
-  @type disk: L{objects.Disk}
-  @param disk: the disk object to assemble
-  @type children: list of L{bdev.BlockDev}
-  @param children: the list of block devices that are children of the device
-                  represented by the disk parameter
-
-  """
-  _VerifyDiskType(disk.dev_type)
-  _VerifyDiskParams(disk)
-  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
-                                  disk.params)
-  device.Assemble()
-  return device
-
-
-def Create(disk, children, excl_stor):
-  """Create a device.
-
-  @type disk: L{objects.Disk}
-  @param disk: the disk object to create
-  @type children: list of L{bdev.BlockDev}
-  @param children: the list of block devices that are children of the device
-                  represented by the disk parameter
-  @type excl_stor: boolean
-  @param excl_stor: Whether exclusive_storage is active
-
-  """
-  _VerifyDiskType(disk.dev_type)
-  _VerifyDiskParams(disk)
-  device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
-                                         disk.params, excl_stor)
-  return device
index 3cd5523..2052f28 100644 (file)
@@ -40,7 +40,7 @@ from ganeti import objects
 from ganeti import ssconf
 from ganeti import serializer
 from ganeti import hypervisor
-from ganeti import bdev
+from ganeti.storage import drbd
 from ganeti import netutils
 from ganeti import luxi
 from ganeti import jstore
@@ -461,18 +461,16 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   else:
     master_netmask = ipcls.iplen
 
-  if vg_name is not None:
+  if vg_name:
     # Check if volume group is valid
     vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
                                           constants.MIN_VG_SIZE)
     if vgstatus:
-      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
-                                 " you are not using lvm" % vgstatus,
-                                 errors.ECODE_INVAL)
+      raise errors.OpPrereqError("Error: %s" % vgstatus, errors.ECODE_INVAL)
 
   if drbd_helper is not None:
     try:
-      curr_helper = bdev.BaseDRBD.GetUsermodeHelper()
+      curr_helper = drbd.DRBD8.GetUsermodeHelper()
     except errors.BlockDeviceError, err:
       raise errors.OpPrereqError("Error while checking drbd helper"
                                  " (specify --no-drbd-storage if you are not"
@@ -611,7 +609,6 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
     mac_prefix=mac_prefix,
     volume_group_name=vg_name,
     tcpudp_port_pool=set(),
-    master_node=hostname.name,
     master_ip=clustername.ip,
     master_netmask=master_netmask,
     master_netdev=master_netdev,
@@ -690,13 +687,14 @@ def InitConfig(version, cluster_config, master_node_config,
                                                 _INITCONF_ECID)
   master_node_config.uuid = uuid_generator.Generate([], utils.NewUUID,
                                                     _INITCONF_ECID)
+  cluster_config.master_node = master_node_config.uuid
   nodes = {
-    master_node_config.name: master_node_config,
+    master_node_config.uuid: master_node_config,
     }
   default_nodegroup = objects.NodeGroup(
     uuid=uuid_generator.Generate([], utils.NewUUID, _INITCONF_ECID),
     name=constants.INITIAL_NODE_GROUP_NAME,
-    members=[master_node_config.name],
+    members=[master_node_config.uuid],
     diskparams={},
     )
   nodegroups = {
@@ -716,7 +714,7 @@ def InitConfig(version, cluster_config, master_node_config,
                   mode=0600)
 
 
-def FinalizeClusterDestroy(master):
+def FinalizeClusterDestroy(master_uuid):
   """Execute the last steps of cluster destroy
 
   This function shuts down all the daemons, completing the destroy
@@ -727,22 +725,24 @@ def FinalizeClusterDestroy(master):
   modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
   runner = rpc.BootstrapRunner()
 
+  master_name = cfg.GetNodeName(master_uuid)
+
   master_params = cfg.GetMasterNetworkParameters()
-  master_params.name = master
+  master_params.uuid = master_uuid
   ems = cfg.GetUseExternalMipScript()
-  result = runner.call_node_deactivate_master_ip(master_params.name,
-                                                 master_params, ems)
+  result = runner.call_node_deactivate_master_ip(master_name, master_params,
+                                                 ems)
 
   msg = result.fail_msg
   if msg:
     logging.warning("Could not disable the master IP: %s", msg)
 
-  result = runner.call_node_stop_master(master)
+  result = runner.call_node_stop_master(master_name)
   msg = result.fail_msg
   if msg:
     logging.warning("Could not disable the master role: %s", msg)
 
-  result = runner.call_node_leave_cluster(master, modify_ssh_setup)
+  result = runner.call_node_leave_cluster(master_name, modify_ssh_setup)
   msg = result.fail_msg
   if msg:
     logging.warning("Could not shutdown the node daemon and cleanup"
@@ -790,7 +790,7 @@ def MasterFailover(no_voting=False):
   sstore = ssconf.SimpleStore()
 
   old_master, new_master = ssconf.GetMasterAndMyself(sstore)
-  node_list = sstore.GetNodeList()
+  node_names = sstore.GetNodeList()
   mc_list = sstore.GetMasterCandidates()
 
   if old_master == new_master:
@@ -809,7 +809,7 @@ def MasterFailover(no_voting=False):
                                errors.ECODE_STATE)
 
   if not no_voting:
-    vote_list = GatherMasterVotes(node_list)
+    vote_list = GatherMasterVotes(node_names)
 
     if vote_list:
       voted_master = vote_list[0][0]
@@ -834,8 +834,20 @@ def MasterFailover(no_voting=False):
     # configuration data
     cfg = config.ConfigWriter(accept_foreign=True)
 
+    old_master_node = cfg.GetNodeInfoByName(old_master)
+    if old_master_node is None:
+      raise errors.OpPrereqError("Could not find old master node '%s' in"
+                                 " cluster configuration." % old_master,
+                                 errors.ECODE_NOENT)
+
     cluster_info = cfg.GetClusterInfo()
-    cluster_info.master_node = new_master
+    new_master_node = cfg.GetNodeInfoByName(new_master)
+    if new_master_node is None:
+      raise errors.OpPrereqError("Could not find new master node '%s' in"
+                                 " cluster configuration." % new_master,
+                                 errors.ECODE_NOENT)
+
+    cluster_info.master_node = new_master_node.uuid
     # this will also regenerate the ssconf files, since we updated the
     # cluster info
     cfg.Update(cluster_info, logging.error)
@@ -853,9 +865,9 @@ def MasterFailover(no_voting=False):
 
   runner = rpc.BootstrapRunner()
   master_params = cfg.GetMasterNetworkParameters()
-  master_params.name = old_master
+  master_params.uuid = old_master_node.uuid
   ems = cfg.GetUseExternalMipScript()
-  result = runner.call_node_deactivate_master_ip(master_params.name,
+  result = runner.call_node_deactivate_master_ip(old_master,
                                                  master_params, ems)
 
   msg = result.fail_msg
@@ -919,7 +931,7 @@ def GetMaster():
   return old_master
 
 
-def GatherMasterVotes(node_list):
+def GatherMasterVotes(node_names):
   """Check the agreement on who is the master.
 
   This function will return a list of (node, number of votes), ordered
@@ -933,8 +945,8 @@ def GatherMasterVotes(node_list):
   since we use the same source for configuration information for both
   backend and boostrap, we'll always vote for ourselves.
 
-  @type node_list: list
-  @param node_list: the list of nodes to query for master info; the current
+  @type node_names: list
+  @param node_names: the list of nodes to query for master info; the current
       node will be removed if it is in the list
   @rtype: list
   @return: list of (node, votes)
@@ -942,30 +954,31 @@ def GatherMasterVotes(node_list):
   """
   myself = netutils.Hostname.GetSysName()
   try:
-    node_list.remove(myself)
+    node_names.remove(myself)
   except ValueError:
     pass
-  if not node_list:
+  if not node_names:
     # no nodes left (eventually after removing myself)
     return []
-  results = rpc.BootstrapRunner().call_master_info(node_list)
+  results = rpc.BootstrapRunner().call_master_info(node_names)
   if not isinstance(results, dict):
     # this should not happen (unless internal error in rpc)
     logging.critical("Can't complete rpc call, aborting master startup")
-    return [(None, len(node_list))]
+    return [(None, len(node_names))]
   votes = {}
-  for node in results:
-    nres = results[node]
+  for node_name in results:
+    nres = results[node_name]
     data = nres.payload
     msg = nres.fail_msg
     fail = False
     if msg:
-      logging.warning("Error contacting node %s: %s", node, msg)
+      logging.warning("Error contacting node %s: %s", node_name, msg)
       fail = True
     # for now we accept both length 3, 4 and 5 (data[3] is primary ip version
     # and data[4] is the master netmask)
     elif not isinstance(data, (tuple, list)) or len(data) < 3:
-      logging.warning("Invalid data received from node %s: %s", node, data)
+      logging.warning("Invalid data received from node %s: %s",
+                      node_name, data)
       fail = True
     if fail:
       if None not in votes:
index 32ef069..2e436b6 100644 (file)
@@ -165,6 +165,7 @@ __all__ = [
   "PREALLOC_WIPE_DISKS_OPT",
   "PRIMARY_IP_VERSION_OPT",
   "PRIMARY_ONLY_OPT",
+  "PRINT_JOBID_OPT",
   "PRIORITY_OPT",
   "RAPI_CERT_OPT",
   "READD_OPT",
@@ -198,6 +199,7 @@ __all__ = [
   "SRC_DIR_OPT",
   "SRC_NODE_OPT",
   "SUBMIT_OPT",
+  "SUBMIT_OPTS",
   "STARTUP_PAUSED_OPT",
   "STATIC_OPT",
   "SYNC_OPT",
@@ -832,6 +834,11 @@ SUBMIT_OPT = cli_option("--submit", dest="submit_only",
                         help=("Submit the job and return the job ID, but"
                               " don't wait for the job to finish"))
 
+PRINT_JOBID_OPT = cli_option("--print-jobid", dest="print_jobid",
+                             default=False, action="store_true",
+                             help=("Additionally print the job as first line"
+                                   " on stdout (for scripting)."))
+
 SYNC_OPT = cli_option("--sync", dest="do_locking",
                       default=False, action="store_true",
                       help=("Grab locks while doing the queries"
@@ -1634,6 +1641,13 @@ INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
+# options related to asynchronous job handling
+
+SUBMIT_OPTS = [
+  SUBMIT_OPT,
+  PRINT_JOBID_OPT,
+  ]
+
 # common options for creating instances. add and import then add their own
 # specific ones.
 COMMON_CREATE_OPTS = [
@@ -1654,6 +1668,7 @@ COMMON_CREATE_OPTS = [
   OSPARAMS_OPT,
   OS_SIZE_OPT,
   SUBMIT_OPT,
+  PRINT_JOBID_OPT,
   TAG_ADD_OPT,
   DRY_RUN_OPT,
   PRIORITY_OPT,
@@ -2248,6 +2263,8 @@ def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
   SetGenericOpcodeOpts([op], opts)
 
   job_id = SendJob([op], cl=cl)
+  if hasattr(opts, "print_jobid") and opts.print_jobid:
+    ToStdout("%d" % job_id)
 
   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
                        reporter=reporter)
@@ -2271,6 +2288,8 @@ def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
     job = [op]
     SetGenericOpcodeOpts(job, opts)
     job_id = SendJob(job, cl=cl)
+    if opts.print_jobid:
+      ToStdout("%d" % job_id)
     raise JobSubmittedException(job_id)
   else:
     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
@@ -2647,6 +2666,9 @@ def GenericInstanceCreate(mode, opts, args):
           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
                                      (didx, err), errors.ECODE_INVAL)
       elif constants.IDISK_ADOPT in ddict:
+        if constants.IDISK_SPINDLES in ddict:
+          raise errors.OpPrereqError("spindles is not a valid option when"
+                                     " adopting a disk", errors.ECODE_INVAL)
         if mode == constants.INSTANCE_IMPORT:
           raise errors.OpPrereqError("Disk adoption not allowed for instance"
                                      " import", errors.ECODE_INVAL)
index 4a18593..e652937 100644 (file)
@@ -150,7 +150,7 @@ commands = {
     ExportInstance, ARGS_ONE_INSTANCE,
     [FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT,
      REMOVE_INSTANCE_OPT, IGNORE_REMOVE_FAILURES_OPT, DRY_RUN_OPT,
-     PRIORITY_OPT, SUBMIT_OPT],
+     PRIORITY_OPT] + SUBMIT_OPTS,
     "-n <target_node> [opts...] <name>",
     "Exports an instance to an image"),
   "import": (
@@ -159,7 +159,7 @@ commands = {
     "Imports an instance from an exported image"),
   "remove": (
     RemoveExport, [ArgUnknown(min=1, max=1)],
-    [DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<name>", "Remove exports of named instance from the filesystem."),
   }
 
index 287ae31..bd61469 100644 (file)
@@ -63,6 +63,19 @@ _EPO_PING_TIMEOUT = 1 # 1 second
 _EPO_REACHABLE_TIMEOUT = 15 * 60 # 15 minutes
 
 
+def _CheckNoLvmStorageOptDeprecated(opts):
+  """Checks if the legacy option '--no-lvm-storage' is used.
+
+  """
+  if not opts.lvm_storage:
+    ToStderr("The option --no-lvm-storage is no longer supported. If you want"
+             " to disable lvm-based storage cluster-wide, use the option"
+             " --enabled-disk-templates to disable all of these lvm-base disk "
+             "  templates: %s" %
+             utils.CommaJoin(utils.GetLvmDiskTemplates()))
+    return 1
+
+
 @UsesRPC
 def InitCluster(opts, args):
   """Initialize the cluster.
@@ -75,13 +88,28 @@ def InitCluster(opts, args):
   @return: the desired exit code
 
   """
-  if not opts.lvm_storage and opts.vg_name:
-    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
+  if _CheckNoLvmStorageOptDeprecated(opts):
     return 1
-
-  vg_name = opts.vg_name
-  if opts.lvm_storage and not opts.vg_name:
-    vg_name = constants.DEFAULT_VG
+  enabled_disk_templates = opts.enabled_disk_templates
+  if enabled_disk_templates:
+    enabled_disk_templates = enabled_disk_templates.split(",")
+  else:
+    enabled_disk_templates = constants.DEFAULT_ENABLED_DISK_TEMPLATES
+
+  vg_name = None
+  if opts.vg_name is not None:
+    vg_name = opts.vg_name
+    if vg_name:
+      if not utils.IsLvmEnabled(enabled_disk_templates):
+        ToStdout("You specified a volume group with --vg-name, but you did not"
+                 " enable any disk template that uses lvm.")
+    else:
+      if utils.IsLvmEnabled(enabled_disk_templates):
+        ToStderr("LVM disk templates are enabled, but vg name not set.")
+        return 1
+  else:
+    if utils.IsLvmEnabled(enabled_disk_templates):
+      vg_name = constants.DEFAULT_VG
 
   if not opts.drbd_storage and opts.drbd_helper:
     ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
@@ -196,12 +224,6 @@ 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,
@@ -253,10 +275,10 @@ def DestroyCluster(opts, args):
     return 1
 
   op = opcodes.OpClusterDestroy()
-  master = SubmitOpCode(op, opts=opts)
+  master_uuid = SubmitOpCode(op, opts=opts)
   # if we reached this, the opcode didn't fail; we can proceed to
   # shutdown all the daemons
-  bootstrap.FinalizeClusterDestroy(master)
+  bootstrap.FinalizeClusterDestroy(master_uuid)
   return 0
 
 
@@ -951,8 +973,7 @@ def SetClusterParams(opts, args):
   @return: the desired exit code
 
   """
-  if not (not opts.lvm_storage or opts.vg_name or
-          not opts.drbd_storage or opts.drbd_helper or
+  if not (opts.vg_name is not None or opts.drbd_helper or
           opts.enabled_hypervisors or opts.hvparams or
           opts.beparams or opts.nicparams or
           opts.ndparams or opts.diskparams or
@@ -978,13 +999,22 @@ def SetClusterParams(opts, args):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
-  vg_name = opts.vg_name
-  if not opts.lvm_storage and opts.vg_name:
-    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
+  if _CheckNoLvmStorageOptDeprecated(opts):
     return 1
 
-  if not opts.lvm_storage:
-    vg_name = ""
+  enabled_disk_templates = None
+  if opts.enabled_disk_templates:
+    enabled_disk_templates = opts.enabled_disk_templates.split(",")
+
+  # consistency between vg name and enabled disk templates
+  vg_name = None
+  if opts.vg_name is not None:
+    vg_name = opts.vg_name
+  if enabled_disk_templates:
+    if vg_name and not utils.IsLvmEnabled(enabled_disk_templates):
+      ToStdout("You specified a volume group with --vg-name, but you did not"
+               " enable any of the following lvm-based disk templates: %s" %
+               utils.CommaJoin(utils.GetLvmDiskTemplates()))
 
   drbd_helper = opts.drbd_helper
   if not opts.drbd_storage and opts.drbd_helper:
@@ -998,10 +1028,6 @@ 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():
@@ -1528,7 +1554,7 @@ commands = {
     "<new_name>",
     "Renames the cluster"),
   "redist-conf": (
-    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    RedistributeConfig, ARGS_NONE, SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT],
     "", "Forces a push of the configuration file and ssconf files"
     " to the nodes in the cluster"),
   "verify": (
@@ -1568,10 +1594,10 @@ commands = {
   "list-tags": (
     ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
   "add-tags": (
-    AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "tag...", "Add tags to the cluster"),
   "remove-tags": (
-    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "tag...", "Remove tags from the cluster"),
   "search-tags": (
     SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
@@ -1596,8 +1622,8 @@ commands = {
      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, ENABLED_DISK_TEMPLATES_OPT,
-     IPOLICY_STD_SPECS_OPT] + INSTANCE_POLICY_OPTS,
+     DISK_STATE_OPT] + SUBMIT_OPTS +
+    [ENABLED_DISK_TEMPLATES_OPT, IPOLICY_STD_SPECS_OPT] + INSTANCE_POLICY_OPTS,
     "[opts...]",
     "Alters the parameters of the cluster"),
   "renew-crypto": (
index 829ed05..ba31adc 100644 (file)
@@ -631,8 +631,7 @@ commands = {
                 action="append", help="Select nodes to sleep on"),
      cli_option("-r", "--repeat", type="int", default="0", dest="repeat",
                 help="Number of times to repeat the sleep"),
-     DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT,
-     ],
+     DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "[opts...] <duration>", "Executes a TestDelay OpCode"),
   "submit-job": (
     GenericOpCodes, [ArgFile(min=1)],
index 5bef440..6cf5b6a 100644 (file)
@@ -323,12 +323,12 @@ commands = {
   "add": (
     AddGroup, ARGS_ONE_GROUP,
     [DRY_RUN_OPT, ALLOC_POLICY_OPT, NODE_PARAMS_OPT, DISK_PARAMS_OPT,
-     HV_STATE_OPT, DISK_STATE_OPT, PRIORITY_OPT,
-     SUBMIT_OPT] + INSTANCE_POLICY_OPTS,
+     HV_STATE_OPT, DISK_STATE_OPT, PRIORITY_OPT]
+    + SUBMIT_OPTS + INSTANCE_POLICY_OPTS,
     "<group_name>", "Add a new node group to the cluster"),
   "assign-nodes": (
     AssignNodes, ARGS_ONE_GROUP + ARGS_MANY_NODES,
-    [DRY_RUN_OPT, FORCE_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [DRY_RUN_OPT, FORCE_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<group_name> <node>...", "Assign nodes to a group"),
   "list": (
     ListGroups, ARGS_MANY_GROUPS,
@@ -342,20 +342,21 @@ commands = {
     "Lists all available fields for node groups"),
   "modify": (
     SetGroupParams, ARGS_ONE_GROUP,
-    [DRY_RUN_OPT, SUBMIT_OPT, ALLOC_POLICY_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
-     DISK_STATE_OPT, DISK_PARAMS_OPT, PRIORITY_OPT] + INSTANCE_POLICY_OPTS,
+    [DRY_RUN_OPT] + SUBMIT_OPTS + [ALLOC_POLICY_OPT, NODE_PARAMS_OPT,
+    HV_STATE_OPT, DISK_STATE_OPT, DISK_PARAMS_OPT, PRIORITY_OPT]
+    + INSTANCE_POLICY_OPTS,
     "<group_name>", "Alters the parameters of a node group"),
   "remove": (
-    RemoveGroup, ARGS_ONE_GROUP, [DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    RemoveGroup, ARGS_ONE_GROUP, [DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "[--dry-run] <group-name>",
     "Remove an (empty) node group from the cluster"),
   "rename": (
     RenameGroup, [ArgGroup(min=2, max=2)],
-    [DRY_RUN_OPT, SUBMIT_OPT, PRIORITY_OPT],
+    [DRY_RUN_OPT] + SUBMIT_OPTS + [PRIORITY_OPT],
     "[--dry-run] <group-name> <new-name>", "Rename a node group"),
   "evacuate": (
     EvacuateGroup, [ArgGroup(min=1, max=1)],
-    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, SUBMIT_OPT, PRIORITY_OPT],
+    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT] + SUBMIT_OPTS,
     "[-I <iallocator>] [--to <group>]",
     "Evacuate all instances within a group"),
   "list-tags": (
@@ -363,11 +364,11 @@ commands = {
     "<group_name>", "List the tags of the given group"),
   "add-tags": (
     AddTags, [ArgGroup(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<group_name> tag...", "Add tags to the given group"),
   "remove-tags": (
     RemoveTags, [ArgGroup(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<group_name> tag...", "Remove tags from the given group"),
   "info": (
     GroupInfo, ARGS_MANY_GROUPS, [], "[<group_name>...]",
index 07db3a6..e69f55e 100644 (file)
@@ -915,25 +915,29 @@ def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
   return constants.EXIT_SUCCESS
 
 
-def _FormatLogicalID(dev_type, logical_id, roman):
+def _FormatDiskDetails(dev_type, dev, roman):
   """Formats the logical_id of a disk.
 
   """
   if dev_type == constants.LD_DRBD8:
-    node_a, node_b, port, minor_a, minor_b, key = logical_id
+    drbd_info = dev["drbd_info"]
     data = [
-      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
-                                                            convert=roman))),
-      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
-                                                            convert=roman))),
-      ("port", str(compat.TryToRoman(port, convert=roman))),
-      ("auth key", str(key)),
+      ("nodeA", "%s, minor=%s" %
+                (drbd_info["primary_node"],
+                 compat.TryToRoman(drbd_info["primary_minor"],
+                                   convert=roman))),
+      ("nodeB", "%s, minor=%s" %
+                (drbd_info["secondary_node"],
+                 compat.TryToRoman(drbd_info["secondary_minor"],
+                                   convert=roman))),
+      ("port", str(compat.TryToRoman(drbd_info["port"], convert=roman))),
+      ("auth key", str(drbd_info["secret"])),
       ]
   elif dev_type == constants.LD_LV:
-    vg_name, lv_name = logical_id
+    vg_name, lv_name = dev["logical_id"]
     data = ["%s/%s" % (vg_name, lv_name)]
   else:
-    data = [str(logical_id)]
+    data = [str(dev["logical_id"])]
 
   return data
 
@@ -1027,10 +1031,12 @@ def _FormatBlockDevInfo(idx, top_level, dev, roman):
     nice_size = str(dev["size"])
   data = [(txt, "%s, size %s" % (dev["dev_type"], nice_size))]
   if top_level:
+    if dev["spindles"] is not None:
+      data.append(("spindles", dev["spindles"]))
     data.append(("access mode", dev["mode"]))
   if dev["logical_id"] is not None:
     try:
-      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
+      l_id = _FormatDiskDetails(dev["dev_type"], dev, roman)
     except ValueError:
       l_id = [str(dev["logical_id"])]
     if len(l_id) == 1:
@@ -1456,7 +1462,7 @@ commands = {
     "Creates and adds a new instance to the cluster"),
   "batch-create": (
     BatchCreate, [ArgFile(min=1, max=1)],
-    [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT, SUBMIT_OPT],
+    [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT] + SUBMIT_OPTS,
     "<instances.json>",
     "Create a bunch of instances based on specs in the file."),
   "console": (
@@ -1465,7 +1471,8 @@ commands = {
     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
   "failover": (
     FailoverInstance, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
+    [FORCE_OPT, IGNORE_CONSIST_OPT] + SUBMIT_OPTS +
+    [SHUTDOWN_TIMEOUT_OPT,
      DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
      IGNORE_IPOLICY_OPT],
     "[-f] <instance>", "Stops the instance, changes its primary node and"
@@ -1476,13 +1483,15 @@ commands = {
     MigrateInstance, ARGS_ONE_INSTANCE,
     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
      PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
-     IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
+     IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT] + SUBMIT_OPTS,
     "[-f] <instance>", "Migrate instance to its secondary node"
     " (only for mirrored instances)"),
   "move": (
     MoveInstance, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
-     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
+    [FORCE_OPT] + SUBMIT_OPTS +
+    [SINGLE_NODE_OPT,
+     SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT,
+     IGNORE_IPOLICY_OPT],
     "[-f] <instance>", "Move instance to an arbitrary node"
     " (only for instances of type file and lv)"),
   "info": (
@@ -1509,30 +1518,31 @@ commands = {
     ReinstallInstance, [ArgInstance()],
     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
-     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
-     SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
+     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT]
+    + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
     "[-f] <instance>", "Reinstall a stopped instance"),
   "remove": (
     RemoveInstance, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
-     DRY_RUN_OPT, PRIORITY_OPT],
+    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT] + SUBMIT_OPTS
+    + [DRY_RUN_OPT, PRIORITY_OPT],
     "[-f] <instance>", "Shuts down the instance and removes it"),
   "rename": (
     RenameInstance,
     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
-    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    [NOIPCHECK_OPT, NONAMECHECK_OPT] + SUBMIT_OPTS
+    + [DRY_RUN_OPT, PRIORITY_OPT],
     "<instance> <new_name>", "Rename the instance"),
   "replace-disks": (
     ReplaceDisks, ARGS_ONE_INSTANCE,
     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
-     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
-     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
+     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT] + SUBMIT_OPTS
+    + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
     "[-s|-p|-a|-n NODE|-I NAME] <instance>",
     "Replaces disks for the instance"),
   "modify": (
     SetInstanceParams, ARGS_ONE_INSTANCE,
-    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
-     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
+    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT] + SUBMIT_OPTS +
+    [DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
      ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
      NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT],
@@ -1541,57 +1551,60 @@ commands = {
     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
     [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
-     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
-     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
+     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT] + SUBMIT_OPTS
+    + [DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
     "<instance>", "Stops an instance"),
   "startup": (
     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
-     m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
+     m_inst_tags_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS +
+    [HVOPTS_OPT,
      BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
      NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
     "<instance>", "Starts an instance"),
   "reboot": (
     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
-     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
-     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
+     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt] + SUBMIT_OPTS +
+    [m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
     "<instance>", "Reboots an instance"),
   "activate-disks": (
     ActivateDisks, ARGS_ONE_INSTANCE,
-    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
+    SUBMIT_OPTS + [IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
     "<instance>", "Activate an instance's disks"),
   "deactivate-disks": (
     DeactivateDisks, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    [FORCE_OPT] + SUBMIT_OPTS + [DRY_RUN_OPT, PRIORITY_OPT],
     "[-f] <instance>", "Deactivate an instance's disks"),
   "recreate-disks": (
     RecreateDisks, ARGS_ONE_INSTANCE,
-    [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
+    SUBMIT_OPTS +
+    [DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
      IALLOCATOR_OPT],
     "<instance>", "Recreate an instance's disks"),
   "grow-disk": (
     GrowDisk,
     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
      ArgUnknown(min=1, max=1)],
-    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
+    SUBMIT_OPTS + [NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
     "<instance> <disk> <size>", "Grow an instance's disk"),
   "change-group": (
     ChangeGroup, ARGS_ONE_INSTANCE,
-    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT]
+    + SUBMIT_OPTS,
     "[-I <iallocator>] [--to <group>]", "Change group of instance"),
   "list-tags": (
     ListTags, ARGS_ONE_INSTANCE, [],
     "<instance_name>", "List the tags of the given instance"),
   "add-tags": (
     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<instance_name> tag...", "Add tags to the given instance"),
   "remove-tags": (
     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<instance_name> tag...", "Remove tags from given instance"),
   }
 
index d8a9d27..ef26c0b 100644 (file)
@@ -440,6 +440,28 @@ def WatchJob(opts, args):
   return retcode
 
 
+def WaitJob(opts, args):
+  """Wait for a job to finish, not producing any output.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: Contains the job ID
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  job_id = args[0]
+
+  retcode = 0
+  try:
+    cli.PollJob(job_id, feedback_fn=lambda _: None)
+  except errors.GenericError, err:
+    (retcode, job_result) = cli.FormatError(err)
+    ToStderr("Job %s failed: %s", job_id, job_result)
+
+  return retcode
+
+
 _PENDING_OPT = \
   cli_option("--pending", default=None,
              action="store_const", dest="status_filter",
@@ -523,6 +545,9 @@ commands = {
     ShowJobs, [ArgJobId(min=1)], [],
     "<job-id> [<job-id> ...]",
     "Show detailed information about the specified jobs"),
+  "wait": (
+    WaitJob, [ArgJobId(min=1, max=1)], [],
+    "<job-id>", "Wait for a job to finish"),
   "watch": (
     WatchJob, [ArgJobId(min=1, max=1)], [],
     "<job-id>", "Follows a job and prints its output as it arrives"),
index a8706c1..03569b4 100644 (file)
@@ -313,7 +313,7 @@ commands = {
     AddNetwork, ARGS_ONE_NETWORK,
     [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT,
      MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
-     NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT, SUBMIT_OPT],
+     NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<network_name>", "Add a new IP network to the cluster"),
   "list": (
     ListNetworks, ARGS_MANY_NETWORKS,
@@ -330,9 +330,10 @@ commands = {
     "[<network_name>...]", "Show information about the network(s)"),
   "modify": (
     SetNetworkParams, ARGS_ONE_NETWORK,
-    [DRY_RUN_OPT, SUBMIT_OPT, ADD_RESERVED_IPS_OPT, REMOVE_RESERVED_IPS_OPT,
-     GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
-     PRIORITY_OPT],
+    [DRY_RUN_OPT] + SUBMIT_OPTS +
+    [ADD_RESERVED_IPS_OPT,
+     REMOVE_RESERVED_IPS_OPT, GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT,
+     GATEWAY6_OPT, PRIORITY_OPT],
     "<network_name>", "Alters the parameters of a network"),
   "connect": (
     ConnectNetwork,
@@ -352,7 +353,7 @@ commands = {
     "Unmap a given network from a specified node group"),
   "remove": (
     RemoveNetwork, ARGS_ONE_NETWORK,
-    [FORCE_OPT, DRY_RUN_OPT, SUBMIT_OPT, PRIORITY_OPT],
+    [FORCE_OPT, DRY_RUN_OPT] + SUBMIT_OPTS + [PRIORITY_OPT],
     "[--dry-run] <network_id>",
     "Remove an (empty) network from the cluster"),
   "list-tags": (
@@ -360,11 +361,11 @@ commands = {
     "<network_name>", "List the tags of the given network"),
   "add-tags": (
     AddTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<network_name> tag...", "Add tags to the given network"),
   "remove-tags": (
     RemoveTags, [ArgNetwork(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<network_name> tag...", "Remove tags from given network"),
 }
 
index 8fd28df..48ed7dd 100644 (file)
@@ -1090,7 +1090,7 @@ commands = {
   "evacuate": (
     EvacuateNode, ARGS_ONE_NODE,
     [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
-     PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT, SUBMIT_OPT],
+     PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT] + SUBMIT_OPTS,
     "[-f] {-I <iallocator> | -n <dst>} [-p | -s] [options...] <node>",
     "Relocate the primary and/or secondary instances from a node"),
   "failover": (
@@ -1103,7 +1103,7 @@ commands = {
     MigrateNode, ARGS_ONE_NODE,
     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
      IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT,
-     NORUNTIME_CHGS_OPT, SUBMIT_OPT],
+     NORUNTIME_CHGS_OPT] + SUBMIT_OPTS,
     "[-f] <node>",
     "Migrate all the primary instance on a node away from it"
     " (only for instances of type drbd)"),
@@ -1126,21 +1126,24 @@ commands = {
     "Lists all available fields for nodes"),
   "modify": (
     SetNodeParams, ARGS_ONE_NODE,
-    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
+    [FORCE_OPT] + SUBMIT_OPTS +
+    [MC_OPT, DRAINED_OPT, OFFLINE_OPT,
      CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
      AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
      NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
     "<node_name>", "Alters the parameters of a node"),
   "powercycle": (
     PowercycleNode, ARGS_ONE_NODE,
-    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<node_name>", "Tries to forcefully powercycle a node"),
   "power": (
     PowerNode,
     [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
      ArgNode()],
-    [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
-     FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
+    SUBMIT_OPTS +
+    [AUTO_PROMOTE_OPT, PRIORITY_OPT,
+     IGNORE_STATUS_OPT, FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT,
+     POWER_DELAY_OPT],
     "on|off|cycle|status [nodes...]",
     "Change power state of node by calling out-of-band helper."),
   "remove": (
@@ -1162,14 +1165,14 @@ commands = {
     [ArgNode(min=1, max=1),
      ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
      ArgFile(min=1, max=1)],
-    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
   "repair-storage": (
     RepairStorage,
     [ArgNode(min=1, max=1),
      ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
      ArgFile(min=1, max=1)],
-    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<node_name> <storage_type> <name>",
     "Repairs a storage volume on a node"),
   "list-tags": (
@@ -1177,11 +1180,11 @@ commands = {
     "<node_name>", "List the tags of the given node"),
   "add-tags": (
     AddTags, [ArgNode(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<node_name> tag...", "Add tags to the given node"),
   "remove-tags": (
     RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT] + SUBMIT_OPTS,
     "<node_name> tag...", "Remove tags from the given node"),
   "health": (
     Health, ARGS_MANY_NODES,
@@ -1193,7 +1196,7 @@ commands = {
     "[<node_name>]", "Query the list of used DRBD minors on the given node"),
   "restricted-command": (
     RestrictedCommand, [ArgUnknown(min=1, max=1)] + ARGS_MANY_NODES,
-    [SYNC_OPT, PRIORITY_OPT, SUBMIT_OPT, SHOW_MACHINE_OPT, NODEGROUP_OPT],
+    [SYNC_OPT, PRIORITY_OPT] + SUBMIT_OPTS + [SHOW_MACHINE_OPT, NODEGROUP_OPT],
     "<command> <node_name> [<node_name>...]",
     "Executes a restricted command on node(s)"),
   }
index 4bec976..f522633 100644 (file)
@@ -289,7 +289,7 @@ commands = {
   "modify": (
     ModifyOS, ARGS_ONE_OS,
     [HVLIST_OPT, OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT,
-     HID_OS_OPT, BLK_OS_OPT, SUBMIT_OPT],
+     HID_OS_OPT, BLK_OS_OPT] + SUBMIT_OPTS,
     "", "Modify the OS parameters"),
   }
 
index f853a84..54f7d90 100644 (file)
@@ -35,7 +35,7 @@ from ganeti import utils
 
 from ganeti.cmdlib.base import QueryBase, NoHooksLU, LogicalUnit
 from ganeti.cmdlib.common import GetWantedNodes, ShareAll, CheckNodeOnline, \
-  ExpandNodeName
+  ExpandNodeUuidAndName
 from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
   ShutdownInstanceDisks
 from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
@@ -53,7 +53,7 @@ class ExportQuery(QueryBase):
 
     # The following variables interact with _QueryBase._GetNames
     if self.names:
-      self.wanted = GetWantedNodes(lu, self.names)
+      (self.wanted, _) = GetWantedNodes(lu, self.names)
     else:
       self.wanted = locking.ALL_SET
 
@@ -82,15 +82,15 @@ class ExportQuery(QueryBase):
                            if level != locking.LEVEL_CLUSTER) or
                 self.do_locking or self.use_locking)
 
-    nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
+    node_uuids = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
 
     result = []
 
-    for (node, nres) in lu.rpc.call_export_list(nodes).items():
+    for (node_uuid, nres) in lu.rpc.call_export_list(node_uuids).items():
       if nres.fail_msg:
-        result.append((node, None))
+        result.append((node_uuid, None))
       else:
-        result.extend((node, expname) for expname in nres.payload)
+        result.extend((node_uuid, expname) for expname in nres.payload)
 
     return result
 
@@ -136,9 +136,7 @@ class LUBackupPrepare(NoHooksLU):
     """Check prerequisites.
 
     """
-    instance_name = self.op.instance_name
-
-    self.instance = self.cfg.GetInstanceInfo(instance_name)
+    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
     assert self.instance is not None, \
           "Cannot retrieve locked instance %s" % self.op.instance_name
     CheckNodeOnline(self, self.instance.primary_node)
@@ -149,15 +147,15 @@ class LUBackupPrepare(NoHooksLU):
     """Prepares an instance for an export.
 
     """
-    instance = self.instance
-
     if self.op.mode == constants.EXPORT_MODE_REMOTE:
       salt = utils.GenerateSecret(8)
 
-      feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
-      result = self.rpc.call_x509_cert_create(instance.primary_node,
+      feedback_fn("Generating X509 certificate on %s" %
+                  self.cfg.GetNodeName(self.instance.primary_node))
+      result = self.rpc.call_x509_cert_create(self.instance.primary_node,
                                               constants.RIE_CERT_VALIDITY)
-      result.Raise("Can't create X509 key and certificate on %s" % result.node)
+      result.Raise("Can't create X509 key and certificate on %s" %
+                   self.cfg.GetNodeName(result.node))
 
       (name, cert_pem) = result.payload
 
@@ -203,6 +201,9 @@ class LUBackupExport(LogicalUnit):
 
     # Lock all nodes for local exports
     if self.op.mode == constants.EXPORT_MODE_LOCAL:
+      (self.op.target_node_uuid, self.op.target_node) = \
+        ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
+                              self.op.target_node)
       # FIXME: lock only instance primary and destination node
       #
       # Sad but true, for now we have do lock all nodes, as we don't know where
@@ -248,7 +249,7 @@ class LUBackupExport(LogicalUnit):
     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
 
     if self.op.mode == constants.EXPORT_MODE_LOCAL:
-      nl.append(self.op.target_node)
+      nl.append(self.op.target_node_uuid)
 
     return (nl, nl)
 
@@ -258,9 +259,7 @@ class LUBackupExport(LogicalUnit):
     This checks that the instance and node names are valid.
 
     """
-    instance_name = self.op.instance_name
-
-    self.instance = self.cfg.GetInstanceInfo(instance_name)
+    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
     assert self.instance is not None, \
           "Cannot retrieve locked instance %s" % self.op.instance_name
     CheckNodeOnline(self, self.instance.primary_node)
@@ -272,12 +271,11 @@ class LUBackupExport(LogicalUnit):
                                  " down before", errors.ECODE_STATE)
 
     if self.op.mode == constants.EXPORT_MODE_LOCAL:
-      self.op.target_node = ExpandNodeName(self.cfg, self.op.target_node)
-      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
+      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
       assert self.dst_node is not None
 
-      CheckNodeOnline(self, self.dst_node.name)
-      CheckNodeNotDrained(self, self.dst_node.name)
+      CheckNodeOnline(self, self.dst_node.uuid)
+      CheckNodeNotDrained(self, self.dst_node.uuid)
 
       self._cds = None
       self.dest_disk_info = None
@@ -289,7 +287,8 @@ class LUBackupExport(LogicalUnit):
       if len(self.op.target_node) != len(self.instance.disks):
         raise errors.OpPrereqError(("Received destination information for %s"
                                     " disks, but instance %s has %s disks") %
-                                   (len(self.op.target_node), instance_name,
+                                   (len(self.op.target_node),
+                                    self.op.instance_name,
                                     len(self.instance.disks)),
                                    errors.ECODE_INVAL)
 
@@ -355,24 +354,25 @@ class LUBackupExport(LogicalUnit):
     """
     assert self.op.mode != constants.EXPORT_MODE_REMOTE
 
-    nodelist = self.cfg.GetNodeList()
-    nodelist.remove(self.dst_node.name)
+    node_uuids = self.cfg.GetNodeList()
+    node_uuids.remove(self.dst_node.uuid)
 
     # on one-node clusters nodelist will be empty after the removal
     # if we proceed the backup would be removed because OpBackupQuery
     # substitutes an empty list with the full cluster node list.
     iname = self.instance.name
-    if nodelist:
+    if node_uuids:
       feedback_fn("Removing old exports for instance %s" % iname)
-      exportlist = self.rpc.call_export_list(nodelist)
-      for node in exportlist:
-        if exportlist[node].fail_msg:
+      exportlist = self.rpc.call_export_list(node_uuids)
+      for node_uuid in exportlist:
+        if exportlist[node_uuid].fail_msg:
           continue
-        if iname in exportlist[node].payload:
-          msg = self.rpc.call_export_remove(node, iname).fail_msg
+        if iname in exportlist[node_uuid].payload:
+          msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
           if msg:
             self.LogWarning("Could not remove older export for instance %s"
-                            " on node %s: %s", iname, node, msg)
+                            " on node %s: %s", iname,
+                            self.cfg.GetNodeName(node_uuid), msg)
 
   def Exec(self, feedback_fn):
     """Export an instance to an image in the cluster.
@@ -380,49 +380,49 @@ class LUBackupExport(LogicalUnit):
     """
     assert self.op.mode in constants.EXPORT_MODES
 
-    instance = self.instance
-    src_node = instance.primary_node
+    src_node_uuid = self.instance.primary_node
 
     if self.op.shutdown:
       # shutdown the instance, but not the disks
-      feedback_fn("Shutting down instance %s" % instance.name)
-      result = self.rpc.call_instance_shutdown(src_node, instance,
+      feedback_fn("Shutting down instance %s" % self.instance.name)
+      result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
                                                self.op.shutdown_timeout,
                                                self.op.reason)
       # TODO: Maybe ignore failures if ignore_remove_failures is set
       result.Raise("Could not shutdown instance %s on"
-                   " node %s" % (instance.name, src_node))
+                   " node %s" % (self.instance.name,
+                                 self.cfg.GetNodeName(src_node_uuid)))
 
     # set the disks ID correctly since call_instance_start needs the
     # correct drbd minor to create the symlinks
-    for disk in instance.disks:
-      self.cfg.SetDiskID(disk, src_node)
+    for disk in self.instance.disks:
+      self.cfg.SetDiskID(disk, src_node_uuid)
 
-    activate_disks = not instance.disks_active
+    activate_disks = not self.instance.disks_active
 
     if activate_disks:
       # Activate the instance disks if we'exporting a stopped instance
-      feedback_fn("Activating disks for %s" % instance.name)
-      StartInstanceDisks(self, instance, None)
+      feedback_fn("Activating disks for %s" % self.instance.name)
+      StartInstanceDisks(self, self.instance, None)
 
     try:
       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
-                                                     instance)
+                                                     self.instance)
 
       helper.CreateSnapshots()
       try:
         if (self.op.shutdown and
-            instance.admin_state == constants.ADMINST_UP and
+            self.instance.admin_state == constants.ADMINST_UP and
             not self.op.remove_instance):
           assert not activate_disks
-          feedback_fn("Starting instance %s" % instance.name)
-          result = self.rpc.call_instance_start(src_node,
-                                                (instance, None, None), False,
-                                                 self.op.reason)
+          feedback_fn("Starting instance %s" % self.instance.name)
+          result = self.rpc.call_instance_start(src_node_uuid,
+                                                (self.instance, None, None),
+                                                False, self.op.reason)
           msg = result.fail_msg
           if msg:
             feedback_fn("Failed to start instance: %s" % msg)
-            ShutdownInstanceDisks(self, instance)
+            ShutdownInstanceDisks(self, self.instance)
             raise errors.OpExecError("Could not start instance: %s" % msg)
 
         if self.op.mode == constants.EXPORT_MODE_LOCAL:
@@ -444,14 +444,14 @@ class LUBackupExport(LogicalUnit):
         helper.Cleanup()
 
       # Check for backwards compatibility
-      assert len(dresults) == len(instance.disks)
+      assert len(dresults) == len(self.instance.disks)
       assert compat.all(isinstance(i, bool) for i in dresults), \
              "Not all results are boolean: %r" % dresults
 
     finally:
       if activate_disks:
-        feedback_fn("Deactivating disks for %s" % instance.name)
-        ShutdownInstanceDisks(self, instance)
+        feedback_fn("Deactivating disks for %s" % self.instance.name)
+        ShutdownInstanceDisks(self, self.instance)
 
     if not (compat.all(dresults) and fin_resu):
       failures = []
@@ -469,8 +469,8 @@ class LUBackupExport(LogicalUnit):
 
     # Remove instance if requested
     if self.op.remove_instance:
-      feedback_fn("Removing instance %s" % instance.name)
-      RemoveInstance(self, feedback_fn, instance,
+      feedback_fn("Removing instance %s" % self.instance.name)
+      RemoveInstance(self, feedback_fn, self.instance,
                      self.op.ignore_remove_failures)
 
     if self.op.mode == constants.EXPORT_MODE_LOCAL:
@@ -504,29 +504,31 @@ class LUBackupRemove(NoHooksLU):
     """Remove any export.
 
     """
-    instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
+    (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
     # If the instance was not found we'll try with the name that was passed in.
     # This will only work if it was an FQDN, though.
     fqdn_warn = False
-    if not instance_name:
+    if not inst_name:
       fqdn_warn = True
-      instance_name = self.op.instance_name
+      inst_name = self.op.instance_name
 
     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
     exportlist = self.rpc.call_export_list(locked_nodes)
     found = False
-    for node in exportlist:
-      msg = exportlist[node].fail_msg
+    for node_uuid in exportlist:
+      msg = exportlist[node_uuid].fail_msg
       if msg:
-        self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
+        self.LogWarning("Failed to query node %s (continuing): %s",
+                        self.cfg.GetNodeName(node_uuid), msg)
         continue
-      if instance_name in exportlist[node].payload:
+      if inst_name in exportlist[node_uuid].payload:
         found = True
-        result = self.rpc.call_export_remove(node, instance_name)
+        result = self.rpc.call_export_remove(node_uuid, inst_name)
         msg = result.fail_msg
         if msg:
           logging.error("Could not remove export for instance %s"
-                        " on node %s: %s", instance_name, node, msg)
+                        " on node %s: %s", inst_name,
+                        self.cfg.GetNodeName(node_uuid), msg)
 
     if fqdn_warn and not found:
       feedback_fn("Export not found. If trying to remove an export belonging"
index 18ab241..46f5a37 100644 (file)
@@ -28,7 +28,7 @@ from ganeti import constants
 from ganeti import locking
 from ganeti import query
 from ganeti import utils
-from ganeti.cmdlib.common import ExpandInstanceName
+from ganeti.cmdlib.common import ExpandInstanceUuidAndName
 
 
 class ResultWithJobs:
@@ -181,7 +181,7 @@ class LogicalUnit(object):
       }
       # Acquire just two nodes
       self.needed_locks = {
-        locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
+        locking.LEVEL_NODE: ['node1-uuid', 'node2-uuid'],
       }
       # Acquire no locks
       self.needed_locks = {} # No, you can't leave it to the default value None
@@ -269,11 +269,14 @@ class LogicalUnit(object):
   def BuildHooksNodes(self):
     """Build list of nodes to run LU's hooks.
 
-    @rtype: tuple; (list, list)
-    @return: Tuple containing a list of node names on which the hook
-      should run before the execution and a list of node names on which the
-      hook should run after the execution. No nodes should be returned as an
-      empty list (and not None).
+    @rtype: tuple; (list, list) or (list, list, list)
+    @return: Tuple containing a list of node UUIDs on which the hook
+      should run before the execution and a list of node UUIDs on which the
+      hook should run after the execution. As it might be possible that the
+      node UUID is not known at the time this method is invoked, an optional
+      third list can be added which contains node names on which the hook
+      should run after the execution (in case of node add, for instance).
+      No nodes should be returned as an empty list (and not None).
     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
       will not be called.
 
@@ -319,8 +322,9 @@ class LogicalUnit(object):
     else:
       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
         "_ExpandAndLockInstance called with instance-level locks set"
-    self.op.instance_name = ExpandInstanceName(self.cfg,
-                                               self.op.instance_name)
+    (self.op.instance_uuid, self.op.instance_name) = \
+      ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
+                                self.op.instance_name)
     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
 
   def _LockInstancesNodes(self, primary_only=False,
@@ -356,17 +360,17 @@ class LogicalUnit(object):
     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
     # future we might want to have different behaviors depending on the value
     # of self.recalculate_locks[locking.LEVEL_NODE]
-    wanted_nodes = []
+    wanted_node_uuids = []
     locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
-    for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
-      wanted_nodes.append(instance.primary_node)
+    for _, instance in self.cfg.GetMultiInstanceInfoByName(locked_i):
+      wanted_node_uuids.append(instance.primary_node)
       if not primary_only:
-        wanted_nodes.extend(instance.secondary_nodes)
+        wanted_node_uuids.extend(instance.secondary_nodes)
 
     if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
-      self.needed_locks[level] = wanted_nodes
+      self.needed_locks[level] = wanted_node_uuids
     elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
-      self.needed_locks[level].extend(wanted_nodes)
+      self.needed_locks[level].extend(wanted_node_uuids)
     else:
       raise errors.ProgrammerError("Unknown recalculation mode")
 
index 9ced853..6043fa8 100644 (file)
@@ -71,7 +71,7 @@ class LUClusterActivateMasterIp(NoHooksLU):
     """
     master_params = self.cfg.GetMasterNetworkParameters()
     ems = self.cfg.GetUseExternalMipScript()
-    result = self.rpc.call_node_activate_master_ip(master_params.name,
+    result = self.rpc.call_node_activate_master_ip(master_params.uuid,
                                                    master_params, ems)
     result.Raise("Could not activate the master IP")
 
@@ -86,7 +86,7 @@ class LUClusterDeactivateMasterIp(NoHooksLU):
     """
     master_params = self.cfg.GetMasterNetworkParameters()
     ems = self.cfg.GetUseExternalMipScript()
-    result = self.rpc.call_node_deactivate_master_ip(master_params.name,
+    result = self.rpc.call_node_deactivate_master_ip(master_params.uuid,
                                                      master_params, ems)
     result.Raise("Could not deactivate the master IP")
 
@@ -163,16 +163,13 @@ class LUClusterDestroy(LogicalUnit):
     master_params = self.cfg.GetMasterNetworkParameters()
 
     # Run post hooks on master node before it's removed
-    RunPostHook(self, master_params.name)
+    RunPostHook(self, self.cfg.GetNodeName(master_params.uuid))
 
     ems = self.cfg.GetUseExternalMipScript()
-    result = self.rpc.call_node_deactivate_master_ip(master_params.name,
+    result = self.rpc.call_node_deactivate_master_ip(master_params.uuid,
                                                      master_params, ems)
-    if result.fail_msg:
-      self.LogWarning("Error disabling the master IP address: %s",
-                      result.fail_msg)
-
-    return master_params.name
+    result.Warn("Error disabling the master IP address", self.LogWarning)
+    return master_params.uuid
 
 
 class LUClusterPostInit(LogicalUnit):
@@ -235,8 +232,10 @@ class ClusterQuery(QueryBase):
 
     if query.CQ_CONFIG in self.requested_data:
       cluster = lu.cfg.GetClusterInfo()
+      nodes = lu.cfg.GetAllNodesInfo()
     else:
       cluster = NotImplemented
+      nodes = NotImplemented
 
     if query.CQ_QUEUE_DRAINED in self.requested_data:
       drain_flag = os.path.exists(pathutils.JOB_QUEUE_DRAIN_FILE)
@@ -244,17 +243,17 @@ class ClusterQuery(QueryBase):
       drain_flag = NotImplemented
 
     if query.CQ_WATCHER_PAUSE in self.requested_data:
-      master_name = lu.cfg.GetMasterNode()
+      master_node_uuid = lu.cfg.GetMasterNode()
 
-      result = lu.rpc.call_get_watcher_pause(master_name)
+      result = lu.rpc.call_get_watcher_pause(master_node_uuid)
       result.Raise("Can't retrieve watcher pause from master node '%s'" %
-                   master_name)
+                   lu.cfg.GetMasterNodeName())
 
       watcher_pause = result.payload
     else:
       watcher_pause = NotImplemented
 
-    return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
+    return query.ClusterQueryData(cluster, nodes, drain_flag, watcher_pause)
 
 
 class LUClusterQuery(NoHooksLU):
@@ -293,7 +292,7 @@ class LUClusterQuery(NoHooksLU):
       "export_version": constants.EXPORT_VERSION,
       "architecture": runtime.GetArchInfo(),
       "name": cluster.cluster_name,
-      "master": cluster.master_node,
+      "master": self.cfg.GetMasterNodeName(),
       "default_hypervisor": cluster.primary_hypervisor,
       "enabled_hypervisors": cluster.enabled_hypervisors,
       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
@@ -409,7 +408,7 @@ class LUClusterRename(LogicalUnit):
     # shutdown the master IP
     master_params = self.cfg.GetMasterNetworkParameters()
     ems = self.cfg.GetUseExternalMipScript()
-    result = self.rpc.call_node_deactivate_master_ip(master_params.name,
+    result = self.rpc.call_node_deactivate_master_ip(master_params.uuid,
                                                      master_params, ems)
     result.Raise("Could not disable the master role")
 
@@ -423,18 +422,16 @@ class LUClusterRename(LogicalUnit):
       ssh.WriteKnownHostsFile(self.cfg, pathutils.SSH_KNOWN_HOSTS_FILE)
       node_list = self.cfg.GetOnlineNodeList()
       try:
-        node_list.remove(master_params.name)
+        node_list.remove(master_params.uuid)
       except ValueError:
         pass
       UploadHelper(self, node_list, pathutils.SSH_KNOWN_HOSTS_FILE)
     finally:
       master_params.ip = new_ip
-      result = self.rpc.call_node_activate_master_ip(master_params.name,
+      result = self.rpc.call_node_activate_master_ip(master_params.uuid,
                                                      master_params, ems)
-      msg = result.fail_msg
-      if msg:
-        self.LogWarning("Could not re-enable the master role on"
-                        " the master, please restart manually: %s", msg)
+      result.Warn("Could not re-enable the master role on the master,"
+                  " please restart manually", self.LogWarning)
 
     return clustername
 
@@ -447,7 +444,7 @@ class LUClusterRepairDiskSizes(NoHooksLU):
 
   def ExpandNames(self):
     if self.op.instances:
-      self.wanted_names = GetWantedInstances(self, self.op.instances)
+      (_, self.wanted_names) = GetWantedInstances(self, self.op.instances)
       # Not getting the node allocation lock as only a specific set of
       # instances (and their nodes) is going to be acquired
       self.needed_locks = {
@@ -485,7 +482,7 @@ class LUClusterRepairDiskSizes(NoHooksLU):
       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
 
     self.wanted_instances = \
-        map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
+        map(compat.snd, self.cfg.GetMultiInstanceInfoByName(self.wanted_names))
 
   def _EnsureChildSizes(self, disk):
     """Ensure children of the disk have the needed disk size.
@@ -529,27 +526,38 @@ class LUClusterRepairDiskSizes(NoHooksLU):
       "Not owning correct locks"
     assert not self.owned_locks(locking.LEVEL_NODE)
 
+    es_flags = rpc.GetExclusiveStorageForNodes(self.cfg,
+                                               per_node_disks.keys())
+
     changed = []
-    for node, dskl in per_node_disks.items():
+    for node_uuid, dskl in per_node_disks.items():
       newl = [v[2].Copy() for v in dskl]
       for dsk in newl:
-        self.cfg.SetDiskID(dsk, node)
-      result = self.rpc.call_blockdev_getsize(node, newl)
+        self.cfg.SetDiskID(dsk, node_uuid)
+      node_name = self.cfg.GetNodeName(node_uuid)
+      result = self.rpc.call_blockdev_getdimensions(node_uuid, newl)
       if result.fail_msg:
-        self.LogWarning("Failure in blockdev_getsize call to node"
-                        " %s, ignoring", node)
+        self.LogWarning("Failure in blockdev_getdimensions call to node"
+                        " %s, ignoring", node_name)
         continue
       if len(result.payload) != len(dskl):
         logging.warning("Invalid result from node %s: len(dksl)=%d,"
-                        " result.payload=%s", node, len(dskl), result.payload)
+                        " result.payload=%s", node_name, len(dskl),
+                        result.payload)
         self.LogWarning("Invalid result from node %s, ignoring node results",
-                        node)
+                        node_name)
         continue
-      for ((instance, idx, disk), size) in zip(dskl, result.payload):
-        if size is None:
+      for ((instance, idx, disk), dimensions) in zip(dskl, result.payload):
+        if dimensions is None:
           self.LogWarning("Disk %d of instance %s did not return size"
                           " information, ignoring", idx, instance.name)
           continue
+        if not isinstance(dimensions, (tuple, list)):
+          self.LogWarning("Disk %d of instance %s did not return valid"
+                          " dimension information, ignoring", idx,
+                          instance.name)
+          continue
+        (size, spindles) = dimensions
         if not isinstance(size, (int, long)):
           self.LogWarning("Disk %d of instance %s did not return valid"
                           " size information, ignoring", idx, instance.name)
@@ -561,10 +569,22 @@ class LUClusterRepairDiskSizes(NoHooksLU):
                        instance.name, disk.size, size)
           disk.size = size
           self.cfg.Update(instance, feedback_fn)
-          changed.append((instance.name, idx, size))
+          changed.append((instance.name, idx, "size", size))
+        if es_flags[node_uuid]:
+          if spindles is None:
+            self.LogWarning("Disk %d of instance %s did not return valid"
+                            " spindles information, ignoring", idx,
+                            instance.name)
+          elif disk.spindles is None or disk.spindles != spindles:
+            self.LogInfo("Disk %d of instance %s has mismatched spindles,"
+                         " correcting: recorded %s, actual %s",
+                         idx, instance.name, disk.spindles, spindles)
+            disk.spindles = spindles
+            self.cfg.Update(instance, feedback_fn)
+            changed.append((instance.name, idx, "spindles", disk.spindles))
         if self._EnsureChildSizes(disk):
           self.cfg.Update(instance, feedback_fn)
-          changed.append((instance.name, idx, disk.size))
+          changed.append((instance.name, idx, "size", disk.size))
     return changed
 
 
@@ -651,11 +671,10 @@ class LUClusterSetParams(LogicalUnit):
     mn = self.cfg.GetMasterNode()
     return ([mn], [mn])
 
-  def CheckPrereq(self):
-    """Check prerequisites.
-
-    This checks whether the given params don't conflict and
-    if the given volume group is valid.
+  def _CheckVgName(self, node_uuids, enabled_disk_templates,
+                   new_enabled_disk_templates):
+    """Check the consistency of the vg name on all nodes and in case it gets
+       unset whether there are instances still using it.
 
     """
     if self.op.vg_name is not None and not self.op.vg_name:
@@ -663,53 +682,95 @@ class LUClusterSetParams(LogicalUnit):
         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
                                    " instances exist", errors.ECODE_INVAL)
 
+    if (self.op.vg_name is not None and
+        utils.IsLvmEnabled(enabled_disk_templates)) or \
+           (self.cfg.GetVGName() is not None and
+            utils.LvmGetsEnabled(enabled_disk_templates,
+                                 new_enabled_disk_templates)):
+      self._CheckVgNameOnNodes(node_uuids)
+
+  def _CheckVgNameOnNodes(self, node_uuids):
+    """Check the status of the volume group on each node.
+
+    """
+    vglist = self.rpc.call_vg_list(node_uuids)
+    for node_uuid in node_uuids:
+      msg = vglist[node_uuid].fail_msg
+      if msg:
+        # ignoring down node
+        self.LogWarning("Error while gathering data on node %s"
+                        " (ignoring node): %s",
+                        self.cfg.GetNodeName(node_uuid), msg)
+        continue
+      vgstatus = utils.CheckVolumeGroupSize(vglist[node_uuid].payload,
+                                            self.op.vg_name,
+                                            constants.MIN_VG_SIZE)
+      if vgstatus:
+        raise errors.OpPrereqError("Error on node '%s': %s" %
+                                   (self.cfg.GetNodeName(node_uuid), vgstatus),
+                                   errors.ECODE_ENVIRON)
+
+  def _GetEnabledDiskTemplates(self, cluster):
+    """Determines the enabled disk templates and the subset of disk templates
+       that are newly enabled by this operation.
+
+    """
+    enabled_disk_templates = None
+    new_enabled_disk_templates = []
+    if self.op.enabled_disk_templates:
+      enabled_disk_templates = self.op.enabled_disk_templates
+      new_enabled_disk_templates = \
+        list(set(enabled_disk_templates)
+             - set(cluster.enabled_disk_templates))
+    else:
+      enabled_disk_templates = cluster.enabled_disk_templates
+    return (enabled_disk_templates, new_enabled_disk_templates)
+
+  def CheckPrereq(self):
+    """Check prerequisites.
+
+    This checks whether the given params don't conflict and
+    if the given volume group is valid.
+
+    """
     if self.op.drbd_helper is not None and not self.op.drbd_helper:
       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
         raise errors.OpPrereqError("Cannot disable drbd helper while"
                                    " drbd-based instances exist",
                                    errors.ECODE_INVAL)
 
-    node_list = self.owned_locks(locking.LEVEL_NODE)
+    node_uuids = self.owned_locks(locking.LEVEL_NODE)
+    self.cluster = cluster = self.cfg.GetClusterInfo()
 
-    vm_capable_nodes = [node.name
-                        for node in self.cfg.GetAllNodesInfo().values()
-                        if node.name in node_list and node.vm_capable]
+    vm_capable_node_uuids = [node.uuid
+                             for node in self.cfg.GetAllNodesInfo().values()
+                             if node.uuid in node_uuids and node.vm_capable]
 
-    # if vg_name not None, checks given volume group on all nodes
-    if self.op.vg_name:
-      vglist = self.rpc.call_vg_list(vm_capable_nodes)
-      for node in vm_capable_nodes:
-        msg = vglist[node].fail_msg
-        if msg:
-          # ignoring down node
-          self.LogWarning("Error while gathering data on node %s"
-                          " (ignoring node): %s", node, msg)
-          continue
-        vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
-                                              self.op.vg_name,
-                                              constants.MIN_VG_SIZE)
-        if vgstatus:
-          raise errors.OpPrereqError("Error on node '%s': %s" %
-                                     (node, vgstatus), errors.ECODE_ENVIRON)
+    (enabled_disk_templates, new_enabled_disk_templates) = \
+      self._GetEnabledDiskTemplates(cluster)
+
+    self._CheckVgName(vm_capable_node_uuids, enabled_disk_templates,
+                      new_enabled_disk_templates)
 
     if self.op.drbd_helper:
       # checks given drbd helper on all nodes
-      helpers = self.rpc.call_drbd_helper(node_list)
-      for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
+      helpers = self.rpc.call_drbd_helper(node_uuids)
+      for (_, ninfo) in self.cfg.GetMultiNodeInfo(node_uuids):
         if ninfo.offline:
-          self.LogInfo("Not checking drbd helper on offline node %s", node)
+          self.LogInfo("Not checking drbd helper on offline node %s",
+                       ninfo.name)
           continue
-        msg = helpers[node].fail_msg
+        msg = helpers[ninfo.uuid].fail_msg
         if msg:
           raise errors.OpPrereqError("Error checking drbd helper on node"
-                                     " '%s': %s" % (node, msg),
+                                     " '%s': %s" % (ninfo.name, msg),
                                      errors.ECODE_ENVIRON)
-        node_helper = helpers[node].payload
+        node_helper = helpers[ninfo.uuid].payload
         if node_helper != self.op.drbd_helper:
           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
-                                     (node, node_helper), errors.ECODE_ENVIRON)
+                                     (ninfo.name, node_helper),
+                                     errors.ECODE_ENVIRON)
 
-    self.cluster = cluster = self.cfg.GetClusterInfo()
     # validate params changes
     if self.op.beparams:
       objects.UpgradeBeParams(self.op.beparams)
@@ -748,12 +809,12 @@ class LUClusterSetParams(LogicalUnit):
       violations = set()
       for group in self.cfg.GetAllNodeGroupsInfo().values():
         instances = frozenset([inst for inst in all_instances
-                               if compat.any(node in group.members
-                                             for node in inst.all_nodes)])
+                               if compat.any(nuuid in group.members
+                                             for nuuid in inst.all_nodes)])
         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
         ipol = masterd.instance.CalculateGroupIPolicy(cluster, group)
-        new = ComputeNewInstanceViolations(ipol,
-                                           new_ipolicy, instances, self.cfg)
+        new = ComputeNewInstanceViolations(ipol, new_ipolicy, instances,
+                                           self.cfg)
         if new:
           violations.update(new)
 
@@ -868,7 +929,7 @@ class LUClusterSetParams(LogicalUnit):
           hv_class = hypervisor.GetHypervisorClass(hv_name)
           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
           hv_class.CheckParameterSyntax(hv_params)
-          CheckHVParams(self, node_list, hv_name, hv_params)
+          CheckHVParams(self, node_uuids, hv_name, hv_params)
 
     self._CheckDiskTemplateConsistency()
 
@@ -883,7 +944,7 @@ class LUClusterSetParams(LogicalUnit):
           new_osp = objects.FillDict(cluster_defaults, hv_params)
           hv_class = hypervisor.GetHypervisorClass(hv_name)
           hv_class.CheckParameterSyntax(new_osp)
-          CheckHVParams(self, node_list, hv_name, new_osp)
+          CheckHVParams(self, node_uuids, hv_name, new_osp)
 
     if self.op.default_iallocator:
       alloc_script = utils.FindFile(self.op.default_iallocator,
@@ -911,20 +972,46 @@ class LUClusterSetParams(LogicalUnit):
                                      " because instance '%s' is using it." %
                                      (instance.disk_template, instance.name))
 
-  def Exec(self, feedback_fn):
-    """Change the parameters of the cluster.
+  def _SetVgName(self, feedback_fn):
+    """Determines and sets the new volume group name.
 
     """
     if self.op.vg_name is not None:
+      if self.op.vg_name and not \
+           utils.IsLvmEnabled(self.cluster.enabled_disk_templates):
+        feedback_fn("Note that you specified a volume group, but did not"
+                    " enable any lvm disk template.")
       new_volume = self.op.vg_name
       if not new_volume:
+        if utils.IsLvmEnabled(self.cluster.enabled_disk_templates):
+          raise errors.OpPrereqError("Cannot unset volume group if lvm-based"
+                                     " disk templates are enabled.")
         new_volume = None
       if new_volume != self.cfg.GetVGName():
         self.cfg.SetVGName(new_volume)
       else:
         feedback_fn("Cluster LVM configuration already in desired"
                     " state, not changing")
+    else:
+      if utils.IsLvmEnabled(self.cluster.enabled_disk_templates) and \
+          not self.cfg.GetVGName():
+        raise errors.OpPrereqError("Please specify a volume group when"
+                                   " enabling lvm-based disk-templates.")
+
+  def Exec(self, feedback_fn):
+    """Change the parameters of the cluster.
+
+    """
+    if self.op.enabled_disk_templates:
+      self.cluster.enabled_disk_templates = \
+        list(set(self.op.enabled_disk_templates))
+
+    self._SetVgName(feedback_fn)
+
     if self.op.drbd_helper is not None:
+      if not constants.DT_DRBD8 in self.cluster.enabled_disk_templates:
+        feedback_fn("Note that you specified a drbd user helper, but did"
+                    " enabled the drbd disk template.")
       new_helper = self.op.drbd_helper
       if not new_helper:
         new_helper = None
@@ -940,9 +1027,6 @@ class LUClusterSetParams(LogicalUnit):
     if self.op.enabled_hypervisors is not None:
       self.cluster.hvparams = self.new_hvparams
       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
-    if self.op.enabled_disk_templates:
-      self.cluster.enabled_disk_templates = \
-        list(set(self.op.enabled_disk_templates))
     if self.op.beparams:
       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
     if self.op.nicparams:
@@ -1020,7 +1104,7 @@ class LUClusterSetParams(LogicalUnit):
       ems = self.cfg.GetUseExternalMipScript()
       feedback_fn("Shutting down master ip on the current netdev (%s)" %
                   self.cluster.master_netdev)
-      result = self.rpc.call_node_deactivate_master_ip(master_params.name,
+      result = self.rpc.call_node_deactivate_master_ip(master_params.uuid,
                                                        master_params, ems)
       if not self.op.force:
         result.Raise("Could not disable the master ip")
@@ -1036,15 +1120,11 @@ class LUClusterSetParams(LogicalUnit):
     if self.op.master_netmask:
       master_params = self.cfg.GetMasterNetworkParameters()
       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
-      result = self.rpc.call_node_change_master_netmask(master_params.name,
-                                                        master_params.netmask,
-                                                        self.op.master_netmask,
-                                                        master_params.ip,
-                                                        master_params.netdev)
-      if result.fail_msg:
-        msg = "Could not change the master IP netmask: %s" % result.fail_msg
-        feedback_fn(msg)
-
+      result = self.rpc.call_node_change_master_netmask(
+                 master_params.uuid, master_params.netmask,
+                 self.op.master_netmask, master_params.ip,
+                 master_params.netdev)
+      result.Warn("Could not change the master IP netmask", feedback_fn)
       self.cluster.master_netmask = self.op.master_netmask
 
     self.cfg.Update(self.cluster, feedback_fn)
@@ -1054,12 +1134,10 @@ class LUClusterSetParams(LogicalUnit):
       feedback_fn("Starting the master ip on the new master netdev (%s)" %
                   self.op.master_netdev)
       ems = self.cfg.GetUseExternalMipScript()
-      result = self.rpc.call_node_activate_master_ip(master_params.name,
+      result = self.rpc.call_node_activate_master_ip(master_params.uuid,
                                                      master_params, ems)
-      if result.fail_msg:
-        self.LogWarning("Could not re-enable the master ip on"
-                        " the master, please restart manually: %s",
-                        result.fail_msg)
+      result.Warn("Could not re-enable the master ip on the master,"
+                  " please restart manually", self.LogWarning)
 
 
 class LUClusterVerify(NoHooksLU):
@@ -1288,23 +1366,24 @@ class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
     # occur, it would never be caught by VerifyGroup, which only acts on
     # nodes/instances reachable from existing node groups.
 
-    dangling_nodes = set(node.name for node in self.all_node_info.values()
+    dangling_nodes = set(node for node in self.all_node_info.values()
                          if node.group not in self.all_group_info)
 
     dangling_instances = {}
     no_node_instances = []
 
     for inst in self.all_inst_info.values():
-      if inst.primary_node in dangling_nodes:
-        dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
+      if inst.primary_node in [node.uuid for node in dangling_nodes]:
+        dangling_instances.setdefault(inst.primary_node, []).append(inst)
       elif inst.primary_node not in self.all_node_info:
-        no_node_instances.append(inst.name)
+        no_node_instances.append(inst)
 
     pretty_dangling = [
         "%s (%s)" %
         (node.name,
-         utils.CommaJoin(dangling_instances.get(node.name,
-                                                ["no instances"])))
+         utils.CommaJoin(
+           self.cfg.GetInstanceNames(
+             dangling_instances.get(node.uuid, ["no instances"]))))
         for node in dangling_nodes]
 
     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
@@ -1315,7 +1394,8 @@ class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
                   None,
                   "the following instances have a non-existing primary-node:"
-                  " %s", utils.CommaJoin(no_node_instances))
+                  " %s", utils.CommaJoin(
+                           self.cfg.GetInstanceNames(no_node_instances)))
 
     return not self.bad
 
@@ -1333,8 +1413,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
   class NodeImage(object):
     """A class representing the logical and physical status of a node.
 
-    @type name: string
-    @ivar name: the node name to which this object refers
+    @type uuid: string
+    @ivar uuid: the node UUID to which this object refers
     @ivar volumes: a structure as returned from
         L{ganeti.backend.GetVolumeList} (runtime)
     @ivar instances: a list of running instances (runtime)
@@ -1366,8 +1446,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @ivar pv_max: size in MiB of the biggest PVs
 
     """
-    def __init__(self, offline=False, name=None, vm_capable=True):
-      self.name = name
+    def __init__(self, offline=False, uuid=None, vm_capable=True):
+      self.uuid = uuid
       self.volumes = {}
       self.instances = []
       self.pinst = []
@@ -1391,11 +1471,11 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
 
     # Get instances in node group; this is unsafe and needs verification later
-    inst_names = \
+    inst_uuids = \
       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
 
     self.needed_locks = {
-      locking.LEVEL_INSTANCE: inst_names,
+      locking.LEVEL_INSTANCE: self.cfg.GetInstanceNames(inst_uuids),
       locking.LEVEL_NODEGROUP: [self.group_uuid],
       locking.LEVEL_NODE: [],
 
@@ -1412,17 +1492,16 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       # Get members of node group; this is unsafe and needs verification later
       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
 
-      all_inst_info = self.cfg.GetAllInstancesInfo()
-
       # In Exec(), we warn about mirrored instances that have primary and
       # secondary living in separate node groups. To fully verify that
       # volumes for these instances are healthy, we will need to do an
       # extra call to their secondaries. We ensure here those nodes will
       # be locked.
-      for inst in self.owned_locks(locking.LEVEL_INSTANCE):
+      for inst_name in self.owned_locks(locking.LEVEL_INSTANCE):
         # Important: access only the instances whose lock is owned
-        if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
-          nodes.update(all_inst_info[inst].secondary_nodes)
+        instance = self.cfg.GetInstanceInfoByName(inst_name)
+        if instance.disk_template in constants.DTS_INT_MIRROR:
+          nodes.update(instance.secondary_nodes)
 
       self.needed_locks[locking.LEVEL_NODE] = nodes
 
@@ -1430,37 +1509,40 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
 
-    group_nodes = set(self.group_info.members)
-    group_instances = \
+    group_node_uuids = set(self.group_info.members)
+    group_inst_uuids = \
       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
 
-    unlocked_nodes = \
-        group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
+    unlocked_node_uuids = \
+        group_node_uuids.difference(self.owned_locks(locking.LEVEL_NODE))
 
-    unlocked_instances = \
-        group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
+    unlocked_inst_uuids = \
+        group_inst_uuids.difference(
+          [self.cfg.GetInstanceInfoByName(name).uuid
+           for name in self.owned_locks(locking.LEVEL_INSTANCE)])
 
-    if unlocked_nodes:
-      raise errors.OpPrereqError("Missing lock for nodes: %s" %
-                                 utils.CommaJoin(unlocked_nodes),
-                                 errors.ECODE_STATE)
+    if unlocked_node_uuids:
+      raise errors.OpPrereqError(
+        "Missing lock for nodes: %s" %
+        utils.CommaJoin(self.cfg.GetNodeNames(unlocked_node_uuids)),
+        errors.ECODE_STATE)
 
-    if unlocked_instances:
-      raise errors.OpPrereqError("Missing lock for instances: %s" %
-                                 utils.CommaJoin(unlocked_instances),
-                                 errors.ECODE_STATE)
+    if unlocked_inst_uuids:
+      raise errors.OpPrereqError(
+        "Missing lock for instances: %s" %
+        utils.CommaJoin(self.cfg.GetInstanceNames(unlocked_inst_uuids)),
+        errors.ECODE_STATE)
 
     self.all_node_info = self.cfg.GetAllNodesInfo()
     self.all_inst_info = self.cfg.GetAllInstancesInfo()
 
-    self.my_node_names = utils.NiceSort(group_nodes)
-    self.my_inst_names = utils.NiceSort(group_instances)
+    self.my_node_uuids = group_node_uuids
+    self.my_node_info = dict((node_uuid, self.all_node_info[node_uuid])
+                             for node_uuid in group_node_uuids)
 
-    self.my_node_info = dict((name, self.all_node_info[name])
-                             for name in self.my_node_names)
-
-    self.my_inst_info = dict((name, self.all_inst_info[name])
-                             for name in self.my_inst_names)
+    self.my_inst_uuids = group_inst_uuids
+    self.my_inst_info = dict((inst_uuid, self.all_inst_info[inst_uuid])
+                             for inst_uuid in group_inst_uuids)
 
     # We detect here the nodes that will need the extra RPC calls for verifying
     # split LV volumes; they should be locked.
@@ -1468,9 +1550,9 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     for inst in self.my_inst_info.values():
       if inst.disk_template in constants.DTS_INT_MIRROR:
-        for nname in inst.all_nodes:
-          if self.all_node_info[nname].group != self.group_uuid:
-            extra_lv_nodes.add(nname)
+        for nuuid in inst.all_nodes:
+          if self.all_node_info[nuuid].group != self.group_uuid:
+            extra_lv_nodes.add(nuuid)
 
     unlocked_lv_nodes = \
         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
@@ -1496,12 +1578,9 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
          reasonable values in the respose)
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     # main result, nresult should be a non-empty dict
     test = not nresult or not isinstance(nresult, dict)
-    _ErrorIf(test, constants.CV_ENODERPC, node,
+    self._ErrorIf(test, constants.CV_ENODERPC, ninfo.name,
                   "unable to verify node: no data returned")
     if test:
       return False
@@ -1512,15 +1591,15 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     test = not (remote_version and
                 isinstance(remote_version, (list, tuple)) and
                 len(remote_version) == 2)
-    _ErrorIf(test, constants.CV_ENODERPC, node,
-             "connection to node returned invalid data")
+    self._ErrorIf(test, constants.CV_ENODERPC, ninfo.name,
+                  "connection to node returned invalid data")
     if test:
       return False
 
     test = local_version != remote_version[0]
-    _ErrorIf(test, constants.CV_ENODEVERSION, node,
-             "incompatible protocol versions: master %s,"
-             " node %s", local_version, remote_version[0])
+    self._ErrorIf(test, constants.CV_ENODEVERSION, ninfo.name,
+                  "incompatible protocol versions: master %s,"
+                  " node %s", local_version, remote_version[0])
     if test:
       return False
 
@@ -1528,7 +1607,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     # full package version
     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
-                  constants.CV_ENODEVERSION, node,
+                  constants.CV_ENODEVERSION, ninfo.name,
                   "software version mismatch: master %s, node %s",
                   constants.RELEASE_VERSION, remote_version[1],
                   code=self.ETYPE_WARNING)
@@ -1537,20 +1616,20 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if ninfo.vm_capable and isinstance(hyp_result, dict):
       for hv_name, hv_result in hyp_result.iteritems():
         test = hv_result is not None
-        _ErrorIf(test, constants.CV_ENODEHV, node,
-                 "hypervisor %s verify failure: '%s'", hv_name, hv_result)
+        self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
+                      "hypervisor %s verify failure: '%s'", hv_name, hv_result)
 
     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
     if ninfo.vm_capable and isinstance(hvp_result, list):
       for item, hv_name, hv_result in hvp_result:
-        _ErrorIf(True, constants.CV_ENODEHV, node,
-                 "hypervisor %s parameter verify failure (source %s): %s",
-                 hv_name, item, hv_result)
+        self._ErrorIf(True, constants.CV_ENODEHV, ninfo.name,
+                      "hypervisor %s parameter verify failure (source %s): %s",
+                      hv_name, item, hv_result)
 
     test = nresult.get(constants.NV_NODESETUP,
                        ["Missing NODESETUP results"])
-    _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
-             "; ".join(test))
+    self._ErrorIf(test, constants.CV_ENODESETUP, ninfo.name,
+                  "node setup error: %s", "; ".join(test))
 
     return True
 
@@ -1565,14 +1644,12 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param nvinfo_endtime: the end time of the RPC call
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     ntime = nresult.get(constants.NV_TIME, None)
     try:
       ntime_merged = utils.MergeTime(ntime)
     except (ValueError, TypeError):
-      _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
+      self._ErrorIf(True, constants.CV_ENODETIME, ninfo.name,
+                    "Node returned invalid time")
       return
 
     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
@@ -1582,9 +1659,9 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     else:
       ntime_diff = None
 
-    _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
-             "Node time diverges by at least %s from master node time",
-             ntime_diff)
+    self._ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, ninfo.name,
+                  "Node time diverges by at least %s from master node time",
+                  ntime_diff)
 
   def _UpdateVerifyNodeLVM(self, ninfo, nresult, vg_name, nimg):
     """Check the node LVM results and update info for cross-node checks.
@@ -1600,25 +1677,43 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if vg_name is None:
       return
 
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     # checks vg existence and size > 20G
     vglist = nresult.get(constants.NV_VGLIST, None)
     test = not vglist
-    _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
+    self._ErrorIf(test, constants.CV_ENODELVM, ninfo.name,
+                  "unable to check volume groups")
     if not test:
       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
                                             constants.MIN_VG_SIZE)
-      _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
+      self._ErrorIf(vgstatus, constants.CV_ENODELVM, ninfo.name, vgstatus)
 
     # Check PVs
     (errmsgs, pvminmax) = CheckNodePVs(nresult, self._exclusive_storage)
     for em in errmsgs:
-      self._Error(constants.CV_ENODELVM, node, em)
+      self._Error(constants.CV_ENODELVM, ninfo.name, em)
     if pvminmax is not None:
       (nimg.pv_min, nimg.pv_max) = pvminmax
 
+  def _VerifyGroupDRBDVersion(self, node_verify_infos):
+    """Check cross-node DRBD version consistency.
+
+    @type node_verify_infos: dict
+    @param node_verify_infos: infos about nodes as returned from the
+      node_verify call.
+
+    """
+    node_versions = {}
+    for node_uuid, ndata in node_verify_infos.items():
+      nresult = ndata.payload
+      version = nresult.get(constants.NV_DRBDVERSION, "Missing DRBD version")
+      node_versions[node_uuid] = version
+
+    if len(set(node_versions.values())) > 1:
+      for node_uuid, version in sorted(node_versions.items()):
+        msg = "DRBD version mismatch: %s" % version
+        self._Error(constants.CV_ENODEDRBDHELPER, node_uuid, msg,
+                    code=self.ETYPE_WARNING)
+
   def _VerifyGroupLVM(self, node_image, vg_name):
     """Check cross-node consistency in LVM.
 
@@ -1631,7 +1726,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if vg_name is None:
       return
 
-    # Only exlcusive storage needs this kind of checks
+    # Only exclusive storage needs this kind of checks
     if not self._exclusive_storage:
       return
 
@@ -1641,13 +1736,14 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     vals = filter((lambda ni: ni.pv_min is not None), node_image.values())
     if not vals:
       return
-    (pvmin, minnode) = min((ni.pv_min, ni.name) for ni in vals)
-    (pvmax, maxnode) = max((ni.pv_max, ni.name) for ni in vals)
+    (pvmin, minnode_uuid) = min((ni.pv_min, ni.uuid) for ni in vals)
+    (pvmax, maxnode_uuid) = max((ni.pv_max, ni.uuid) for ni in vals)
     bad = utils.LvmExclusiveTestBadPvSizes(pvmin, pvmax)
     self._ErrorIf(bad, constants.CV_EGROUPDIFFERENTPVSIZE, self.group_info.name,
                   "PV sizes differ too much in the group; smallest (%s MB) is"
                   " on %s, biggest (%s MB) is on %s",
-                  pvmin, minnode, pvmax, maxnode)
+                  pvmin, self.cfg.GetNodeName(minnode_uuid),
+                  pvmax, self.cfg.GetNodeName(maxnode_uuid))
 
   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
     """Check the node bridges.
@@ -1661,16 +1757,13 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if not bridges:
       return
 
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     missing = nresult.get(constants.NV_BRIDGES, None)
     test = not isinstance(missing, list)
-    _ErrorIf(test, constants.CV_ENODENET, node,
-             "did not return valid bridge information")
+    self._ErrorIf(test, constants.CV_ENODENET, ninfo.name,
+                  "did not return valid bridge information")
     if not test:
-      _ErrorIf(bool(missing), constants.CV_ENODENET, node,
-               "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
+      self._ErrorIf(bool(missing), constants.CV_ENODENET, ninfo.name,
+                    "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
 
   def _VerifyNodeUserScripts(self, ninfo, nresult):
     """Check the results of user scripts presence and executability on the node
@@ -1680,15 +1773,13 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param nresult: the remote results for the node
 
     """
-    node = ninfo.name
-
     test = not constants.NV_USERSCRIPTS in nresult
-    self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
+    self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, ninfo.name,
                   "did not return user scripts information")
 
     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
     if not test:
-      self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
+      self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, ninfo.name,
                     "user scripts not present or not executable: %s" %
                     utils.CommaJoin(sorted(broken_scripts)))
 
@@ -1700,42 +1791,38 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param nresult: the remote results for the node
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     test = constants.NV_NODELIST not in nresult
-    _ErrorIf(test, constants.CV_ENODESSH, node,
-             "node hasn't returned node ssh connectivity data")
+    self._ErrorIf(test, constants.CV_ENODESSH, ninfo.name,
+                  "node hasn't returned node ssh connectivity data")
     if not test:
       if nresult[constants.NV_NODELIST]:
         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
-          _ErrorIf(True, constants.CV_ENODESSH, node,
-                   "ssh communication with node '%s': %s", a_node, a_msg)
+          self._ErrorIf(True, constants.CV_ENODESSH, ninfo.name,
+                        "ssh communication with node '%s': %s", a_node, a_msg)
 
     test = constants.NV_NODENETTEST not in nresult
-    _ErrorIf(test, constants.CV_ENODENET, node,
-             "node hasn't returned node tcp connectivity data")
+    self._ErrorIf(test, constants.CV_ENODENET, ninfo.name,
+                  "node hasn't returned node tcp connectivity data")
     if not test:
       if nresult[constants.NV_NODENETTEST]:
         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
         for anode in nlist:
-          _ErrorIf(True, constants.CV_ENODENET, node,
-                   "tcp communication with node '%s': %s",
-                   anode, nresult[constants.NV_NODENETTEST][anode])
+          self._ErrorIf(True, constants.CV_ENODENET, ninfo.name,
+                        "tcp communication with node '%s': %s",
+                        anode, nresult[constants.NV_NODENETTEST][anode])
 
     test = constants.NV_MASTERIP not in nresult
-    _ErrorIf(test, constants.CV_ENODENET, node,
-             "node hasn't returned node master IP reachability data")
+    self._ErrorIf(test, constants.CV_ENODENET, ninfo.name,
+                  "node hasn't returned node master IP reachability data")
     if not test:
       if not nresult[constants.NV_MASTERIP]:
-        if node == self.master_node:
+        if ninfo.uuid == self.master_node:
           msg = "the master node cannot reach the master IP (not configured?)"
         else:
           msg = "cannot reach the master IP"
-        _ErrorIf(True, constants.CV_ENODENET, node, msg)
+        self._ErrorIf(True, constants.CV_ENODENET, ninfo.name, msg)
 
-  def _VerifyInstance(self, instance, inst_config, node_image,
-                      diskstatus):
+  def _VerifyInstance(self, instance, node_image, diskstatus):
     """Verify an instance.
 
     This function checks to see if the required block devices are
@@ -1743,39 +1830,39 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     state.
 
     """
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-    pnode = inst_config.primary_node
-    pnode_img = node_image[pnode]
+    pnode_uuid = instance.primary_node
+    pnode_img = node_image[pnode_uuid]
     groupinfo = self.cfg.GetAllNodeGroupsInfo()
 
     node_vol_should = {}
-    inst_config.MapLVsByNode(node_vol_should)
+    instance.MapLVsByNode(node_vol_should)
 
     cluster = self.cfg.GetClusterInfo()
     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
                                                             self.group_info)
-    err = ComputeIPolicyInstanceViolation(ipolicy, inst_config, self.cfg)
-    _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, utils.CommaJoin(err),
-             code=self.ETYPE_WARNING)
+    err = ComputeIPolicyInstanceViolation(ipolicy, instance, self.cfg)
+    self._ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance.name,
+                  utils.CommaJoin(err), code=self.ETYPE_WARNING)
 
-    for node in node_vol_should:
-      n_img = node_image[node]
+    for node_uuid in node_vol_should:
+      n_img = node_image[node_uuid]
       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
         # ignore missing volumes on offline or broken nodes
         continue
-      for volume in node_vol_should[node]:
+      for volume in node_vol_should[node_uuid]:
         test = volume not in n_img.volumes
-        _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
-                 "volume %s missing on node %s", volume, node)
-
-    if inst_config.admin_state == constants.ADMINST_UP:
-      test = instance not in pnode_img.instances and not pnode_img.offline
-      _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
-               "instance not running on its primary node %s",
-               pnode)
-      _ErrorIf(pnode_img.offline, constants.CV_EINSTANCEBADNODE, instance,
-               "instance is marked as running and lives on offline node %s",
-               pnode)
+        self._ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance.name,
+                      "volume %s missing on node %s", volume,
+                      self.cfg.GetNodeName(node_uuid))
+
+    if instance.admin_state == constants.ADMINST_UP:
+      test = instance.uuid not in pnode_img.instances and not pnode_img.offline
+      self._ErrorIf(test, constants.CV_EINSTANCEDOWN, instance.name,
+                    "instance not running on its primary node %s",
+                     self.cfg.GetNodeName(pnode_uuid))
+      self._ErrorIf(pnode_img.offline, constants.CV_EINSTANCEBADNODE,
+                    instance.name, "instance is marked as running and lives on"
+                    " offline node %s", self.cfg.GetNodeName(pnode_uuid))
 
     diskdata = [(nname, success, status, idx)
                 for (nname, disks) in diskstatus.items()
@@ -1786,79 +1873,101 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       # node here
       snode = node_image[nname]
       bad_snode = snode.ghost or snode.offline
-      _ErrorIf(inst_config.disks_active and
-               not success and not bad_snode,
-               constants.CV_EINSTANCEFAULTYDISK, instance,
-               "couldn't retrieve status for disk/%s on %s: %s",
-               idx, nname, bdev_status)
-      _ErrorIf((inst_config.disks_active and
-                success and bdev_status.ldisk_status == constants.LDS_FAULTY),
-               constants.CV_EINSTANCEFAULTYDISK, instance,
-               "disk/%s on %s is faulty", idx, nname)
-
-    _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
-             constants.CV_ENODERPC, pnode, "instance %s, connection to"
-             " primary node failed", instance)
-
-    _ErrorIf(len(inst_config.secondary_nodes) > 1,
-             constants.CV_EINSTANCELAYOUT,
-             instance, "instance has multiple secondary nodes: %s",
-             utils.CommaJoin(inst_config.secondary_nodes),
-             code=self.ETYPE_WARNING)
-
-    if inst_config.disk_template not in constants.DTS_EXCL_STORAGE:
-      # Disk template not compatible with exclusive_storage: no instance
-      # node should have the flag set
-      es_flags = rpc.GetExclusiveStorageForNodeNames(self.cfg,
-                                                     inst_config.all_nodes)
-      es_nodes = [n for (n, es) in es_flags.items()
-                  if es]
-      _ErrorIf(es_nodes, constants.CV_EINSTANCEUNSUITABLENODE, instance,
-               "instance has template %s, which is not supported on nodes"
-               " that have exclusive storage set: %s",
-               inst_config.disk_template, utils.CommaJoin(es_nodes))
-
-    if inst_config.disk_template in constants.DTS_INT_MIRROR:
-      instance_nodes = utils.NiceSort(inst_config.all_nodes)
+      self._ErrorIf(instance.disks_active and
+                    not success and not bad_snode,
+                    constants.CV_EINSTANCEFAULTYDISK, instance.name,
+                    "couldn't retrieve status for disk/%s on %s: %s",
+                    idx, self.cfg.GetNodeName(nname), bdev_status)
+
+      if instance.disks_active and success and \
+         (bdev_status.is_degraded or
+          bdev_status.ldisk_status != constants.LDS_OKAY):
+        msg = "disk/%s on %s" % (idx, self.cfg.GetNodeName(nname))
+        if bdev_status.is_degraded:
+          msg += " is degraded"
+        if bdev_status.ldisk_status != constants.LDS_OKAY:
+          msg += "; state is '%s'" % \
+                 constants.LDS_NAMES[bdev_status.ldisk_status]
+
+        self._Error(constants.CV_EINSTANCEFAULTYDISK, instance.name, msg)
+
+    self._ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
+                  constants.CV_ENODERPC, self.cfg.GetNodeName(pnode_uuid),
+                  "instance %s, connection to primary node failed",
+                  instance.name)
+
+    self._ErrorIf(len(instance.secondary_nodes) > 1,
+                  constants.CV_EINSTANCELAYOUT, instance.name,
+                  "instance has multiple secondary nodes: %s",
+                  utils.CommaJoin(instance.secondary_nodes),
+                  code=self.ETYPE_WARNING)
+
+    es_flags = rpc.GetExclusiveStorageForNodes(self.cfg, instance.all_nodes)
+    if any(es_flags.values()):
+      if instance.disk_template not in constants.DTS_EXCL_STORAGE:
+        # Disk template not compatible with exclusive_storage: no instance
+        # node should have the flag set
+        es_nodes = [n
+                    for (n, es) in es_flags.items()
+                    if es]
+        self._Error(constants.CV_EINSTANCEUNSUITABLENODE, instance.name,
+                    "instance has template %s, which is not supported on nodes"
+                    " that have exclusive storage set: %s",
+                    instance.disk_template,
+                    utils.CommaJoin(self.cfg.GetNodeNames(es_nodes)))
+      for (idx, disk) in enumerate(instance.disks):
+        self._ErrorIf(disk.spindles is None,
+                      constants.CV_EINSTANCEMISSINGCFGPARAMETER, instance.name,
+                      "number of spindles not configured for disk %s while"
+                      " exclusive storage is enabled, try running"
+                      " gnt-cluster repair-disk-sizes", idx)
+
+    if instance.disk_template in constants.DTS_INT_MIRROR:
+      instance_nodes = utils.NiceSort(instance.all_nodes)
       instance_groups = {}
 
-      for node in instance_nodes:
-        instance_groups.setdefault(self.all_node_info[node].group,
-                                   []).append(node)
+      for node_uuid in instance_nodes:
+        instance_groups.setdefault(self.all_node_info[node_uuid].group,
+                                   []).append(node_uuid)
 
       pretty_list = [
-        "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
+        "%s (group %s)" % (utils.CommaJoin(self.cfg.GetNodeNames(nodes)),
+                           groupinfo[group].name)
         # Sort so that we always list the primary node first.
         for group, nodes in sorted(instance_groups.items(),
-                                   key=lambda (_, nodes): pnode in nodes,
+                                   key=lambda (_, nodes): pnode_uuid in nodes,
                                    reverse=True)]
 
       self._ErrorIf(len(instance_groups) > 1,
                     constants.CV_EINSTANCESPLITGROUPS,
-                    instance, "instance has primary and secondary nodes in"
+                    instance.name, "instance has primary and secondary nodes in"
                     " different groups: %s", utils.CommaJoin(pretty_list),
                     code=self.ETYPE_WARNING)
 
     inst_nodes_offline = []
-    for snode in inst_config.secondary_nodes:
+    for snode in instance.secondary_nodes:
       s_img = node_image[snode]
-      _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
-               snode, "instance %s, connection to secondary node failed",
-               instance)
+      self._ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
+                    self.cfg.GetNodeName(snode),
+                    "instance %s, connection to secondary node failed",
+                    instance.name)
 
       if s_img.offline:
         inst_nodes_offline.append(snode)
 
     # warn that the instance lives on offline nodes
-    _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
-             "instance has offline secondary node(s) %s",
-             utils.CommaJoin(inst_nodes_offline))
+    self._ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE,
+                  instance.name, "instance has offline secondary node(s) %s",
+                  utils.CommaJoin(self.cfg.GetNodeNames(inst_nodes_offline)))
     # ... or ghost/non-vm_capable nodes
-    for node in inst_config.all_nodes:
-      _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
-               instance, "instance lives on ghost node %s", node)
-      _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
-               instance, "instance lives on non-vm_capable node %s", node)
+    for node_uuid in instance.all_nodes:
+      self._ErrorIf(node_image[node_uuid].ghost, constants.CV_EINSTANCEBADNODE,
+                    instance.name, "instance lives on ghost node %s",
+                    self.cfg.GetNodeName(node_uuid))
+      self._ErrorIf(not node_image[node_uuid].vm_capable,
+                    constants.CV_EINSTANCEBADNODE, instance.name,
+                    "instance lives on non-vm_capable node %s",
+                    self.cfg.GetNodeName(node_uuid))
 
   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
     """Verify if there are any unknown volumes in the cluster.
@@ -1870,19 +1979,20 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param reserved: a FieldSet of reserved volume names
 
     """
-    for node, n_img in node_image.items():
+    for node_uuid, n_img in node_image.items():
       if (n_img.offline or n_img.rpc_fail or n_img.lvm_fail or
-          self.all_node_info[node].group != self.group_uuid):
+          self.all_node_info[node_uuid].group != self.group_uuid):
         # skip non-healthy nodes
         continue
       for volume in n_img.volumes:
-        test = ((node not in node_vol_should or
-                volume not in node_vol_should[node]) and
+        test = ((node_uuid not in node_vol_should or
+                volume not in node_vol_should[node_uuid]) and
                 not reserved.Matches(volume))
-        self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
+        self._ErrorIf(test, constants.CV_ENODEORPHANLV,
+                      self.cfg.GetNodeName(node_uuid),
                       "volume %s is unknown", volume)
 
-  def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
+  def _VerifyNPlusOneMemory(self, node_image, all_insts):
     """Verify N+1 Memory Resilience.
 
     Check that if one single node dies we can still start all the
@@ -1890,7 +2000,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     cluster_info = self.cfg.GetClusterInfo()
-    for node, n_img in node_image.items():
+    for node_uuid, n_img in node_image.items():
       # This code checks that every node which is now listed as
       # secondary has enough memory to host all instances it is
       # supposed to should a single other node in the cluster fail.
@@ -1899,33 +2009,33 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       # WARNING: we currently take into account down instances as well
       # as up ones, considering that even if they're down someone
       # might want to start them even in the event of a node failure.
-      if n_img.offline or self.all_node_info[node].group != self.group_uuid:
+      if n_img.offline or \
+         self.all_node_info[node_uuid].group != self.group_uuid:
         # we're skipping nodes marked offline and nodes in other groups from
         # the N+1 warning, since most likely we don't have good memory
         # infromation from them; we already list instances living on such
         # nodes, and that's enough warning
         continue
       #TODO(dynmem): also consider ballooning out other instances
-      for prinode, instances in n_img.sbp.items():
+      for prinode, inst_uuids in n_img.sbp.items():
         needed_mem = 0
-        for instance in instances:
-          bep = cluster_info.FillBE(instance_cfg[instance])
+        for inst_uuid in inst_uuids:
+          bep = cluster_info.FillBE(all_insts[inst_uuid])
           if bep[constants.BE_AUTO_BALANCE]:
             needed_mem += bep[constants.BE_MINMEM]
         test = n_img.mfree < needed_mem
-        self._ErrorIf(test, constants.CV_ENODEN1, node,
+        self._ErrorIf(test, constants.CV_ENODEN1,
+                      self.cfg.GetNodeName(node_uuid),
                       "not enough memory to accomodate instance failovers"
                       " should node %s fail (%dMiB needed, %dMiB available)",
-                      prinode, needed_mem, n_img.mfree)
+                      self.cfg.GetNodeName(prinode), needed_mem, n_img.mfree)
 
-  @classmethod
-  def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
+  def _VerifyFiles(self, nodes, master_node_uuid, all_nvinfo,
                    (files_all, files_opt, files_mc, files_vm)):
     """Verifies file checksums collected from all nodes.
 
-    @param errorif: Callback for reporting errors
-    @param nodeinfo: List of L{objects.Node} objects
-    @param master_node: Name of master node
+    @param nodes: List of L{objects.Node} objects
+    @param master_node_uuid: UUID of master node
     @param all_nvinfo: RPC results
 
     """
@@ -1933,7 +2043,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     files2nodefn = [
       (files_all, None),
       (files_mc, lambda node: (node.master_candidate or
-                               node.name == master_node)),
+                               node.uuid == master_node_uuid)),
       (files_vm, lambda node: node.vm_capable),
       ]
 
@@ -1941,11 +2051,11 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     nodefiles = {}
     for (files, fn) in files2nodefn:
       if fn is None:
-        filenodes = nodeinfo
+        filenodes = nodes
       else:
-        filenodes = filter(fn, nodeinfo)
+        filenodes = filter(fn, nodes)
       nodefiles.update((filename,
-                        frozenset(map(operator.attrgetter("name"), filenodes)))
+                        frozenset(map(operator.attrgetter("uuid"), filenodes)))
                        for filename in files)
 
     assert set(nodefiles) == (files_all | files_mc | files_vm)
@@ -1953,12 +2063,12 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     fileinfo = dict((filename, {}) for filename in nodefiles)
     ignore_nodes = set()
 
-    for node in nodeinfo:
+    for node in nodes:
       if node.offline:
-        ignore_nodes.add(node.name)
+        ignore_nodes.add(node.uuid)
         continue
 
-      nresult = all_nvinfo[node.name]
+      nresult = all_nvinfo[node.uuid]
 
       if nresult.fail_msg or not nresult.payload:
         node_files = None
@@ -1969,24 +2079,24 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         del fingerprints
 
       test = not (node_files and isinstance(node_files, dict))
-      errorif(test, constants.CV_ENODEFILECHECK, node.name,
-              "Node did not return file checksum data")
+      self._ErrorIf(test, constants.CV_ENODEFILECHECK, node.name,
+                    "Node did not return file checksum data")
       if test:
-        ignore_nodes.add(node.name)
+        ignore_nodes.add(node.uuid)
         continue
 
       # Build per-checksum mapping from filename to nodes having it
       for (filename, checksum) in node_files.items():
         assert filename in nodefiles
-        fileinfo[filename].setdefault(checksum, set()).add(node.name)
+        fileinfo[filename].setdefault(checksum, set()).add(node.uuid)
 
     for (filename, checksums) in fileinfo.items():
       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
 
       # Nodes having the file
-      with_file = frozenset(node_name
-                            for nodes in fileinfo[filename].values()
-                            for node_name in nodes) - ignore_nodes
+      with_file = frozenset(node_uuid
+                            for node_uuids in fileinfo[filename].values()
+                            for node_uuid in node_uuids) - ignore_nodes
 
       expected_nodes = nodefiles[filename] - ignore_nodes
 
@@ -1995,36 +2105,44 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
       if filename in files_opt:
         # All or no nodes
-        errorif(missing_file and missing_file != expected_nodes,
-                constants.CV_ECLUSTERFILECHECK, None,
-                "File %s is optional, but it must exist on all or no"
-                " nodes (not found on %s)",
-                filename, utils.CommaJoin(utils.NiceSort(missing_file)))
+        self._ErrorIf(missing_file and missing_file != expected_nodes,
+                      constants.CV_ECLUSTERFILECHECK, None,
+                      "File %s is optional, but it must exist on all or no"
+                      " nodes (not found on %s)",
+                      filename,
+                      utils.CommaJoin(
+                        utils.NiceSort(
+                          map(self.cfg.GetNodeName, missing_file))))
       else:
-        errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
-                "File %s is missing from node(s) %s", filename,
-                utils.CommaJoin(utils.NiceSort(missing_file)))
+        self._ErrorIf(missing_file, constants.CV_ECLUSTERFILECHECK, None,
+                      "File %s is missing from node(s) %s", filename,
+                      utils.CommaJoin(
+                        utils.NiceSort(
+                          map(self.cfg.GetNodeName, missing_file))))
 
         # Warn if a node has a file it shouldn't
         unexpected = with_file - expected_nodes
-        errorif(unexpected,
-                constants.CV_ECLUSTERFILECHECK, None,
-                "File %s should not exist on node(s) %s",
-                filename, utils.CommaJoin(utils.NiceSort(unexpected)))
+        self._ErrorIf(unexpected,
+                      constants.CV_ECLUSTERFILECHECK, None,
+                      "File %s should not exist on node(s) %s",
+                      filename, utils.CommaJoin(
+                        utils.NiceSort(map(self.cfg.GetNodeName, unexpected))))
 
       # See if there are multiple versions of the file
       test = len(checksums) > 1
       if test:
         variants = ["variant %s on %s" %
-                    (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
-                    for (idx, (checksum, nodes)) in
+                    (idx + 1,
+                     utils.CommaJoin(utils.NiceSort(
+                       map(self.cfg.GetNodeName, node_uuids))))
+                    for (idx, (checksum, node_uuids)) in
                       enumerate(sorted(checksums.items()))]
       else:
         variants = []
 
-      errorif(test, constants.CV_ECLUSTERFILECHECK, None,
-              "File %s found with %s different checksums (%s)",
-              filename, len(checksums), "; ".join(variants))
+      self._ErrorIf(test, constants.CV_ECLUSTERFILECHECK, None,
+                    "File %s found with %s different checksums (%s)",
+                    filename, len(checksums), "; ".join(variants))
 
   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
                       drbd_map):
@@ -2039,55 +2157,53 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     if drbd_helper:
       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
       test = (helper_result is None)
-      _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
-               "no drbd usermode helper returned")
+      self._ErrorIf(test, constants.CV_ENODEDRBDHELPER, ninfo.name,
+                    "no drbd usermode helper returned")
       if helper_result:
         status, payload = helper_result
         test = not status
-        _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
-                 "drbd usermode helper check unsuccessful: %s", payload)
+        self._ErrorIf(test, constants.CV_ENODEDRBDHELPER, ninfo.name,
+                      "drbd usermode helper check unsuccessful: %s", payload)
         test = status and (payload != drbd_helper)
-        _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
-                 "wrong drbd usermode helper: %s", payload)
+        self._ErrorIf(test, constants.CV_ENODEDRBDHELPER, ninfo.name,
+                      "wrong drbd usermode helper: %s", payload)
 
     # compute the DRBD minors
     node_drbd = {}
-    for minor, instance in drbd_map[node].items():
-      test = instance not in instanceinfo
-      _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
-               "ghost instance '%s' in temporary DRBD map", instance)
+    for minor, inst_uuid in drbd_map[ninfo.uuid].items():
+      test = inst_uuid not in instanceinfo
+      self._ErrorIf(test, constants.CV_ECLUSTERCFG, None,
+                    "ghost instance '%s' in temporary DRBD map", inst_uuid)
         # ghost instance should not be running, but otherwise we
         # don't give double warnings (both ghost instance and
         # unallocated minor in use)
       if test:
-        node_drbd[minor] = (instance, False)
+        node_drbd[minor] = (inst_uuid, False)
       else:
-        instance = instanceinfo[instance]
-        node_drbd[minor] = (instance.name, instance.disks_active)
+        instance = instanceinfo[inst_uuid]
+        node_drbd[minor] = (inst_uuid, instance.disks_active)
 
     # and now check them
     used_minors = nresult.get(constants.NV_DRBDLIST, [])
     test = not isinstance(used_minors, (tuple, list))
-    _ErrorIf(test, constants.CV_ENODEDRBD, node,
-             "cannot parse drbd status file: %s", str(used_minors))
+    self._ErrorIf(test, constants.CV_ENODEDRBD, ninfo.name,
+                  "cannot parse drbd status file: %s", str(used_minors))
     if test:
       # we cannot check drbd status
       return
 
-    for minor, (iname, must_exist) in node_drbd.items():
+    for minor, (inst_uuid, must_exist) in node_drbd.items():
       test = minor not in used_minors and must_exist
-      _ErrorIf(test, constants.CV_ENODEDRBD, node,
-               "drbd minor %d of instance %s is not active", minor, iname)
+      self._ErrorIf(test, constants.CV_ENODEDRBD, ninfo.name,
+                    "drbd minor %d of instance %s is not active", minor,
+                    self.cfg.GetInstanceName(inst_uuid))
     for minor in used_minors:
       test = minor not in node_drbd
-      _ErrorIf(test, constants.CV_ENODEDRBD, node,
-               "unallocated drbd minor %d is in use", minor)
+      self._ErrorIf(test, constants.CV_ENODEDRBD, ninfo.name,
+                    "unallocated drbd minor %d is in use", minor)
 
   def _UpdateNodeOS(self, ninfo, nresult, nimg):
     """Builds the node OS structures.
@@ -2098,16 +2214,13 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param nimg: the node image object
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     remote_os = nresult.get(constants.NV_OSLIST, None)
     test = (not isinstance(remote_os, list) or
             not compat.all(isinstance(v, list) and len(v) == 7
                            for v in remote_os))
 
-    _ErrorIf(test, constants.CV_ENODEOS, node,
-             "node hasn't returned valid OS data")
+    self._ErrorIf(test, constants.CV_ENODEOS, ninfo.name,
+                  "node hasn't returned valid OS data")
 
     nimg.os_fail = test
 
@@ -2139,25 +2252,24 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param base: the 'template' node we match against (e.g. from the master)
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
 
     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
     for os_name, os_data in nimg.oslist.items():
       assert os_data, "Empty OS status for OS %s?!" % os_name
       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
-      _ErrorIf(not f_status, constants.CV_ENODEOS, node,
-               "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
-      _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
-               "OS '%s' has multiple entries (first one shadows the rest): %s",
-               os_name, utils.CommaJoin([v[0] for v in os_data]))
+      self._ErrorIf(not f_status, constants.CV_ENODEOS, ninfo.name,
+                    "Invalid OS %s (located at %s): %s",
+                    os_name, f_path, f_diag)
+      self._ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, ninfo.name,
+                    "OS '%s' has multiple entries"
+                    " (first one shadows the rest): %s",
+                    os_name, utils.CommaJoin([v[0] for v in os_data]))
       # comparisons with the 'base' image
       test = os_name not in base.oslist
-      _ErrorIf(test, constants.CV_ENODEOS, node,
-               "Extra OS %s not present on reference node (%s)",
-               os_name, base.name)
+      self._ErrorIf(test, constants.CV_ENODEOS, ninfo.name,
+                    "Extra OS %s not present on reference node (%s)",
+                    os_name, self.cfg.GetNodeName(base.uuid))
       if test:
         continue
       assert base.oslist[os_name], "Base node has empty OS status?"
@@ -2169,16 +2281,18 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
                          ("variants list", f_var, b_var),
                          ("parameters", beautify_params(f_param),
                           beautify_params(b_param))]:
-        _ErrorIf(a != b, constants.CV_ENODEOS, node,
-                 "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
-                 kind, os_name, base.name,
-                 utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
+        self._ErrorIf(a != b, constants.CV_ENODEOS, ninfo.name,
+                      "OS %s for %s differs from reference node %s:"
+                      " [%s] vs. [%s]", kind, os_name,
+                      self.cfg.GetNodeName(base.uuid),
+                      utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
 
     # check any missing OSes
     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
-    _ErrorIf(missing, constants.CV_ENODEOS, node,
-             "OSes present on reference node %s but missing on this node: %s",
-             base.name, utils.CommaJoin(missing))
+    self._ErrorIf(missing, constants.CV_ENODEOS, ninfo.name,
+                  "OSes present on reference node %s"
+                  " but missing on this node: %s",
+                  self.cfg.GetNodeName(base.uuid), utils.CommaJoin(missing))
 
   def _VerifyFileStoragePaths(self, ninfo, nresult, is_master):
     """Verifies paths in L{pathutils.FILE_STORAGE_PATHS_FILE}.
@@ -2190,8 +2304,6 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param is_master: Whether node is the master node
 
     """
-    node = ninfo.name
-
     if (is_master and
         (constants.ENABLE_FILE_STORAGE or
          constants.ENABLE_SHARED_FILE_STORAGE)):
@@ -2199,15 +2311,15 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         fspaths = nresult[constants.NV_FILE_STORAGE_PATHS]
       except KeyError:
         # This should never happen
-        self._ErrorIf(True, constants.CV_ENODEFILESTORAGEPATHS, node,
+        self._ErrorIf(True, constants.CV_ENODEFILESTORAGEPATHS, ninfo.name,
                       "Node did not return forbidden file storage paths")
       else:
-        self._ErrorIf(fspaths, constants.CV_ENODEFILESTORAGEPATHS, node,
+        self._ErrorIf(fspaths, constants.CV_ENODEFILESTORAGEPATHS, ninfo.name,
                       "Found forbidden file storage paths: %s",
                       utils.CommaJoin(fspaths))
     else:
       self._ErrorIf(constants.NV_FILE_STORAGE_PATHS in nresult,
-                    constants.CV_ENODEFILESTORAGEPATHS, node,
+                    constants.CV_ENODEFILESTORAGEPATHS, ninfo.name,
                     "Node should not have returned forbidden file storage"
                     " paths")
 
@@ -2219,13 +2331,13 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param nresult: the remote results for the node
 
     """
-    node = ninfo.name
     # We just have to verify the paths on master and/or master candidates
     # as the oob helper is invoked on the master
     if ((ninfo.master_candidate or ninfo.master_capable) and
         constants.NV_OOB_PATHS in nresult):
       for path_result in nresult[constants.NV_OOB_PATHS]:
-        self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
+        self._ErrorIf(path_result, constants.CV_ENODEOOBPATH,
+                      ninfo.name, path_result)
 
   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
     """Verifies and updates the node volume data.
@@ -2240,19 +2352,16 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param vg_name: the configured VG name
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     nimg.lvm_fail = True
     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
     if vg_name is None:
       pass
     elif isinstance(lvdata, basestring):
-      _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
-               utils.SafeEncode(lvdata))
+      self._ErrorIf(True, constants.CV_ENODELVM, ninfo.name,
+                    "LVM problem on node: %s", utils.SafeEncode(lvdata))
     elif not isinstance(lvdata, dict):
-      _ErrorIf(True, constants.CV_ENODELVM, node,
-               "rpc call to node failed (lvlist)")
+      self._ErrorIf(True, constants.CV_ENODELVM, ninfo.name,
+                    "rpc call to node failed (lvlist)")
     else:
       nimg.volumes = lvdata
       nimg.lvm_fail = False
@@ -2278,7 +2387,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if test:
       nimg.hyp_fail = True
     else:
-      nimg.instances = idata
+      nimg.instances = [inst.uuid for (_, inst) in
+                        self.cfg.GetMultiInstanceInfoByName(idata)]
 
   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
     """Verifies and computes a node information map
@@ -2290,43 +2400,40 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     @param vg_name: the configured VG name
 
     """
-    node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     # try to read free memory (from the hypervisor)
     hv_info = nresult.get(constants.NV_HVINFO, None)
     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
-    _ErrorIf(test, constants.CV_ENODEHV, node,
-             "rpc call to node failed (hvinfo)")
+    self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
+                  "rpc call to node failed (hvinfo)")
     if not test:
       try:
         nimg.mfree = int(hv_info["memory_free"])
       except (ValueError, TypeError):
-        _ErrorIf(True, constants.CV_ENODERPC, node,
-                 "node returned invalid nodeinfo, check hypervisor")
+        self._ErrorIf(True, constants.CV_ENODERPC, ninfo.name,
+                      "node returned invalid nodeinfo, check hypervisor")
 
     # FIXME: devise a free space model for file based instances as well
     if vg_name is not None:
       test = (constants.NV_VGLIST not in nresult or
               vg_name not in nresult[constants.NV_VGLIST])
-      _ErrorIf(test, constants.CV_ENODELVM, node,
-               "node didn't return data for the volume group '%s'"
-               " - it is either missing or broken", vg_name)
+      self._ErrorIf(test, constants.CV_ENODELVM, ninfo.name,
+                    "node didn't return data for the volume group '%s'"
+                    " - it is either missing or broken", vg_name)
       if not test:
         try:
           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
         except (ValueError, TypeError):
-          _ErrorIf(True, constants.CV_ENODERPC, node,
-                   "node returned invalid LVM info, check LVM status")
+          self._ErrorIf(True, constants.CV_ENODERPC, ninfo.name,
+                        "node returned invalid LVM info, check LVM status")
 
-  def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
+  def _CollectDiskInfo(self, node_uuids, node_image, instanceinfo):
     """Gets per-disk status information for all instances.
 
-    @type nodelist: list of strings
-    @param nodelist: Node names
-    @type node_image: dict of (name, L{objects.Node})
+    @type node_uuids: list of strings
+    @param node_uuids: Node UUIDs
+    @type node_image: dict of (UUID, L{objects.Node})
     @param node_image: Node objects
-    @type instanceinfo: dict of (name, L{objects.Instance})
+    @type instanceinfo: dict of (UUID, L{objects.Instance})
     @param instanceinfo: Instance objects
     @rtype: {instance: {node: [(succes, payload)]}}
     @return: a dictionary of per-instance dictionaries with nodes as
@@ -2334,36 +2441,35 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         list of tuples (success, payload)
 
     """
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
-
     node_disks = {}
     node_disks_devonly = {}
     diskless_instances = set()
     diskless = constants.DT_DISKLESS
 
-    for nname in nodelist:
-      node_instances = list(itertools.chain(node_image[nname].pinst,
-                                            node_image[nname].sinst))
-      diskless_instances.update(inst for inst in node_instances
-                                if instanceinfo[inst].disk_template == diskless)
-      disks = [(inst, disk)
-               for inst in node_instances
-               for disk in instanceinfo[inst].disks]
+    for nuuid in node_uuids:
+      node_inst_uuids = list(itertools.chain(node_image[nuuid].pinst,
+                                             node_image[nuuid].sinst))
+      diskless_instances.update(uuid for uuid in node_inst_uuids
+                                if instanceinfo[uuid].disk_template == diskless)
+      disks = [(inst_uuid, disk)
+               for inst_uuid in node_inst_uuids
+               for disk in instanceinfo[inst_uuid].disks]
 
       if not disks:
         # No need to collect data
         continue
 
-      node_disks[nname] = disks
+      node_disks[nuuid] = disks
 
       # _AnnotateDiskParams makes already copies of the disks
       devonly = []
-      for (inst, dev) in disks:
-        (anno_disk,) = AnnotateDiskParams(instanceinfo[inst], [dev], self.cfg)
-        self.cfg.SetDiskID(anno_disk, nname)
+      for (inst_uuid, dev) in disks:
+        (anno_disk,) = AnnotateDiskParams(instanceinfo[inst_uuid], [dev],
+                                          self.cfg)
+        self.cfg.SetDiskID(anno_disk, nuuid)
         devonly.append(anno_disk)
 
-      node_disks_devonly[nname] = devonly
+      node_disks_devonly[nuuid] = devonly
 
     assert len(node_disks) == len(node_disks_devonly)
 
@@ -2375,16 +2481,17 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     instdisk = {}
 
-    for (nname, nres) in result.items():
-      disks = node_disks[nname]
+    for (nuuid, nres) in result.items():
+      node = self.cfg.GetNodeInfo(nuuid)
+      disks = node_disks[node.uuid]
 
       if nres.offline:
         # No data from this node
         data = len(disks) * [(False, "node offline")]
       else:
         msg = nres.fail_msg
-        _ErrorIf(msg, constants.CV_ENODERPC, nname,
-                 "while getting disk information: %s", msg)
+        self._ErrorIf(msg, constants.CV_ENODERPC, node.name,
+                      "while getting disk information: %s", msg)
         if msg:
           # No data from this node
           data = len(disks) * [(False, msg)]
@@ -2395,23 +2502,24 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
               data.append(i)
             else:
               logging.warning("Invalid result from node %s, entry %d: %s",
-                              nname, idx, i)
+                              node.name, idx, i)
               data.append((False, "Invalid result from the remote node"))
 
-      for ((inst, _), status) in zip(disks, data):
-        instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
+      for ((inst_uuid, _), status) in zip(disks, data):
+        instdisk.setdefault(inst_uuid, {}).setdefault(node.uuid, []) \
+          .append(status)
 
     # Add empty entries for diskless instances.
-    for inst in diskless_instances:
-      assert inst not in instdisk
-      instdisk[inst] = {}
+    for inst_uuid in diskless_instances:
+      assert inst_uuid not in instdisk
+      instdisk[inst_uuid] = {}
 
     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
-                      len(nnames) <= len(instanceinfo[inst].all_nodes) and
+                      len(nuuids) <= len(instanceinfo[inst].all_nodes) and
                       compat.all(isinstance(s, (tuple, list)) and
                                  len(s) == 2 for s in statuses)
-                      for inst, nnames in instdisk.items()
-                      for nname, statuses in nnames.items())
+                      for inst, nuuids in instdisk.items()
+                      for nuuid, statuses in nuuids.items())
     if __debug__:
       instdisk_keys = set(instdisk)
       instanceinfo_keys = set(instanceinfo)
@@ -2475,7 +2583,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     """Build hooks nodes.
 
     """
-    return ([], self.my_node_names)
+    return ([], list(self.my_node_info.keys()))
 
   def Exec(self, feedback_fn):
     """Verify integrity of the node group, performing various test on nodes.
@@ -2484,13 +2592,12 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     # This method has too many local variables. pylint: disable=R0914
     feedback_fn("* Verifying group '%s'" % self.group_info.name)
 
-    if not self.my_node_names:
+    if not self.my_node_uuids:
       # empty node group
       feedback_fn("* Empty node group, skipping verification")
       return True
 
     self.bad = False
-    _ErrorIf = self._ErrorIf # pylint: disable=C0103
     verbose = self.op.verbose
     self._feedback_fn = feedback_fn
 
@@ -2498,7 +2605,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     drbd_helper = self.cfg.GetDRBDHelper()
     cluster = self.cfg.GetClusterInfo()
     hypervisors = cluster.enabled_hypervisors
-    node_data_list = [self.my_node_info[name] for name in self.my_node_names]
+    node_data_list = self.my_node_info.values()
 
     i_non_redundant = [] # Non redundant instances
     i_non_a_balanced = [] # Non auto-balanced instances
@@ -2513,10 +2620,10 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     filemap = ComputeAncillaryFiles(cluster, False)
 
     # do local checksums
-    master_node = self.master_node = self.cfg.GetMasterNode()
+    master_node_uuid = self.master_node = self.cfg.GetMasterNode()
     master_ip = self.cfg.GetMasterIP()
 
-    feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
+    feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_uuids))
 
     user_scripts = []
     if self.cfg.GetUseExternalMipScript():
@@ -2542,7 +2649,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
       constants.NV_NODESETUP: None,
       constants.NV_TIME: None,
-      constants.NV_MASTERIP: (master_node, master_ip),
+      constants.NV_MASTERIP: (self.cfg.GetMasterNodeName(), master_ip),
       constants.NV_OSLIST: None,
       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
       constants.NV_USERSCRIPTS: user_scripts,
@@ -2554,12 +2661,14 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       node_verify_param[constants.NV_PVLIST] = [vg_name]
 
     if drbd_helper:
+      node_verify_param[constants.NV_DRBDVERSION] = None
       node_verify_param[constants.NV_DRBDLIST] = None
       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
 
     if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
       # Load file storage paths only from master node
-      node_verify_param[constants.NV_FILE_STORAGE_PATHS] = master_node
+      node_verify_param[constants.NV_FILE_STORAGE_PATHS] = \
+        self.cfg.GetMasterNodeName()
 
     # bridge checks
     # FIXME: this needs to be changed per node-group, not cluster-wide
@@ -2567,8 +2676,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
       bridges.add(default_nicpp[constants.NIC_LINK])
-    for instance in self.my_inst_info.values():
-      for nic in instance.nics:
+    for inst_uuid in self.my_inst_info.values():
+      for nic in inst_uuid.nics:
         full_nic = cluster.SimpleFillNIC(nic.nicparams)
         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
           bridges.add(full_nic[constants.NIC_LINK])
@@ -2577,8 +2686,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       node_verify_param[constants.NV_BRIDGES] = list(bridges)
 
     # Build our expected cluster state
-    node_image = dict((node.name, self.NodeImage(offline=node.offline,
-                                                 name=node.name,
+    node_image = dict((node.uuid, self.NodeImage(offline=node.offline,
+                                                 uuid=node.uuid,
                                                  vm_capable=node.vm_capable))
                       for node in node_data_list)
 
@@ -2592,30 +2701,31 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     if oob_paths:
       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
 
-    for instance in self.my_inst_names:
-      inst_config = self.my_inst_info[instance]
-      if inst_config.admin_state == constants.ADMINST_OFFLINE:
+    for inst_uuid in self.my_inst_uuids:
+      instance = self.my_inst_info[inst_uuid]
+      if instance.admin_state == constants.ADMINST_OFFLINE:
         i_offline += 1
 
-      for nname in inst_config.all_nodes:
-        if nname not in node_image:
-          gnode = self.NodeImage(name=nname)
-          gnode.ghost = (nname not in self.all_node_info)
-          node_image[nname] = gnode
+      for nuuid in instance.all_nodes:
+        if nuuid not in node_image:
+          gnode = self.NodeImage(uuid=nuuid)
+          gnode.ghost = (nuuid not in self.all_node_info)
+          node_image[nuuid] = gnode
 
-      inst_config.MapLVsByNode(node_vol_should)
+      instance.MapLVsByNode(node_vol_should)
 
-      pnode = inst_config.primary_node
-      node_image[pnode].pinst.append(instance)
+      pnode = instance.primary_node
+      node_image[pnode].pinst.append(instance.uuid)
 
-      for snode in inst_config.secondary_nodes:
+      for snode in instance.secondary_nodes:
         nimg = node_image[snode]
-        nimg.sinst.append(instance)
+        nimg.sinst.append(instance.uuid)
         if pnode not in nimg.sbp:
           nimg.sbp[pnode] = []
-        nimg.sbp[pnode].append(instance)
+        nimg.sbp[pnode].append(instance.uuid)
 
-    es_flags = rpc.GetExclusiveStorageForNodeNames(self.cfg, self.my_node_names)
+    es_flags = rpc.GetExclusiveStorageForNodes(self.cfg,
+                                               self.my_node_info.keys())
     # The value of exclusive_storage should be the same across the group, so if
     # it's True for at least a node, we act as if it were set for all the nodes
     self._exclusive_storage = compat.any(es_flags.values())
@@ -2630,72 +2740,73 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     # time before and after executing the request, we can at least have a time
     # window.
     nvinfo_starttime = time.time()
-    all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
+    all_nvinfo = self.rpc.call_node_verify(self.my_node_uuids,
                                            node_verify_param,
-                                           self.cfg.GetClusterName())
+                                           self.cfg.GetClusterName(),
+                                           self.cfg.GetClusterInfo().hvparams)
     nvinfo_endtime = time.time()
 
     if self.extra_lv_nodes and vg_name is not None:
       extra_lv_nvinfo = \
           self.rpc.call_node_verify(self.extra_lv_nodes,
                                     {constants.NV_LVLIST: vg_name},
-                                    self.cfg.GetClusterName())
+                                    self.cfg.GetClusterName(),
+                                    self.cfg.GetClusterInfo().hvparams)
     else:
       extra_lv_nvinfo = {}
 
     all_drbd_map = self.cfg.ComputeDRBDMap()
 
     feedback_fn("* Gathering disk information (%s nodes)" %
-                len(self.my_node_names))
-    instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
+                len(self.my_node_uuids))
+    instdisk = self._CollectDiskInfo(self.my_node_info.keys(), node_image,
                                      self.my_inst_info)
 
     feedback_fn("* Verifying configuration file consistency")
 
     # If not all nodes are being checked, we need to make sure the master node
     # and a non-checked vm_capable node are in the list.
-    absent_nodes = set(self.all_node_info).difference(self.my_node_info)
-    if absent_nodes:
+    absent_node_uuids = set(self.all_node_info).difference(self.my_node_info)
+    if absent_node_uuids:
       vf_nvinfo = all_nvinfo.copy()
       vf_node_info = list(self.my_node_info.values())
-      additional_nodes = []
-      if master_node not in self.my_node_info:
-        additional_nodes.append(master_node)
-        vf_node_info.append(self.all_node_info[master_node])
+      additional_node_uuids = []
+      if master_node_uuid not in self.my_node_info:
+        additional_node_uuids.append(master_node_uuid)
+        vf_node_info.append(self.all_node_info[master_node_uuid])
       # Add the first vm_capable node we find which is not included,
       # excluding the master node (which we already have)
-      for node in absent_nodes:
-        nodeinfo = self.all_node_info[node]
+      for node_uuid in absent_node_uuids:
+        nodeinfo = self.all_node_info[node_uuid]
         if (nodeinfo.vm_capable and not nodeinfo.offline and
-            node != master_node):
-          additional_nodes.append(node)
-          vf_node_info.append(self.all_node_info[node])
+            node_uuid != master_node_uuid):
+          additional_node_uuids.append(node_uuid)
+          vf_node_info.append(self.all_node_info[node_uuid])
           break
       key = constants.NV_FILELIST
-      vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
-                                                 {key: node_verify_param[key]},
-                                                 self.cfg.GetClusterName()))
+      vf_nvinfo.update(self.rpc.call_node_verify(
+         additional_node_uuids, {key: node_verify_param[key]},
+         self.cfg.GetClusterName(), self.cfg.GetClusterInfo().hvparams))
     else:
       vf_nvinfo = all_nvinfo
       vf_node_info = self.my_node_info.values()
 
-    self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
+    self._VerifyFiles(vf_node_info, master_node_uuid, vf_nvinfo, filemap)
 
     feedback_fn("* Verifying node status")
 
     refos_img = None
 
     for node_i in node_data_list:
-      node = node_i.name
-      nimg = node_image[node]
+      nimg = node_image[node_i.uuid]
 
       if node_i.offline:
         if verbose:
-          feedback_fn("* Skipping offline node %s" % (node,))
+          feedback_fn("* Skipping offline node %s" % (node_i.name,))
         n_offline += 1
         continue
 
-      if node == master_node:
+      if node_i.uuid == master_node_uuid:
         ntype = "master"
       elif node_i.master_candidate:
         ntype = "master candidate"
@@ -2705,16 +2816,16 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       else:
         ntype = "regular"
       if verbose:
-        feedback_fn("* Verifying node %s (%s)" % (node, ntype))
+        feedback_fn("* Verifying node %s (%s)" % (node_i.name, ntype))
 
-      msg = all_nvinfo[node].fail_msg
-      _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
-               msg)
+      msg = all_nvinfo[node_i.uuid].fail_msg
+      self._ErrorIf(msg, constants.CV_ENODERPC, node_i.name,
+                    "while contacting node: %s", msg)
       if msg:
         nimg.rpc_fail = True
         continue
 
-      nresult = all_nvinfo[node].payload
+      nresult = all_nvinfo[node_i.uuid].payload
 
       nimg.call_ok = self._VerifyNode(node_i, nresult)
       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
@@ -2722,7 +2833,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       self._VerifyNodeUserScripts(node_i, nresult)
       self._VerifyOob(node_i, nresult)
       self._VerifyFileStoragePaths(node_i, nresult,
-                                   node == master_node)
+                                   node_i.uuid == master_node_uuid)
 
       if nimg.vm_capable:
         self._UpdateVerifyNodeLVM(node_i, nresult, vg_name, nimg)
@@ -2740,38 +2851,39 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
           self._VerifyNodeOS(node_i, nimg, refos_img)
         self._VerifyNodeBridges(node_i, nresult, bridges)
 
-        # Check whether all running instancies are primary for the node. (This
+        # Check whether all running instances are primary for the node. (This
         # can no longer be done from _VerifyInstance below, since some of the
         # wrong instances could be from other node groups.)
-        non_primary_inst = set(nimg.instances).difference(nimg.pinst)
+        non_primary_inst_uuids = set(nimg.instances).difference(nimg.pinst)
 
-        for inst in non_primary_inst:
-          test = inst in self.all_inst_info
-          _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
-                   "instance should not run on node %s", node_i.name)
-          _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
-                   "node is running unknown instance %s", inst)
+        for inst_uuid in non_primary_inst_uuids:
+          test = inst_uuid in self.all_inst_info
+          self._ErrorIf(test, constants.CV_EINSTANCEWRONGNODE,
+                        self.cfg.GetInstanceName(inst_uuid),
+                        "instance should not run on node %s", node_i.name)
+          self._ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
+                        "node is running unknown instance %s", inst_uuid)
 
+    self._VerifyGroupDRBDVersion(all_nvinfo)
     self._VerifyGroupLVM(node_image, vg_name)
 
-    for node, result in extra_lv_nvinfo.items():
-      self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
-                              node_image[node], vg_name)
+    for node_uuid, result in extra_lv_nvinfo.items():
+      self._UpdateNodeVolumes(self.all_node_info[node_uuid], result.payload,
+                              node_image[node_uuid], vg_name)
 
     feedback_fn("* Verifying instance status")
-    for instance in self.my_inst_names:
+    for inst_uuid in self.my_inst_uuids:
+      instance = self.my_inst_info[inst_uuid]
       if verbose:
-        feedback_fn("* Verifying instance %s" % instance)
-      inst_config = self.my_inst_info[instance]
-      self._VerifyInstance(instance, inst_config, node_image,
-                           instdisk[instance])
+        feedback_fn("* Verifying instance %s" % instance.name)
+      self._VerifyInstance(instance, node_image, instdisk[inst_uuid])
 
       # If the instance is non-redundant we cannot survive losing its primary
       # node, so we are not N+1 compliant.
-      if inst_config.disk_template not in constants.DTS_MIRRORED:
+      if instance.disk_template not in constants.DTS_MIRRORED:
         i_non_redundant.append(instance)
 
-      if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
+      if not cluster.FillBE(instance)[constants.BE_AUTO_BALANCE]:
         i_non_a_balanced.append(instance)
 
     feedback_fn("* Verifying orphan volumes")
@@ -2780,11 +2892,11 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     # We will get spurious "unknown volume" warnings if any node of this group
     # is secondary for an instance whose primary is in another group. To avoid
     # them, we find these instances and add their volumes to node_vol_should.
-    for inst in self.all_inst_info.values():
-      for secondary in inst.secondary_nodes:
+    for instance in self.all_inst_info.values():
+      for secondary in instance.secondary_nodes:
         if (secondary in self.my_node_info
-            and inst.name not in self.my_inst_info):
-          inst.MapLVsByNode(node_vol_should)
+            and instance.name not in self.my_inst_info):
+          instance.MapLVsByNode(node_vol_should)
           break
 
     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
@@ -2830,7 +2942,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     """
     # We only really run POST phase hooks, only for non-empty groups,
     # and are only interested in their results
-    if not self.my_node_names:
+    if not self.my_node_uuids:
       # empty node group
       pass
     elif phase == constants.HOOKS_PHASE_POST:
index 4759736..7621f63 100644 (file)
@@ -48,31 +48,54 @@ CAN_CHANGE_INSTANCE_OFFLINE = (frozenset(INSTANCE_DOWN) | frozenset([
   ]))
 
 
-def _ExpandItemName(fn, name, kind):
+def _ExpandItemName(expand_fn, name, kind):
   """Expand an item name.
 
-  @param fn: the function to use for expansion
+  @param expand_fn: the function to use for expansion
   @param name: requested item name
   @param kind: text description ('Node' or 'Instance')
-  @return: the resolved (full) name
+  @return: the result of the expand_fn, if successful
   @raise errors.OpPrereqError: if the item is not found
 
   """
-  full_name = fn(name)
+  full_name = expand_fn(name)
   if full_name is None:
     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
                                errors.ECODE_NOENT)
   return full_name
 
 
-def ExpandInstanceName(cfg, name):
+def ExpandInstanceUuidAndName(cfg, expected_uuid, name):
   """Wrapper over L{_ExpandItemName} for instance."""
-  return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
+  (uuid, full_name) = _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
+  if expected_uuid is not None and uuid != expected_uuid:
+    raise errors.OpPrereqError(
+      "The instances UUID '%s' does not match the expected UUID '%s' for"
+      " instance '%s'. Maybe the instance changed since you submitted this"
+      " job." % (uuid, expected_uuid, full_name), errors.ECODE_NOTUNIQUE)
+  return (uuid, full_name)
 
 
-def ExpandNodeName(cfg, name):
-  """Wrapper over L{_ExpandItemName} for nodes."""
-  return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
+def ExpandNodeUuidAndName(cfg, expected_uuid, name):
+  """Expand a short node name into the node UUID and full name.
+
+  @type cfg: L{config.ConfigWriter}
+  @param cfg: The cluster configuration
+  @type expected_uuid: string
+  @param expected_uuid: expected UUID for the node (or None if there is no
+        expectation). If it does not match, a L{errors.OpPrereqError} is
+        raised.
+  @type name: string
+  @param name: the short node name
+
+  """
+  (uuid, full_name) = _ExpandItemName(cfg.ExpandNodeName, name, "Node")
+  if expected_uuid is not None and uuid != expected_uuid:
+    raise errors.OpPrereqError(
+      "The nodes UUID '%s' does not match the expected UUID '%s' for node"
+      " '%s'. Maybe the node changed since you submitted this job." %
+      (uuid, expected_uuid, full_name), errors.ECODE_NOTUNIQUE)
+  return (uuid, full_name)
 
 
 def ShareAll():
@@ -82,66 +105,71 @@ def ShareAll():
   return dict.fromkeys(locking.LEVELS, 1)
 
 
-def CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
+def CheckNodeGroupInstances(cfg, group_uuid, owned_instance_names):
   """Checks if the instances in a node group are still correct.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: The cluster configuration
   @type group_uuid: string
   @param group_uuid: Node group UUID
-  @type owned_instances: set or frozenset
-  @param owned_instances: List of currently owned instances
+  @type owned_instance_names: set or frozenset
+  @param owned_instance_names: List of currently owned instances
 
   """
-  wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
-  if owned_instances != wanted_instances:
+  wanted_instances = frozenset(cfg.GetInstanceNames(
+                                 cfg.GetNodeGroupInstances(group_uuid)))
+  if owned_instance_names != wanted_instances:
     raise errors.OpPrereqError("Instances in node group '%s' changed since"
                                " locks were acquired, wanted '%s', have '%s';"
                                " retry the operation" %
                                (group_uuid,
                                 utils.CommaJoin(wanted_instances),
-                                utils.CommaJoin(owned_instances)),
+                                utils.CommaJoin(owned_instance_names)),
                                errors.ECODE_STATE)
 
   return wanted_instances
 
 
-def GetWantedNodes(lu, nodes):
+def GetWantedNodes(lu, short_node_names):
   """Returns list of checked and expanded node names.
 
   @type lu: L{LogicalUnit}
   @param lu: the logical unit on whose behalf we execute
-  @type nodes: list
-  @param nodes: list of node names or None for all nodes
-  @rtype: list
-  @return: the list of nodes, sorted
+  @type short_node_names: list
+  @param short_node_names: list of node names or None for all nodes
+  @rtype: tuple of lists
+  @return: tupe with (list of node UUIDs, list of node names)
   @raise errors.ProgrammerError: if the nodes parameter is wrong type
 
   """
-  if nodes:
-    return [ExpandNodeName(lu.cfg, name) for name in nodes]
+  if short_node_names:
+    node_uuids = [ExpandNodeUuidAndName(lu.cfg, None, name)[0]
+                  for name in short_node_names]
+  else:
+    node_uuids = lu.cfg.GetNodeList()
 
-  return utils.NiceSort(lu.cfg.GetNodeList())
+  return (node_uuids, [lu.cfg.GetNodeName(uuid) for uuid in node_uuids])
 
 
-def GetWantedInstances(lu, instances):
+def GetWantedInstances(lu, short_inst_names):
   """Returns list of checked and expanded instance names.
 
   @type lu: L{LogicalUnit}
   @param lu: the logical unit on whose behalf we execute
-  @type instances: list
-  @param instances: list of instance names or None for all instances
-  @rtype: list
-  @return: the list of instances, sorted
+  @type short_inst_names: list
+  @param short_inst_names: list of instance names or None for all instances
+  @rtype: tuple of lists
+  @return: tuple of (instance UUIDs, instance names)
   @raise errors.OpPrereqError: if the instances parameter is wrong type
   @raise errors.OpPrereqError: if any of the passed instances is not found
 
   """
-  if instances:
-    wanted = [ExpandInstanceName(lu.cfg, name) for name in instances]
+  if short_inst_names:
+    inst_uuids = [ExpandInstanceUuidAndName(lu.cfg, None, name)[0]
+                  for name in short_inst_names]
   else:
-    wanted = utils.NiceSort(lu.cfg.GetInstanceList())
-  return wanted
+    inst_uuids = lu.cfg.GetInstanceList()
+  return (inst_uuids, [lu.cfg.GetInstanceName(uuid) for uuid in inst_uuids])
 
 
 def RunPostHook(lu, node_name):
@@ -150,42 +178,33 @@ def RunPostHook(lu, node_name):
   """
   hm = lu.proc.BuildHooksManager(lu)
   try:
-    hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
+    hm.RunPhase(constants.HOOKS_PHASE_POST, node_names=[node_name])
   except Exception, err: # pylint: disable=W0703
     lu.LogWarning("Errors occurred running hooks on %s: %s",
                   node_name, err)
 
 
-def RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
+def RedistributeAncillaryFiles(lu):
   """Distribute additional files which are part of the cluster configuration.
 
   ConfigWriter takes care of distributing the config and ssconf files, but
   there are more files which should be distributed to all nodes. This function
   makes sure those are copied.
 
-  @param lu: calling logical unit
-  @param additional_nodes: list of nodes not in the config to distribute to
-  @type additional_vm: boolean
-  @param additional_vm: whether the additional nodes are vm-capable or not
-
   """
   # Gather target nodes
   cluster = lu.cfg.GetClusterInfo()
   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
 
-  online_nodes = lu.cfg.GetOnlineNodeList()
-  online_set = frozenset(online_nodes)
-  vm_nodes = list(online_set.intersection(lu.cfg.GetVmCapableNodeList()))
-
-  if additional_nodes is not None:
-    online_nodes.extend(additional_nodes)
-    if additional_vm:
-      vm_nodes.extend(additional_nodes)
+  online_node_uuids = lu.cfg.GetOnlineNodeList()
+  online_node_uuid_set = frozenset(online_node_uuids)
+  vm_node_uuids = list(online_node_uuid_set.intersection(
+                         lu.cfg.GetVmCapableNodeList()))
 
   # Never distribute to master node
-  for nodelist in [online_nodes, vm_nodes]:
-    if master_info.name in nodelist:
-      nodelist.remove(master_info.name)
+  for node_uuids in [online_node_uuids, vm_node_uuids]:
+    if master_info.uuid in node_uuids:
+      node_uuids.remove(master_info.uuid)
 
   # Gather file lists
   (files_all, _, files_mc, files_vm) = \
@@ -197,14 +216,14 @@ def RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
   assert not files_mc, "Master candidates not handled in this function"
 
   filemap = [
-    (online_nodes, files_all),
-    (vm_nodes, files_vm),
+    (online_node_uuids, files_all),
+    (vm_node_uuids, files_vm),
     ]
 
   # Upload the files
-  for (node_list, files) in filemap:
+  for (node_uuids, files) in filemap:
     for fname in files:
-      UploadHelper(lu, node_list, fname)
+      UploadHelper(lu, node_uuids, fname)
 
 
 def ComputeAncillaryFiles(cluster, redist):
@@ -286,17 +305,17 @@ def ComputeAncillaryFiles(cluster, redist):
   return (files_all, files_opt, files_mc, files_vm)
 
 
-def UploadHelper(lu, nodes, fname):
+def UploadHelper(lu, node_uuids, fname):
   """Helper for uploading a file and showing warnings.
 
   """
   if os.path.exists(fname):
-    result = lu.rpc.call_upload_file(nodes, fname)
-    for to_node, to_result in result.items():
+    result = lu.rpc.call_upload_file(node_uuids, fname)
+    for to_node_uuids, to_result in result.items():
       msg = to_result.fail_msg
       if msg:
         msg = ("Copy of file %s to node %s failed: %s" %
-               (fname, to_node, msg))
+               (fname, lu.cfg.GetNodeName(to_node_uuids), msg))
         lu.LogWarning(msg)
 
 
@@ -345,7 +364,7 @@ def MergeAndVerifyDiskState(op_input, obj_input):
   return None
 
 
-def CheckOSParams(lu, required, nodenames, osname, osparams):
+def CheckOSParams(lu, required, node_uuids, osname, osparams):
   """OS parameters validation.
 
   @type lu: L{LogicalUnit}
@@ -353,8 +372,8 @@ def CheckOSParams(lu, required, nodenames, osname, osparams):
   @type required: boolean
   @param required: whether the validation should fail if the OS is not
       found
-  @type nodenames: list
-  @param nodenames: the list of nodes on which we should check
+  @type node_uuids: list
+  @param node_uuids: the list of nodes on which we should check
   @type osname: string
   @param osname: the name of the hypervisor we should use
   @type osparams: dict
@@ -362,20 +381,21 @@ def CheckOSParams(lu, required, nodenames, osname, osparams):
   @raise errors.OpPrereqError: if the parameters are not valid
 
   """
-  nodenames = _FilterVmNodes(lu, nodenames)
-  result = lu.rpc.call_os_validate(nodenames, required, osname,
+  node_uuids = _FilterVmNodes(lu, node_uuids)
+  result = lu.rpc.call_os_validate(node_uuids, required, osname,
                                    [constants.OS_VALIDATE_PARAMETERS],
                                    osparams)
-  for node, nres in result.items():
+  for node_uuid, nres in result.items():
     # we don't check for offline cases since this should be run only
     # against the master node and/or an instance's nodes
-    nres.Raise("OS Parameters validation failed on node %s" % node)
+    nres.Raise("OS Parameters validation failed on node %s" %
+               lu.cfg.GetNodeName(node_uuid))
     if not nres.payload:
       lu.LogInfo("OS %s not found on node %s, validation skipped",
-                 osname, node)
+                 osname, lu.cfg.GetNodeName(node_uuid))
 
 
-def CheckHVParams(lu, nodenames, hvname, hvparams):
+def CheckHVParams(lu, node_uuids, hvname, hvparams):
   """Hypervisor parameter validation.
 
   This function abstract the hypervisor parameter validation to be
@@ -383,8 +403,8 @@ def CheckHVParams(lu, nodenames, hvname, hvparams):
 
   @type lu: L{LogicalUnit}
   @param lu: the logical unit for which we check
-  @type nodenames: list
-  @param nodenames: the list of nodes on which we should check
+  @type node_uuids: list
+  @param node_uuids: the list of nodes on which we should check
   @type hvname: string
   @param hvname: the name of the hypervisor we should use
   @type hvparams: dict
@@ -392,17 +412,18 @@ def CheckHVParams(lu, nodenames, hvname, hvparams):
   @raise errors.OpPrereqError: if the parameters are not valid
 
   """
-  nodenames = _FilterVmNodes(lu, nodenames)
+  node_uuids = _FilterVmNodes(lu, node_uuids)
 
   cluster = lu.cfg.GetClusterInfo()
   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
 
-  hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
-  for node in nodenames:
-    info = hvinfo[node]
+  hvinfo = lu.rpc.call_hypervisor_validate_params(node_uuids, hvname, hvfull)
+  for node_uuid in node_uuids:
+    info = hvinfo[node_uuid]
     if info.offline:
       continue
-    info.Raise("Hypervisor parameter validation failed on node %s" % node)
+    info.Raise("Hypervisor parameter validation failed on node %s" %
+               lu.cfg.GetNodeName(node_uuid))
 
 
 def AdjustCandidatePool(lu, exceptions):
@@ -413,8 +434,8 @@ def AdjustCandidatePool(lu, exceptions):
   if mod_list:
     lu.LogInfo("Promoted nodes to master candidate role: %s",
                utils.CommaJoin(node.name for node in mod_list))
-    for name in mod_list:
-      lu.context.ReaddNode(name)
+    for node in mod_list:
+      lu.context.ReaddNode(node)
   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
   if mc_now > mc_max:
     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
@@ -544,17 +565,30 @@ def ComputeIPolicyInstanceViolation(ipolicy, instance, cfg,
   @see: L{ComputeIPolicySpecViolation}
 
   """
+  ret = []
   be_full = cfg.GetClusterInfo().FillBE(instance)
   mem_size = be_full[constants.BE_MAXMEM]
   cpu_count = be_full[constants.BE_VCPUS]
-  spindle_use = be_full[constants.BE_SPINDLE_USE]
+  es_flags = rpc.GetExclusiveStorageForNodes(cfg, instance.all_nodes)
+  if any(es_flags.values()):
+    # With exclusive storage use the actual spindles
+    try:
+      spindle_use = sum([disk.spindles for disk in instance.disks])
+    except TypeError:
+      ret.append("Number of spindles not configured for disks of instance %s"
+                 " while exclusive storage is enabled, try running gnt-cluster"
+                 " repair-disk-sizes" % instance.name)
+      # _ComputeMinMaxSpec ignores 'None's
+      spindle_use = None
+  else:
+    spindle_use = be_full[constants.BE_SPINDLE_USE]
   disk_count = len(instance.disks)
   disk_sizes = [disk.size for disk in instance.disks]
   nic_count = len(instance.nics)
   disk_template = instance.disk_template
 
-  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
-                     disk_sizes, spindle_use, disk_template)
+  return ret + _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
+                           disk_sizes, spindle_use, disk_template)
 
 
 def _ComputeViolatingInstances(ipolicy, instances, cfg):
@@ -723,19 +757,19 @@ def _UpdateAndVerifySubDict(base, updates, type_check):
   return ret
 
 
-def _FilterVmNodes(lu, nodenames):
+def _FilterVmNodes(lu, node_uuids):
   """Filters out non-vm_capable nodes from a list.
 
   @type lu: L{LogicalUnit}
   @param lu: the logical unit for which we check
-  @type nodenames: list
-  @param nodenames: the list of nodes on which we should check
+  @type node_uuids: list
+  @param node_uuids: the list of nodes on which we should check
   @rtype: list
   @return: the list of vm-capable nodes
 
   """
   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
-  return [name for name in nodenames if name not in vm_nodes]
+  return [uuid for uuid in node_uuids if uuid not in vm_nodes]
 
 
 def GetDefaultIAllocator(cfg, ialloc):
@@ -761,54 +795,53 @@ def GetDefaultIAllocator(cfg, ialloc):
   return ialloc
 
 
-def CheckInstancesNodeGroups(cfg, instances, owned_groups, owned_nodes,
+def CheckInstancesNodeGroups(cfg, instances, owned_groups, owned_node_uuids,
                              cur_group_uuid):
   """Checks if node groups for locked instances are still correct.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: Cluster configuration
   @type instances: dict; string as key, L{objects.Instance} as value
-  @param instances: Dictionary, instance name as key, instance object as value
+  @param instances: Dictionary, instance UUID as key, instance object as value
   @type owned_groups: iterable of string
   @param owned_groups: List of owned groups
-  @type owned_nodes: iterable of string
-  @param owned_nodes: List of owned nodes
+  @type owned_node_uuids: iterable of string
+  @param owned_node_uuids: List of owned nodes
   @type cur_group_uuid: string or None
   @param cur_group_uuid: Optional group UUID to check against instance's groups
 
   """
-  for (name, inst) in instances.items():
-    assert owned_nodes.issuperset(inst.all_nodes), \
-      "Instance %s's nodes changed while we kept the lock" % name
+  for (uuid, inst) in instances.items():
+    assert owned_node_uuids.issuperset(inst.all_nodes), \
+      "Instance %s's nodes changed while we kept the lock" % inst.name
 
-    inst_groups = CheckInstanceNodeGroups(cfg, name, owned_groups)
+    inst_groups = CheckInstanceNodeGroups(cfg, uuid, owned_groups)
 
     assert cur_group_uuid is None or cur_group_uuid in inst_groups, \
-      "Instance %s has no node in group %s" % (name, cur_group_uuid)
+      "Instance %s has no node in group %s" % (inst.name, cur_group_uuid)
 
 
-def CheckInstanceNodeGroups(cfg, instance_name, owned_groups,
-                            primary_only=False):
+def CheckInstanceNodeGroups(cfg, inst_uuid, owned_groups, primary_only=False):
   """Checks if the owned node groups are still correct for an instance.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: The cluster configuration
-  @type instance_name: string
-  @param instance_name: Instance name
+  @type inst_uuid: string
+  @param inst_uuid: Instance UUID
   @type owned_groups: set or frozenset
   @param owned_groups: List of currently owned node groups
   @type primary_only: boolean
   @param primary_only: Whether to check node groups for only the primary node
 
   """
-  inst_groups = cfg.GetInstanceNodeGroups(instance_name, primary_only)
+  inst_groups = cfg.GetInstanceNodeGroups(inst_uuid, primary_only)
 
   if not owned_groups.issuperset(inst_groups):
     raise errors.OpPrereqError("Instance %s's node groups changed since"
                                " locks were acquired, current groups are"
                                " are '%s', owning groups '%s'; retry the"
                                " operation" %
-                               (instance_name,
+                               (cfg.GetInstanceName(inst_uuid),
                                 utils.CommaJoin(inst_groups),
                                 utils.CommaJoin(owned_groups)),
                                errors.ECODE_STATE)
@@ -842,21 +875,22 @@ def LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
 
   if moved:
     lu.LogInfo("Instances to be moved: %s",
-               utils.CommaJoin("%s (to %s)" %
-                               (name, _NodeEvacDest(use_nodes, group, nodes))
-                               for (name, group, nodes) in moved))
+               utils.CommaJoin(
+                 "%s (to %s)" %
+                 (name, _NodeEvacDest(use_nodes, group, node_names))
+                 for (name, group, node_names) in moved))
 
   return [map(compat.partial(_SetOpEarlyRelease, early_release),
               map(opcodes.OpCode.LoadOpCode, ops))
           for ops in jobs]
 
 
-def _NodeEvacDest(use_nodes, group, nodes):
+def _NodeEvacDest(use_nodes, group, node_names):
   """Returns group or nodes depending on caller's choice.
 
   """
   if use_nodes:
-    return utils.CommaJoin(nodes)
+    return utils.CommaJoin(node_names)
   else:
     return group
 
@@ -877,12 +911,12 @@ def MapInstanceDisksToNodes(instances):
   """Creates a map from (node, volume) to instance name.
 
   @type instances: list of L{objects.Instance}
-  @rtype: dict; tuple of (node name, volume name) as key, instance name as value
+  @rtype: dict; tuple of (node uuid, volume name) as key, instance name as value
 
   """
-  return dict(((node, vol), inst.name)
+  return dict(((node_uuid, vol), inst.name)
               for inst in instances
-              for (node, vols) in inst.MapLVsByNode().items()
+              for (node_uuid, vols) in inst.MapLVsByNode().items()
               for vol in vols)
 
 
@@ -947,10 +981,13 @@ def CheckInstanceState(lu, instance, req_states, msg=None):
                                errors.ECODE_STATE)
 
   if constants.ADMINST_UP not in req_states:
-    pnode = instance.primary_node
-    if not lu.cfg.GetNodeInfo(pnode).offline:
-      ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
-      ins_l.Raise("Can't contact node %s for instance information" % pnode,
+    pnode_uuid = instance.primary_node
+    if not lu.cfg.GetNodeInfo(pnode_uuid).offline:
+      all_hvparams = lu.cfg.GetClusterInfo().hvparams
+      ins_l = lu.rpc.call_instance_list(
+                [pnode_uuid], [instance.hypervisor], all_hvparams)[pnode_uuid]
+      ins_l.Raise("Can't contact node %s for instance information" %
+                  lu.cfg.GetNodeName(pnode_uuid),
                   prereq=True, ecode=errors.ECODE_ENVIRON)
       if instance.name in ins_l.payload:
         raise errors.OpPrereqError("Instance %s is running, %s" %
@@ -996,16 +1033,16 @@ def CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
                                  " iallocator", errors.ECODE_INVAL)
 
 
-def FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
+def FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_uuid, prereq):
   faulty = []
 
   for dev in instance.disks:
-    cfg.SetDiskID(dev, node_name)
+    cfg.SetDiskID(dev, node_uuid)
 
-  result = rpc_runner.call_blockdev_getmirrorstatus(node_name,
-                                                    (instance.disks,
-                                                     instance))
-  result.Raise("Failed to get disk status from node %s" % node_name,
+  result = rpc_runner.call_blockdev_getmirrorstatus(
+             node_uuid, (instance.disks, instance))
+  result.Raise("Failed to get disk status from node %s" %
+               cfg.GetNodeName(node_uuid),
                prereq=prereq, ecode=errors.ECODE_ENVIRON)
 
   for idx, bdev_status in enumerate(result.payload):
@@ -1015,16 +1052,17 @@ def FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
   return faulty
 
 
-def CheckNodeOnline(lu, node, msg=None):
+def CheckNodeOnline(lu, node_uuid, msg=None):
   """Ensure that a given node is online.
 
   @param lu: the LU on behalf of which we make the check
-  @param node: the node to check
+  @param node_uuid: the node to check
   @param msg: if passed, should be a message to replace the default one
   @raise errors.OpPrereqError: if the node is offline
 
   """
   if msg is None:
     msg = "Can't use offline node"
-  if lu.cfg.GetNodeInfo(node).offline:
-    raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
+  if lu.cfg.GetNodeInfo(node_uuid).offline:
+    raise errors.OpPrereqError("%s: %s" % (msg, lu.cfg.GetNodeName(node_uuid)),
+                               errors.ECODE_STATE)
index 676803c..8a15234 100644 (file)
@@ -152,14 +152,14 @@ class LUGroupAssignNodes(NoHooksLU):
   def ExpandNames(self):
     # These raise errors.OpPrereqError on their own:
     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
-    self.op.nodes = GetWantedNodes(self, self.op.nodes)
+    (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
 
     # We want to lock all the affected nodes and groups. We have readily
     # available the list of nodes, and the *destination* group. To gather the
     # list of "source" groups, we need to fetch node information later on.
     self.needed_locks = {
       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
-      locking.LEVEL_NODE: self.op.nodes,
+      locking.LEVEL_NODE: self.op.node_uuids,
       }
 
   def DeclareLocks(self, level):
@@ -168,7 +168,7 @@ class LUGroupAssignNodes(NoHooksLU):
 
       # Try to get all affected nodes' groups without having the group or node
       # lock yet. Needs verification later in the code flow.
-      groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
+      groups = self.cfg.GetNodeGroupsFromNodes(self.op.node_uuids)
 
       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
 
@@ -178,10 +178,10 @@ class LUGroupAssignNodes(NoHooksLU):
     """
     assert self.needed_locks[locking.LEVEL_NODEGROUP]
     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
-            frozenset(self.op.nodes))
+            frozenset(self.op.node_uuids))
 
     expected_locks = (set([self.group_uuid]) |
-                      self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
+                      self.cfg.GetNodeGroupsFromNodes(self.op.node_uuids))
     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
     if actual_locks != expected_locks:
       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
@@ -198,12 +198,13 @@ class LUGroupAssignNodes(NoHooksLU):
                                (self.op.group_name, self.group_uuid))
 
     (new_splits, previous_splits) = \
-      self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
-                                             for node in self.op.nodes],
+      self.CheckAssignmentForSplitInstances([(uuid, self.group_uuid)
+                                             for uuid in self.op.node_uuids],
                                             self.node_data, instance_data)
 
     if new_splits:
-      fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
+      fmt_new_splits = utils.CommaJoin(utils.NiceSort(
+                         self.cfg.GetInstanceNames(new_splits)))
 
       if not self.op.force:
         raise errors.OpExecError("The following instances get split by this"
@@ -216,13 +217,14 @@ class LUGroupAssignNodes(NoHooksLU):
         if previous_splits:
           self.LogWarning("In addition, these already-split instances continue"
                           " to be split across groups: %s",
-                          utils.CommaJoin(utils.NiceSort(previous_splits)))
+                          utils.CommaJoin(utils.NiceSort(
+                            self.cfg.GetInstanceNames(previous_splits))))
 
   def Exec(self, feedback_fn):
     """Assign nodes to a new group.
 
     """
-    mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
+    mods = [(node_uuid, self.group_uuid) for node_uuid in self.op.node_uuids]
 
     self.cfg.AssignGroupNodes(mods)
 
@@ -240,7 +242,7 @@ class LUGroupAssignNodes(NoHooksLU):
     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
     considered.
 
-    @type changes: list of (node_name, new_group_uuid) pairs.
+    @type changes: list of (node_uuid, new_group_uuid) pairs.
     @param changes: list of node assignments to consider.
     @param node_data: a dict with data for all nodes
     @param instance_data: a dict with all instances to consider
@@ -250,27 +252,23 @@ class LUGroupAssignNodes(NoHooksLU):
       split and this change does not fix.
 
     """
-    changed_nodes = dict((node, group) for node, group in changes
-                         if node_data[node].group != group)
+    changed_nodes = dict((uuid, group) for uuid, group in changes
+                         if node_data[uuid].group != group)
 
     all_split_instances = set()
     previously_split_instances = set()
 
-    def InstanceNodes(instance):
-      return [instance.primary_node] + list(instance.secondary_nodes)
-
     for inst in instance_data.values():
       if inst.disk_template not in constants.DTS_INT_MIRROR:
         continue
 
-      instance_nodes = InstanceNodes(inst)
-
-      if len(set(node_data[node].group for node in instance_nodes)) > 1:
-        previously_split_instances.add(inst.name)
+      if len(set(node_data[node_uuid].group
+                 for node_uuid in inst.all_nodes)) > 1:
+        previously_split_instances.add(inst.uuid)
 
-      if len(set(changed_nodes.get(node, node_data[node].group)
-                 for node in instance_nodes)) > 1:
-        all_split_instances.add(inst.name)
+      if len(set(changed_nodes.get(node_uuid, node_data[node_uuid].group)
+                 for node_uuid in inst.all_nodes)) > 1:
+        all_split_instances.add(inst.uuid)
 
     return (list(all_split_instances - previously_split_instances),
             list(previously_split_instances & all_split_instances))
@@ -333,8 +331,8 @@ class GroupQuery(QueryBase):
 
       for node in all_nodes.values():
         if node.group in group_to_nodes:
-          group_to_nodes[node.group].append(node.name)
-          node_to_group[node.name] = node.group
+          group_to_nodes[node.group].append(node.uuid)
+          node_to_group[node.uuid] = node.group
 
       if do_instances:
         all_instances = lu.cfg.GetAllInstancesInfo()
@@ -343,7 +341,7 @@ class GroupQuery(QueryBase):
         for instance in all_instances.values():
           node = instance.primary_node
           if node in node_to_group:
-            group_to_instances[node_to_group[node]].append(instance.name)
+            group_to_instances[node_to_group[node]].append(instance.uuid)
 
         if not do_nodes:
           # Do not pass on node information if it was not requested.
@@ -416,7 +414,8 @@ class LUGroupSetParams(LogicalUnit):
       # Lock instances optimistically, needs verification once group lock has
       # been acquired
       self.needed_locks[locking.LEVEL_INSTANCE] = \
-          self.cfg.GetNodeGroupInstances(self.group_uuid)
+        self.cfg.GetInstanceNames(
+          self.cfg.GetNodeGroupInstances(self.group_uuid))
 
   @staticmethod
   def _UpdateAndVerifyDiskParams(old, new):
@@ -431,10 +430,10 @@ class LUGroupSetParams(LogicalUnit):
     """Check prerequisites.
 
     """
-    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
+    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
 
     # Check if locked instances are still correct
-    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
+    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
 
     self.group = self.cfg.GetNodeGroup(self.group_uuid)
     cluster = self.cfg.GetClusterInfo()
@@ -481,8 +480,7 @@ class LUGroupSetParams(LogicalUnit):
                                            group_policy=True)
 
       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
-      inst_filter = lambda inst: inst.name in owned_instances
-      instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
+      instances = self.cfg.GetMultiInstanceInfoByName(owned_instance_names)
       gmi = ganeti.masterd.instance
       violations = \
           ComputeNewInstanceViolations(gmi.CalculateGroupIPolicy(cluster,
@@ -561,7 +559,7 @@ class LUGroupRemove(LogicalUnit):
 
     """
     # Verify that the group is empty.
-    group_nodes = [node.name
+    group_nodes = [node.uuid
                    for node in self.cfg.GetAllNodesInfo().values()
                    if node.group == self.group_uuid]
 
@@ -654,7 +652,7 @@ class LUGroupRename(LogicalUnit):
     all_nodes.pop(mn, None)
 
     run_nodes = [mn]
-    run_nodes.extend(node.name for node in all_nodes.values()
+    run_nodes.extend(node.uuid for node in all_nodes.values()
                      if node.group == self.group_uuid)
 
     return (run_nodes, run_nodes)
@@ -713,7 +711,8 @@ class LUGroupEvacuate(LogicalUnit):
       # Lock instances optimistically, needs verification once node and group
       # locks have been acquired
       self.needed_locks[locking.LEVEL_INSTANCE] = \
-        self.cfg.GetNodeGroupInstances(self.group_uuid)
+        self.cfg.GetInstanceNames(
+          self.cfg.GetNodeGroupInstances(self.group_uuid))
 
     elif level == locking.LEVEL_NODEGROUP:
       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
@@ -727,7 +726,9 @@ class LUGroupEvacuate(LogicalUnit):
                            for instance_name in
                              self.owned_locks(locking.LEVEL_INSTANCE)
                            for group_uuid in
-                             self.cfg.GetInstanceNodeGroups(instance_name))
+                             self.cfg.GetInstanceNodeGroups(
+                               self.cfg.GetInstanceInfoByName(instance_name)
+                                 .uuid))
       else:
         # No target groups, need to lock all of them
         lock_groups = locking.ALL_SET
@@ -743,28 +744,30 @@ class LUGroupEvacuate(LogicalUnit):
       # Lock all nodes in group to be evacuated and target groups
       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
       assert self.group_uuid in owned_groups
-      member_nodes = [node_name
-                      for group in owned_groups
-                      for node_name in self.cfg.GetNodeGroup(group).members]
-      self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
+      member_node_uuids = [node_uuid
+                           for group in owned_groups
+                           for node_uuid in
+                             self.cfg.GetNodeGroup(group).members]
+      self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
 
   def CheckPrereq(self):
-    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
+    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
-    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
+    owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
 
     assert owned_groups.issuperset(self.req_target_uuids)
     assert self.group_uuid in owned_groups
 
     # Check if locked instances are still correct
-    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
+    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
 
     # Get instance information
-    self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
+    self.instances = \
+      dict(self.cfg.GetMultiInstanceInfoByName(owned_instance_names))
 
     # Check if node groups for locked instances are still correct
     CheckInstancesNodeGroups(self.cfg, self.instances,
-                             owned_groups, owned_nodes, self.group_uuid)
+                             owned_groups, owned_node_uuids, self.group_uuid)
 
     if self.req_target_uuids:
       # User requested specific target groups
@@ -800,11 +803,11 @@ class LUGroupEvacuate(LogicalUnit):
     return (run_nodes, run_nodes)
 
   def Exec(self, feedback_fn):
-    instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
+    inst_names = list(self.owned_locks(locking.LEVEL_INSTANCE))
 
     assert self.group_uuid not in self.target_uuids
 
-    req = iallocator.IAReqGroupChange(instances=instances,
+    req = iallocator.IAReqGroupChange(instances=inst_names,
                                       target_groups=self.target_uuids)
     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
 
@@ -854,7 +857,8 @@ class LUGroupVerifyDisks(NoHooksLU):
       # Lock instances optimistically, needs verification once node and group
       # locks have been acquired
       self.needed_locks[locking.LEVEL_INSTANCE] = \
-        self.cfg.GetNodeGroupInstances(self.group_uuid)
+        self.cfg.GetInstanceNames(
+          self.cfg.GetNodeGroupInstances(self.group_uuid))
 
     elif level == locking.LEVEL_NODEGROUP:
       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
@@ -866,7 +870,9 @@ class LUGroupVerifyDisks(NoHooksLU):
             # later on
             [group_uuid
              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
-             for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
+             for group_uuid in
+               self.cfg.GetInstanceNodeGroups(
+                 self.cfg.GetInstanceInfoByName(instance_name).uuid)])
 
     elif level == locking.LEVEL_NODE:
       # This will only lock the nodes in the group to be verified which contain
@@ -876,25 +882,25 @@ class LUGroupVerifyDisks(NoHooksLU):
 
       # Lock all nodes in group to be verified
       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
-      member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
-      self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
+      member_node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
+      self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
 
   def CheckPrereq(self):
-    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
+    owned_inst_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
-    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
+    owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
 
     assert self.group_uuid in owned_groups
 
     # Check if locked instances are still correct
-    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
+    CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_inst_names)
 
     # Get instance information
-    self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
+    self.instances = dict(self.cfg.GetMultiInstanceInfoByName(owned_inst_names))
 
     # Check if node groups for locked instances are still correct
     CheckInstancesNodeGroups(self.cfg, self.instances,
-                             owned_groups, owned_nodes, self.group_uuid)
+                             owned_groups, owned_node_uuids, self.group_uuid)
 
   def Exec(self, feedback_fn):
     """Verify integrity of cluster disks.
@@ -913,23 +919,24 @@ class LUGroupVerifyDisks(NoHooksLU):
       [inst for inst in self.instances.values() if inst.disks_active])
 
     if nv_dict:
-      nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
-                             set(self.cfg.GetVmCapableNodeList()))
+      node_uuids = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
+                                  set(self.cfg.GetVmCapableNodeList()))
 
-      node_lvs = self.rpc.call_lv_list(nodes, [])
+      node_lvs = self.rpc.call_lv_list(node_uuids, [])
 
-      for (node, node_res) in node_lvs.items():
+      for (node_uuid, node_res) in node_lvs.items():
         if node_res.offline:
           continue
 
         msg = node_res.fail_msg
         if msg:
-          logging.warning("Error enumerating LVs on node %s: %s", node, msg)
-          res_nodes[node] = msg
+          logging.warning("Error enumerating LVs on node %s: %s",
+                          self.cfg.GetNodeName(node_uuid), msg)
+          res_nodes[node_uuid] = msg
           continue
 
         for lv_name, (_, _, lv_online) in node_res.payload.items():
-          inst = nv_dict.pop((node, lv_name), None)
+          inst = nv_dict.pop((node_uuid, lv_name), None)
           if not (lv_online or inst is None):
             res_instances.add(inst)
 
index 1739d8d..922dd64 100644 (file)
@@ -48,13 +48,14 @@ from ganeti.cmdlib.common import INSTANCE_DOWN, \
   ShareAll, GetDefaultIAllocator, CheckInstanceNodeGroups, \
   LoadNodeEvacResult, CheckIAllocatorOrNode, CheckParamsNotGlobal, \
   IsExclusiveStorageEnabledNode, CheckHVParams, CheckOSParams, \
-  AnnotateDiskParams, GetUpdatedParams, ExpandInstanceName, \
-  ComputeIPolicySpecViolation, CheckInstanceState, ExpandNodeName
+  AnnotateDiskParams, GetUpdatedParams, ExpandInstanceUuidAndName, \
+  ComputeIPolicySpecViolation, CheckInstanceState, ExpandNodeUuidAndName
 from ganeti.cmdlib.instance_storage import CreateDisks, \
   CheckNodesFreeDiskPerVG, WipeDisks, WipeOrCleanupDisks, WaitForSync, \
-  IsExclusiveStorageEnabledNodeName, CreateSingleBlockDev, ComputeDisks, \
+  IsExclusiveStorageEnabledNodeUuid, CreateSingleBlockDev, ComputeDisks, \
   CheckRADOSFreeSpace, ComputeDiskSizePerVG, GenerateDiskTemplate, \
-  StartInstanceDisks, ShutdownInstanceDisks, AssembleInstanceDisks
+  StartInstanceDisks, ShutdownInstanceDisks, AssembleInstanceDisks, \
+  CheckSpindlesExclusiveStorage
 from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
   GetClusterDomainSecret, BuildInstanceHookEnv, NICListToTuple, \
   NICToTuple, CheckNodeNotDrained, RemoveInstance, CopyLockList, \
@@ -105,14 +106,14 @@ def _CheckOpportunisticLocking(op):
                                errors.ECODE_INVAL)
 
 
-def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_whitelist):
+def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_name_whitelist):
   """Wrapper around IAReqInstanceAlloc.
 
   @param op: The instance opcode
   @param disks: The computed disks
   @param nics: The computed nics
   @param beparams: The full filled beparams
-  @param node_whitelist: List of nodes which should appear as online to the
+  @param node_name_whitelist: List of nodes which should appear as online to the
     allocator (unless the node is already marked offline)
 
   @returns: A filled L{iallocator.IAReqInstanceAlloc}
@@ -129,7 +130,7 @@ def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_whitelist):
                                        disks=disks,
                                        nics=[n.ToDict() for n in nics],
                                        hypervisor=op.hypervisor,
-                                       node_whitelist=node_whitelist)
+                                       node_whitelist=node_name_whitelist)
 
 
 def _ComputeFullBeParams(op, cluster):
@@ -244,16 +245,16 @@ def _ComputeNics(op, cluster, default_ip, cfg, ec_id):
   return nics
 
 
-def _CheckForConflictingIp(lu, ip, node):
+def _CheckForConflictingIp(lu, ip, node_uuid):
   """In case of conflicting IP address raise error.
 
   @type ip: string
   @param ip: IP address
-  @type node: string
-  @param node: node name
+  @type node_uuid: string
+  @param node_uuid: node UUID
 
   """
-  (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node)
+  (conf_net, _) = lu.cfg.CheckIPInNodeGroup(ip, node_uuid)
   if conf_net is not None:
     raise errors.OpPrereqError(("The requested IP address (%s) belongs to"
                                 " network %s, but the target NIC does not." %
@@ -391,10 +392,10 @@ class LUInstanceCreate(LogicalUnit):
 
     # instance name verification
     if self.op.name_check:
-      self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
-      self.op.instance_name = self.hostname1.name
+      self.hostname = _CheckHostnameSane(self, self.op.instance_name)
+      self.op.instance_name = self.hostname.name
       # used in CheckPrereq for ip ping check
-      self.check_ip = self.hostname1.ip
+      self.check_ip = self.hostname.ip
     else:
       self.check_ip = None
 
@@ -500,14 +501,14 @@ class LUInstanceCreate(LogicalUnit):
     """
     self.needed_locks = {}
 
-    instance_name = self.op.instance_name
     # this is just a preventive check, but someone might still add this
     # instance in the meantime, and creation will fail at lock-add time
-    if instance_name in self.cfg.GetInstanceList():
+    if self.op.instance_name in\
+      [inst.name for inst in self.cfg.GetAllInstancesInfo().values()]:
       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
-                                 instance_name, errors.ECODE_EXISTS)
+                                 self.op.instance_name, errors.ECODE_EXISTS)
 
-    self.add_locks[locking.LEVEL_INSTANCE] = instance_name
+    self.add_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
 
     if self.op.iallocator:
       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
@@ -520,11 +521,13 @@ class LUInstanceCreate(LogicalUnit):
         self.opportunistic_locks[locking.LEVEL_NODE] = True
         self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
     else:
-      self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
-      nodelist = [self.op.pnode]
+      (self.op.pnode_uuid, self.op.pnode) = \
+        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
+      nodelist = [self.op.pnode_uuid]
       if self.op.snode is not None:
-        self.op.snode = ExpandNodeName(self.cfg, self.op.snode)
-        nodelist.append(self.op.snode)
+        (self.op.snode_uuid, self.op.snode) = \
+          ExpandNodeUuidAndName(self.cfg, self.op.snode_uuid, self.op.snode)
+        nodelist.append(self.op.snode_uuid)
       self.needed_locks[locking.LEVEL_NODE] = nodelist
 
     # in case of import lock the source node too
@@ -544,9 +547,10 @@ class LUInstanceCreate(LogicalUnit):
                                      " requires a source node option",
                                      errors.ECODE_INVAL)
       else:
-        self.op.src_node = src_node = ExpandNodeName(self.cfg, src_node)
+        (self.op.src_node_uuid, self.op.src_node) = (_, src_node) = \
+          ExpandNodeUuidAndName(self.cfg, self.op.src_node_uuid, src_node)
         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
-          self.needed_locks[locking.LEVEL_NODE].append(src_node)
+          self.needed_locks[locking.LEVEL_NODE].append(self.op.src_node_uuid)
         if not os.path.isabs(src_path):
           self.op.src_path = src_path = \
             utils.PathJoin(pathutils.EXPORT_DIR, src_path)
@@ -560,15 +564,16 @@ class LUInstanceCreate(LogicalUnit):
     """
     if self.op.opportunistic_locking:
       # Only consider nodes for which a lock is held
-      node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
+      node_name_whitelist = self.cfg.GetNodeNames(
+        self.owned_locks(locking.LEVEL_NODE))
     else:
-      node_whitelist = None
+      node_name_whitelist = None
 
     #TODO Export network to iallocator so that it chooses a pnode
     #     in a nodegroup that has the desired network connected to
     req = _CreateInstanceAllocRequest(self.op, self.disks,
                                       self.nics, self.be_full,
-                                      node_whitelist)
+                                      node_name_whitelist)
     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
 
     ial.Run(self.op.iallocator)
@@ -585,7 +590,8 @@ class LUInstanceCreate(LogicalUnit):
                                  (self.op.iallocator, ial.info),
                                  ecode)
 
-    self.op.pnode = ial.result[0]
+    (self.op.pnode_uuid, self.op.pnode) = \
+      ExpandNodeUuidAndName(self.cfg, None, ial.result[0])
     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
                  self.op.instance_name, self.op.iallocator,
                  utils.CommaJoin(ial.result))
@@ -593,7 +599,8 @@ class LUInstanceCreate(LogicalUnit):
     assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
 
     if req.RequiredNodes() == 2:
-      self.op.snode = ial.result[1]
+      (self.op.snode_uuid, self.op.snode) = \
+        ExpandNodeUuidAndName(self.cfg, None, ial.result[1])
 
   def BuildHooksEnv(self):
     """Build hooks env.
@@ -611,8 +618,8 @@ class LUInstanceCreate(LogicalUnit):
 
     env.update(BuildInstanceHookEnv(
       name=self.op.instance_name,
-      primary_node=self.op.pnode,
-      secondary_nodes=self.secondaries,
+      primary_node_name=self.op.pnode,
+      secondary_node_names=self.cfg.GetNodeNames(self.secondaries),
       status=self.op.start,
       os_type=self.op.os_type,
       minmem=self.be_full[constants.BE_MINMEM],
@@ -635,7 +642,7 @@ class LUInstanceCreate(LogicalUnit):
     """Build hooks nodes.
 
     """
-    nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
+    nl = [self.cfg.GetMasterNode(), self.op.pnode_uuid] + self.secondaries
     return nl, nl
 
   def _ReadExportInfo(self):
@@ -649,29 +656,28 @@ class LUInstanceCreate(LogicalUnit):
     """
     assert self.op.mode == constants.INSTANCE_IMPORT
 
-    src_node = self.op.src_node
-    src_path = self.op.src_path
-
-    if src_node is None:
+    if self.op.src_node_uuid is None:
       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
       exp_list = self.rpc.call_export_list(locked_nodes)
       found = False
       for node in exp_list:
         if exp_list[node].fail_msg:
           continue
-        if src_path in exp_list[node].payload:
+        if self.op.src_path in exp_list[node].payload:
           found = True
-          self.op.src_node = src_node = node
-          self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
-                                                       src_path)
+          self.op.src_node = node
+          self.op.src_node_uuid = self.cfg.GetNodeInfoByName(node).uuid
+          self.op.src_path = utils.PathJoin(pathutils.EXPORT_DIR,
+                                            self.op.src_path)
           break
       if not found:
         raise errors.OpPrereqError("No export found for relative path %s" %
-                                   src_path, errors.ECODE_INVAL)
+                                   self.op.src_path, errors.ECODE_INVAL)
 
-    CheckNodeOnline(self, src_node)
-    result = self.rpc.call_export_info(src_node, src_path)
-    result.Raise("No export or invalid export found in dir %s" % src_path)
+    CheckNodeOnline(self, self.op.src_node_uuid)
+    result = self.rpc.call_export_info(self.op.src_node_uuid, self.op.src_path)
+    result.Raise("No export or invalid export found in dir %s" %
+                 self.op.src_path)
 
     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
     if not export_info.has_section(constants.INISECT_EXP):
@@ -679,7 +685,7 @@ class LUInstanceCreate(LogicalUnit):
                                    errors.ECODE_ENVIRON)
 
     ei_version = export_info.get(constants.INISECT_EXP, "version")
-    if (int(ei_version) != constants.EXPORT_VERSION):
+    if int(ei_version) != constants.EXPORT_VERSION:
       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
                                  (ei_version, constants.EXPORT_VERSION),
                                  errors.ECODE_ENVIRON)
@@ -940,7 +946,8 @@ class LUInstanceCreate(LogicalUnit):
       self._RunAllocator()
 
     # Release all unneeded node locks
-    keep_locks = filter(None, [self.op.pnode, self.op.snode, self.op.src_node])
+    keep_locks = filter(None, [self.op.pnode_uuid, self.op.snode_uuid,
+                               self.op.src_node_uuid])
     ReleaseLocks(self, locking.LEVEL_NODE, keep=keep_locks)
     ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=keep_locks)
     ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
@@ -952,9 +959,9 @@ class LUInstanceCreate(LogicalUnit):
     #### node related checks
 
     # check primary node
-    self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
+    self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode_uuid)
     assert self.pnode is not None, \
-      "Cannot retrieve locked node %s" % self.op.pnode
+      "Cannot retrieve locked node %s" % self.op.pnode_uuid
     if pnode.offline:
       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
                                  pnode.name, errors.ECODE_STATE)
@@ -973,7 +980,7 @@ class LUInstanceCreate(LogicalUnit):
       net_uuid = nic.network
       if net_uuid is not None:
         nobj = self.cfg.GetNetwork(net_uuid)
-        netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.name)
+        netparams = self.cfg.GetGroupNetParams(net_uuid, self.pnode.uuid)
         if netparams is None:
           raise errors.OpPrereqError("No netparams found for network"
                                      " %s. Propably not connected to"
@@ -1003,36 +1010,38 @@ class LUInstanceCreate(LogicalUnit):
 
       # net is None, ip None or given
       elif self.op.conflicts_check:
-        _CheckForConflictingIp(self, nic.ip, self.pnode.name)
+        _CheckForConflictingIp(self, nic.ip, self.pnode.uuid)
 
     # mirror node verification
     if self.op.disk_template in constants.DTS_INT_MIRROR:
-      if self.op.snode == pnode.name:
+      if self.op.snode_uuid == pnode.uuid:
         raise errors.OpPrereqError("The secondary node cannot be the"
                                    " primary node", errors.ECODE_INVAL)
-      CheckNodeOnline(self, self.op.snode)
-      CheckNodeNotDrained(self, self.op.snode)
-      CheckNodeVmCapable(self, self.op.snode)
-      self.secondaries.append(self.op.snode)
+      CheckNodeOnline(self, self.op.snode_uuid)
+      CheckNodeNotDrained(self, self.op.snode_uuid)
+      CheckNodeVmCapable(self, self.op.snode_uuid)
+      self.secondaries.append(self.op.snode_uuid)
 
-      snode = self.cfg.GetNodeInfo(self.op.snode)
+      snode = self.cfg.GetNodeInfo(self.op.snode_uuid)
       if pnode.group != snode.group:
         self.LogWarning("The primary and secondary nodes are in two"
                         " different node groups; the disk parameters"
                         " from the first disk's node group will be"
                         " used")
 
-    if not self.op.disk_template in constants.DTS_EXCL_STORAGE:
-      nodes = [pnode]
-      if self.op.disk_template in constants.DTS_INT_MIRROR:
-        nodes.append(snode)
-      has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
-      if compat.any(map(has_es, nodes)):
-        raise errors.OpPrereqError("Disk template %s not supported with"
-                                   " exclusive storage" % self.op.disk_template,
-                                   errors.ECODE_STATE)
+    nodes = [pnode]
+    if self.op.disk_template in constants.DTS_INT_MIRROR:
+      nodes.append(snode)
+    has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
+    excl_stor = compat.any(map(has_es, nodes))
+    if excl_stor and not self.op.disk_template in constants.DTS_EXCL_STORAGE:
+      raise errors.OpPrereqError("Disk template %s not supported with"
+                                 " exclusive storage" % self.op.disk_template,
+                                 errors.ECODE_STATE)
+    for disk in self.disks:
+      CheckSpindlesExclusiveStorage(disk, excl_stor, True)
 
-    nodenames = [pnode.name] + self.secondaries
+    node_uuids = [pnode.uuid] + self.secondaries
 
     if not self.adopt_disks:
       if self.op.disk_template == constants.DT_RBD:
@@ -1043,10 +1052,13 @@ class LUInstanceCreate(LogicalUnit):
       elif self.op.disk_template == constants.DT_EXT:
         # FIXME: Function that checks prereqs if needed
         pass
-      else:
+      elif self.op.disk_template in utils.GetLvmDiskTemplates():
         # Check lv size requirements, if not adopting
         req_sizes = ComputeDiskSizePerVG(self.op.disk_template, self.disks)
-        CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
+        CheckNodesFreeDiskPerVG(self, node_uuids, req_sizes)
+      else:
+        # FIXME: add checks for other, non-adopting, non-lvm disk templates
+        pass
 
     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
@@ -1064,11 +1076,11 @@ class LUInstanceCreate(LogicalUnit):
           raise errors.OpPrereqError("LV named %s used by another instance" %
                                      lv_name, errors.ECODE_NOTUNIQUE)
 
-      vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
+      vg_names = self.rpc.call_vg_list([pnode.uuid])[pnode.uuid]
       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
 
-      node_lvs = self.rpc.call_lv_list([pnode.name],
-                                       vg_names.payload.keys())[pnode.name]
+      node_lvs = self.rpc.call_lv_list([pnode.uuid],
+                                       vg_names.payload.keys())[pnode.uuid]
       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
       node_lvs = node_lvs.payload
 
@@ -1104,8 +1116,8 @@ class LUInstanceCreate(LogicalUnit):
                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
                                    errors.ECODE_INVAL)
 
-      node_disks = self.rpc.call_bdev_sizes([pnode.name],
-                                            list(all_disks))[pnode.name]
+      node_disks = self.rpc.call_bdev_sizes([pnode.uuid],
+                                            list(all_disks))[pnode.uuid]
       node_disks.Raise("Cannot get block device information from node %s" %
                        pnode.name)
       node_disks = node_disks.payload
@@ -1139,13 +1151,13 @@ class LUInstanceCreate(LogicalUnit):
              (pnode.group, group_info.name, utils.CommaJoin(res)))
       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
 
-    CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
+    CheckHVParams(self, node_uuids, self.op.hypervisor, self.op.hvparams)
 
-    CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
+    CheckNodeHasOS(self, pnode.uuid, self.op.os_type, self.op.force_variant)
     # check OS parameters (remotely)
-    CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
+    CheckOSParams(self, True, node_uuids, self.op.os_type, self.os_full)
 
-    CheckNicsBridgesExist(self, self.nics, self.pnode.name)
+    CheckNicsBridgesExist(self, self.nics, self.pnode.uuid)
 
     #TODO: _CheckExtParams (remotely)
     # Check parameters for extstorage
@@ -1153,20 +1165,19 @@ class LUInstanceCreate(LogicalUnit):
     # memory check on primary node
     #TODO(dynmem): use MINMEM for checking
     if self.op.start:
-      CheckNodeFreeMemory(self, self.pnode.name,
+      hvfull = objects.FillDict(cluster.hvparams.get(self.op.hypervisor, {}),
+                                self.op.hvparams)
+      CheckNodeFreeMemory(self, self.pnode.uuid,
                           "creating instance %s" % self.op.instance_name,
                           self.be_full[constants.BE_MAXMEM],
-                          self.op.hypervisor)
+                          self.op.hypervisor, hvfull)
 
-    self.dry_run_result = list(nodenames)
+    self.dry_run_result = list(node_uuids)
 
   def Exec(self, feedback_fn):
     """Create and add the instance to the cluster.
 
     """
-    instance = self.op.instance_name
-    pnode_name = self.pnode.name
-
     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
                 self.owned_locks(locking.LEVEL_NODE)), \
       "Node locks differ from node resource locks"
@@ -1178,14 +1189,15 @@ class LUInstanceCreate(LogicalUnit):
     else:
       network_port = None
 
+    instance_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
+
     # This is ugly but we got a chicken-egg problem here
     # We can only take the group disk parameters, as the instance
     # has no disks yet (we are generating them right here).
-    node = self.cfg.GetNodeInfo(pnode_name)
-    nodegroup = self.cfg.GetNodeGroup(node.group)
+    nodegroup = self.cfg.GetNodeGroup(self.pnode.group)
     disks = GenerateDiskTemplate(self,
                                  self.op.disk_template,
-                                 instance, pnode_name,
+                                 instance_uuid, self.pnode.uuid,
                                  self.secondaries,
                                  self.disks,
                                  self.instance_file_storage_dir,
@@ -1194,8 +1206,10 @@ class LUInstanceCreate(LogicalUnit):
                                  feedback_fn,
                                  self.cfg.GetGroupDiskParams(nodegroup))
 
-    iobj = objects.Instance(name=instance, os=self.op.os_type,
-                            primary_node=pnode_name,
+    iobj = objects.Instance(name=self.op.instance_name,
+                            uuid=instance_uuid,
+                            os=self.op.os_type,
+                            primary_node=self.pnode.uuid,
                             nics=self.nics, disks=disks,
                             disk_template=self.op.disk_template,
                             disks_active=False,
@@ -1220,8 +1234,8 @@ class LUInstanceCreate(LogicalUnit):
         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
           rename_to.append(t_dsk.logical_id)
           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
-          self.cfg.SetDiskID(t_dsk, pnode_name)
-        result = self.rpc.call_blockdev_rename(pnode_name,
+          self.cfg.SetDiskID(t_dsk, self.pnode.uuid)
+        result = self.rpc.call_blockdev_rename(self.pnode.uuid,
                                                zip(tmp_disks, rename_to))
         result.Raise("Failed to rename adoped LVs")
     else:
@@ -1230,10 +1244,10 @@ class LUInstanceCreate(LogicalUnit):
         CreateDisks(self, iobj)
       except errors.OpExecError:
         self.LogWarning("Device creation failed")
-        self.cfg.ReleaseDRBDMinors(instance)
+        self.cfg.ReleaseDRBDMinors(self.op.instance_name)
         raise
 
-    feedback_fn("adding instance %s to cluster config" % instance)
+    feedback_fn("adding instance %s to cluster config" % self.op.instance_name)
 
     self.cfg.AddInstance(iobj, self.proc.GetECId())
 
@@ -1243,7 +1257,7 @@ class LUInstanceCreate(LogicalUnit):
 
     if self.op.mode == constants.INSTANCE_IMPORT:
       # Release unused nodes
-      ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
+      ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node_uuid])
     else:
       # Release all nodes
       ReleaseLocks(self, locking.LEVEL_NODE)
@@ -1272,7 +1286,7 @@ class LUInstanceCreate(LogicalUnit):
 
     if disk_abort:
       RemoveDisks(self, iobj)
-      self.cfg.RemoveInstance(iobj.name)
+      self.cfg.RemoveInstance(iobj.uuid)
       # Make sure the instance lock gets removed
       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
       raise errors.OpExecError("There are some degraded disks for"
@@ -1289,38 +1303,39 @@ class LUInstanceCreate(LogicalUnit):
       # preceding code might or might have not done it, depending on
       # disk template and other options
       for disk in iobj.disks:
-        self.cfg.SetDiskID(disk, pnode_name)
+        self.cfg.SetDiskID(disk, self.pnode.uuid)
       if self.op.mode == constants.INSTANCE_CREATE:
         if not self.op.no_install:
           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
                         not self.op.wait_for_sync)
           if pause_sync:
             feedback_fn("* pausing disk sync to install instance OS")
-            result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
+            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
                                                               (iobj.disks,
                                                                iobj), True)
             for idx, success in enumerate(result.payload):
               if not success:
                 logging.warn("pause-sync of instance %s for disk %d failed",
-                             instance, idx)
+                             self.op.instance_name, idx)
 
           feedback_fn("* running the instance OS create scripts...")
           # FIXME: pass debug option from opcode to backend
           os_add_result = \
-            self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
+            self.rpc.call_instance_os_add(self.pnode.uuid, (iobj, None), False,
                                           self.op.debug_level)
           if pause_sync:
             feedback_fn("* resuming disk sync")
-            result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
+            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
                                                               (iobj.disks,
                                                                iobj), False)
             for idx, success in enumerate(result.payload):
               if not success:
                 logging.warn("resume-sync of instance %s for disk %d failed",
-                             instance, idx)
+                             self.op.instance_name, idx)
 
           os_add_result.Raise("Could not add os for instance %s"
-                              " on node %s" % (instance, pnode_name))
+                              " on node %s" % (self.op.instance_name,
+                                               self.pnode.name))
 
       else:
         if self.op.mode == constants.INSTANCE_IMPORT:
@@ -1342,12 +1357,14 @@ class LUInstanceCreate(LogicalUnit):
 
           import_result = \
             masterd.instance.TransferInstanceData(self, feedback_fn,
-                                                  self.op.src_node, pnode_name,
+                                                  self.op.src_node_uuid,
+                                                  self.pnode.uuid,
                                                   self.pnode.secondary_ip,
                                                   iobj, transfers)
           if not compat.all(import_result):
             self.LogWarning("Some disks for instance %s on node %s were not"
-                            " imported successfully" % (instance, pnode_name))
+                            " imported successfully" % (self.op.instance_name,
+                                                        self.pnode.name))
 
           rename_from = self._old_instance_name
 
@@ -1361,7 +1378,7 @@ class LUInstanceCreate(LogicalUnit):
                              self.op.source_shutdown_timeout)
           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
 
-          assert iobj.primary_node == self.pnode.name
+          assert iobj.primary_node == self.pnode.uuid
           disk_results = \
             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
                                           self.source_x509_ca,
@@ -1370,7 +1387,8 @@ class LUInstanceCreate(LogicalUnit):
             # TODO: Should the instance still be started, even if some disks
             # failed to import (valid for local imports, too)?
             self.LogWarning("Some disks for instance %s on node %s were not"
-                            " imported successfully" % (instance, pnode_name))
+                            " imported successfully" % (self.op.instance_name,
+                                                        self.pnode.name))
 
           rename_from = self.source_instance_name
 
@@ -1380,23 +1398,23 @@ class LUInstanceCreate(LogicalUnit):
                                        % self.op.mode)
 
         # Run rename script on newly imported instance
-        assert iobj.name == instance
-        feedback_fn("Running rename script for %s" % instance)
-        result = self.rpc.call_instance_run_rename(pnode_name, iobj,
+        assert iobj.name == self.op.instance_name
+        feedback_fn("Running rename script for %s" % self.op.instance_name)
+        result = self.rpc.call_instance_run_rename(self.pnode.uuid, iobj,
                                                    rename_from,
                                                    self.op.debug_level)
-        if result.fail_msg:
-          self.LogWarning("Failed to run rename script for %s on node"
-                          " %s: %s" % (instance, pnode_name, result.fail_msg))
+        result.Warn("Failed to run rename script for %s on node %s" %
+                    (self.op.instance_name, self.pnode.name), self.LogWarning)
 
     assert not self.owned_locks(locking.LEVEL_NODE_RES)
 
     if self.op.start:
       iobj.admin_state = constants.ADMINST_UP
       self.cfg.Update(iobj, feedback_fn)
-      logging.info("Starting instance %s on node %s", instance, pnode_name)
+      logging.info("Starting instance %s on node %s", self.op.instance_name,
+                   self.pnode.name)
       feedback_fn("* starting instance...")
-      result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
+      result = self.rpc.call_instance_start(self.pnode.uuid, (iobj, None, None),
                                             False, self.op.reason)
       result.Raise("Could not start instance")
 
@@ -1442,9 +1460,10 @@ class LUInstanceRename(LogicalUnit):
     This checks that the instance is in the cluster and is not running.
 
     """
-    self.op.instance_name = ExpandInstanceName(self.cfg,
-                                               self.op.instance_name)
-    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    (self.op.instance_uuid, self.op.instance_name) = \
+      ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
+                                self.op.instance_name)
+    instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert instance is not None
     CheckNodeOnline(self, instance.primary_node)
     CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
@@ -1461,8 +1480,9 @@ class LUInstanceRename(LogicalUnit):
                                    (hostname.ip, new_name),
                                    errors.ECODE_NOTUNIQUE)
 
-    instance_list = self.cfg.GetInstanceList()
-    if new_name in instance_list and new_name != instance.name:
+    instance_names = [inst.name for
+                      inst in self.cfg.GetAllInstancesInfo().values()]
+    if new_name in instance_names and new_name != instance.name:
       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
                                  new_name, errors.ECODE_EXISTS)
 
@@ -1470,16 +1490,16 @@ class LUInstanceRename(LogicalUnit):
     """Rename the instance.
 
     """
-    inst = self.instance
-    old_name = inst.name
+    old_name = self.instance.name
 
     rename_file_storage = False
-    if (inst.disk_template in constants.DTS_FILEBASED and
-        self.op.new_name != inst.name):
-      old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
+    if (self.instance.disk_template in constants.DTS_FILEBASED and
+        self.op.new_name != self.instance.name):
+      old_file_storage_dir = os.path.dirname(
+                               self.instance.disks[0].logical_id[1])
       rename_file_storage = True
 
-    self.cfg.RenameInstance(inst.name, self.op.new_name)
+    self.cfg.RenameInstance(self.instance.uuid, self.op.new_name)
     # Change the instance lock. This is definitely safe while we hold the BGL.
     # Otherwise the new lock would have to be added in acquired mode.
     assert self.REQ_BGL
@@ -1488,41 +1508,41 @@ class LUInstanceRename(LogicalUnit):
     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
 
     # re-read the instance from the configuration after rename
-    inst = self.cfg.GetInstanceInfo(self.op.new_name)
+    renamed_inst = self.cfg.GetInstanceInfo(self.instance.uuid)
 
     if rename_file_storage:
-      new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
-      result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
+      new_file_storage_dir = os.path.dirname(
+                               renamed_inst.disks[0].logical_id[1])
+      result = self.rpc.call_file_storage_dir_rename(renamed_inst.primary_node,
                                                      old_file_storage_dir,
                                                      new_file_storage_dir)
       result.Raise("Could not rename on node %s directory '%s' to '%s'"
                    " (but the instance has been renamed in Ganeti)" %
-                   (inst.primary_node, old_file_storage_dir,
-                    new_file_storage_dir))
+                   (self.cfg.GetNodeName(renamed_inst.primary_node),
+                    old_file_storage_dir, new_file_storage_dir))
 
-    StartInstanceDisks(self, inst, None)
+    StartInstanceDisks(self, renamed_inst, None)
     # update info on disks
-    info = GetInstanceInfoText(inst)
-    for (idx, disk) in enumerate(inst.disks):
-      for node in inst.all_nodes:
-        self.cfg.SetDiskID(disk, node)
-        result = self.rpc.call_blockdev_setinfo(node, disk, info)
-        if result.fail_msg:
-          self.LogWarning("Error setting info on node %s for disk %s: %s",
-                          node, idx, result.fail_msg)
+    info = GetInstanceInfoText(renamed_inst)
+    for (idx, disk) in enumerate(renamed_inst.disks):
+      for node_uuid in renamed_inst.all_nodes:
+        self.cfg.SetDiskID(disk, node_uuid)
+        result = self.rpc.call_blockdev_setinfo(node_uuid, disk, info)
+        result.Warn("Error setting info on node %s for disk %s" %
+                    (self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
     try:
-      result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
-                                                 old_name, self.op.debug_level)
-      msg = result.fail_msg
-      if msg:
-        msg = ("Could not run OS rename script for instance %s on node %s"
-               " (but the instance has been renamed in Ganeti): %s" %
-               (inst.name, inst.primary_node, msg))
-        self.LogWarning(msg)
+      result = self.rpc.call_instance_run_rename(renamed_inst.primary_node,
+                                                 renamed_inst, old_name,
+                                                 self.op.debug_level)
+      result.Warn("Could not run OS rename script for instance %s on node %s"
+                  " (but the instance has been renamed in Ganeti)" %
+                  (renamed_inst.name,
+                   self.cfg.GetNodeName(renamed_inst.primary_node)),
+                  self.LogWarning)
     finally:
-      ShutdownInstanceDisks(self, inst)
+      ShutdownInstanceDisks(self, renamed_inst)
 
-    return inst.name
+    return renamed_inst.name
 
 
 class LUInstanceRemove(LogicalUnit):
@@ -1571,7 +1591,7 @@ class LUInstanceRemove(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
 
@@ -1579,29 +1599,27 @@ class LUInstanceRemove(LogicalUnit):
     """Remove the instance.
 
     """
-    instance = self.instance
-    logging.info("Shutting down instance %s on node %s",
-                 instance.name, instance.primary_node)
+    logging.info("Shutting down instance %s on node %s", self.instance.name,
+                 self.cfg.GetNodeName(self.instance.primary_node))
 
-    result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
+    result = self.rpc.call_instance_shutdown(self.instance.primary_node,
+                                             self.instance,
                                              self.op.shutdown_timeout,
                                              self.op.reason)
-    msg = result.fail_msg
-    if msg:
-      if self.op.ignore_failures:
-        feedback_fn("Warning: can't shutdown instance: %s" % msg)
-      else:
-        raise errors.OpExecError("Could not shutdown instance %s on"
-                                 " node %s: %s" %
-                                 (instance.name, instance.primary_node, msg))
+    if self.op.ignore_failures:
+      result.Warn("Warning: can't shutdown instance", feedback_fn)
+    else:
+      result.Raise("Could not shutdown instance %s on node %s" %
+                   (self.instance.name,
+                    self.cfg.GetNodeName(self.instance.primary_node)))
 
     assert (self.owned_locks(locking.LEVEL_NODE) ==
             self.owned_locks(locking.LEVEL_NODE_RES))
-    assert not (set(instance.all_nodes) -
+    assert not (set(self.instance.all_nodes) -
                 self.owned_locks(locking.LEVEL_NODE)), \
       "Not owning correct locks"
 
-    RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
+    RemoveInstance(self, feedback_fn, self.instance, self.op.ignore_failures)
 
 
 class LUInstanceMove(LogicalUnit):
@@ -1614,9 +1632,10 @@ class LUInstanceMove(LogicalUnit):
 
   def ExpandNames(self):
     self._ExpandAndLockInstance()
-    target_node = ExpandNodeName(self.cfg, self.op.target_node)
-    self.op.target_node = target_node
-    self.needed_locks[locking.LEVEL_NODE] = [target_node]
+    (self.op.target_node_uuid, self.op.target_node) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
+                            self.op.target_node)
+    self.needed_locks[locking.LEVEL_NODE] = [self.op.target_node]
     self.needed_locks[locking.LEVEL_NODE_RES] = []
     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
 
@@ -1648,7 +1667,7 @@ class LUInstanceMove(LogicalUnit):
     nl = [
       self.cfg.GetMasterNode(),
       self.instance.primary_node,
-      self.op.target_node,
+      self.op.target_node_uuid,
       ]
     return (nl, nl)
 
@@ -1658,53 +1677,54 @@ class LUInstanceMove(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
 
-    if instance.disk_template not in constants.DTS_COPYABLE:
+    if self.instance.disk_template not in constants.DTS_COPYABLE:
       raise errors.OpPrereqError("Disk template %s not suitable for copying" %
-                                 instance.disk_template, errors.ECODE_STATE)
+                                 self.instance.disk_template,
+                                 errors.ECODE_STATE)
 
-    node = self.cfg.GetNodeInfo(self.op.target_node)
-    assert node is not None, \
+    target_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
+    assert target_node is not None, \
       "Cannot retrieve locked node %s" % self.op.target_node
 
-    self.target_node = target_node = node.name
-
-    if target_node == instance.primary_node:
+    self.target_node_uuid = target_node.uuid
+    if target_node.uuid == self.instance.primary_node:
       raise errors.OpPrereqError("Instance %s is already on the node %s" %
-                                 (instance.name, target_node),
+                                 (self.instance.name, target_node.name),
                                  errors.ECODE_STATE)
 
-    bep = self.cfg.GetClusterInfo().FillBE(instance)
+    bep = self.cfg.GetClusterInfo().FillBE(self.instance)
 
-    for idx, dsk in enumerate(instance.disks):
+    for idx, dsk in enumerate(self.instance.disks):
       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
                                    " cannot copy" % idx, errors.ECODE_STATE)
 
-    CheckNodeOnline(self, target_node)
-    CheckNodeNotDrained(self, target_node)
-    CheckNodeVmCapable(self, target_node)
+    CheckNodeOnline(self, target_node.uuid)
+    CheckNodeNotDrained(self, target_node.uuid)
+    CheckNodeVmCapable(self, target_node.uuid)
     cluster = self.cfg.GetClusterInfo()
-    group_info = self.cfg.GetNodeGroup(node.group)
+    group_info = self.cfg.GetNodeGroup(target_node.group)
     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
-    CheckTargetNodeIPolicy(self, ipolicy, instance, node, self.cfg,
+    CheckTargetNodeIPolicy(self, ipolicy, self.instance, target_node, self.cfg,
                            ignore=self.op.ignore_ipolicy)
 
-    if instance.admin_state == constants.ADMINST_UP:
+    if self.instance.admin_state == constants.ADMINST_UP:
       # check memory requirements on the secondary node
-      CheckNodeFreeMemory(self, target_node,
-                          "failing over instance %s" %
-                          instance.name, bep[constants.BE_MAXMEM],
-                          instance.hypervisor)
+      CheckNodeFreeMemory(
+          self, target_node.uuid, "failing over instance %s" %
+          self.instance.name, bep[constants.BE_MAXMEM],
+          self.instance.hypervisor,
+          self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
     else:
       self.LogInfo("Not checking memory on the secondary node as"
                    " instance will not be started")
 
     # check bridge existance
-    CheckInstanceBridgesExist(self, instance, node=target_node)
+    CheckInstanceBridgesExist(self, self.instance, node_uuid=target_node.uuid)
 
   def Exec(self, feedback_fn):
     """Move an instance.
@@ -1713,56 +1733,53 @@ class LUInstanceMove(LogicalUnit):
     the data over (slow) and starting it on the new node.
 
     """
-    instance = self.instance
-
-    source_node = instance.primary_node
-    target_node = self.target_node
+    source_node = self.cfg.GetNodeInfo(self.instance.primary_node)
+    target_node = self.cfg.GetNodeInfo(self.target_node_uuid)
 
     self.LogInfo("Shutting down instance %s on source node %s",
-                 instance.name, source_node)
+                 self.instance.name, source_node.name)
 
     assert (self.owned_locks(locking.LEVEL_NODE) ==
             self.owned_locks(locking.LEVEL_NODE_RES))
 
-    result = self.rpc.call_instance_shutdown(source_node, instance,
+    result = self.rpc.call_instance_shutdown(source_node.uuid, self.instance,
                                              self.op.shutdown_timeout,
                                              self.op.reason)
-    msg = result.fail_msg
-    if msg:
-      if self.op.ignore_consistency:
-        self.LogWarning("Could not shutdown instance %s on node %s."
-                        " Proceeding anyway. Please make sure node"
-                        " %s is down. Error details: %s",
-                        instance.name, source_node, source_node, msg)
-      else:
-        raise errors.OpExecError("Could not shutdown instance %s on"
-                                 " node %s: %s" %
-                                 (instance.name, source_node, msg))
+    if self.op.ignore_consistency:
+      result.Warn("Could not shutdown instance %s on node %s. Proceeding"
+                  " anyway. Please make sure node %s is down. Error details" %
+                  (self.instance.name, source_node.name, source_node.name),
+                  self.LogWarning)
+    else:
+      result.Raise("Could not shutdown instance %s on node %s" %
+                   (self.instance.name, source_node.name))
 
     # create the target disks
     try:
-      CreateDisks(self, instance, target_node=target_node)
+      CreateDisks(self, self.instance, target_node_uuid=target_node.uuid)
     except errors.OpExecError:
       self.LogWarning("Device creation failed")
-      self.cfg.ReleaseDRBDMinors(instance.name)
+      self.cfg.ReleaseDRBDMinors(self.instance.uuid)
       raise
 
     cluster_name = self.cfg.GetClusterInfo().cluster_name
 
     errs = []
     # activate, get path, copy the data over
-    for idx, disk in enumerate(instance.disks):
+    for idx, disk in enumerate(self.instance.disks):
       self.LogInfo("Copying data for disk %d", idx)
-      result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
-                                               instance.name, True, idx)
+      result = self.rpc.call_blockdev_assemble(
+                 target_node.uuid, (disk, self.instance), self.instance.name,
+                 True, idx)
       if result.fail_msg:
         self.LogWarning("Can't assemble newly created disk %d: %s",
                         idx, result.fail_msg)
         errs.append(result.fail_msg)
         break
       dev_path = result.payload
-      result = self.rpc.call_blockdev_export(source_node, (disk, instance),
-                                             target_node, dev_path,
+      result = self.rpc.call_blockdev_export(source_node.uuid, (disk,
+                                                                self.instance),
+                                             target_node.name, dev_path,
                                              cluster_name)
       if result.fail_msg:
         self.LogWarning("Can't copy data over for disk %d: %s",
@@ -1773,37 +1790,37 @@ class LUInstanceMove(LogicalUnit):
     if errs:
       self.LogWarning("Some disks failed to copy, aborting")
       try:
-        RemoveDisks(self, instance, target_node=target_node)
+        RemoveDisks(self, self.instance, target_node_uuid=target_node.uuid)
       finally:
-        self.cfg.ReleaseDRBDMinors(instance.name)
+        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
         raise errors.OpExecError("Errors during disk copy: %s" %
                                  (",".join(errs),))
 
-    instance.primary_node = target_node
-    self.cfg.Update(instance, feedback_fn)
+    self.instance.primary_node = target_node.uuid
+    self.cfg.Update(self.instance, feedback_fn)
 
     self.LogInfo("Removing the disks on the original node")
-    RemoveDisks(self, instance, target_node=source_node)
+    RemoveDisks(self, self.instance, target_node_uuid=source_node.uuid)
 
     # Only start the instance if it's marked as up
-    if instance.admin_state == constants.ADMINST_UP:
+    if self.instance.admin_state == constants.ADMINST_UP:
       self.LogInfo("Starting instance %s on node %s",
-                   instance.name, target_node)
+                   self.instance.name, target_node.name)
 
-      disks_ok, _ = AssembleInstanceDisks(self, instance,
+      disks_ok, _ = AssembleInstanceDisks(self, self.instance,
                                           ignore_secondaries=True)
       if not disks_ok:
-        ShutdownInstanceDisks(self, instance)
+        ShutdownInstanceDisks(self, self.instance)
         raise errors.OpExecError("Can't activate the instance's disks")
 
-      result = self.rpc.call_instance_start(target_node,
-                                            (instance, None, None), False,
+      result = self.rpc.call_instance_start(target_node.uuid,
+                                            (self.instance, None, None), False,
                                             self.op.reason)
       msg = result.fail_msg
       if msg:
-        ShutdownInstanceDisks(self, instance)
+        ShutdownInstanceDisks(self, self.instance)
         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
-                                 (instance.name, target_node, msg))
+                                 (self.instance.name, target_node.name, msg))
 
 
 class LUInstanceMultiAlloc(NoHooksLU):
@@ -1871,10 +1888,12 @@ class LUInstanceMultiAlloc(NoHooksLU):
     else:
       nodeslist = []
       for inst in self.op.instances:
-        inst.pnode = ExpandNodeName(self.cfg, inst.pnode)
+        (inst.pnode_uuid, inst.pnode) = \
+          ExpandNodeUuidAndName(self.cfg, inst.pnode_uuid, inst.pnode)
         nodeslist.append(inst.pnode)
         if inst.snode is not None:
-          inst.snode = ExpandNodeName(self.cfg, inst.snode)
+          (inst.snode_uuid, inst.snode) = \
+            ExpandNodeUuidAndName(self.cfg, inst.snode_uuid, inst.snode)
           nodeslist.append(inst.snode)
 
       self.needed_locks[locking.LEVEL_NODE] = nodeslist
@@ -1892,7 +1911,8 @@ class LUInstanceMultiAlloc(NoHooksLU):
 
     if self.op.opportunistic_locking:
       # Only consider nodes for which a lock is held
-      node_whitelist = list(self.owned_locks(locking.LEVEL_NODE))
+      node_whitelist = self.cfg.GetNodeNames(
+                         list(self.owned_locks(locking.LEVEL_NODE)))
     else:
       node_whitelist = None
 
@@ -1940,13 +1960,14 @@ class LUInstanceMultiAlloc(NoHooksLU):
     (allocatable, failed) = self.ia_result
 
     jobs = []
-    for (name, nodes) in allocatable:
+    for (name, node_names) in allocatable:
       op = op2inst.pop(name)
 
-      if len(nodes) > 1:
-        (op.pnode, op.snode) = nodes
-      else:
-        (op.pnode,) = nodes
+      (op.pnode_uuid, op.pnode) = \
+        ExpandNodeUuidAndName(self.cfg, None, node_names[0])
+      if len(node_names) > 1:
+        (op.snode_uuid, op.snode) = \
+          ExpandNodeUuidAndName(self.cfg, None, node_names[1])
 
       jobs.append([op])
 
@@ -1987,7 +2008,7 @@ def _PrepareContainerMods(mods, private_fn):
   return [(op, idx, params, fn()) for (op, idx, params) in mods]
 
 
-def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
+def _CheckNodesPhysicalCPUs(lu, node_uuids, requested, hypervisor_specs):
   """Checks if nodes have enough physical CPUs
 
   This function checks if all given nodes have the needed number of
@@ -1997,28 +2018,32 @@ def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
 
   @type lu: C{LogicalUnit}
   @param lu: a logical unit from which we get configuration data
-  @type nodenames: C{list}
-  @param nodenames: the list of node names to check
+  @type node_uuids: C{list}
+  @param node_uuids: the list of node UUIDs to check
   @type requested: C{int}
   @param requested: the minimum acceptable number of physical CPUs
+  @type hypervisor_specs: list of pairs (string, dict of strings)
+  @param hypervisor_specs: list of hypervisor specifications in
+      pairs (hypervisor_name, hvparams)
   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
       or we cannot check the node
 
   """
-  nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name], None)
-  for node in nodenames:
-    info = nodeinfo[node]
-    info.Raise("Cannot get current information from node %s" % node,
+  nodeinfo = lu.rpc.call_node_info(node_uuids, None, hypervisor_specs, None)
+  for node_uuid in node_uuids:
+    info = nodeinfo[node_uuid]
+    node_name = lu.cfg.GetNodeName(node_uuid)
+    info.Raise("Cannot get current information from node %s" % node_name,
                prereq=True, ecode=errors.ECODE_ENVIRON)
     (_, _, (hv_info, )) = info.payload
     num_cpus = hv_info.get("cpu_total", None)
     if not isinstance(num_cpus, int):
       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
                                  " on node %s, result was '%s'" %
-                                 (node, num_cpus), errors.ECODE_ENVIRON)
+                                 (node_name, num_cpus), errors.ECODE_ENVIRON)
     if requested > num_cpus:
       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
-                                 "required" % (node, num_cpus, requested),
+                                 "required" % (node_name, num_cpus, requested),
                                  errors.ECODE_NORES)
 
 
@@ -2214,7 +2239,7 @@ class LUInstanceSetParams(LogicalUnit):
         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
 
   @staticmethod
-  def _VerifyDiskModification(op, params):
+  def _VerifyDiskModification(op, params, excl_stor):
     """Verifies a disk modification.
 
     """
@@ -2240,6 +2265,8 @@ class LUInstanceSetParams(LogicalUnit):
       if name is not None and name.lower() == constants.VALUE_NONE:
         params[constants.IDISK_NAME] = None
 
+      CheckSpindlesExclusiveStorage(params, excl_stor, True)
+
     elif op == constants.DDM_MODIFY:
       if constants.IDISK_SIZE in params:
         raise errors.OpPrereqError("Disk size change not possible, use"
@@ -2336,7 +2363,8 @@ class LUInstanceSetParams(LogicalUnit):
                     self._VerifyNicModification)
 
     if self.op.pnode:
-      self.op.pnode = ExpandNodeName(self.cfg, self.op.pnode)
+      (self.op.pnode_uuid, self.op.pnode) = \
+        ExpandNodeUuidAndName(self.cfg, self.op.pnode_uuid, self.op.pnode)
 
   def ExpandNames(self):
     self._ExpandAndLockInstance()
@@ -2355,12 +2383,14 @@ class LUInstanceSetParams(LogicalUnit):
       # Acquire locks for the instance's nodegroups optimistically. Needs
       # to be verified in CheckPrereq
       self.needed_locks[locking.LEVEL_NODEGROUP] = \
-        self.cfg.GetInstanceNodeGroups(self.op.instance_name)
+        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
     elif level == locking.LEVEL_NODE:
       self._LockInstancesNodes()
       if self.op.disk_template and self.op.remote_node:
-        self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
-        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
+        (self.op.remote_node_uuid, self.op.remote_node) = \
+          ExpandNodeUuidAndName(self.cfg, self.op.remote_node_uuid,
+                                self.op.remote_node)
+        self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node_uuid)
     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
       # Copy node locks
       self.needed_locks[locking.LEVEL_NODE_RES] = \
@@ -2409,7 +2439,7 @@ class LUInstanceSetParams(LogicalUnit):
     return (nl, nl)
 
   def _PrepareNicModification(self, params, private, old_ip, old_net_uuid,
-                              old_params, cluster, pnode):
+                              old_params, cluster, pnode_uuid):
 
     update_params_dict = dict([(key, params[key])
                                for key in constants.NICS_PARAMETERS
@@ -2428,7 +2458,7 @@ class LUInstanceSetParams(LogicalUnit):
       old_net_obj = self.cfg.GetNetwork(old_net_uuid)
 
     if new_net_uuid:
-      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode)
+      netparams = self.cfg.GetGroupNetParams(new_net_uuid, pnode_uuid)
       if not netparams:
         raise errors.OpPrereqError("No netparams found for the network"
                                    " %s, probably not connected" %
@@ -2445,9 +2475,10 @@ class LUInstanceSetParams(LogicalUnit):
     new_mode = new_filled_params[constants.NIC_MODE]
     if new_mode == constants.NIC_MODE_BRIDGED:
       bridge = new_filled_params[constants.NIC_LINK]
-      msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
+      msg = self.rpc.call_bridges_exist(pnode_uuid, [bridge]).fail_msg
       if msg:
-        msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
+        msg = "Error checking bridges on node '%s': %s" % \
+                (self.cfg.GetNodeName(pnode_uuid), msg)
         if self.op.force:
           self.warn.append(msg)
         else:
@@ -2528,7 +2559,7 @@ class LUInstanceSetParams(LogicalUnit):
                                        errors.ECODE_NOTUNIQUE)
         # new network is None so check if new IP is a conflicting IP
         elif self.op.conflicts_check:
-          _CheckForConflictingIp(self, new_ip, pnode)
+          _CheckForConflictingIp(self, new_ip, pnode_uuid)
 
       # release old IP if old network is not None
       if old_ip and old_net_uuid:
@@ -2551,41 +2582,40 @@ class LUInstanceSetParams(LogicalUnit):
   def _PreCheckDiskTemplate(self, pnode_info):
     """CheckPrereq checks related to a new disk template."""
     # Arguments are passed to avoid configuration lookups
-    instance = self.instance
-    pnode = instance.primary_node
-    cluster = self.cluster
-    if instance.disk_template == self.op.disk_template:
+    pnode_uuid = self.instance.primary_node
+    if self.instance.disk_template == self.op.disk_template:
       raise errors.OpPrereqError("Instance already has disk template %s" %
-                                 instance.disk_template, errors.ECODE_INVAL)
+                                 self.instance.disk_template,
+                                 errors.ECODE_INVAL)
 
-    if (instance.disk_template,
+    if (self.instance.disk_template,
         self.op.disk_template) not in self._DISK_CONVERSIONS:
       raise errors.OpPrereqError("Unsupported disk template conversion from"
-                                 " %s to %s" % (instance.disk_template,
+                                 " %s to %s" % (self.instance.disk_template,
                                                 self.op.disk_template),
                                  errors.ECODE_INVAL)
-    CheckInstanceState(self, instance, INSTANCE_DOWN,
+    CheckInstanceState(self, self.instance, INSTANCE_DOWN,
                        msg="cannot change disk template")
     if self.op.disk_template in constants.DTS_INT_MIRROR:
-      if self.op.remote_node == pnode:
+      if self.op.remote_node_uuid == pnode_uuid:
         raise errors.OpPrereqError("Given new secondary node %s is the same"
                                    " as the primary node of the instance" %
                                    self.op.remote_node, errors.ECODE_STATE)
-      CheckNodeOnline(self, self.op.remote_node)
-      CheckNodeNotDrained(self, self.op.remote_node)
+      CheckNodeOnline(self, self.op.remote_node_uuid)
+      CheckNodeNotDrained(self, self.op.remote_node_uuid)
       # FIXME: here we assume that the old instance type is DT_PLAIN
-      assert instance.disk_template == constants.DT_PLAIN
+      assert self.instance.disk_template == constants.DT_PLAIN
       disks = [{constants.IDISK_SIZE: d.size,
                 constants.IDISK_VG: d.logical_id[0]}
-               for d in instance.disks]
+               for d in self.instance.disks]
       required = ComputeDiskSizePerVG(self.op.disk_template, disks)
-      CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
+      CheckNodesFreeDiskPerVG(self, [self.op.remote_node_uuid], required)
 
-      snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
+      snode_info = self.cfg.GetNodeInfo(self.op.remote_node_uuid)
       snode_group = self.cfg.GetNodeGroup(snode_info.group)
-      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
+      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
                                                               snode_group)
-      CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info, self.cfg,
+      CheckTargetNodeIPolicy(self, ipolicy, self.instance, snode_info, self.cfg,
                              ignore=self.op.ignore_ipolicy)
       if pnode_info.group != snode_info.group:
         self.LogWarning("The primary and secondary nodes are in two"
@@ -2602,66 +2632,37 @@ class LUInstanceSetParams(LogicalUnit):
       has_es = lambda n: IsExclusiveStorageEnabledNode(self.cfg, n)
       if compat.any(map(has_es, nodes)):
         errmsg = ("Cannot convert disk template from %s to %s when exclusive"
-                  " storage is enabled" % (instance.disk_template,
+                  " storage is enabled" % (self.instance.disk_template,
                                            self.op.disk_template))
         raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
 
-  def CheckPrereq(self):
-    """Check prerequisites.
+  def _PreCheckDisks(self, ispec):
+    """CheckPrereq checks related to disk changes.
 
-    This only checks the instance list against the existing names.
+    @type ispec: dict
+    @param ispec: instance specs to be updated with the new disks
 
     """
-    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
-    instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
-
-    cluster = self.cluster = self.cfg.GetClusterInfo()
-    assert self.instance is not None, \
-      "Cannot retrieve locked instance %s" % self.op.instance_name
+    self.diskparams = self.cfg.GetInstanceDiskParams(self.instance)
 
-    pnode = instance.primary_node
-
-    self.warn = []
-
-    if (self.op.pnode is not None and self.op.pnode != pnode and
-        not self.op.force):
-      # verify that the instance is not up
-      instance_info = self.rpc.call_instance_info(pnode, instance.name,
-                                                  instance.hypervisor)
-      if instance_info.fail_msg:
-        self.warn.append("Can't get instance runtime information: %s" %
-                         instance_info.fail_msg)
-      elif instance_info.payload:
-        raise errors.OpPrereqError("Instance is still running on %s" % pnode,
-                                   errors.ECODE_STATE)
-
-    assert pnode in self.owned_locks(locking.LEVEL_NODE)
-    nodelist = list(instance.all_nodes)
-    pnode_info = self.cfg.GetNodeInfo(pnode)
-    self.diskparams = self.cfg.GetInstanceDiskParams(instance)
-
-    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
-    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
-    group_info = self.cfg.GetNodeGroup(pnode_info.group)
-
-    # dictionary with instance information after the modification
-    ispec = {}
+    excl_stor = compat.any(
+      rpc.GetExclusiveStorageForNodes(self.cfg,
+                                      self.instance.all_nodes).values()
+      )
 
     # Check disk modifications. This is done here and not in CheckArguments
     # (as with NICs), because we need to know the instance's disk template
-    if instance.disk_template == constants.DT_EXT:
-      self._CheckMods("disk", self.op.disks, {},
-                      self._VerifyDiskModification)
+    ver_fn = lambda op, par: self._VerifyDiskModification(op, par, excl_stor)
+    if self.instance.disk_template == constants.DT_EXT:
+      self._CheckMods("disk", self.op.disks, {}, ver_fn)
     else:
       self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
-                      self._VerifyDiskModification)
+                      ver_fn)
 
-    # Prepare disk/NIC modifications
     self.diskmod = _PrepareContainerMods(self.op.disks, None)
-    self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
 
     # Check the validity of the `provider' parameter
-    if instance.disk_template in constants.DT_EXT:
+    if self.instance.disk_template in constants.DT_EXT:
       for mod in self.diskmod:
         ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
         if mod[0] == constants.DDM_ADD:
@@ -2687,13 +2688,84 @@ class LUInstanceSetParams(LogicalUnit):
                                       constants.DT_EXT),
                                      errors.ECODE_INVAL)
 
+    if self.op.disks and self.instance.disk_template == constants.DT_DISKLESS:
+      raise errors.OpPrereqError("Disk operations not supported for"
+                                 " diskless instances", errors.ECODE_INVAL)
+
+    def _PrepareDiskMod(_, disk, params, __):
+      disk.name = params.get(constants.IDISK_NAME, None)
+
+    # Verify disk changes (operating on a copy)
+    disks = copy.deepcopy(self.instance.disks)
+    _ApplyContainerMods("disk", disks, None, self.diskmod, None,
+                        _PrepareDiskMod, None)
+    utils.ValidateDeviceNames("disk", disks)
+    if len(disks) > constants.MAX_DISKS:
+      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
+                                 " more" % constants.MAX_DISKS,
+                                 errors.ECODE_STATE)
+    disk_sizes = [disk.size for disk in self.instance.disks]
+    disk_sizes.extend(params["size"] for (op, idx, params, private) in
+                      self.diskmod if op == constants.DDM_ADD)
+    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
+    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
+
+    if self.op.offline is not None and self.op.offline:
+      CheckInstanceState(self, self.instance, CAN_CHANGE_INSTANCE_OFFLINE,
+                         msg="can't change to offline")
+
+  def CheckPrereq(self):
+    """Check prerequisites.
+
+    This only checks the instance list against the existing names.
+
+    """
+    assert self.op.instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
+    self.cluster = self.cfg.GetClusterInfo()
+
+    assert self.instance is not None, \
+      "Cannot retrieve locked instance %s" % self.op.instance_name
+
+    pnode_uuid = self.instance.primary_node
+
+    self.warn = []
+
+    if (self.op.pnode_uuid is not None and self.op.pnode_uuid != pnode_uuid and
+        not self.op.force):
+      # verify that the instance is not up
+      instance_info = self.rpc.call_instance_info(
+          pnode_uuid, self.instance.name, self.instance.hypervisor,
+          self.instance.hvparams)
+      if instance_info.fail_msg:
+        self.warn.append("Can't get instance runtime information: %s" %
+                         instance_info.fail_msg)
+      elif instance_info.payload:
+        raise errors.OpPrereqError("Instance is still running on %s" %
+                                   self.cfg.GetNodeName(pnode_uuid),
+                                   errors.ECODE_STATE)
+
+    assert pnode_uuid in self.owned_locks(locking.LEVEL_NODE)
+    node_uuids = list(self.instance.all_nodes)
+    pnode_info = self.cfg.GetNodeInfo(pnode_uuid)
+
+    #_CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
+    assert pnode_info.group in self.owned_locks(locking.LEVEL_NODEGROUP)
+    group_info = self.cfg.GetNodeGroup(pnode_info.group)
+
+    # dictionary with instance information after the modification
+    ispec = {}
+
+    # Prepare NIC modifications
+    self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
+
     # OS change
     if self.op.os_name and not self.op.force:
-      CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
+      CheckNodeHasOS(self, self.instance.primary_node, self.op.os_name,
                      self.op.force_variant)
       instance_os = self.op.os_name
     else:
-      instance_os = instance.os
+      instance_os = self.instance.os
 
     assert not (self.op.disk_template and self.op.disks), \
       "Can't modify disk template and apply disk changes at the same time"
@@ -2701,36 +2773,39 @@ class LUInstanceSetParams(LogicalUnit):
     if self.op.disk_template:
       self._PreCheckDiskTemplate(pnode_info)
 
+    self._PreCheckDisks(ispec)
+
     # hvparams processing
     if self.op.hvparams:
-      hv_type = instance.hypervisor
-      i_hvdict = GetUpdatedParams(instance.hvparams, self.op.hvparams)
+      hv_type = self.instance.hypervisor
+      i_hvdict = GetUpdatedParams(self.instance.hvparams, self.op.hvparams)
       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
-      hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
+      hv_new = self.cluster.SimpleFillHV(hv_type, self.instance.os, i_hvdict)
 
       # local check
       hypervisor.GetHypervisorClass(hv_type).CheckParameterSyntax(hv_new)
-      CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
+      CheckHVParams(self, node_uuids, self.instance.hypervisor, hv_new)
       self.hv_proposed = self.hv_new = hv_new # the new actual values
       self.hv_inst = i_hvdict # the new dict (without defaults)
     else:
-      self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
-                                              instance.hvparams)
+      self.hv_proposed = self.cluster.SimpleFillHV(self.instance.hypervisor,
+                                                   self.instance.os,
+                                                   self.instance.hvparams)
       self.hv_new = self.hv_inst = {}
 
     # beparams processing
     if self.op.beparams:
-      i_bedict = GetUpdatedParams(instance.beparams, self.op.beparams,
+      i_bedict = GetUpdatedParams(self.instance.beparams, self.op.beparams,
                                   use_none=True)
       objects.UpgradeBeParams(i_bedict)
       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
-      be_new = cluster.SimpleFillBE(i_bedict)
+      be_new = self.cluster.SimpleFillBE(i_bedict)
       self.be_proposed = self.be_new = be_new # the new actual values
       self.be_inst = i_bedict # the new dict (without defaults)
     else:
       self.be_new = self.be_inst = {}
-      self.be_proposed = cluster.SimpleFillBE(instance.beparams)
-    be_old = cluster.FillBE(instance)
+      self.be_proposed = self.cluster.SimpleFillBE(self.instance.beparams)
+    be_old = self.cluster.FillBE(self.instance)
 
     # CPU param validation -- checking every time a parameter is
     # changed to cover all cases where either CPU mask or vcpus have
@@ -2756,13 +2831,17 @@ class LUInstanceSetParams(LogicalUnit):
         max_requested_cpu = max(map(max, cpu_list))
         # Check that all of the instance's nodes have enough physical CPUs to
         # satisfy the requested CPU mask
-        _CheckNodesPhysicalCPUs(self, instance.all_nodes,
-                                max_requested_cpu + 1, instance.hypervisor)
+        hvspecs = [(self.instance.hypervisor,
+                    self.cfg.GetClusterInfo()
+                      .hvparams[self.instance.hypervisor])]
+        _CheckNodesPhysicalCPUs(self, self.instance.all_nodes,
+                                max_requested_cpu + 1,
+                                hvspecs)
 
     # osparams processing
     if self.op.osparams:
-      i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
-      CheckOSParams(self, True, nodelist, instance_os, i_osdict)
+      i_osdict = GetUpdatedParams(self.instance.osparams, self.op.osparams)
+      CheckOSParams(self, True, node_uuids, instance_os, i_osdict)
       self.os_inst = i_osdict # the new dict (without defaults)
     else:
       self.os_inst = {}
@@ -2770,25 +2849,29 @@ class LUInstanceSetParams(LogicalUnit):
     #TODO(dynmem): do the appropriate check involving MINMEM
     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
-      mem_check_list = [pnode]
+      mem_check_list = [pnode_uuid]
       if be_new[constants.BE_AUTO_BALANCE]:
         # either we changed auto_balance to yes or it was from before
-        mem_check_list.extend(instance.secondary_nodes)
-      instance_info = self.rpc.call_instance_info(pnode, instance.name,
-                                                  instance.hypervisor)
+        mem_check_list.extend(self.instance.secondary_nodes)
+      instance_info = self.rpc.call_instance_info(
+          pnode_uuid, self.instance.name, self.instance.hypervisor,
+          self.instance.hvparams)
+      hvspecs = [(self.instance.hypervisor,
+                  self.cluster.hvparams[self.instance.hypervisor])]
       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
-                                         [instance.hypervisor], False)
-      pninfo = nodeinfo[pnode]
+                                         hvspecs, False)
+      pninfo = nodeinfo[pnode_uuid]
       msg = pninfo.fail_msg
       if msg:
         # Assume the primary node is unreachable and go ahead
         self.warn.append("Can't get info from primary node %s: %s" %
-                         (pnode, msg))
+                         (self.cfg.GetNodeName(pnode_uuid), msg))
       else:
         (_, _, (pnhvinfo, )) = pninfo.payload
         if not isinstance(pnhvinfo.get("memory_free", None), int):
           self.warn.append("Node data from primary node %s doesn't contain"
-                           " free memory information" % pnode)
+                           " free memory information" %
+                           self.cfg.GetNodeName(pnode_uuid))
         elif instance_info.fail_msg:
           self.warn.append("Can't get instance runtime information: %s" %
                            instance_info.fail_msg)
@@ -2811,31 +2894,36 @@ class LUInstanceSetParams(LogicalUnit):
                                        miss_mem, errors.ECODE_NORES)
 
       if be_new[constants.BE_AUTO_BALANCE]:
-        for node, nres in nodeinfo.items():
-          if node not in instance.secondary_nodes:
+        for node_uuid, nres in nodeinfo.items():
+          if node_uuid not in self.instance.secondary_nodes:
             continue
-          nres.Raise("Can't get info from secondary node %s" % node,
-                     prereq=True, ecode=errors.ECODE_STATE)
+          nres.Raise("Can't get info from secondary node %s" %
+                     self.cfg.GetNodeName(node_uuid), prereq=True,
+                     ecode=errors.ECODE_STATE)
           (_, _, (nhvinfo, )) = nres.payload
           if not isinstance(nhvinfo.get("memory_free", None), int):
             raise errors.OpPrereqError("Secondary node %s didn't return free"
-                                       " memory information" % node,
+                                       " memory information" %
+                                       self.cfg.GetNodeName(node_uuid),
                                        errors.ECODE_STATE)
           #TODO(dynmem): do the appropriate check involving MINMEM
           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
             raise errors.OpPrereqError("This change will prevent the instance"
                                        " from failover to its secondary node"
-                                       " %s, due to not enough memory" % node,
+                                       " %s, due to not enough memory" %
+                                       self.cfg.GetNodeName(node_uuid),
                                        errors.ECODE_STATE)
 
     if self.op.runtime_mem:
-      remote_info = self.rpc.call_instance_info(instance.primary_node,
-                                                instance.name,
-                                                instance.hypervisor)
-      remote_info.Raise("Error checking node %s" % instance.primary_node)
+      remote_info = self.rpc.call_instance_info(
+         self.instance.primary_node, self.instance.name,
+         self.instance.hypervisor,
+         self.cluster.hvparams[self.instance.hypervisor])
+      remote_info.Raise("Error checking node %s" %
+                        self.cfg.GetNodeName(self.instance.primary_node))
       if not remote_info.payload: # not running already
         raise errors.OpPrereqError("Instance %s is not running" %
-                                   instance.name, errors.ECODE_STATE)
+                                   self.instance.name, errors.ECODE_STATE)
 
       current_memory = remote_info.payload["memory"]
       if (not self.op.force and
@@ -2844,29 +2932,30 @@ class LUInstanceSetParams(LogicalUnit):
         raise errors.OpPrereqError("Instance %s must have memory between %d"
                                    " and %d MB of memory unless --force is"
                                    " given" %
-                                   (instance.name,
+                                   (self.instance.name,
                                     self.be_proposed[constants.BE_MINMEM],
                                     self.be_proposed[constants.BE_MAXMEM]),
                                    errors.ECODE_INVAL)
 
       delta = self.op.runtime_mem - current_memory
       if delta > 0:
-        CheckNodeFreeMemory(self, instance.primary_node,
-                            "ballooning memory for instance %s" %
-                            instance.name, delta, instance.hypervisor)
+        CheckNodeFreeMemory(
+            self, self.instance.primary_node,
+            "ballooning memory for instance %s" % self.instance.name, delta,
+            self.instance.hypervisor,
+            self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
 
-    if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
-      raise errors.OpPrereqError("Disk operations not supported for"
-                                 " diskless instances", errors.ECODE_INVAL)
+    # make self.cluster visible in the functions below
+    cluster = self.cluster
 
     def _PrepareNicCreate(_, params, private):
       self._PrepareNicModification(params, private, None, None,
-                                   {}, cluster, pnode)
+                                   {}, cluster, pnode_uuid)
       return (None, None)
 
     def _PrepareNicMod(_, nic, params, private):
       self._PrepareNicModification(params, private, nic.ip, nic.network,
-                                   nic.nicparams, cluster, pnode)
+                                   nic.nicparams, cluster, pnode_uuid)
       return None
 
     def _PrepareNicRemove(_, params, __):
@@ -2876,7 +2965,7 @@ class LUInstanceSetParams(LogicalUnit):
         self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
 
     # Verify NIC changes (operating on copy)
-    nics = instance.nics[:]
+    nics = self.instance.nics[:]
     _ApplyContainerMods("NIC", nics, None, self.nicmod,
                         _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
     if len(nics) > constants.MAX_NICS:
@@ -2884,33 +2973,11 @@ class LUInstanceSetParams(LogicalUnit):
                                  " (%d), cannot add more" % constants.MAX_NICS,
                                  errors.ECODE_STATE)
 
-    def _PrepareDiskMod(_, disk, params, __):
-      disk.name = params.get(constants.IDISK_NAME, None)
-
-    # Verify disk changes (operating on a copy)
-    disks = copy.deepcopy(instance.disks)
-    _ApplyContainerMods("disk", disks, None, self.diskmod, None,
-                        _PrepareDiskMod, None)
-    utils.ValidateDeviceNames("disk", disks)
-    if len(disks) > constants.MAX_DISKS:
-      raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
-                                 " more" % constants.MAX_DISKS,
-                                 errors.ECODE_STATE)
-    disk_sizes = [disk.size for disk in instance.disks]
-    disk_sizes.extend(params["size"] for (op, idx, params, private) in
-                      self.diskmod if op == constants.DDM_ADD)
-    ispec[constants.ISPEC_DISK_COUNT] = len(disk_sizes)
-    ispec[constants.ISPEC_DISK_SIZE] = disk_sizes
-
-    if self.op.offline is not None and self.op.offline:
-      CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE,
-                         msg="can't change to offline")
-
     # Pre-compute NIC changes (necessary to use result in hooks)
     self._nic_chgdesc = []
     if self.nicmod:
       # Operate on copies as this is still in prereq
-      nics = [nic.Copy() for nic in instance.nics]
+      nics = [nic.Copy() for nic in self.instance.nics]
       _ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
                           self._CreateNewNic, self._ApplyNicMods, None)
       # Verify that NIC names are unique and valid
@@ -2919,10 +2986,10 @@ class LUInstanceSetParams(LogicalUnit):
       ispec[constants.ISPEC_NIC_COUNT] = len(self._new_nics)
     else:
       self._new_nics = None
-      ispec[constants.ISPEC_NIC_COUNT] = len(instance.nics)
+      ispec[constants.ISPEC_NIC_COUNT] = len(self.instance.nics)
 
     if not self.op.ignore_ipolicy:
-      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
+      ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(self.cluster,
                                                               group_info)
 
       # Fill ispec with backend parameters
@@ -2935,7 +3002,7 @@ class LUInstanceSetParams(LogicalUnit):
       if self.op.disk_template:
         new_disk_template = self.op.disk_template
       else:
-        new_disk_template = instance.disk_template
+        new_disk_template = self.instance.disk_template
       ispec_max = ispec.copy()
       ispec_max[constants.ISPEC_MEM_SIZE] = \
         self.be_new.get(constants.BE_MAXMEM, None)
@@ -2960,72 +3027,72 @@ class LUInstanceSetParams(LogicalUnit):
 
     """
     feedback_fn("Converting template to drbd")
-    instance = self.instance
-    pnode = instance.primary_node
-    snode = self.op.remote_node
+    pnode_uuid = self.instance.primary_node
+    snode_uuid = self.op.remote_node_uuid
 
-    assert instance.disk_template == constants.DT_PLAIN
+    assert self.instance.disk_template == constants.DT_PLAIN
 
     # create a fake disk info for _GenerateDiskTemplate
     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
                   constants.IDISK_VG: d.logical_id[0],
                   constants.IDISK_NAME: d.name}
-                 for d in instance.disks]
+                 for d in self.instance.disks]
     new_disks = GenerateDiskTemplate(self, self.op.disk_template,
-                                     instance.name, pnode, [snode],
-                                     disk_info, None, None, 0, feedback_fn,
-                                     self.diskparams)
+                                     self.instance.uuid, pnode_uuid,
+                                     [snode_uuid], disk_info, None, None, 0,
+                                     feedback_fn, self.diskparams)
     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
                                         self.diskparams)
-    p_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, pnode)
-    s_excl_stor = IsExclusiveStorageEnabledNodeName(self.cfg, snode)
-    info = GetInstanceInfoText(instance)
+    p_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, pnode_uuid)
+    s_excl_stor = IsExclusiveStorageEnabledNodeUuid(self.cfg, snode_uuid)
+    info = GetInstanceInfoText(self.instance)
     feedback_fn("Creating additional volumes...")
     # first, create the missing data and meta devices
     for disk in anno_disks:
       # unfortunately this is... not too nice
-      CreateSingleBlockDev(self, pnode, instance, disk.children[1],
+      CreateSingleBlockDev(self, pnode_uuid, self.instance, disk.children[1],
                            info, True, p_excl_stor)
       for child in disk.children:
-        CreateSingleBlockDev(self, snode, instance, child, info, True,
+        CreateSingleBlockDev(self, snode_uuid, self.instance, child, info, True,
                              s_excl_stor)
     # at this stage, all new LVs have been created, we can rename the
     # old ones
     feedback_fn("Renaming original volumes...")
     rename_list = [(o, n.children[0].logical_id)
-                   for (o, n) in zip(instance.disks, new_disks)]
-    result = self.rpc.call_blockdev_rename(pnode, rename_list)
+                   for (o, n) in zip(self.instance.disks, new_disks)]
+    result = self.rpc.call_blockdev_rename(pnode_uuid, rename_list)
     result.Raise("Failed to rename original LVs")
 
     feedback_fn("Initializing DRBD devices...")
     # all child devices are in place, we can now create the DRBD devices
     try:
       for disk in anno_disks:
-        for (node, excl_stor) in [(pnode, p_excl_stor), (snode, s_excl_stor)]:
-          f_create = node == pnode
-          CreateSingleBlockDev(self, node, instance, disk, info, f_create,
-                               excl_stor)
+        for (node_uuid, excl_stor) in [(pnode_uuid, p_excl_stor),
+                                       (snode_uuid, s_excl_stor)]:
+          f_create = node_uuid == pnode_uuid
+          CreateSingleBlockDev(self, node_uuid, self.instance, disk, info,
+                               f_create, excl_stor)
     except errors.GenericError, e:
       feedback_fn("Initializing of DRBD devices failed;"
                   " renaming back original volumes...")
       for disk in new_disks:
-        self.cfg.SetDiskID(disk, pnode)
+        self.cfg.SetDiskID(disk, pnode_uuid)
       rename_back_list = [(n.children[0], o.logical_id)
-                          for (n, o) in zip(new_disks, instance.disks)]
-      result = self.rpc.call_blockdev_rename(pnode, rename_back_list)
+                          for (n, o) in zip(new_disks, self.instance.disks)]
+      result = self.rpc.call_blockdev_rename(pnode_uuid, rename_back_list)
       result.Raise("Failed to rename LVs back after error %s" % str(e))
       raise
 
     # at this point, the instance has been modified
-    instance.disk_template = constants.DT_DRBD8
-    instance.disks = new_disks
-    self.cfg.Update(instance, feedback_fn)
+    self.instance.disk_template = constants.DT_DRBD8
+    self.instance.disks = new_disks
+    self.cfg.Update(self.instance, feedback_fn)
 
     # Release node locks while waiting for sync
     ReleaseLocks(self, locking.LEVEL_NODE)
 
     # disks are created, waiting for sync
-    disk_abort = not WaitForSync(self, instance,
+    disk_abort = not WaitForSync(self, self.instance,
                                  oneshot=not self.op.wait_for_sync)
     if disk_abort:
       raise errors.OpExecError("There are some degraded disks for"
@@ -3037,17 +3104,15 @@ class LUInstanceSetParams(LogicalUnit):
     """Converts an instance from drbd to plain.
 
     """
-    instance = self.instance
-
-    assert len(instance.secondary_nodes) == 1
-    assert instance.disk_template == constants.DT_DRBD8
+    assert len(self.instance.secondary_nodes) == 1
+    assert self.instance.disk_template == constants.DT_DRBD8
 
-    pnode = instance.primary_node
-    snode = instance.secondary_nodes[0]
+    pnode_uuid = self.instance.primary_node
+    snode_uuid = self.instance.secondary_nodes[0]
     feedback_fn("Converting template to plain")
 
-    old_disks = AnnotateDiskParams(instance, instance.disks, self.cfg)
-    new_disks = [d.children[0] for d in instance.disks]
+    old_disks = AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
+    new_disks = [d.children[0] for d in self.instance.disks]
 
     # copy over size, mode and name
     for parent, child in zip(old_disks, new_disks):
@@ -3062,55 +3127,55 @@ class LUInstanceSetParams(LogicalUnit):
       self.cfg.AddTcpUdpPort(tcp_port)
 
     # update instance structure
-    instance.disks = new_disks
-    instance.disk_template = constants.DT_PLAIN
-    _UpdateIvNames(0, instance.disks)
-    self.cfg.Update(instance, feedback_fn)
+    self.instance.disks = new_disks
+    self.instance.disk_template = constants.DT_PLAIN
+    _UpdateIvNames(0, self.instance.disks)
+    self.cfg.Update(self.instance, feedback_fn)
 
     # Release locks in case removing disks takes a while
     ReleaseLocks(self, locking.LEVEL_NODE)
 
     feedback_fn("Removing volumes on the secondary node...")
     for disk in old_disks:
-      self.cfg.SetDiskID(disk, snode)
-      msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
+      self.cfg.SetDiskID(disk, snode_uuid)
+      msg = self.rpc.call_blockdev_remove(snode_uuid, disk).fail_msg
       if msg:
         self.LogWarning("Could not remove block device %s on node %s,"
-                        " continuing anyway: %s", disk.iv_name, snode, msg)
+                        " continuing anyway: %s", disk.iv_name,
+                        self.cfg.GetNodeName(snode_uuid), msg)
 
     feedback_fn("Removing unneeded volumes on the primary node...")
     for idx, disk in enumerate(old_disks):
       meta = disk.children[1]
-      self.cfg.SetDiskID(meta, pnode)
-      msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
+      self.cfg.SetDiskID(meta, pnode_uuid)
+      msg = self.rpc.call_blockdev_remove(pnode_uuid, meta).fail_msg
       if msg:
         self.LogWarning("Could not remove metadata for disk %d on node %s,"
-                        " continuing anyway: %s", idx, pnode, msg)
+                        " continuing anyway: %s", idx,
+                        self.cfg.GetNodeName(pnode_uuid), msg)
 
   def _CreateNewDisk(self, idx, params, _):
     """Creates a new disk.
 
     """
-    instance = self.instance
-
     # add a new disk
-    if instance.disk_template in constants.DTS_FILEBASED:
-      (file_driver, file_path) = instance.disks[0].logical_id
+    if self.instance.disk_template in constants.DTS_FILEBASED:
+      (file_driver, file_path) = self.instance.disks[0].logical_id
       file_path = os.path.dirname(file_path)
     else:
       file_driver = file_path = None
 
     disk = \
-      GenerateDiskTemplate(self, instance.disk_template, instance.name,
-                           instance.primary_node, instance.secondary_nodes,
-                           [params], file_path, file_driver, idx,
-                           self.Log, self.diskparams)[0]
+      GenerateDiskTemplate(self, self.instance.disk_template,
+                           self.instance.uuid, self.instance.primary_node,
+                           self.instance.secondary_nodes, [params], file_path,
+                           file_driver, idx, self.Log, self.diskparams)[0]
 
-    new_disks = CreateDisks(self, instance, disks=[disk])
+    new_disks = CreateDisks(self, self.instance, disks=[disk])
 
     if self.cluster.prealloc_wipe_disks:
       # Wipe new disk
-      WipeOrCleanupDisks(self, instance,
+      WipeOrCleanupDisks(self, self.instance,
                          disks=[(idx, disk, 0)],
                          cleanup=new_disks)
 
@@ -3140,12 +3205,14 @@ class LUInstanceSetParams(LogicalUnit):
 
     """
     (anno_disk,) = AnnotateDiskParams(self.instance, [root], self.cfg)
-    for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
-      self.cfg.SetDiskID(disk, node)
-      msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
+    for node_uuid, disk in anno_disk.ComputeNodeTree(
+                             self.instance.primary_node):
+      self.cfg.SetDiskID(disk, node_uuid)
+      msg = self.rpc.call_blockdev_remove(node_uuid, disk).fail_msg
       if msg:
         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
-                        " continuing anyway", idx, node, msg)
+                        " continuing anyway", idx,
+                        self.cfg.GetNodeName(node_uuid), msg)
 
     # if this is a DRBD disk, return its port to the pool
     if root.dev_type in constants.LDS_DRBD:
@@ -3216,52 +3283,51 @@ class LUInstanceSetParams(LogicalUnit):
       "Not owning any node resource locks"
 
     result = []
-    instance = self.instance
 
     # New primary node
-    if self.op.pnode:
-      instance.primary_node = self.op.pnode
+    if self.op.pnode_uuid:
+      self.instance.primary_node = self.op.pnode_uuid
 
     # runtime memory
     if self.op.runtime_mem:
-      rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
-                                                     instance,
+      rpcres = self.rpc.call_instance_balloon_memory(self.instance.primary_node,
+                                                     self.instance,
                                                      self.op.runtime_mem)
       rpcres.Raise("Cannot modify instance runtime memory")
       result.append(("runtime_memory", self.op.runtime_mem))
 
     # Apply disk changes
-    _ApplyContainerMods("disk", instance.disks, result, self.diskmod,
+    _ApplyContainerMods("disk", self.instance.disks, result, self.diskmod,
                         self._CreateNewDisk, self._ModifyDisk,
                         self._RemoveDisk)
-    _UpdateIvNames(0, instance.disks)
+    _UpdateIvNames(0, self.instance.disks)
 
     if self.op.disk_template:
       if __debug__:
-        check_nodes = set(instance.all_nodes)
-        if self.op.remote_node:
-          check_nodes.add(self.op.remote_node)
+        check_nodes = set(self.instance.all_nodes)
+        if self.op.remote_node_uuid:
+          check_nodes.add(self.op.remote_node_uuid)
         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
           owned = self.owned_locks(level)
           assert not (check_nodes - owned), \
             ("Not owning the correct locks, owning %r, expected at least %r" %
              (owned, check_nodes))
 
-      r_shut = ShutdownInstanceDisks(self, instance)
+      r_shut = ShutdownInstanceDisks(self, self.instance)
       if not r_shut:
         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
                                  " proceed with disk template conversion")
-      mode = (instance.disk_template, self.op.disk_template)
+      mode = (self.instance.disk_template, self.op.disk_template)
       try:
         self._DISK_CONVERSIONS[mode](self, feedback_fn)
       except:
-        self.cfg.ReleaseDRBDMinors(instance.name)
+        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
         raise
       result.append(("disk_template", self.op.disk_template))
 
-      assert instance.disk_template == self.op.disk_template, \
+      assert self.instance.disk_template == self.op.disk_template, \
         ("Expected disk template '%s', found '%s'" %
-         (self.op.disk_template, instance.disk_template))
+         (self.op.disk_template, self.instance.disk_template))
 
     # Release node and resource locks if there are any (they might already have
     # been released during disk conversion)
@@ -3270,28 +3336,28 @@ class LUInstanceSetParams(LogicalUnit):
 
     # Apply NIC changes
     if self._new_nics is not None:
-      instance.nics = self._new_nics
+      self.instance.nics = self._new_nics
       result.extend(self._nic_chgdesc)
 
     # hvparams changes
     if self.op.hvparams:
-      instance.hvparams = self.hv_inst
+      self.instance.hvparams = self.hv_inst
       for key, val in self.op.hvparams.iteritems():
         result.append(("hv/%s" % key, val))
 
     # beparams changes
     if self.op.beparams:
-      instance.beparams = self.be_inst
+      self.instance.beparams = self.be_inst
       for key, val in self.op.beparams.iteritems():
         result.append(("be/%s" % key, val))
 
     # OS change
     if self.op.os_name:
-      instance.os = self.op.os_name
+      self.instance.os = self.op.os_name
 
     # osparams changes
     if self.op.osparams:
-      instance.osparams = self.os_inst
+      self.instance.osparams = self.os_inst
       for key, val in self.op.osparams.iteritems():
         result.append(("os/%s" % key, val))
 
@@ -3300,14 +3366,14 @@ class LUInstanceSetParams(LogicalUnit):
       pass
     elif self.op.offline:
       # Mark instance as offline
-      self.cfg.MarkInstanceOffline(instance.name)
+      self.cfg.MarkInstanceOffline(self.instance.uuid)
       result.append(("admin_state", constants.ADMINST_OFFLINE))
     else:
       # Mark instance as online, but stopped
-      self.cfg.MarkInstanceDown(instance.name)
+      self.cfg.MarkInstanceDown(self.instance.uuid)
       result.append(("admin_state", constants.ADMINST_DOWN))
 
-    self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
+    self.cfg.Update(self.instance, feedback_fn, self.proc.GetECId())
 
     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
                 self.owned_locks(locking.LEVEL_NODE)), \
@@ -3354,7 +3420,7 @@ class LUInstanceChangeGroup(LogicalUnit):
 
         # Lock all groups used by instance optimistically; this requires going
         # via the node before it's locked, requiring verification later on
-        instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
+        instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
         lock_groups.update(instance_groups)
       else:
         # No target groups, need to lock all of them
@@ -3370,33 +3436,33 @@ class LUInstanceChangeGroup(LogicalUnit):
 
         # Lock all nodes in all potential target groups
         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
-                       self.cfg.GetInstanceNodeGroups(self.op.instance_name))
-        member_nodes = [node_name
+                       self.cfg.GetInstanceNodeGroups(self.op.instance_uuid))
+        member_nodes = [node_uuid
                         for group in lock_groups
-                        for node_name in self.cfg.GetNodeGroup(group).members]
+                        for node_uuid in self.cfg.GetNodeGroup(group).members]
         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
       else:
         # Lock all nodes as all groups are potential targets
         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
 
   def CheckPrereq(self):
-    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
+    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
 
     assert (self.req_target_uuids is None or
             owned_groups.issuperset(self.req_target_uuids))
-    assert owned_instances == set([self.op.instance_name])
+    assert owned_instance_names == set([self.op.instance_name])
 
     # Get instance information
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
 
     # Check if node groups for locked instance are still correct
     assert owned_nodes.issuperset(self.instance.all_nodes), \
       ("Instance %s's nodes changed while we kept the lock" %
        self.op.instance_name)
 
-    inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
+    inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid,
                                           owned_groups)
 
     if self.req_target_uuids:
index 9b715ae..5ebd794 100644 (file)
@@ -30,8 +30,8 @@ from ganeti import locking
 from ganeti.masterd import iallocator
 from ganeti import utils
 from ganeti.cmdlib.base import LogicalUnit, Tasklet
-from ganeti.cmdlib.common import ExpandInstanceName, \
-  CheckIAllocatorOrNode, ExpandNodeName
+from ganeti.cmdlib.common import ExpandInstanceUuidAndName, \
+  CheckIAllocatorOrNode, ExpandNodeUuidAndName
 from ganeti.cmdlib.instance_storage import CheckDiskConsistency, \
   ExpandCheckDisks, ShutdownInstanceDisks, AssembleInstanceDisks
 from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
@@ -48,7 +48,8 @@ def _ExpandNamesForMigration(lu):
 
   """
   if lu.op.target_node is not None:
-    lu.op.target_node = ExpandNodeName(lu.cfg, lu.op.target_node)
+    (lu.op.target_node_uuid, lu.op.target_node) = \
+      ExpandNodeUuidAndName(lu.cfg, lu.op.target_node_uuid, lu.op.target_node)
 
   lu.needed_locks[locking.LEVEL_NODE] = []
   lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
@@ -71,7 +72,7 @@ def _DeclareLocksForMigration(lu, level):
   if level == locking.LEVEL_NODE_ALLOC:
     assert lu.op.instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
 
-    instance = lu.cfg.GetInstanceInfo(lu.op.instance_name)
+    instance = lu.cfg.GetInstanceInfo(lu.op.instance_uuid)
 
     # Node locks are already declared here rather than at LEVEL_NODE as we need
     # the instance object anyway to declare the node allocation lock.
@@ -81,7 +82,7 @@ def _DeclareLocksForMigration(lu, level):
         lu.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
       else:
         lu.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
-                                               lu.op.target_node]
+                                               lu.op.target_node_uuid]
       del lu.recalculate_locks[locking.LEVEL_NODE]
     else:
       lu._LockInstancesNodes() # pylint: disable=W0212
@@ -117,8 +118,8 @@ class LUInstanceFailover(LogicalUnit):
     _ExpandNamesForMigration(self)
 
     self._migrater = \
-      TLMigrateInstance(self, self.op.instance_name, False, True, False,
-                        self.op.ignore_consistency, True,
+      TLMigrateInstance(self, self.op.instance_uuid, self.op.instance_name,
+                        False, True, False, self.op.ignore_consistency, True,
                         self.op.shutdown_timeout, self.op.ignore_ipolicy)
 
     self.tasklets = [self._migrater]
@@ -133,18 +134,17 @@ class LUInstanceFailover(LogicalUnit):
 
     """
     instance = self._migrater.instance
-    source_node = instance.primary_node
-    target_node = self.op.target_node
+    source_node_uuid = instance.primary_node
     env = {
       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
-      "OLD_PRIMARY": source_node,
-      "NEW_PRIMARY": target_node,
+      "OLD_PRIMARY": self.cfg.GetNodeName(source_node_uuid),
+      "NEW_PRIMARY": self.op.target_node,
       }
 
     if instance.disk_template in constants.DTS_INT_MIRROR:
-      env["OLD_SECONDARY"] = instance.secondary_nodes[0]
-      env["NEW_SECONDARY"] = source_node
+      env["OLD_SECONDARY"] = self.cfg.GetNodeName(instance.secondary_nodes[0])
+      env["NEW_SECONDARY"] = self.cfg.GetNodeName(source_node_uuid)
     else:
       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
 
@@ -177,8 +177,8 @@ class LUInstanceMigrate(LogicalUnit):
     _ExpandNamesForMigration(self)
 
     self._migrater = \
-      TLMigrateInstance(self, self.op.instance_name, self.op.cleanup,
-                        False, self.op.allow_failover, False,
+      TLMigrateInstance(self, self.op.instance_uuid, self.op.instance_name,
+                        self.op.cleanup, False, self.op.allow_failover, False,
                         self.op.allow_runtime_changes,
                         constants.DEFAULT_SHUTDOWN_TIMEOUT,
                         self.op.ignore_ipolicy)
@@ -195,20 +195,19 @@ class LUInstanceMigrate(LogicalUnit):
 
     """
     instance = self._migrater.instance
-    source_node = instance.primary_node
-    target_node = self.op.target_node
+    source_node_uuid = instance.primary_node
     env = BuildInstanceHookEnvByObject(self, instance)
     env.update({
       "MIGRATE_LIVE": self._migrater.live,
       "MIGRATE_CLEANUP": self.op.cleanup,
-      "OLD_PRIMARY": source_node,
-      "NEW_PRIMARY": target_node,
+      "OLD_PRIMARY": self.cfg.GetNodeName(source_node_uuid),
+      "NEW_PRIMARY": self.op.target_node,
       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
       })
 
     if instance.disk_template in constants.DTS_INT_MIRROR:
-      env["OLD_SECONDARY"] = target_node
-      env["NEW_SECONDARY"] = source_node
+      env["OLD_SECONDARY"] = self.cfg.GetNodeName(instance.secondary_nodes[0])
+      env["NEW_SECONDARY"] = self.cfg.GetNodeName(source_node_uuid)
     else:
       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
 
@@ -219,8 +218,8 @@ class LUInstanceMigrate(LogicalUnit):
 
     """
     instance = self._migrater.instance
-    snodes = list(instance.secondary_nodes)
-    nl = [self.cfg.GetMasterNode(), instance.primary_node] + snodes
+    snode_uuids = list(instance.secondary_nodes)
+    nl = [self.cfg.GetMasterNode(), instance.primary_node] + snode_uuids
     return (nl, nl)
 
 
@@ -234,8 +233,9 @@ class TLMigrateInstance(Tasklet):
   @ivar cleanup: Wheater we cleanup from a failed migration
   @type iallocator: string
   @ivar iallocator: The iallocator used to determine target_node
-  @type target_node: string
-  @ivar target_node: If given, the target_node to reallocate the instance to
+  @type target_node_uuid: string
+  @ivar target_node_uuid: If given, the target node UUID to reallocate the
+      instance to
   @type failover: boolean
   @ivar failover: Whether operation results in failover or migration
   @type fallback: boolean
@@ -255,15 +255,16 @@ class TLMigrateInstance(Tasklet):
   _MIGRATION_POLL_INTERVAL = 1      # seconds
   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
 
-  def __init__(self, lu, instance_name, cleanup, failover, fallback,
-               ignore_consistency, allow_runtime_changes, shutdown_timeout,
-               ignore_ipolicy):
+  def __init__(self, lu, instance_uuid, instance_name, cleanup, failover,
+               fallback, ignore_consistency, allow_runtime_changes,
+               shutdown_timeout, ignore_ipolicy):
     """Initializes this class.
 
     """
     Tasklet.__init__(self, lu)
 
     # Parameters
+    self.instance_uuid = instance_uuid
     self.instance_name = instance_name
     self.cleanup = cleanup
     self.live = False # will be overridden later
@@ -280,74 +281,77 @@ class TLMigrateInstance(Tasklet):
     This checks that the instance is in the cluster.
 
     """
-    instance_name = ExpandInstanceName(self.lu.cfg, self.instance_name)
-    instance = self.cfg.GetInstanceInfo(instance_name)
-    assert instance is not None
-    self.instance = instance
+    (self.instance_uuid, self.instance_name) = \
+      ExpandInstanceUuidAndName(self.lu.cfg, self.instance_uuid,
+                                self.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.instance_uuid)
+    assert self.instance is not None
     cluster = self.cfg.GetClusterInfo()
 
     if (not self.cleanup and
-        not instance.admin_state == constants.ADMINST_UP and
+        not self.instance.admin_state == constants.ADMINST_UP and
         not self.failover and self.fallback):
       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
                       " switching to failover")
       self.failover = True
 
-    if instance.disk_template not in constants.DTS_MIRRORED:
+    if self.instance.disk_template not in constants.DTS_MIRRORED:
       if self.failover:
         text = "failovers"
       else:
         text = "migrations"
       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
-                                 " %s" % (instance.disk_template, text),
+                                 " %s" % (self.instance.disk_template, text),
                                  errors.ECODE_STATE)
 
-    if instance.disk_template in constants.DTS_EXT_MIRROR:
+    if self.instance.disk_template in constants.DTS_EXT_MIRROR:
       CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
 
       if self.lu.op.iallocator:
         assert locking.NAL in self.lu.owned_locks(locking.LEVEL_NODE_ALLOC)
         self._RunAllocator()
       else:
-        # We set set self.target_node as it is required by
+        # We set set self.target_node_uuid as it is required by
         # BuildHooksEnv
-        self.target_node = self.lu.op.target_node
+        self.target_node_uuid = self.lu.op.target_node_uuid
 
       # Check that the target node is correct in terms of instance policy
-      nodeinfo = self.cfg.GetNodeInfo(self.target_node)
+      nodeinfo = self.cfg.GetNodeInfo(self.target_node_uuid)
       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
                                                               group_info)
-      CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo, self.cfg,
-                             ignore=self.ignore_ipolicy)
+      CheckTargetNodeIPolicy(self.lu, ipolicy, self.instance, nodeinfo,
+                             self.cfg, ignore=self.ignore_ipolicy)
 
       # self.target_node is already populated, either directly or by the
       # iallocator run
-      target_node = self.target_node
-      if self.target_node == instance.primary_node:
-        raise errors.OpPrereqError("Cannot migrate instance %s"
-                                   " to its primary (%s)" %
-                                   (instance.name, instance.primary_node),
-                                   errors.ECODE_STATE)
+      target_node_uuid = self.target_node_uuid
+      if self.target_node_uuid == self.instance.primary_node:
+        raise errors.OpPrereqError(
+          "Cannot migrate instance %s to its primary (%s)" %
+          (self.instance.name,
+           self.cfg.GetNodeName(self.instance.primary_node)),
+          errors.ECODE_STATE)
 
       if len(self.lu.tasklets) == 1:
         # It is safe to release locks only when we're the only tasklet
         # in the LU
         ReleaseLocks(self.lu, locking.LEVEL_NODE,
-                     keep=[instance.primary_node, self.target_node])
+                     keep=[self.instance.primary_node, self.target_node_uuid])
         ReleaseLocks(self.lu, locking.LEVEL_NODE_ALLOC)
 
     else:
       assert not self.lu.glm.is_owned(locking.LEVEL_NODE_ALLOC)
 
-      secondary_nodes = instance.secondary_nodes
-      if not secondary_nodes:
+      secondary_node_uuids = self.instance.secondary_nodes
+      if not secondary_node_uuids:
         raise errors.ConfigurationError("No secondary node but using"
                                         " %s disk template" %
-                                        instance.disk_template)
-      target_node = secondary_nodes[0]
-      if self.lu.op.iallocator or (self.lu.op.target_node and
-                                   self.lu.op.target_node != target_node):
+                                        self.instance.disk_template)
+      target_node_uuid = secondary_node_uuids[0]
+      if self.lu.op.iallocator or \
+        (self.lu.op.target_node_uuid and
+         self.lu.op.target_node_uuid != target_node_uuid):
         if self.failover:
           text = "failed over"
         else:
@@ -356,25 +360,26 @@ class TLMigrateInstance(Tasklet):
                                    " be %s to arbitrary nodes"
                                    " (neither an iallocator nor a target"
                                    " node can be passed)" %
-                                   (instance.disk_template, text),
+                                   (self.instance.disk_template, text),
                                    errors.ECODE_INVAL)
-      nodeinfo = self.cfg.GetNodeInfo(target_node)
+      nodeinfo = self.cfg.GetNodeInfo(target_node_uuid)
       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
                                                               group_info)
-      CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo, self.cfg,
-                             ignore=self.ignore_ipolicy)
+      CheckTargetNodeIPolicy(self.lu, ipolicy, self.instance, nodeinfo,
+                             self.cfg, ignore=self.ignore_ipolicy)
 
-    i_be = cluster.FillBE(instance)
+    i_be = cluster.FillBE(self.instance)
 
     # check memory requirements on the secondary node
     if (not self.cleanup and
-         (not self.failover or instance.admin_state == constants.ADMINST_UP)):
-      self.tgt_free_mem = CheckNodeFreeMemory(self.lu, target_node,
-                                              "migrating instance %s" %
-                                              instance.name,
-                                              i_be[constants.BE_MINMEM],
-                                              instance.hypervisor)
+         (not self.failover or
+           self.instance.admin_state == constants.ADMINST_UP)):
+      self.tgt_free_mem = CheckNodeFreeMemory(
+          self.lu, target_node_uuid,
+          "migrating instance %s" % self.instance.name,
+          i_be[constants.BE_MINMEM], self.instance.hypervisor,
+          self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
     else:
       self.lu.LogInfo("Not checking memory on the secondary node as"
                       " instance will not be started")
@@ -387,13 +392,14 @@ class TLMigrateInstance(Tasklet):
       self.failover = True
 
     # check bridge existance
-    CheckInstanceBridgesExist(self.lu, instance, node=target_node)
+    CheckInstanceBridgesExist(self.lu, self.instance,
+                              node_uuid=target_node_uuid)
 
     if not self.cleanup:
-      CheckNodeNotDrained(self.lu, target_node)
+      CheckNodeNotDrained(self.lu, target_node_uuid)
       if not self.failover:
-        result = self.rpc.call_instance_migratable(instance.primary_node,
-                                                   instance)
+        result = self.rpc.call_instance_migratable(self.instance.primary_node,
+                                                   self.instance)
         if result.fail_msg and self.fallback:
           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
                           " failover")
@@ -428,11 +434,11 @@ class TLMigrateInstance(Tasklet):
       self.live = False
 
     if not (self.failover or self.cleanup):
-      remote_info = self.rpc.call_instance_info(instance.primary_node,
-                                                instance.name,
-                                                instance.hypervisor)
+      remote_info = self.rpc.call_instance_info(
+          self.instance.primary_node, self.instance.name,
+          self.instance.hypervisor, cluster.hvparams[self.instance.hypervisor])
       remote_info.Raise("Error checking instance on node %s" %
-                        instance.primary_node)
+                        self.cfg.GetNodeName(self.instance.primary_node))
       instance_running = bool(remote_info.payload)
       if instance_running:
         self.current_mem = int(remote_info.payload["memory"])
@@ -444,8 +450,9 @@ class TLMigrateInstance(Tasklet):
     assert locking.NAL in self.lu.owned_locks(locking.LEVEL_NODE_ALLOC)
 
     # FIXME: add a self.ignore_ipolicy option
-    req = iallocator.IAReqRelocate(name=self.instance_name,
-                                   relocate_from=[self.instance.primary_node])
+    req = iallocator.IAReqRelocate(
+          inst_uuid=self.instance_uuid,
+          relocate_from_node_uuids=[self.instance.primary_node])
     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
 
     ial.Run(self.lu.op.iallocator)
@@ -455,7 +462,7 @@ class TLMigrateInstance(Tasklet):
                                  " iallocator '%s': %s" %
                                  (self.lu.op.iallocator, ial.info),
                                  errors.ECODE_NORES)
-    self.target_node = ial.result[0]
+    self.target_node_uuid = self.cfg.GetNodeInfoByName(ial.result[0]).uuid
     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
                     self.instance_name, self.lu.op.iallocator,
                     utils.CommaJoin(ial.result))
@@ -470,13 +477,14 @@ class TLMigrateInstance(Tasklet):
     all_done = False
     while not all_done:
       all_done = True
-      result = self.rpc.call_drbd_wait_sync(self.all_nodes,
+      result = self.rpc.call_drbd_wait_sync(self.all_node_uuids,
                                             self.nodes_ip,
                                             (self.instance.disks,
                                              self.instance))
       min_percent = 100
-      for node, nres in result.items():
-        nres.Raise("Cannot resync disks on node %s" % node)
+      for node_uuid, nres in result.items():
+        nres.Raise("Cannot resync disks on node %s" %
+                   self.cfg.GetNodeName(node_uuid))
         node_done, node_percent = nres.payload
         all_done = all_done and node_done
         if node_percent is not None:
@@ -486,28 +494,32 @@ class TLMigrateInstance(Tasklet):
           self.feedback_fn("   - progress: %.1f%%" % min_percent)
         time.sleep(2)
 
-  def _EnsureSecondary(self, node):
+  def _EnsureSecondary(self, node_uuid):
     """Demote a node to secondary.
 
     """
-    self.feedback_fn("* switching node %s to secondary mode" % node)
+    self.feedback_fn("* switching node %s to secondary mode" %
+                     self.cfg.GetNodeName(node_uuid))
 
     for dev in self.instance.disks:
-      self.cfg.SetDiskID(dev, node)
+      self.cfg.SetDiskID(dev, node_uuid)
 
-    result = self.rpc.call_blockdev_close(node, self.instance.name,
+    result = self.rpc.call_blockdev_close(node_uuid, self.instance.name,
                                           self.instance.disks)
-    result.Raise("Cannot change disk to secondary on node %s" % node)
+    result.Raise("Cannot change disk to secondary on node %s" %
+                 self.cfg.GetNodeName(node_uuid))
 
   def _GoStandalone(self):
     """Disconnect from the network.
 
     """
     self.feedback_fn("* changing into standalone mode")
-    result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
+    result = self.rpc.call_drbd_disconnect_net(self.all_node_uuids,
+                                               self.nodes_ip,
                                                self.instance.disks)
-    for node, nres in result.items():
-      nres.Raise("Cannot disconnect disks node %s" % node)
+    for node_uuid, nres in result.items():
+      nres.Raise("Cannot disconnect disks node %s" %
+                 self.cfg.GetNodeName(node_uuid))
 
   def _GoReconnect(self, multimaster):
     """Reconnect to the network.
@@ -518,11 +530,12 @@ class TLMigrateInstance(Tasklet):
     else:
       msg = "single-master"
     self.feedback_fn("* changing disks into %s mode" % msg)
-    result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
+    result = self.rpc.call_drbd_attach_net(self.all_node_uuids, self.nodes_ip,
                                            (self.instance.disks, self.instance),
                                            self.instance.name, multimaster)
-    for node, nres in result.items():
-      nres.Raise("Cannot change disks config on node %s" % node)
+    for node_uuid, nres in result.items():
+      nres.Raise("Cannot change disks config on node %s" %
+                 self.cfg.GetNodeName(node_uuid))
 
   def _ExecCleanup(self):
     """Try to cleanup after a failed migration.
@@ -537,20 +550,21 @@ class TLMigrateInstance(Tasklet):
       - wait again until disks are fully synchronized
 
     """
-    instance = self.instance
-    target_node = self.target_node
-    source_node = self.source_node
-
     # check running on only one node
     self.feedback_fn("* checking where the instance actually runs"
                      " (if this hangs, the hypervisor might be in"
                      " a bad state)")
-    ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
-    for node, result in ins_l.items():
-      result.Raise("Can't contact node %s" % node)
-
-    runningon_source = instance.name in ins_l[source_node].payload
-    runningon_target = instance.name in ins_l[target_node].payload
+    cluster_hvparams = self.cfg.GetClusterInfo().hvparams
+    ins_l = self.rpc.call_instance_list(self.all_node_uuids,
+                                        [self.instance.hypervisor],
+                                        cluster_hvparams)
+    for node_uuid, result in ins_l.items():
+      result.Raise("Can't contact node %s" % node_uuid)
+
+    runningon_source = self.instance.name in \
+                         ins_l[self.source_node_uuid].payload
+    runningon_target = self.instance.name in \
+                         ins_l[self.target_node_uuid].payload
 
     if runningon_source and runningon_target:
       raise errors.OpExecError("Instance seems to be running on two nodes,"
@@ -567,17 +581,19 @@ class TLMigrateInstance(Tasklet):
     if runningon_target:
       # the migration has actually succeeded, we need to update the config
       self.feedback_fn("* instance running on secondary node (%s),"
-                       " updating config" % target_node)
-      instance.primary_node = target_node
-      self.cfg.Update(instance, self.feedback_fn)
-      demoted_node = source_node
+                       " updating config" %
+                       self.cfg.GetNodeName(self.target_node_uuid))
+      self.instance.primary_node = self.target_node_uuid
+      self.cfg.Update(self.instance, self.feedback_fn)
+      demoted_node_uuid = self.source_node_uuid
     else:
       self.feedback_fn("* instance confirmed to be running on its"
-                       " primary node (%s)" % source_node)
-      demoted_node = target_node
+                       " primary node (%s)" %
+                       self.cfg.GetNodeName(self.source_node_uuid))
+      demoted_node_uuid = self.target_node_uuid
 
-    if instance.disk_template in constants.DTS_INT_MIRROR:
-      self._EnsureSecondary(demoted_node)
+    if self.instance.disk_template in constants.DTS_INT_MIRROR:
+      self._EnsureSecondary(demoted_node_uuid)
       try:
         self._WaitUntilSync()
       except errors.OpExecError:
@@ -594,12 +610,11 @@ class TLMigrateInstance(Tasklet):
     """Try to revert the disk status after a failed migration.
 
     """
-    target_node = self.target_node
     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
       return
 
     try:
-      self._EnsureSecondary(target_node)
+      self._EnsureSecondary(self.target_node_uuid)
       self._GoStandalone()
       self._GoReconnect(False)
       self._WaitUntilSync()
@@ -612,28 +627,22 @@ class TLMigrateInstance(Tasklet):
     """Call the hypervisor code to abort a started migration.
 
     """
-    instance = self.instance
-    target_node = self.target_node
-    source_node = self.source_node
-    migration_info = self.migration_info
-
-    abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
-                                                                 instance,
-                                                                 migration_info,
-                                                                 False)
+    abort_result = self.rpc.call_instance_finalize_migration_dst(
+                     self.target_node_uuid, self.instance, self.migration_info,
+                     False)
     abort_msg = abort_result.fail_msg
     if abort_msg:
       logging.error("Aborting migration failed on target node %s: %s",
-                    target_node, abort_msg)
+                    self.cfg.GetNodeName(self.target_node_uuid), abort_msg)
       # Don't raise an exception here, as we stil have to try to revert the
       # disk status, even if this step failed.
 
     abort_result = self.rpc.call_instance_finalize_migration_src(
-      source_node, instance, False, self.live)
+      self.source_node_uuid, self.instance, False, self.live)
     abort_msg = abort_result.fail_msg
     if abort_msg:
       logging.error("Aborting migration failed on source node %s: %s",
-                    source_node, abort_msg)
+                    self.cfg.GetNodeName(self.source_node_uuid), abort_msg)
 
   def _ExecMigration(self):
     """Migrate an instance.
@@ -647,18 +656,17 @@ class TLMigrateInstance(Tasklet):
       - change disks into single-master mode
 
     """
-    instance = self.instance
-    target_node = self.target_node
-    source_node = self.source_node
-
     # Check for hypervisor version mismatch and warn the user.
-    nodeinfo = self.rpc.call_node_info([source_node, target_node],
-                                       None, [self.instance.hypervisor], False)
+    hvspecs = [(self.instance.hypervisor,
+                self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])]
+    nodeinfo = self.rpc.call_node_info(
+                 [self.source_node_uuid, self.target_node_uuid], None, hvspecs,
+                 False)
     for ninfo in nodeinfo.values():
       ninfo.Raise("Unable to retrieve node information from node '%s'" %
                   ninfo.node)
-    (_, _, (src_info, )) = nodeinfo[source_node].payload
-    (_, _, (dst_info, )) = nodeinfo[target_node].payload
+    (_, _, (src_info, )) = nodeinfo[self.source_node_uuid].payload
+    (_, _, (dst_info, )) = nodeinfo[self.target_node_uuid].payload
 
     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
@@ -670,8 +678,10 @@ class TLMigrateInstance(Tasklet):
                          (src_version, dst_version))
 
     self.feedback_fn("* checking disk consistency between source and target")
-    for (idx, dev) in enumerate(instance.disks):
-      if not CheckDiskConsistency(self.lu, instance, dev, target_node, False):
+    for (idx, dev) in enumerate(self.instance.disks):
+      if not CheckDiskConsistency(self.lu, self.instance, dev,
+                                  self.target_node_uuid,
+                                  False):
         raise errors.OpExecError("Disk %s is degraded or not fully"
                                  " synchronized on target node,"
                                  " aborting migration" % idx)
@@ -681,20 +691,21 @@ class TLMigrateInstance(Tasklet):
         raise errors.OpExecError("Memory ballooning not allowed and not enough"
                                  " free memory to fit instance %s on target"
                                  " node %s (have %dMB, need %dMB)" %
-                                 (instance.name, target_node,
+                                 (self.instance.name,
+                                  self.cfg.GetNodeName(self.target_node_uuid),
                                   self.tgt_free_mem, self.current_mem))
       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
-      rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
-                                                     instance,
+      rpcres = self.rpc.call_instance_balloon_memory(self.instance.primary_node,
+                                                     self.instance,
                                                      self.tgt_free_mem)
       rpcres.Raise("Cannot modify instance runtime memory")
 
     # First get the migration information from the remote node
-    result = self.rpc.call_migration_info(source_node, instance)
+    result = self.rpc.call_migration_info(self.source_node_uuid, self.instance)
     msg = result.fail_msg
     if msg:
       log_err = ("Failed fetching source migration information from %s: %s" %
-                 (source_node, msg))
+                 (self.cfg.GetNodeName(self.source_node_uuid), msg))
       logging.error(log_err)
       raise errors.OpExecError(log_err)
 
@@ -702,16 +713,17 @@ class TLMigrateInstance(Tasklet):
 
     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
       # Then switch the disks to master/master mode
-      self._EnsureSecondary(target_node)
+      self._EnsureSecondary(self.target_node_uuid)
       self._GoStandalone()
       self._GoReconnect(True)
       self._WaitUntilSync()
 
-    self.feedback_fn("* preparing %s to accept the instance" % target_node)
-    result = self.rpc.call_accept_instance(target_node,
-                                           instance,
+    self.feedback_fn("* preparing %s to accept the instance" %
+                     self.cfg.GetNodeName(self.target_node_uuid))
+    result = self.rpc.call_accept_instance(self.target_node_uuid,
+                                           self.instance,
                                            migration_info,
-                                           self.nodes_ip[target_node])
+                                           self.nodes_ip[self.target_node_uuid])
 
     msg = result.fail_msg
     if msg:
@@ -721,12 +733,14 @@ class TLMigrateInstance(Tasklet):
       self._AbortMigration()
       self._RevertDiskStatus()
       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
-                               (instance.name, msg))
+                               (self.instance.name, msg))
 
-    self.feedback_fn("* migrating instance to %s" % target_node)
-    result = self.rpc.call_instance_migrate(source_node, instance,
-                                            self.nodes_ip[target_node],
-                                            self.live)
+    self.feedback_fn("* migrating instance to %s" %
+                     self.cfg.GetNodeName(self.target_node_uuid))
+    cluster = self.cfg.GetClusterInfo()
+    result = self.rpc.call_instance_migrate(
+        self.source_node_uuid, cluster.cluster_name, self.instance,
+        self.nodes_ip[self.target_node_uuid], self.live)
     msg = result.fail_msg
     if msg:
       logging.error("Instance migration failed, trying to revert"
@@ -735,13 +749,13 @@ class TLMigrateInstance(Tasklet):
       self._AbortMigration()
       self._RevertDiskStatus()
       raise errors.OpExecError("Could not migrate instance %s: %s" %
-                               (instance.name, msg))
+                               (self.instance.name, msg))
 
     self.feedback_fn("* starting memory transfer")
     last_feedback = time.time()
     while True:
-      result = self.rpc.call_instance_get_migration_status(source_node,
-                                                           instance)
+      result = self.rpc.call_instance_get_migration_status(
+                 self.source_node_uuid, self.instance)
       msg = result.fail_msg
       ms = result.payload   # MigrationStatus instance
       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
@@ -753,7 +767,7 @@ class TLMigrateInstance(Tasklet):
         if not msg:
           msg = "hypervisor returned failure"
         raise errors.OpExecError("Could not migrate instance %s: %s" %
-                                 (instance.name, msg))
+                                 (self.instance.name, msg))
 
       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
         self.feedback_fn("* memory transfer complete")
@@ -768,10 +782,8 @@ class TLMigrateInstance(Tasklet):
 
       time.sleep(self._MIGRATION_POLL_INTERVAL)
 
-    result = self.rpc.call_instance_finalize_migration_src(source_node,
-                                                           instance,
-                                                           True,
-                                                           self.live)
+    result = self.rpc.call_instance_finalize_migration_src(
+               self.source_node_uuid, self.instance, True, self.live)
     msg = result.fail_msg
     if msg:
       logging.error("Instance migration succeeded, but finalization failed"
@@ -779,15 +791,13 @@ class TLMigrateInstance(Tasklet):
       raise errors.OpExecError("Could not finalize instance migration: %s" %
                                msg)
 
-    instance.primary_node = target_node
+    self.instance.primary_node = self.target_node_uuid
 
     # distribute new instance config to the other nodes
-    self.cfg.Update(instance, self.feedback_fn)
+    self.cfg.Update(self.instance, self.feedback_fn)
 
-    result = self.rpc.call_instance_finalize_migration_dst(target_node,
-                                                           instance,
-                                                           migration_info,
-                                                           True)
+    result = self.rpc.call_instance_finalize_migration_dst(
+               self.target_node_uuid, self.instance, migration_info, True)
     msg = result.fail_msg
     if msg:
       logging.error("Instance migration succeeded, but finalization failed"
@@ -796,7 +806,7 @@ class TLMigrateInstance(Tasklet):
                                msg)
 
     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
-      self._EnsureSecondary(source_node)
+      self._EnsureSecondary(self.source_node_uuid)
       self._WaitUntilSync()
       self._GoStandalone()
       self._GoReconnect(False)
@@ -805,17 +815,21 @@ class TLMigrateInstance(Tasklet):
     # If the instance's disk template is `rbd' or `ext' and there was a
     # successful migration, unmap the device from the source node.
     if self.instance.disk_template in (constants.DT_RBD, constants.DT_EXT):
-      disks = ExpandCheckDisks(instance, instance.disks)
-      self.feedback_fn("* unmapping instance's disks from %s" % source_node)
+      disks = ExpandCheckDisks(self.instance, self.instance.disks)
+      self.feedback_fn("* unmapping instance's disks from %s" %
+                       self.cfg.GetNodeName(self.source_node_uuid))
       for disk in disks:
-        result = self.rpc.call_blockdev_shutdown(source_node, (disk, instance))
+        result = self.rpc.call_blockdev_shutdown(self.source_node_uuid,
+                                                 (disk, self.instance))
         msg = result.fail_msg
         if msg:
           logging.error("Migration was successful, but couldn't unmap the"
                         " block device %s on source node %s: %s",
-                        disk.iv_name, source_node, msg)
+                        disk.iv_name,
+                        self.cfg.GetNodeName(self.source_node_uuid), msg)
           logging.error("You need to unmap the device %s manually on %s",
-                        disk.iv_name, source_node)
+                        disk.iv_name,
+                        self.cfg.GetNodeName(self.source_node_uuid))
 
     self.feedback_fn("* done")
 
@@ -826,22 +840,21 @@ class TLMigrateInstance(Tasklet):
     starting it on the secondary.
 
     """
-    instance = self.instance
-    primary_node = self.cfg.GetNodeInfo(instance.primary_node)
+    primary_node = self.cfg.GetNodeInfo(self.instance.primary_node)
 
-    source_node = instance.primary_node
-    target_node = self.target_node
+    source_node_uuid = self.instance.primary_node
 
-    if instance.disks_active:
+    if self.instance.disks_active:
       self.feedback_fn("* checking disk consistency between source and target")
-      for (idx, dev) in enumerate(instance.disks):
+      for (idx, dev) in enumerate(self.instance.disks):
         # for drbd, these are drbd over lvm
-        if not CheckDiskConsistency(self.lu, instance, dev, target_node,
-                                    False):
+        if not CheckDiskConsistency(self.lu, self.instance, dev,
+                                    self.target_node_uuid, False):
           if primary_node.offline:
             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
                              " target node %s" %
-                             (primary_node.name, idx, target_node))
+                             (primary_node.name, idx,
+                              self.cfg.GetNodeName(self.target_node_uuid)))
           elif not self.ignore_consistency:
             raise errors.OpExecError("Disk %s is degraded on target node,"
                                      " aborting failover" % idx)
@@ -851,9 +864,9 @@ class TLMigrateInstance(Tasklet):
 
     self.feedback_fn("* shutting down instance on source node")
     logging.info("Shutting down instance %s on node %s",
-                 instance.name, source_node)
+                 self.instance.name, self.cfg.GetNodeName(source_node_uuid))
 
-    result = self.rpc.call_instance_shutdown(source_node, instance,
+    result = self.rpc.call_instance_shutdown(source_node_uuid, self.instance,
                                              self.shutdown_timeout,
                                              self.lu.op.reason)
     msg = result.fail_msg
@@ -862,59 +875,65 @@ class TLMigrateInstance(Tasklet):
         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
                            " proceeding anyway; please make sure node"
                            " %s is down; error details: %s",
-                           instance.name, source_node, source_node, msg)
+                           self.instance.name,
+                           self.cfg.GetNodeName(source_node_uuid),
+                           self.cfg.GetNodeName(source_node_uuid), msg)
       else:
         raise errors.OpExecError("Could not shutdown instance %s on"
                                  " node %s: %s" %
-                                 (instance.name, source_node, msg))
+                                 (self.instance.name,
+                                  self.cfg.GetNodeName(source_node_uuid), msg))
 
     self.feedback_fn("* deactivating the instance's disks on source node")
-    if not ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
+    if not ShutdownInstanceDisks(self.lu, self.instance, ignore_primary=True):
       raise errors.OpExecError("Can't shut down the instance's disks")
 
-    instance.primary_node = target_node
+    self.instance.primary_node = self.target_node_uuid
     # distribute new instance config to the other nodes
-    self.cfg.Update(instance, self.feedback_fn)
+    self.cfg.Update(self.instance, self.feedback_fn)
 
     # Only start the instance if it's marked as up
-    if instance.admin_state == constants.ADMINST_UP:
+    if self.instance.admin_state == constants.ADMINST_UP:
       self.feedback_fn("* activating the instance's disks on target node %s" %
-                       target_node)
-      logging.info("Starting instance %s on node %s",
-                   instance.name, target_node)
+                       self.cfg.GetNodeName(self.target_node_uuid))
+      logging.info("Starting instance %s on node %s", self.instance.name,
+                   self.cfg.GetNodeName(self.target_node_uuid))
 
-      disks_ok, _ = AssembleInstanceDisks(self.lu, instance,
+      disks_ok, _ = AssembleInstanceDisks(self.lu, self.instance,
                                           ignore_secondaries=True)
       if not disks_ok:
-        ShutdownInstanceDisks(self.lu, instance)
+        ShutdownInstanceDisks(self.lu, self.instance)
         raise errors.OpExecError("Can't activate the instance's disks")
 
       self.feedback_fn("* starting the instance on the target node %s" %
-                       target_node)
-      result = self.rpc.call_instance_start(target_node, (instance, None, None),
-                                            False, self.lu.op.reason)
+                       self.cfg.GetNodeName(self.target_node_uuid))
+      result = self.rpc.call_instance_start(self.target_node_uuid,
+                                            (self.instance, None, None), False,
+                                            self.lu.op.reason)
       msg = result.fail_msg
       if msg:
-        ShutdownInstanceDisks(self.lu, instance)
+        ShutdownInstanceDisks(self.lu, self.instance)
         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
-                                 (instance.name, target_node, msg))
+                                 (self.instance.name,
+                                  self.cfg.GetNodeName(self.target_node_uuid),
+                                  msg))
 
   def Exec(self, feedback_fn):
     """Perform the migration.
 
     """
     self.feedback_fn = feedback_fn
-    self.source_node = self.instance.primary_node
+    self.source_node_uuid = self.instance.primary_node
 
     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
     if self.instance.disk_template in constants.DTS_INT_MIRROR:
-      self.target_node = self.instance.secondary_nodes[0]
+      self.target_node_uuid = self.instance.secondary_nodes[0]
       # Otherwise self.target_node has been populated either
       # directly, or through an iallocator.
 
-    self.all_nodes = [self.source_node, self.target_node]
-    self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
-                         in self.cfg.GetMultiNodeInfo(self.all_nodes))
+    self.all_node_uuids = [self.source_node_uuid, self.target_node_uuid]
+    self.nodes_ip = dict((uuid, node.secondary_ip) for (uuid, node)
+                         in self.cfg.GetMultiNodeInfo(self.all_node_uuids))
 
     if self.failover:
       feedback_fn("Failover instance %s" % self.instance.name)
index 7137c15..54d58f2 100644 (file)
@@ -36,8 +36,8 @@ from ganeti import objects
 from ganeti import utils
 from ganeti.cmdlib.base import LogicalUnit, NoHooksLU
 from ganeti.cmdlib.common import INSTANCE_ONLINE, INSTANCE_DOWN, \
-  CheckHVParams, CheckInstanceState, CheckNodeOnline, ExpandNodeName, \
-  GetUpdatedParams, CheckOSParams, ShareAll
+  CheckHVParams, CheckInstanceState, CheckNodeOnline, GetUpdatedParams, \
+  CheckOSParams, ShareAll
 from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
   ShutdownInstanceDisks
 from ganeti.cmdlib.instance_utils import BuildInstanceHookEnvByObject, \
@@ -94,24 +94,26 @@ class LUInstanceStartup(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
 
+    cluster = self.cfg.GetClusterInfo()
     # extra hvparams
     if self.op.hvparams:
       # check hypervisor parameter syntax (locally)
-      cluster = self.cfg.GetClusterInfo()
       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
-      filled_hvp = cluster.FillHV(instance)
+      filled_hvp = cluster.FillHV(self.instance)
       filled_hvp.update(self.op.hvparams)
-      hv_type = hypervisor.GetHypervisorClass(instance.hypervisor)
+      hv_type = hypervisor.GetHypervisorClass(self.instance.hypervisor)
       hv_type.CheckParameterSyntax(filled_hvp)
-      CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
+      CheckHVParams(self, self.instance.all_nodes, self.instance.hypervisor,
+                    filled_hvp)
 
-    CheckInstanceState(self, instance, INSTANCE_ONLINE)
+    CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
 
-    self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
+    self.primary_offline = \
+      self.cfg.GetNodeInfo(self.instance.primary_node).offline
 
     if self.primary_offline and self.op.ignore_offline_nodes:
       self.LogWarning("Ignoring offline primary node")
@@ -119,51 +121,48 @@ class LUInstanceStartup(LogicalUnit):
       if self.op.hvparams or self.op.beparams:
         self.LogWarning("Overridden parameters are ignored")
     else:
-      CheckNodeOnline(self, instance.primary_node)
+      CheckNodeOnline(self, self.instance.primary_node)
 
-      bep = self.cfg.GetClusterInfo().FillBE(instance)
+      bep = self.cfg.GetClusterInfo().FillBE(self.instance)
       bep.update(self.op.beparams)
 
       # check bridges existence
-      CheckInstanceBridgesExist(self, instance)
+      CheckInstanceBridgesExist(self, self.instance)
 
-      remote_info = self.rpc.call_instance_info(instance.primary_node,
-                                                instance.name,
-                                                instance.hypervisor)
-      remote_info.Raise("Error checking node %s" % instance.primary_node,
+      remote_info = self.rpc.call_instance_info(
+          self.instance.primary_node, self.instance.name,
+          self.instance.hypervisor, cluster.hvparams[self.instance.hypervisor])
+      remote_info.Raise("Error checking node %s" %
+                        self.cfg.GetNodeName(self.instance.primary_node),
                         prereq=True, ecode=errors.ECODE_ENVIRON)
       if not remote_info.payload: # not running already
-        CheckNodeFreeMemory(self, instance.primary_node,
-                            "starting instance %s" % instance.name,
-                            bep[constants.BE_MINMEM], instance.hypervisor)
+        CheckNodeFreeMemory(
+            self, self.instance.primary_node,
+            "starting instance %s" % self.instance.name,
+            bep[constants.BE_MINMEM], self.instance.hypervisor,
+            self.cfg.GetClusterInfo().hvparams[self.instance.hypervisor])
 
   def Exec(self, feedback_fn):
     """Start the instance.
 
     """
-    instance = self.instance
-    force = self.op.force
-    reason = self.op.reason
-
     if not self.op.no_remember:
-      self.cfg.MarkInstanceUp(instance.name)
+      self.cfg.MarkInstanceUp(self.instance.uuid)
 
     if self.primary_offline:
       assert self.op.ignore_offline_nodes
       self.LogInfo("Primary node offline, marked instance as started")
     else:
-      node_current = instance.primary_node
-
-      StartInstanceDisks(self, instance, force)
+      StartInstanceDisks(self, self.instance, self.op.force)
 
       result = \
-        self.rpc.call_instance_start(node_current,
-                                     (instance, self.op.hvparams,
+        self.rpc.call_instance_start(self.instance.primary_node,
+                                     (self.instance, self.op.hvparams,
                                       self.op.beparams),
-                                     self.op.startup_paused, reason)
+                                     self.op.startup_paused, self.op.reason)
       msg = result.fail_msg
       if msg:
-        ShutdownInstanceDisks(self, instance)
+        ShutdownInstanceDisks(self, self.instance)
         raise errors.OpExecError("Could not start instance: %s" % msg)
 
 
@@ -201,7 +200,7 @@ class LUInstanceShutdown(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
 
@@ -222,27 +221,23 @@ class LUInstanceShutdown(LogicalUnit):
     """Shutdown the instance.
 
     """
-    instance = self.instance
-    node_current = instance.primary_node
-    timeout = self.op.timeout
-    reason = self.op.reason
-
     # If the instance is offline we shouldn't mark it as down, as that
     # resets the offline flag.
-    if not self.op.no_remember and instance.admin_state in INSTANCE_ONLINE:
-      self.cfg.MarkInstanceDown(instance.name)
+    if not self.op.no_remember and self.instance.admin_state in INSTANCE_ONLINE:
+      self.cfg.MarkInstanceDown(self.instance.uuid)
 
     if self.primary_offline:
       assert self.op.ignore_offline_nodes
       self.LogInfo("Primary node offline, marked instance as stopped")
     else:
-      result = self.rpc.call_instance_shutdown(node_current, instance, timeout,
-                                               reason)
+      result = self.rpc.call_instance_shutdown(self.instance.primary_node,
+                                               self.instance,
+                                               self.op.timeout, self.op.reason)
       msg = result.fail_msg
       if msg:
         self.LogWarning("Could not shutdown instance: %s", msg)
 
-      ShutdownInstanceDisks(self, instance)
+      ShutdownInstanceDisks(self, self.instance)
 
 
 class LUInstanceReinstall(LogicalUnit):
@@ -277,7 +272,7 @@ class LUInstanceReinstall(LogicalUnit):
     This checks that the instance is in the cluster and is not running.
 
     """
-    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
     CheckNodeOnline(self, instance.primary_node, "Instance primary node"
@@ -291,17 +286,17 @@ class LUInstanceReinstall(LogicalUnit):
 
     if self.op.os_type is not None:
       # OS verification
-      pnode = ExpandNodeName(self.cfg, instance.primary_node)
-      CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
+      CheckNodeHasOS(self, instance.primary_node, self.op.os_type,
+                     self.op.force_variant)
       instance_os = self.op.os_type
     else:
       instance_os = instance.os
 
-    nodelist = list(instance.all_nodes)
+    node_uuids = list(instance.all_nodes)
 
     if self.op.osparams:
       i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
-      CheckOSParams(self, True, nodelist, instance_os, i_osdict)
+      CheckOSParams(self, True, node_uuids, instance_os, i_osdict)
       self.os_inst = i_osdict # the new dict (without defaults)
     else:
       self.os_inst = None
@@ -312,25 +307,24 @@ class LUInstanceReinstall(LogicalUnit):
     """Reinstall the instance.
 
     """
-    inst = self.instance
-
     if self.op.os_type is not None:
       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
-      inst.os = self.op.os_type
+      self.instance.os = self.op.os_type
       # Write to configuration
-      self.cfg.Update(inst, feedback_fn)
+      self.cfg.Update(self.instance, feedback_fn)
 
-    StartInstanceDisks(self, inst, None)
+    StartInstanceDisks(self, self.instance, None)
     try:
       feedback_fn("Running the instance OS create scripts...")
       # FIXME: pass debug option from opcode to backend
-      result = self.rpc.call_instance_os_add(inst.primary_node,
-                                             (inst, self.os_inst), True,
-                                             self.op.debug_level)
+      result = self.rpc.call_instance_os_add(self.instance.primary_node,
+                                             (self.instance, self.os_inst),
+                                             True, self.op.debug_level)
       result.Raise("Could not install OS for instance %s on node %s" %
-                   (inst.name, inst.primary_node))
+                   (self.instance.name,
+                    self.cfg.GetNodeName(self.instance.primary_node)))
     finally:
-      ShutdownInstanceDisks(self, inst)
+      ShutdownInstanceDisks(self, self.instance)
 
 
 class LUInstanceReboot(LogicalUnit):
@@ -373,77 +367,78 @@ class LUInstanceReboot(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
-    CheckInstanceState(self, instance, INSTANCE_ONLINE)
-    CheckNodeOnline(self, instance.primary_node)
+    CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
+    CheckNodeOnline(self, self.instance.primary_node)
 
     # check bridges existence
-    CheckInstanceBridgesExist(self, instance)
+    CheckInstanceBridgesExist(self, self.instance)
 
   def Exec(self, feedback_fn):
     """Reboot the instance.
 
     """
-    instance = self.instance
-    ignore_secondaries = self.op.ignore_secondaries
-    reboot_type = self.op.reboot_type
-    reason = self.op.reason
-
-    remote_info = self.rpc.call_instance_info(instance.primary_node,
-                                              instance.name,
-                                              instance.hypervisor)
-    remote_info.Raise("Error checking node %s" % instance.primary_node)
+    cluster = self.cfg.GetClusterInfo()
+    remote_info = self.rpc.call_instance_info(
+        self.instance.primary_node, self.instance.name,
+        self.instance.hypervisor, cluster.hvparams[self.instance.hypervisor])
+    remote_info.Raise("Error checking node %s" %
+                      self.cfg.GetNodeName(self.instance.primary_node))
     instance_running = bool(remote_info.payload)
 
-    node_current = instance.primary_node
-
-    if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
-                                            constants.INSTANCE_REBOOT_HARD]:
-      for disk in instance.disks:
-        self.cfg.SetDiskID(disk, node_current)
-      result = self.rpc.call_instance_reboot(node_current, instance,
-                                             reboot_type,
-                                             self.op.shutdown_timeout, reason)
+    current_node_uuid = self.instance.primary_node
+
+    if instance_running and \
+        self.op.reboot_type in [constants.INSTANCE_REBOOT_SOFT,
+                                constants.INSTANCE_REBOOT_HARD]:
+      for disk in self.instance.disks:
+        self.cfg.SetDiskID(disk, current_node_uuid)
+      result = self.rpc.call_instance_reboot(current_node_uuid, self.instance,
+                                             self.op.reboot_type,
+                                             self.op.shutdown_timeout,
+                                             self.op.reason)
       result.Raise("Could not reboot instance")
     else:
       if instance_running:
-        result = self.rpc.call_instance_shutdown(node_current, instance,
+        result = self.rpc.call_instance_shutdown(current_node_uuid,
+                                                 self.instance,
                                                  self.op.shutdown_timeout,
-                                                 reason)
+                                                 self.op.reason)
         result.Raise("Could not shutdown instance for full reboot")
-        ShutdownInstanceDisks(self, instance)
+        ShutdownInstanceDisks(self, self.instance)
       else:
         self.LogInfo("Instance %s was already stopped, starting now",
-                     instance.name)
-      StartInstanceDisks(self, instance, ignore_secondaries)
-      result = self.rpc.call_instance_start(node_current,
-                                            (instance, None, None), False,
-                                            reason)
+                     self.instance.name)
+      StartInstanceDisks(self, self.instance, self.op.ignore_secondaries)
+      result = self.rpc.call_instance_start(current_node_uuid,
+                                            (self.instance, None, None), False,
+                                            self.op.reason)
       msg = result.fail_msg
       if msg:
-        ShutdownInstanceDisks(self, instance)
+        ShutdownInstanceDisks(self, self.instance)
         raise errors.OpExecError("Could not start instance for"
                                  " full reboot: %s" % msg)
 
-    self.cfg.MarkInstanceUp(instance.name)
+    self.cfg.MarkInstanceUp(self.instance.uuid)
 
 
-def GetInstanceConsole(cluster, instance):
+def GetInstanceConsole(cluster, instance, primary_node):
   """Returns console information for an instance.
 
   @type cluster: L{objects.Cluster}
   @type instance: L{objects.Instance}
+  @type primary_node: L{objects.Node}
   @rtype: dict
 
   """
-  hyper = hypervisor.GetHypervisorClass(instance.hypervisor)
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
   # beparams and hvparams are passed separately, to avoid editing the
   # instance and then saving the defaults in the instance itself.
   hvparams = cluster.FillHV(instance)
   beparams = cluster.FillBE(instance)
-  console = hyper.GetInstanceConsole(instance, hvparams, beparams)
+  console = hyper.GetInstanceConsole(instance, primary_node, hvparams, beparams)
 
   assert console.instance == instance.name
   assert console.Validate()
@@ -471,7 +466,7 @@ class LUInstanceConsole(NoHooksLU):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
     CheckNodeOnline(self, self.instance.primary_node)
@@ -480,23 +475,27 @@ class LUInstanceConsole(NoHooksLU):
     """Connect to the console of an instance
 
     """
-    instance = self.instance
-    node = instance.primary_node
+    node_uuid = self.instance.primary_node
 
-    node_insts = self.rpc.call_instance_list([node],
-                                             [instance.hypervisor])[node]
-    node_insts.Raise("Can't get node information from %s" % node)
+    cluster_hvparams = self.cfg.GetClusterInfo().hvparams
+    node_insts = self.rpc.call_instance_list(
+                   [node_uuid], [self.instance.hypervisor],
+                   cluster_hvparams)[node_uuid]
+    node_insts.Raise("Can't get node information from %s" %
+                     self.cfg.GetNodeName(node_uuid))
 
-    if instance.name not in node_insts.payload:
-      if instance.admin_state == constants.ADMINST_UP:
+    if self.instance.name not in node_insts.payload:
+      if self.instance.admin_state == constants.ADMINST_UP:
         state = constants.INSTST_ERRORDOWN
-      elif instance.admin_state == constants.ADMINST_DOWN:
+      elif self.instance.admin_state == constants.ADMINST_DOWN:
         state = constants.INSTST_ADMINDOWN
       else:
         state = constants.INSTST_ADMINOFFLINE
       raise errors.OpExecError("Instance %s is not running (state %s)" %
-                               (instance.name, state))
+                               (self.instance.name, state))
 
-    logging.debug("Connecting to console of %s on %s", instance.name, node)
+    logging.debug("Connecting to console of %s on %s", self.instance.name,
+                  self.cfg.GetNodeName(node_uuid))
 
-    return GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
+    return GetInstanceConsole(self.cfg.GetClusterInfo(), self.instance,
+                              self.cfg.GetNodeInfo(self.instance.primary_node))
index d8c5363..5c55c7b 100644 (file)
@@ -47,7 +47,7 @@ class InstanceQuery(QueryBase):
     lu.share_locks = ShareAll()
 
     if self.names:
-      self.wanted = GetWantedInstances(lu, self.names)
+      (_, self.wanted) = GetWantedInstances(lu, self.names)
     else:
       self.wanted = locking.ALL_SET
 
@@ -73,7 +73,9 @@ class InstanceQuery(QueryBase):
         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
           set(group_uuid
               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
-              for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
+              for group_uuid in
+                lu.cfg.GetInstanceNodeGroups(
+                  lu.cfg.GetInstanceInfoByName(instance_name).uuid))
       elif level == locking.LEVEL_NODE:
         lu._LockInstancesNodes() # pylint: disable=W0212
 
@@ -81,16 +83,19 @@ class InstanceQuery(QueryBase):
         lu.needed_locks[locking.LEVEL_NETWORK] = \
           frozenset(net_uuid
                     for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
-                    for net_uuid in lu.cfg.GetInstanceNetworks(instance_name))
+                    for net_uuid in
+                      lu.cfg.GetInstanceNetworks(
+                        lu.cfg.GetInstanceInfoByName(instance_name).uuid))
 
   @staticmethod
   def _CheckGroupLocks(lu):
-    owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
+    owned_instance_names = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
 
     # Check if node groups for locked instances are still correct
-    for instance_name in owned_instances:
-      CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
+    for instance_name in owned_instance_names:
+      instance = lu.cfg.GetInstanceInfoByName(instance_name)
+      CheckInstanceNodeGroups(lu.cfg, instance.uuid, owned_groups)
 
   def _GetQueryData(self, lu):
     """Computes the list of instances and their attributes.
@@ -100,49 +105,54 @@ class InstanceQuery(QueryBase):
       self._CheckGroupLocks(lu)
 
     cluster = lu.cfg.GetClusterInfo()
-    all_info = lu.cfg.GetAllInstancesInfo()
+    insts_by_name = dict((inst.name, inst) for
+                         inst in lu.cfg.GetAllInstancesInfo().values())
 
-    instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
+    instance_names = self._GetNames(lu, insts_by_name.keys(),
+                                    locking.LEVEL_INSTANCE)
 
-    instance_list = [all_info[name] for name in instance_names]
-    nodes = frozenset(itertools.chain(*(inst.all_nodes
-                                        for inst in instance_list)))
+    instance_list = [insts_by_name[node] for node in instance_names]
+    node_uuids = frozenset(itertools.chain(*(inst.all_nodes
+                                             for inst in instance_list)))
     hv_list = list(set([inst.hypervisor for inst in instance_list]))
-    bad_nodes = []
-    offline_nodes = []
-    wrongnode_inst = set()
+    bad_node_uuids = []
+    offline_node_uuids = []
+    wrongnode_inst_uuids = set()
 
     # Gather data as requested
     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
       live_data = {}
-      node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
-      for name in nodes:
-        result = node_data[name]
+      node_data = lu.rpc.call_all_instances_info(node_uuids, hv_list,
+                                                 cluster.hvparams)
+      for node_uuid in node_uuids:
+        result = node_data[node_uuid]
         if result.offline:
           # offline nodes will be in both lists
           assert result.fail_msg
-          offline_nodes.append(name)
+          offline_node_uuids.append(node_uuid)
         if result.fail_msg:
-          bad_nodes.append(name)
+          bad_node_uuids.append(node_uuid)
         elif result.payload:
-          for inst in result.payload:
-            if inst in all_info:
-              if all_info[inst].primary_node == name:
-                live_data.update(result.payload)
+          for inst_name in result.payload:
+            if inst_name in insts_by_name:
+              instance = insts_by_name[inst_name]
+              if instance.primary_node == node_uuid:
+                for iname in result.payload:
+                  live_data[insts_by_name[iname].uuid] = result.payload[iname]
               else:
-                wrongnode_inst.add(inst)
+                wrongnode_inst_uuids.add(instance.uuid)
             else:
               # orphan instance; we don't list it here as we don't
               # handle this case yet in the output of instance listing
               logging.warning("Orphan instance '%s' found on node %s",
-                              inst, name)
+                              inst_name, lu.cfg.GetNodeName(node_uuid))
               # else no instance is alive
     else:
       live_data = {}
 
     if query.IQ_DISKUSAGE in self.requested_data:
       gmi = ganeti.masterd.instance
-      disk_usage = dict((inst.name,
+      disk_usage = dict((inst.uuid,
                          gmi.ComputeDiskSize(inst.disk_template,
                                              [{constants.IDISK_SIZE: disk.size}
                                               for disk in inst.disks]))
@@ -153,19 +163,18 @@ class InstanceQuery(QueryBase):
     if query.IQ_CONSOLE in self.requested_data:
       consinfo = {}
       for inst in instance_list:
-        if inst.name in live_data:
+        if inst.uuid in live_data:
           # Instance is running
-          consinfo[inst.name] = GetInstanceConsole(cluster, inst)
+          consinfo[inst.uuid] = \
+            GetInstanceConsole(cluster, inst,
+                               lu.cfg.GetNodeInfo(inst.primary_node))
         else:
-          consinfo[inst.name] = None
-      assert set(consinfo.keys()) == set(instance_names)
+          consinfo[inst.uuid] = None
     else:
       consinfo = None
 
     if query.IQ_NODES in self.requested_data:
-      node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
-                                            instance_list)))
-      nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
+      nodes = dict(lu.cfg.GetMultiNodeInfo(node_uuids))
       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
                     for uuid in set(map(operator.attrgetter("group"),
                                         nodes.values())))
@@ -174,16 +183,17 @@ class InstanceQuery(QueryBase):
       groups = None
 
     if query.IQ_NETWORKS in self.requested_data:
-      net_uuids = itertools.chain(*(lu.cfg.GetInstanceNetworks(i.name)
+      net_uuids = itertools.chain(*(lu.cfg.GetInstanceNetworks(i.uuid)
                                     for i in instance_list))
       networks = dict((uuid, lu.cfg.GetNetwork(uuid)) for uuid in net_uuids)
     else:
       networks = None
 
     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
-                                   disk_usage, offline_nodes, bad_nodes,
-                                   live_data, wrongnode_inst, consinfo,
-                                   nodes, groups, networks)
+                                   disk_usage, offline_node_uuids,
+                                   bad_node_uuids, live_data,
+                                   wrongnode_inst_uuids, consinfo, nodes,
+                                   groups, networks)
 
 
 class LUInstanceQuery(NoHooksLU):
@@ -223,7 +233,7 @@ class LUInstanceQueryData(NoHooksLU):
 
     if self.op.instances or not self.op.use_locking:
       # Expand instance names right here
-      self.wanted_names = GetWantedInstances(self, self.op.instances)
+      (_, self.wanted_names) = GetWantedInstances(self, self.op.instances)
     else:
       # Will use acquired locks
       self.wanted_names = None
@@ -243,16 +253,17 @@ class LUInstanceQueryData(NoHooksLU):
 
   def DeclareLocks(self, level):
     if self.op.use_locking:
-      owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
+      owned_instances = dict(self.cfg.GetMultiInstanceInfoByName(
+                               self.owned_locks(locking.LEVEL_INSTANCE)))
       if level == locking.LEVEL_NODEGROUP:
 
         # Lock all groups used by instances optimistically; this requires going
         # via the node before it's locked, requiring verification later on
         self.needed_locks[locking.LEVEL_NODEGROUP] = \
           frozenset(group_uuid
-                    for instance_name in owned_instances
+                    for instance_uuid in owned_instances.keys()
                     for group_uuid in
-                    self.cfg.GetInstanceNodeGroups(instance_name))
+                    self.cfg.GetInstanceNodeGroups(instance_uuid))
 
       elif level == locking.LEVEL_NODE:
         self._LockInstancesNodes()
@@ -260,9 +271,9 @@ class LUInstanceQueryData(NoHooksLU):
       elif level == locking.LEVEL_NETWORK:
         self.needed_locks[locking.LEVEL_NETWORK] = \
           frozenset(net_uuid
-                    for instance_name in owned_instances
+                    for instance_uuid in owned_instances.keys()
                     for net_uuid in
-                    self.cfg.GetInstanceNetworks(instance_name))
+                    self.cfg.GetInstanceNetworks(instance_uuid))
 
   def CheckPrereq(self):
     """Check prerequisites.
@@ -272,34 +283,34 @@ class LUInstanceQueryData(NoHooksLU):
     """
     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
-    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
+    owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
     owned_networks = frozenset(self.owned_locks(locking.LEVEL_NETWORK))
 
     if self.wanted_names is None:
       assert self.op.use_locking, "Locking was not used"
       self.wanted_names = owned_instances
 
-    instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
+    instances = dict(self.cfg.GetMultiInstanceInfoByName(self.wanted_names))
 
     if self.op.use_locking:
-      CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
-                               None)
+      CheckInstancesNodeGroups(self.cfg, instances, owned_groups,
+                               owned_node_uuids, None)
     else:
       assert not (owned_instances or owned_groups or
-                  owned_nodes or owned_networks)
+                  owned_node_uuids or owned_networks)
 
     self.wanted_instances = instances.values()
 
-  def _ComputeBlockdevStatus(self, node, instance, dev):
+  def _ComputeBlockdevStatus(self, node_uuid, instance, dev):
     """Returns the status of a block device
 
     """
-    if self.op.static or not node:
+    if self.op.static or not node_uuid:
       return None
 
-    self.cfg.SetDiskID(dev, node)
+    self.cfg.SetDiskID(dev, node_uuid)
 
-    result = self.rpc.call_blockdev_find(node, dev)
+    result = self.rpc.call_blockdev_find(node_uuid, dev)
     if result.offline:
       return None
 
@@ -313,34 +324,46 @@ class LUInstanceQueryData(NoHooksLU):
             status.sync_percent, status.estimated_time,
             status.is_degraded, status.ldisk_status)
 
-  def _ComputeDiskStatus(self, instance, snode, dev):
+  def _ComputeDiskStatus(self, instance, node_uuid2name_fn, dev):
     """Compute block device status.
 
     """
     (anno_dev,) = AnnotateDiskParams(instance, [dev], self.cfg)
 
-    return self._ComputeDiskStatusInner(instance, snode, anno_dev)
+    return self._ComputeDiskStatusInner(instance, None, node_uuid2name_fn,
+                                        anno_dev)
 
-  def _ComputeDiskStatusInner(self, instance, snode, dev):
+  def _ComputeDiskStatusInner(self, instance, snode_uuid, node_uuid2name_fn,
+                              dev):
     """Compute block device status.
 
     @attention: The device has to be annotated already.
 
     """
+    drbd_info = None
     if dev.dev_type in constants.LDS_DRBD:
       # we change the snode then (otherwise we use the one passed in)
       if dev.logical_id[0] == instance.primary_node:
-        snode = dev.logical_id[1]
+        snode_uuid = dev.logical_id[1]
       else:
-        snode = dev.logical_id[0]
+        snode_uuid = dev.logical_id[0]
+      drbd_info = {
+        "primary_node": node_uuid2name_fn(instance.primary_node),
+        "primary_minor": dev.logical_id[3],
+        "secondary_node": node_uuid2name_fn(snode_uuid),
+        "secondary_minor": dev.logical_id[4],
+        "port": dev.logical_id[2],
+        "secret": dev.logical_id[5],
+      }
 
     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
                                               instance, dev)
-    dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
+    dev_sstatus = self._ComputeBlockdevStatus(snode_uuid, instance, dev)
 
     if dev.children:
       dev_children = map(compat.partial(self._ComputeDiskStatusInner,
-                                        instance, snode),
+                                        instance, snode_uuid,
+                                        node_uuid2name_fn),
                          dev.children)
     else:
       dev_children = []
@@ -349,12 +372,14 @@ class LUInstanceQueryData(NoHooksLU):
       "iv_name": dev.iv_name,
       "dev_type": dev.dev_type,
       "logical_id": dev.logical_id,
+      "drbd_info": drbd_info,
       "physical_id": dev.physical_id,
       "pstatus": dev_pstatus,
       "sstatus": dev_sstatus,
       "children": dev_children,
       "mode": dev.mode,
       "size": dev.size,
+      "spindles": dev.spindles,
       "name": dev.name,
       "uuid": dev.uuid,
       }
@@ -365,13 +390,12 @@ class LUInstanceQueryData(NoHooksLU):
 
     cluster = self.cfg.GetClusterInfo()
 
-    node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
-    nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
+    node_uuids = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
+    nodes = dict(self.cfg.GetMultiNodeInfo(node_uuids))
 
     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
                                                  for node in nodes.values()))
 
-    group2name_fn = lambda uuid: groups[uuid].name
     for instance in self.wanted_instances:
       pnode = nodes[instance.primary_node]
 
@@ -382,10 +406,10 @@ class LUInstanceQueryData(NoHooksLU):
                           " information only for instance %s" %
                           (pnode.name, instance.name))
       else:
-        remote_info = self.rpc.call_instance_info(instance.primary_node,
-                                                  instance.name,
-                                                  instance.hypervisor)
-        remote_info.Raise("Error checking node %s" % instance.primary_node)
+        remote_info = self.rpc.call_instance_info(
+            instance.primary_node, instance.name, instance.hypervisor,
+            cluster.hvparams[instance.hypervisor])
+        remote_info.Raise("Error checking node %s" % pnode.name)
         remote_info = remote_info.payload
         if remote_info and "state" in remote_info:
           remote_state = "up"
@@ -395,20 +419,24 @@ class LUInstanceQueryData(NoHooksLU):
           else:
             remote_state = instance.admin_state
 
-      disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
+      group2name_fn = lambda uuid: groups[uuid].name
+      node_uuid2name_fn = lambda uuid: nodes[uuid].name
+
+      disks = map(compat.partial(self._ComputeDiskStatus, instance,
+                                 node_uuid2name_fn),
                   instance.disks)
 
-      snodes_group_uuids = [nodes[snode_name].group
-                            for snode_name in instance.secondary_nodes]
+      snodes_group_uuids = [nodes[snode_uuid].group
+                            for snode_uuid in instance.secondary_nodes]
 
       result[instance.name] = {
         "name": instance.name,
         "config_state": instance.admin_state,
         "run_state": remote_state,
-        "pnode": instance.primary_node,
+        "pnode": pnode.name,
         "pnode_group_uuid": pnode.group,
         "pnode_group_name": group2name_fn(pnode.group),
-        "snodes": instance.secondary_nodes,
+        "snodes": map(node_uuid2name_fn, instance.secondary_nodes),
         "snodes_group_uuids": snodes_group_uuids,
         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
         "os": instance.os,
index 6478e51..d159187 100644 (file)
@@ -38,9 +38,9 @@ from ganeti import opcodes
 from ganeti import rpc
 from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, Tasklet
 from ganeti.cmdlib.common import INSTANCE_DOWN, INSTANCE_NOT_RUNNING, \
-  AnnotateDiskParams, CheckIAllocatorOrNode, ExpandNodeName, \
+  AnnotateDiskParams, CheckIAllocatorOrNode, ExpandNodeUuidAndName, \
   CheckNodeOnline, CheckInstanceNodeGroups, CheckInstanceState, \
-  IsExclusiveStorageEnabledNode, FindFaultyInstanceDisks
+  IsExclusiveStorageEnabledNode, FindFaultyInstanceDisks, GetWantedNodes
 from ganeti.cmdlib.instance_utils import GetInstanceInfoText, \
   CopyLockList, ReleaseLocks, CheckNodeVmCapable, \
   BuildInstanceHookEnvByObject, CheckNodeNotDrained, CheckTargetNodeIPolicy
@@ -65,7 +65,7 @@ _DISK_TEMPLATE_DEVICE_TYPE = {
   }
 
 
-def CreateSingleBlockDev(lu, node, instance, device, info, force_open,
+def CreateSingleBlockDev(lu, node_uuid, instance, device, info, force_open,
                          excl_stor):
   """Create a single block device on a given node.
 
@@ -73,7 +73,7 @@ def CreateSingleBlockDev(lu, node, instance, device, info, force_open,
   created in advance.
 
   @param lu: the lu on whose behalf we execute
-  @param node: the node on which to create the device
+  @param node_uuid: the node on which to create the device
   @type instance: L{objects.Instance}
   @param instance: the instance which owns the device
   @type device: L{objects.Disk}
@@ -89,17 +89,19 @@ def CreateSingleBlockDev(lu, node, instance, device, info, force_open,
   @param excl_stor: Whether exclusive_storage is active for the node
 
   """
-  lu.cfg.SetDiskID(device, node)
-  result = lu.rpc.call_blockdev_create(node, device, device.size,
+  lu.cfg.SetDiskID(device, node_uuid)
+  result = lu.rpc.call_blockdev_create(node_uuid, device, device.size,
                                        instance.name, force_open, info,
                                        excl_stor)
   result.Raise("Can't create block device %s on"
-               " node %s for instance %s" % (device, node, instance.name))
+               " node %s for instance %s" % (device,
+                                             lu.cfg.GetNodeName(node_uuid),
+                                             instance.name))
   if device.physical_id is None:
     device.physical_id = result.payload
 
 
-def _CreateBlockDevInner(lu, node, instance, device, force_create,
+def _CreateBlockDevInner(lu, node_uuid, instance, device, force_create,
                          info, force_open, excl_stor):
   """Create a tree of block devices on a given node.
 
@@ -111,7 +113,7 @@ def _CreateBlockDevInner(lu, node, instance, device, force_create,
   @attention: The device has to be annotated already.
 
   @param lu: the lu on whose behalf we execute
-  @param node: the node on which to create the device
+  @param node_uuid: the node on which to create the device
   @type instance: L{objects.Instance}
   @param instance: the instance which owns the device
   @type device: L{objects.Disk}
@@ -139,18 +141,18 @@ def _CreateBlockDevInner(lu, node, instance, device, force_create,
 
     if device.children:
       for child in device.children:
-        devs = _CreateBlockDevInner(lu, node, instance, child, force_create,
-                                    info, force_open, excl_stor)
+        devs = _CreateBlockDevInner(lu, node_uuid, instance, child,
+                                    force_create, info, force_open, excl_stor)
         created_devices.extend(devs)
 
     if not force_create:
       return created_devices
 
-    CreateSingleBlockDev(lu, node, instance, device, info, force_open,
+    CreateSingleBlockDev(lu, node_uuid, instance, device, info, force_open,
                          excl_stor)
     # The device has been completely created, so there is no point in keeping
     # its subdevices in the list. We just add the device itself instead.
-    created_devices = [(node, device)]
+    created_devices = [(node_uuid, device)]
     return created_devices
 
   except errors.DeviceCreationError, e:
@@ -160,26 +162,26 @@ def _CreateBlockDevInner(lu, node, instance, device, force_create,
     raise errors.DeviceCreationError(str(e), created_devices)
 
 
-def IsExclusiveStorageEnabledNodeName(cfg, nodename):
+def IsExclusiveStorageEnabledNodeUuid(cfg, node_uuid):
   """Whether exclusive_storage is in effect for the given node.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: The cluster configuration
-  @type nodename: string
-  @param nodename: The node
+  @type node_uuid: string
+  @param node_uuid: The node UUID
   @rtype: bool
   @return: The effective value of exclusive_storage
   @raise errors.OpPrereqError: if no node exists with the given name
 
   """
-  ni = cfg.GetNodeInfo(nodename)
+  ni = cfg.GetNodeInfo(node_uuid)
   if ni is None:
-    raise errors.OpPrereqError("Invalid node name %s" % nodename,
+    raise errors.OpPrereqError("Invalid node UUID %s" % node_uuid,
                                errors.ECODE_NOENT)
   return IsExclusiveStorageEnabledNode(cfg, ni)
 
 
-def _CreateBlockDev(lu, node, instance, device, force_create, info,
+def _CreateBlockDev(lu, node_uuid, instance, device, force_create, info,
                     force_open):
   """Wrapper around L{_CreateBlockDevInner}.
 
@@ -187,8 +189,8 @@ def _CreateBlockDev(lu, node, instance, device, force_create, info,
 
   """
   (disk,) = AnnotateDiskParams(instance, [device], lu.cfg)
-  excl_stor = IsExclusiveStorageEnabledNodeName(lu.cfg, node)
-  return _CreateBlockDevInner(lu, node, instance, disk, force_create, info,
+  excl_stor = IsExclusiveStorageEnabledNodeUuid(lu.cfg, node_uuid)
+  return _CreateBlockDevInner(lu, node_uuid, instance, disk, force_create, info,
                               force_open, excl_stor)
 
 
@@ -203,15 +205,14 @@ def _UndoCreateDisks(lu, disks_created):
   @param disks_created: the result returned by L{CreateDisks}
 
   """
-  for (node, disk) in disks_created:
-    lu.cfg.SetDiskID(disk, node)
-    result = lu.rpc.call_blockdev_remove(node, disk)
-    if result.fail_msg:
-      logging.warning("Failed to remove newly-created disk %s on node %s:"
-                      " %s", disk, node, result.fail_msg)
+  for (node_uuid, disk) in disks_created:
+    lu.cfg.SetDiskID(disk, node_uuid)
+    result = lu.rpc.call_blockdev_remove(node_uuid, disk)
+    result.Warn("Failed to remove newly-created disk %s on node %s" %
+                (disk, lu.cfg.GetNodeName(node_uuid)), logging.warning)
 
 
-def CreateDisks(lu, instance, to_skip=None, target_node=None, disks=None):
+def CreateDisks(lu, instance, to_skip=None, target_node_uuid=None, disks=None):
   """Create all disks for an instance.
 
   This abstracts away some work from AddInstance.
@@ -222,8 +223,8 @@ def CreateDisks(lu, instance, to_skip=None, target_node=None, disks=None):
   @param instance: the instance whose disks we should create
   @type to_skip: list
   @param to_skip: list of indices to skip
-  @type target_node: string
-  @param target_node: if passed, overrides the target node for creation
+  @type target_node_uuid: string
+  @param target_node_uuid: if passed, overrides the target node for creation
   @type disks: list of {objects.Disk}
   @param disks: the disks to create; if not specified, all the disks of the
       instance are created
@@ -233,33 +234,35 @@ def CreateDisks(lu, instance, to_skip=None, target_node=None, disks=None):
 
   """
   info = GetInstanceInfoText(instance)
-  if target_node is None:
-    pnode = instance.primary_node
-    all_nodes = instance.all_nodes
+  if target_node_uuid is None:
+    pnode_uuid = instance.primary_node
+    all_node_uuids = instance.all_nodes
   else:
-    pnode = target_node
-    all_nodes = [pnode]
+    pnode_uuid = target_node_uuid
+    all_node_uuids = [pnode_uuid]
 
   if disks is None:
     disks = instance.disks
 
   if instance.disk_template in constants.DTS_FILEBASED:
     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
-    result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
+    result = lu.rpc.call_file_storage_dir_create(pnode_uuid, file_storage_dir)
 
     result.Raise("Failed to create directory '%s' on"
-                 " node %s" % (file_storage_dir, pnode))
+                 " node %s" % (file_storage_dir,
+                               lu.cfg.GetNodeName(pnode_uuid)))
 
   disks_created = []
   for idx, device in enumerate(disks):
     if to_skip and idx in to_skip:
       continue
     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
-    for node in all_nodes:
-      f_create = node == pnode
+    for node_uuid in all_node_uuids:
+      f_create = node_uuid == pnode_uuid
       try:
-        _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
-        disks_created.append((node, device))
+        _CreateBlockDev(lu, node_uuid, instance, device, f_create, info,
+                        f_create)
+        disks_created.append((node_uuid, device))
       except errors.DeviceCreationError, e:
         logging.warning("Creating disk %s for instance '%s' failed",
                         idx, instance.name)
@@ -343,10 +346,13 @@ def ComputeDisks(op, default_vg):
       constants.IDISK_NAME: name,
       }
 
-    if constants.IDISK_METAVG in disk:
-      new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
-    if constants.IDISK_ADOPT in disk:
-      new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
+    for key in [
+      constants.IDISK_METAVG,
+      constants.IDISK_ADOPT,
+      constants.IDISK_SPINDLES,
+      ]:
+      if key in disk:
+        new_disk[key] = disk[key]
 
     # For extstorage, demand the `provider' option and add any
     # additional parameters (ext-params) to the dict
@@ -373,7 +379,7 @@ def CheckRADOSFreeSpace():
   pass
 
 
-def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
+def _GenerateDRBD8Branch(lu, primary_uuid, secondary_uuid, size, vgnames, names,
                          iv_name, p_minor, s_minor):
   """Generate a drbd8 device complete with its children.
 
@@ -392,7 +398,7 @@ def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
                           params={})
   dev_meta.uuid = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
-                          logical_id=(primary, secondary, port,
+                          logical_id=(primary_uuid, secondary_uuid, port,
                                       p_minor, s_minor,
                                       shared_secret),
                           children=[dev_data, dev_meta],
@@ -402,7 +408,7 @@ def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
 
 
 def GenerateDiskTemplate(
-  lu, template_name, instance_name, primary_node, secondary_nodes,
+  lu, template_name, instance_uuid, primary_node_uuid, secondary_node_uuids,
   disk_info, file_storage_dir, file_driver, base_index,
   feedback_fn, full_disk_params, _req_file_storage=opcodes.RequireFileStorage,
   _req_shr_file_storage=opcodes.RequireSharedFileStorage):
@@ -416,11 +422,11 @@ def GenerateDiskTemplate(
   if template_name == constants.DT_DISKLESS:
     pass
   elif template_name == constants.DT_DRBD8:
-    if len(secondary_nodes) != 1:
+    if len(secondary_node_uuids) != 1:
       raise errors.ProgrammerError("Wrong template configuration")
-    remote_node = secondary_nodes[0]
+    remote_node_uuid = secondary_node_uuids[0]
     minors = lu.cfg.AllocateDRBDMinor(
-      [primary_node, remote_node] * len(disk_info), instance_name)
+      [primary_node_uuid, remote_node_uuid] * len(disk_info), instance_uuid)
 
     (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
                                                        full_disk_params)
@@ -435,7 +441,7 @@ def GenerateDiskTemplate(
       disk_index = idx + base_index
       data_vg = disk.get(constants.IDISK_VG, vgname)
       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
-      disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
+      disk_dev = _GenerateDRBD8Branch(lu, primary_node_uuid, remote_node_uuid,
                                       disk[constants.IDISK_SIZE],
                                       [data_vg, meta_vg],
                                       names[idx * 2:idx * 2 + 2],
@@ -445,7 +451,7 @@ def GenerateDiskTemplate(
       disk_dev.name = disk.get(constants.IDISK_NAME, None)
       disks.append(disk_dev)
   else:
-    if secondary_nodes:
+    if secondary_node_uuids:
       raise errors.ProgrammerError("Wrong template configuration")
 
     if template_name == constants.DT_FILE:
@@ -507,7 +513,8 @@ def GenerateDiskTemplate(
                               logical_id=logical_id_fn(idx, disk_index, disk),
                               iv_name="disk/%d" % disk_index,
                               mode=disk[constants.IDISK_MODE],
-                              params=params)
+                              params=params,
+                              spindles=disk.get(constants.IDISK_SPINDLES))
       disk_dev.name = disk.get(constants.IDISK_NAME, None)
       disk_dev.uuid = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
       disks.append(disk_dev)
@@ -515,6 +522,30 @@ def GenerateDiskTemplate(
   return disks
 
 
+def CheckSpindlesExclusiveStorage(diskdict, es_flag, required):
+  """Check the presence of the spindle options with exclusive_storage.
+
+  @type diskdict: dict
+  @param diskdict: disk parameters
+  @type es_flag: bool
+  @param es_flag: the effective value of the exlusive_storage flag
+  @type required: bool
+  @param required: whether spindles are required or just optional
+  @raise errors.OpPrereqError when spindles are given and they should not
+
+  """
+  if (not es_flag and constants.IDISK_SPINDLES in diskdict and
+      diskdict[constants.IDISK_SPINDLES] is not None):
+    raise errors.OpPrereqError("Spindles in instance disks cannot be specified"
+                               " when exclusive storage is not active",
+                               errors.ECODE_INVAL)
+  if (es_flag and required and (constants.IDISK_SPINDLES not in diskdict or
+                                diskdict[constants.IDISK_SPINDLES] is None)):
+    raise errors.OpPrereqError("You must specify spindles in instance disks"
+                               " when exclusive storage is active",
+                               errors.ECODE_INVAL)
+
+
 class LUInstanceRecreateDisks(LogicalUnit):
   """Recreate an instance's missing disks.
 
@@ -526,6 +557,7 @@ class LUInstanceRecreateDisks(LogicalUnit):
   _MODIFYABLE = compat.UniqueFrozenset([
     constants.IDISK_SIZE,
     constants.IDISK_MODE,
+    constants.IDISK_SPINDLES,
     ])
 
   # New or changed disk parameters may have different semantics
@@ -564,6 +596,11 @@ class LUInstanceRecreateDisks(LogicalUnit):
     # reasons, then recreating the disks on the same nodes should be fine.
     disk_template = self.instance.disk_template
     spindle_use = be_full[constants.BE_SPINDLE_USE]
+    disks = [{
+      constants.IDISK_SIZE: d.size,
+      constants.IDISK_MODE: d.mode,
+      constants.IDISK_SPINDLES: d.spindles,
+      } for d in self.instance.disks]
     req = iallocator.IAReqInstanceAlloc(name=self.op.instance_name,
                                         disk_template=disk_template,
                                         tags=list(self.instance.GetTags()),
@@ -572,9 +609,7 @@ class LUInstanceRecreateDisks(LogicalUnit):
                                         vcpus=be_full[constants.BE_VCPUS],
                                         memory=be_full[constants.BE_MAXMEM],
                                         spindle_use=spindle_use,
-                                        disks=[{constants.IDISK_SIZE: d.size,
-                                                constants.IDISK_MODE: d.mode}
-                                               for d in self.instance.disks],
+                                        disks=disks,
                                         hypervisor=self.instance.hypervisor,
                                         node_whitelist=None)
     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
@@ -588,10 +623,10 @@ class LUInstanceRecreateDisks(LogicalUnit):
                                  " %s" % (self.op.iallocator, ial.info),
                                  errors.ECODE_NORES)
 
-    self.op.nodes = ial.result
+    (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, ial.result)
     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
                  self.op.instance_name, self.op.iallocator,
-                 utils.CommaJoin(ial.result))
+                 utils.CommaJoin(self.op.nodes))
 
   def CheckArguments(self):
     if self.op.disks and ht.TNonNegativeInt(self.op.disks[0]):
@@ -623,8 +658,8 @@ class LUInstanceRecreateDisks(LogicalUnit):
     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
 
     if self.op.nodes:
-      self.op.nodes = [ExpandNodeName(self.cfg, n) for n in self.op.nodes]
-      self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
+      (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
+      self.needed_locks[locking.LEVEL_NODE] = list(self.op.node_uuids)
     else:
       self.needed_locks[locking.LEVEL_NODE] = []
       if self.op.iallocator:
@@ -644,7 +679,7 @@ class LUInstanceRecreateDisks(LogicalUnit):
       # requires going via the node before it's locked, requiring
       # verification later on
       self.needed_locks[locking.LEVEL_NODEGROUP] = \
-        self.cfg.GetInstanceNodeGroups(self.op.instance_name, primary_only=True)
+        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid, primary_only=True)
 
     elif level == locking.LEVEL_NODE:
       # If an allocator is used, then we lock all the nodes in the current
@@ -691,21 +726,21 @@ class LUInstanceRecreateDisks(LogicalUnit):
     This checks that the instance is in the cluster and is not running.
 
     """
-    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
-    if self.op.nodes:
-      if len(self.op.nodes) != len(instance.all_nodes):
+    if self.op.node_uuids:
+      if len(self.op.node_uuids) != len(instance.all_nodes):
         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
                                    " %d replacement nodes were specified" %
                                    (instance.name, len(instance.all_nodes),
-                                    len(self.op.nodes)),
+                                    len(self.op.node_uuids)),
                                    errors.ECODE_INVAL)
       assert instance.disk_template != constants.DT_DRBD8 or \
-             len(self.op.nodes) == 2
+             len(self.op.node_uuids) == 2
       assert instance.disk_template != constants.DT_PLAIN or \
-             len(self.op.nodes) == 1
-      primary_node = self.op.nodes[0]
+             len(self.op.node_uuids) == 1
+      primary_node = self.op.node_uuids[0]
     else:
       primary_node = instance.primary_node
     if not self.op.iallocator:
@@ -720,13 +755,13 @@ class LUInstanceRecreateDisks(LogicalUnit):
     if owned_groups:
       # Node group locks are acquired only for the primary node (and only
       # when the allocator is used)
-      CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups,
+      CheckInstanceNodeGroups(self.cfg, instance.uuid, owned_groups,
                               primary_only=True)
 
     # if we replace nodes *and* the old primary is offline, we don't
     # check the instance state
     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
-    if not ((self.op.iallocator or self.op.nodes) and old_pnode.offline):
+    if not ((self.op.iallocator or self.op.node_uuids) and old_pnode.offline):
       CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
                          msg="cannot recreate disks")
 
@@ -740,7 +775,7 @@ class LUInstanceRecreateDisks(LogicalUnit):
       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
                                  errors.ECODE_INVAL)
 
-    if ((self.op.nodes or self.op.iallocator) and
+    if ((self.op.node_uuids or self.op.iallocator) and
          sorted(self.disks.keys()) != range(len(instance.disks))):
       raise errors.OpPrereqError("Can't recreate disks partially and"
                                  " change the nodes at the same time",
@@ -751,25 +786,33 @@ class LUInstanceRecreateDisks(LogicalUnit):
     if self.op.iallocator:
       self._RunAllocator()
       # Release unneeded node and node resource locks
-      ReleaseLocks(self, locking.LEVEL_NODE, keep=self.op.nodes)
-      ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=self.op.nodes)
+      ReleaseLocks(self, locking.LEVEL_NODE, keep=self.op.node_uuids)
+      ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=self.op.node_uuids)
       ReleaseLocks(self, locking.LEVEL_NODE_ALLOC)
 
     assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
 
+    if self.op.node_uuids:
+      node_uuids = self.op.node_uuids
+    else:
+      node_uuids = instance.all_nodes
+    excl_stor = compat.any(
+      rpc.GetExclusiveStorageForNodes(self.cfg, node_uuids).values()
+      )
+    for new_params in self.disks.values():
+      CheckSpindlesExclusiveStorage(new_params, excl_stor, False)
+
   def Exec(self, feedback_fn):
     """Recreate the disks.
 
     """
-    instance = self.instance
-
     assert (self.owned_locks(locking.LEVEL_NODE) ==
             self.owned_locks(locking.LEVEL_NODE_RES))
 
     to_skip = []
     mods = [] # keeps track of needed changes
 
-    for idx, disk in enumerate(instance.disks):
+    for idx, disk in enumerate(self.instance.disks):
       try:
         changes = self.disks[idx]
       except KeyError:
@@ -778,14 +821,15 @@ class LUInstanceRecreateDisks(LogicalUnit):
         continue
 
       # update secondaries for disks, if needed
-      if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
+      if self.op.node_uuids and disk.dev_type == constants.LD_DRBD8:
         # need to update the nodes and minors
-        assert len(self.op.nodes) == 2
+        assert len(self.op.node_uuids) == 2
         assert len(disk.logical_id) == 6 # otherwise disk internals
                                          # have changed
         (_, _, old_port, _, _, old_secret) = disk.logical_id
-        new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
-        new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
+        new_minors = self.cfg.AllocateDRBDMinor(self.op.node_uuids,
+                                                self.instance.uuid)
+        new_id = (self.op.node_uuids[0], self.op.node_uuids[1], old_port,
                   new_minors[0], new_minors[1], old_secret)
         assert len(disk.logical_id) == len(new_id)
       else:
@@ -796,37 +840,39 @@ class LUInstanceRecreateDisks(LogicalUnit):
     # now that we have passed all asserts above, we can apply the mods
     # in a single run (to avoid partial changes)
     for idx, new_id, changes in mods:
-      disk = instance.disks[idx]
+      disk = self.instance.disks[idx]
       if new_id is not None:
         assert disk.dev_type == constants.LD_DRBD8
         disk.logical_id = new_id
       if changes:
         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
-                    mode=changes.get(constants.IDISK_MODE, None))
+                    mode=changes.get(constants.IDISK_MODE, None),
+                    spindles=changes.get(constants.IDISK_SPINDLES, None))
 
     # change primary node, if needed
-    if self.op.nodes:
-      instance.primary_node = self.op.nodes[0]
+    if self.op.node_uuids:
+      self.instance.primary_node = self.op.node_uuids[0]
       self.LogWarning("Changing the instance's nodes, you will have to"
                       " remove any disks left on the older nodes manually")
 
-    if self.op.nodes:
-      self.cfg.Update(instance, feedback_fn)
+    if self.op.node_uuids:
+      self.cfg.Update(self.instance, feedback_fn)
 
     # All touched nodes must be locked
     mylocks = self.owned_locks(locking.LEVEL_NODE)
-    assert mylocks.issuperset(frozenset(instance.all_nodes))
-    new_disks = CreateDisks(self, instance, to_skip=to_skip)
+    assert mylocks.issuperset(frozenset(self.instance.all_nodes))
+    new_disks = CreateDisks(self, self.instance, to_skip=to_skip)
 
     # TODO: Release node locks before wiping, or explain why it's not possible
     if self.cfg.GetClusterInfo().prealloc_wipe_disks:
       wipedisks = [(idx, disk, 0)
-                   for (idx, disk) in enumerate(instance.disks)
+                   for (idx, disk) in enumerate(self.instance.disks)
                    if idx not in to_skip]
-      WipeOrCleanupDisks(self, instance, disks=wipedisks, cleanup=new_disks)
+      WipeOrCleanupDisks(self, self.instance, disks=wipedisks,
+                         cleanup=new_disks)
 
 
-def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
+def _CheckNodesFreeDiskOnVG(lu, node_uuids, vg, requested):
   """Checks if nodes have enough free disk space in the specified VG.
 
   This function checks if all given nodes have the needed amount of
@@ -836,8 +882,8 @@ def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
 
   @type lu: C{LogicalUnit}
   @param lu: a logical unit from which we get configuration data
-  @type nodenames: C{list}
-  @param nodenames: the list of node names to check
+  @type node_uuids: C{list}
+  @param node_uuids: the list of node UUIDs to check
   @type vg: C{str}
   @param vg: the volume group to check
   @type requested: C{int}
@@ -846,26 +892,31 @@ def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
       or we cannot check the node
 
   """
-  es_flags = rpc.GetExclusiveStorageForNodeNames(lu.cfg, nodenames)
-  nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None, es_flags)
-  for node in nodenames:
+  es_flags = rpc.GetExclusiveStorageForNodes(lu.cfg, node_uuids)
+  hvname = lu.cfg.GetHypervisorType()
+  hvparams = lu.cfg.GetClusterInfo().hvparams
+  nodeinfo = lu.rpc.call_node_info(node_uuids, [(constants.ST_LVM_VG, vg)],
+                                   [(hvname, hvparams[hvname])], es_flags)
+  for node in node_uuids:
+    node_name = lu.cfg.GetNodeName(node)
+
     info = nodeinfo[node]
-    info.Raise("Cannot get current information from node %s" % node,
+    info.Raise("Cannot get current information from node %s" % node_name,
                prereq=True, ecode=errors.ECODE_ENVIRON)
     (_, (vg_info, ), _) = info.payload
-    vg_free = vg_info.get("vg_free", None)
+    vg_free = vg_info.get("storage_free", None)
     if not isinstance(vg_free, int):
       raise errors.OpPrereqError("Can't compute free disk space on node"
                                  " %s for vg %s, result was '%s'" %
-                                 (node, vg, vg_free), errors.ECODE_ENVIRON)
+                                 (node_name, vg, vg_free), errors.ECODE_ENVIRON)
     if requested > vg_free:
       raise errors.OpPrereqError("Not enough disk space on target node %s"
                                  " vg %s: required %d MiB, available %d MiB" %
-                                 (node, vg, requested, vg_free),
+                                 (node_name, vg, requested, vg_free),
                                  errors.ECODE_NORES)
 
 
-def CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
+def CheckNodesFreeDiskPerVG(lu, node_uuids, req_sizes):
   """Checks if nodes have enough free disk space in all the VGs.
 
   This function checks if all given nodes have the needed amount of
@@ -875,8 +926,8 @@ def CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
 
   @type lu: C{LogicalUnit}
   @param lu: a logical unit from which we get configuration data
-  @type nodenames: C{list}
-  @param nodenames: the list of node names to check
+  @type node_uuids: C{list}
+  @param node_uuids: the list of node UUIDs to check
   @type req_sizes: C{dict}
   @param req_sizes: the hash of vg and corresponding amount of disk in
       MiB to check for
@@ -885,7 +936,7 @@ def CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
 
   """
   for vg, req_size in req_sizes.items():
-    _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
+    _CheckNodesFreeDiskOnVG(lu, node_uuids, vg, req_size)
 
 
 def _DiskSizeInBytesToMebibytes(lu, size):
@@ -930,22 +981,23 @@ def WipeDisks(lu, instance, disks=None):
     start offset
 
   """
-  node = instance.primary_node
+  node_uuid = instance.primary_node
+  node_name = lu.cfg.GetNodeName(node_uuid)
 
   if disks is None:
     disks = [(idx, disk, 0)
              for (idx, disk) in enumerate(instance.disks)]
 
   for (_, device, _) in disks:
-    lu.cfg.SetDiskID(device, node)
+    lu.cfg.SetDiskID(device, node_uuid)
 
   logging.info("Pausing synchronization of disks of instance '%s'",
                instance.name)
-  result = lu.rpc.call_blockdev_pause_resume_sync(node,
+  result = lu.rpc.call_blockdev_pause_resume_sync(node_uuid,
                                                   (map(compat.snd, disks),
                                                    instance),
                                                   True)
-  result.Raise("Failed to pause disk synchronization on node '%s'" % node)
+  result.Raise("Failed to pause disk synchronization on node '%s'" % node_name)
 
   for idx, success in enumerate(result.payload):
     if not success:
@@ -974,7 +1026,8 @@ def WipeDisks(lu, instance, disks=None):
       lu.LogInfo("* Wiping disk %s%s", idx, info_text)
 
       logging.info("Wiping disk %d for instance %s on node %s using"
-                   " chunk size %s", idx, instance.name, node, wipe_chunk_size)
+                   " chunk size %s", idx, instance.name, node_name,
+                   wipe_chunk_size)
 
       while offset < size:
         wipe_size = min(wipe_chunk_size, size - offset)
@@ -982,8 +1035,8 @@ def WipeDisks(lu, instance, disks=None):
         logging.debug("Wiping disk %d, offset %s, chunk %s",
                       idx, offset, wipe_size)
 
-        result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
-                                           wipe_size)
+        result = lu.rpc.call_blockdev_wipe(node_uuid, (device, instance),
+                                           offset, wipe_size)
         result.Raise("Could not wipe disk %d at offset %d for size %d" %
                      (idx, offset, wipe_size))
 
@@ -998,14 +1051,14 @@ def WipeDisks(lu, instance, disks=None):
     logging.info("Resuming synchronization of disks for instance '%s'",
                  instance.name)
 
-    result = lu.rpc.call_blockdev_pause_resume_sync(node,
+    result = lu.rpc.call_blockdev_pause_resume_sync(node_uuid,
                                                     (map(compat.snd, disks),
                                                      instance),
                                                     False)
 
     if result.fail_msg:
       lu.LogWarning("Failed to resume disk synchronization on node '%s': %s",
-                    node, result.fail_msg)
+                    node_name, result.fail_msg)
     else:
       for idx, success in enumerate(result.payload):
         if not success:
@@ -1066,10 +1119,11 @@ def WaitForSync(lu, instance, disks=None, oneshot=False):
   if not oneshot:
     lu.LogInfo("Waiting for instance %s to sync disks", instance.name)
 
-  node = instance.primary_node
+  node_uuid = instance.primary_node
+  node_name = lu.cfg.GetNodeName(node_uuid)
 
   for dev in disks:
-    lu.cfg.SetDiskID(dev, node)
+    lu.cfg.SetDiskID(dev, node_uuid)
 
   # TODO: Convert to utils.Retry
 
@@ -1079,14 +1133,14 @@ def WaitForSync(lu, instance, disks=None, oneshot=False):
     max_time = 0
     done = True
     cumul_degraded = False
-    rstats = lu.rpc.call_blockdev_getmirrorstatus(node, (disks, instance))
+    rstats = lu.rpc.call_blockdev_getmirrorstatus(node_uuid, (disks, instance))
     msg = rstats.fail_msg
     if msg:
-      lu.LogWarning("Can't get any data from node %s: %s", node, msg)
+      lu.LogWarning("Can't get any data from node %s: %s", node_name, msg)
       retries += 1
       if retries >= 10:
         raise errors.RemoteError("Can't contact node %s for mirror data,"
-                                 " aborting." % node)
+                                 " aborting." % node_name)
       time.sleep(6)
       continue
     rstats = rstats.payload
@@ -1094,7 +1148,7 @@ def WaitForSync(lu, instance, disks=None, oneshot=False):
     for i, mstat in enumerate(rstats):
       if mstat is None:
         lu.LogWarning("Can't compute data for node %s/%s",
-                      node, disks[i].iv_name)
+                      node_name, disks[i].iv_name)
         continue
 
       cumul_degraded = (cumul_degraded or
@@ -1139,20 +1193,20 @@ def ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
   ignored.
 
   """
-  lu.cfg.MarkInstanceDisksInactive(instance.name)
+  lu.cfg.MarkInstanceDisksInactive(instance.uuid)
   all_result = True
   disks = ExpandCheckDisks(instance, disks)
 
   for disk in disks:
-    for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
-      lu.cfg.SetDiskID(top_disk, node)
-      result = lu.rpc.call_blockdev_shutdown(node, (top_disk, instance))
+    for node_uuid, top_disk in disk.ComputeNodeTree(instance.primary_node):
+      lu.cfg.SetDiskID(top_disk, node_uuid)
+      result = lu.rpc.call_blockdev_shutdown(node_uuid, (top_disk, instance))
       msg = result.fail_msg
       if msg:
         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
-                      disk.iv_name, node, msg)
-        if ((node == instance.primary_node and not ignore_primary) or
-            (node != instance.primary_node and not result.offline)):
+                      disk.iv_name, lu.cfg.GetNodeName(node_uuid), msg)
+        if ((node_uuid == instance.primary_node and not ignore_primary) or
+            (node_uuid != instance.primary_node and not result.offline)):
           all_result = False
   return all_result
 
@@ -1194,7 +1248,6 @@ def AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
   """
   device_info = []
   disks_ok = True
-  iname = instance.name
   disks = ExpandCheckDisks(instance, disks)
 
   # With the two passes mechanism we try to reduce the window of
@@ -1208,24 +1261,25 @@ def AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
 
   # mark instance disks as active before doing actual work, so watcher does
   # not try to shut them down erroneously
-  lu.cfg.MarkInstanceDisksActive(iname)
+  lu.cfg.MarkInstanceDisksActive(instance.uuid)
 
   # 1st pass, assemble on all nodes in secondary mode
   for idx, inst_disk in enumerate(disks):
-    for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
+    for node_uuid, node_disk in inst_disk.ComputeNodeTree(
+                                  instance.primary_node):
       if ignore_size:
         node_disk = node_disk.Copy()
         node_disk.UnsetSize()
-      lu.cfg.SetDiskID(node_disk, node)
-      result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
-                                             False, idx)
+      lu.cfg.SetDiskID(node_disk, node_uuid)
+      result = lu.rpc.call_blockdev_assemble(node_uuid, (node_disk, instance),
+                                             instance.name, False, idx)
       msg = result.fail_msg
       if msg:
-        is_offline_secondary = (node in instance.secondary_nodes and
+        is_offline_secondary = (node_uuid in instance.secondary_nodes and
                                 result.offline)
         lu.LogWarning("Could not prepare block device %s on node %s"
                       " (is_primary=False, pass=1): %s",
-                      inst_disk.iv_name, node, msg)
+                      inst_disk.iv_name, lu.cfg.GetNodeName(node_uuid), msg)
         if not (ignore_secondaries or is_offline_secondary):
           disks_ok = False
 
@@ -1235,25 +1289,27 @@ def AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
   for idx, inst_disk in enumerate(disks):
     dev_path = None
 
-    for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
-      if node != instance.primary_node:
+    for node_uuid, node_disk in inst_disk.ComputeNodeTree(
+                                  instance.primary_node):
+      if node_uuid != instance.primary_node:
         continue
       if ignore_size:
         node_disk = node_disk.Copy()
         node_disk.UnsetSize()
-      lu.cfg.SetDiskID(node_disk, node)
-      result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
-                                             True, idx)
+      lu.cfg.SetDiskID(node_disk, node_uuid)
+      result = lu.rpc.call_blockdev_assemble(node_uuid, (node_disk, instance),
+                                             instance.name, True, idx)
       msg = result.fail_msg
       if msg:
         lu.LogWarning("Could not prepare block device %s on node %s"
                       " (is_primary=True, pass=2): %s",
-                      inst_disk.iv_name, node, msg)
+                      inst_disk.iv_name, lu.cfg.GetNodeName(node_uuid), msg)
         disks_ok = False
       else:
         dev_path = result.payload
 
-    device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
+    device_info.append((lu.cfg.GetNodeName(instance.primary_node),
+                        inst_disk.iv_name, dev_path))
 
   # leave the disks configured for the primary node
   # this is a workaround that would be fixed better by
@@ -1262,7 +1318,7 @@ def AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
     lu.cfg.SetDiskID(disk, instance.primary_node)
 
   if not disks_ok:
-    lu.cfg.MarkInstanceDisksInactive(iname)
+    lu.cfg.MarkInstanceDisksInactive(instance.uuid)
 
   return disks_ok, device_info
 
@@ -1332,20 +1388,18 @@ class LUInstanceGrowDisk(LogicalUnit):
     This checks that the instance is in the cluster.
 
     """
-    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
-    assert instance is not None, \
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
+    assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
-    nodenames = list(instance.all_nodes)
-    for node in nodenames:
-      CheckNodeOnline(self, node)
-
-    self.instance = instance
+    node_uuids = list(self.instance.all_nodes)
+    for node_uuid in node_uuids:
+      CheckNodeOnline(self, node_uuid)
 
-    if instance.disk_template not in constants.DTS_GROWABLE:
+    if self.instance.disk_template not in constants.DTS_GROWABLE:
       raise errors.OpPrereqError("Instance's disk layout does not support"
                                  " growing", errors.ECODE_INVAL)
 
-    self.disk = instance.FindDisk(self.op.disk)
+    self.disk = self.instance.FindDisk(self.op.disk)
 
     if self.op.absolute:
       self.target = self.op.amount
@@ -1364,14 +1418,14 @@ class LUInstanceGrowDisk(LogicalUnit):
                                    utils.FormatUnit(self.delta, "h"),
                                    errors.ECODE_INVAL)
 
-    self._CheckDiskSpace(nodenames, self.disk.ComputeGrowth(self.delta))
+    self._CheckDiskSpace(node_uuids, self.disk.ComputeGrowth(self.delta))
 
-  def _CheckDiskSpace(self, nodenames, req_vgspace):
+  def _CheckDiskSpace(self, node_uuids, req_vgspace):
     template = self.instance.disk_template
     if template not in (constants.DTS_NO_FREE_SPACE_CHECK):
       # TODO: check the free disk space for file, when that feature will be
       # supported
-      nodes = map(self.cfg.GetNodeInfo, nodenames)
+      nodes = map(self.cfg.GetNodeInfo, node_uuids)
       es_nodes = filter(lambda n: IsExclusiveStorageEnabledNode(self.cfg, n),
                         nodes)
       if es_nodes:
@@ -1379,75 +1433,79 @@ class LUInstanceGrowDisk(LogicalUnit):
         # at free space; for now, let's simply abort the operation.
         raise errors.OpPrereqError("Cannot grow disks when exclusive_storage"
                                    " is enabled", errors.ECODE_STATE)
-      CheckNodesFreeDiskPerVG(self, nodenames, req_vgspace)
+      CheckNodesFreeDiskPerVG(self, node_uuids, req_vgspace)
 
   def Exec(self, feedback_fn):
     """Execute disk grow.
 
     """
-    instance = self.instance
-    disk = self.disk
-
-    assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
+    assert set([self.instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
     assert (self.owned_locks(locking.LEVEL_NODE) ==
             self.owned_locks(locking.LEVEL_NODE_RES))
 
     wipe_disks = self.cfg.GetClusterInfo().prealloc_wipe_disks
 
-    disks_ok, _ = AssembleInstanceDisks(self, self.instance, disks=[disk])
+    disks_ok, _ = AssembleInstanceDisks(self, self.instance, disks=[self.disk])
     if not disks_ok:
       raise errors.OpExecError("Cannot activate block device to grow")
 
     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
-                (self.op.disk, instance.name,
+                (self.op.disk, self.instance.name,
                  utils.FormatUnit(self.delta, "h"),
                  utils.FormatUnit(self.target, "h")))
 
     # First run all grow ops in dry-run mode
-    for node in instance.all_nodes:
-      self.cfg.SetDiskID(disk, node)
-      result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
-                                           True, True)
-      result.Raise("Dry-run grow request failed to node %s" % node)
+    for node_uuid in self.instance.all_nodes:
+      self.cfg.SetDiskID(self.disk, node_uuid)
+      result = self.rpc.call_blockdev_grow(node_uuid,
+                                           (self.disk, self.instance),
+                                           self.delta, True, True)
+      result.Raise("Dry-run grow request failed to node %s" %
+                   self.cfg.GetNodeName(node_uuid))
 
     if wipe_disks:
       # Get disk size from primary node for wiping
-      self.cfg.SetDiskID(disk, instance.primary_node)
-      result = self.rpc.call_blockdev_getsize(instance.primary_node, [disk])
+      self.cfg.SetDiskID(self.disk, self.instance.primary_node)
+      result = self.rpc.call_blockdev_getdimensions(self.instance.primary_node,
+                                                    [self.disk])
       result.Raise("Failed to retrieve disk size from node '%s'" %
-                   instance.primary_node)
+                   self.instance.primary_node)
 
-      (disk_size_in_bytes, ) = result.payload
+      (disk_dimensions, ) = result.payload
 
-      if disk_size_in_bytes is None:
+      if disk_dimensions is None:
         raise errors.OpExecError("Failed to retrieve disk size from primary"
-                                 " node '%s'" % instance.primary_node)
+                                 " node '%s'" % self.instance.primary_node)
+      (disk_size_in_bytes, _) = disk_dimensions
 
       old_disk_size = _DiskSizeInBytesToMebibytes(self, disk_size_in_bytes)
 
-      assert old_disk_size >= disk.size, \
+      assert old_disk_size >= self.disk.size, \
         ("Retrieved disk size too small (got %s, should be at least %s)" %
-         (old_disk_size, disk.size))
+         (old_disk_size, self.disk.size))
     else:
       old_disk_size = None
 
     # We know that (as far as we can test) operations across different
     # nodes will succeed, time to run it for real on the backing storage
-    for node in instance.all_nodes:
-      self.cfg.SetDiskID(disk, node)
-      result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
-                                           False, True)
-      result.Raise("Grow request failed to node %s" % node)
+    for node_uuid in self.instance.all_nodes:
+      self.cfg.SetDiskID(self.disk, node_uuid)
+      result = self.rpc.call_blockdev_grow(node_uuid,
+                                           (self.disk, self.instance),
+                                           self.delta, False, True)
+      result.Raise("Grow request failed to node %s" %
+                   self.cfg.GetNodeName(node_uuid))
 
     # And now execute it for logical storage, on the primary node
-    node = instance.primary_node
-    self.cfg.SetDiskID(disk, node)
-    result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
-                                         False, False)
-    result.Raise("Grow request failed to node %s" % node)
-
-    disk.RecordGrow(self.delta)
-    self.cfg.Update(instance, feedback_fn)
+    node_uuid = self.instance.primary_node
+    self.cfg.SetDiskID(self.disk, node_uuid)
+    result = self.rpc.call_blockdev_grow(node_uuid, (self.disk, self.instance),
+                                         self.delta, False, False)
+    result.Raise("Grow request failed to node %s" %
+                 self.cfg.GetNodeName(node_uuid))
+
+    self.disk.RecordGrow(self.delta)
+    self.cfg.Update(self.instance, feedback_fn)
 
     # Changes have been recorded, release node lock
     ReleaseLocks(self, locking.LEVEL_NODE)
@@ -1458,26 +1516,26 @@ class LUInstanceGrowDisk(LogicalUnit):
     assert wipe_disks ^ (old_disk_size is None)
 
     if wipe_disks:
-      assert instance.disks[self.op.disk] == disk
+      assert self.instance.disks[self.op.disk] == self.disk
 
       # Wipe newly added disk space
-      WipeDisks(self, instance,
-                disks=[(self.op.disk, disk, old_disk_size)])
+      WipeDisks(self, self.instance,
+                disks=[(self.op.disk, self.disk, old_disk_size)])
 
     if self.op.wait_for_sync:
-      disk_abort = not WaitForSync(self, instance, disks=[disk])
+      disk_abort = not WaitForSync(self, self.instance, disks=[self.disk])
       if disk_abort:
         self.LogWarning("Disk syncing has not returned a good status; check"
                         " the instance")
-      if not instance.disks_active:
-        _SafeShutdownInstanceDisks(self, instance, disks=[disk])
-    elif not instance.disks_active:
+      if not self.instance.disks_active:
+        _SafeShutdownInstanceDisks(self, self.instance, disks=[self.disk])
+    elif not self.instance.disks_active:
       self.LogWarning("Not shutting down the disk even if the instance is"
                       " not supposed to be running because no wait for"
                       " sync mode was requested")
 
     assert self.owned_locks(locking.LEVEL_NODE_RES)
-    assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
+    assert set([self.instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
 
 
 class LUInstanceReplaceDisks(LogicalUnit):
@@ -1492,17 +1550,15 @@ class LUInstanceReplaceDisks(LogicalUnit):
     """Check arguments.
 
     """
-    remote_node = self.op.remote_node
-    ialloc = self.op.iallocator
     if self.op.mode == constants.REPLACE_DISK_CHG:
-      if remote_node is None and ialloc is None:
+      if self.op.remote_node is None and self.op.iallocator is None:
         raise errors.OpPrereqError("When changing the secondary either an"
                                    " iallocator script must be used or the"
                                    " new node given", errors.ECODE_INVAL)
       else:
         CheckIAllocatorOrNode(self, "iallocator", "remote_node")
 
-    elif remote_node is not None or ialloc is not None:
+    elif self.op.remote_node is not None or self.op.iallocator is not None:
       # Not replacing the secondary
       raise errors.OpPrereqError("The iallocator and new node options can"
                                  " only be used when changing the"
@@ -1519,13 +1575,15 @@ class LUInstanceReplaceDisks(LogicalUnit):
       "Conflicting options"
 
     if self.op.remote_node is not None:
-      self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
+      (self.op.remote_node_uuid, self.op.remote_node) = \
+        ExpandNodeUuidAndName(self.cfg, self.op.remote_node_uuid,
+                              self.op.remote_node)
 
       # Warning: do not remove the locking of the new secondary here
-      # unless DRBD8.AddChildren is changed to work in parallel;
+      # unless DRBD8Dev.AddChildren is changed to work in parallel;
       # currently it doesn't since parallel invocations of
       # FindUnusedMinor will conflict
-      self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
+      self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node_uuid]
       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
     else:
       self.needed_locks[locking.LEVEL_NODE] = []
@@ -1538,8 +1596,9 @@ class LUInstanceReplaceDisks(LogicalUnit):
 
     self.needed_locks[locking.LEVEL_NODE_RES] = []
 
-    self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
-                                   self.op.iallocator, self.op.remote_node,
+    self.replacer = TLReplaceDisks(self, self.op.instance_uuid,
+                                   self.op.instance_name, self.op.mode,
+                                   self.op.iallocator, self.op.remote_node_uuid,
                                    self.op.disks, self.op.early_release,
                                    self.op.ignore_ipolicy)
 
@@ -1547,7 +1606,7 @@ class LUInstanceReplaceDisks(LogicalUnit):
 
   def DeclareLocks(self, level):
     if level == locking.LEVEL_NODEGROUP:
-      assert self.op.remote_node is None
+      assert self.op.remote_node_uuid is None
       assert self.op.iallocator is not None
       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
 
@@ -1555,19 +1614,19 @@ class LUInstanceReplaceDisks(LogicalUnit):
       # Lock all groups used by instance optimistically; this requires going
       # via the node before it's locked, requiring verification later on
       self.needed_locks[locking.LEVEL_NODEGROUP] = \
-        self.cfg.GetInstanceNodeGroups(self.op.instance_name)
+        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
 
     elif level == locking.LEVEL_NODE:
       if self.op.iallocator is not None:
-        assert self.op.remote_node is None
+        assert self.op.remote_node_uuid is None
         assert not self.needed_locks[locking.LEVEL_NODE]
         assert locking.NAL in self.owned_locks(locking.LEVEL_NODE_ALLOC)
 
         # Lock member nodes of all locked groups
         self.needed_locks[locking.LEVEL_NODE] = \
-          [node_name
+          [node_uuid
            for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
-           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
+           for node_uuid in self.cfg.GetNodeGroup(group_uuid).members]
       else:
         assert not self.glm.is_owned(locking.LEVEL_NODE_ALLOC)
 
@@ -1588,7 +1647,7 @@ class LUInstanceReplaceDisks(LogicalUnit):
     env = {
       "MODE": self.op.mode,
       "NEW_SECONDARY": self.op.remote_node,
-      "OLD_SECONDARY": instance.secondary_nodes[0],
+      "OLD_SECONDARY": self.cfg.GetNodeName(instance.secondary_nodes[0]),
       }
     env.update(BuildInstanceHookEnvByObject(self, instance))
     return env
@@ -1602,8 +1661,8 @@ class LUInstanceReplaceDisks(LogicalUnit):
       self.cfg.GetMasterNode(),
       instance.primary_node,
       ]
-    if self.op.remote_node is not None:
-      nl.append(self.op.remote_node)
+    if self.op.remote_node_uuid is not None:
+      nl.append(self.op.remote_node_uuid)
     return nl, nl
 
   def CheckPrereq(self):
@@ -1616,7 +1675,7 @@ class LUInstanceReplaceDisks(LogicalUnit):
     # Verify if node group locks are still correct
     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
     if owned_groups:
-      CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
+      CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid, owned_groups)
 
     return LogicalUnit.CheckPrereq(self)
 
@@ -1642,7 +1701,7 @@ class LUInstanceActivateDisks(NoHooksLU):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
     CheckNodeOnline(self, self.instance.primary_node)
@@ -1659,7 +1718,7 @@ class LUInstanceActivateDisks(NoHooksLU):
 
     if self.op.wait_for_sync:
       if not WaitForSync(self, self.instance):
-        self.cfg.MarkInstanceDisksInactive(self.instance.name)
+        self.cfg.MarkInstanceDisksInactive(self.instance.uuid)
         raise errors.OpExecError("Some disks of the instance are degraded!")
 
     return disks_info
@@ -1686,7 +1745,7 @@ class LUInstanceDeactivateDisks(NoHooksLU):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
+    self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
     assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.op.instance_name
 
@@ -1694,14 +1753,13 @@ class LUInstanceDeactivateDisks(NoHooksLU):
     """Deactivate the disks
 
     """
-    instance = self.instance
     if self.op.force:
-      ShutdownInstanceDisks(self, instance)
+      ShutdownInstanceDisks(self, self.instance)
     else:
-      _SafeShutdownInstanceDisks(self, instance)
+      _SafeShutdownInstanceDisks(self, self.instance)
 
 
-def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
+def _CheckDiskConsistencyInner(lu, instance, dev, node_uuid, on_primary,
                                ldisk=False):
   """Check that mirrors are not degraded.
 
@@ -1712,18 +1770,19 @@ def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
   the device(s)) to the ldisk (representing the local storage status).
 
   """
-  lu.cfg.SetDiskID(dev, node)
+  lu.cfg.SetDiskID(dev, node_uuid)
 
   result = True
 
   if on_primary or dev.AssembleOnSecondary():
-    rstats = lu.rpc.call_blockdev_find(node, dev)
+    rstats = lu.rpc.call_blockdev_find(node_uuid, dev)
     msg = rstats.fail_msg
     if msg:
-      lu.LogWarning("Can't find disk on node %s: %s", node, msg)
+      lu.LogWarning("Can't find disk on node %s: %s",
+                    lu.cfg.GetNodeName(node_uuid), msg)
       result = False
     elif not rstats.payload:
-      lu.LogWarning("Can't find disk on node %s", node)
+      lu.LogWarning("Can't find disk on node %s", lu.cfg.GetNodeName(node_uuid))
       result = False
     else:
       if ldisk:
@@ -1733,33 +1792,33 @@ def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
 
   if dev.children:
     for child in dev.children:
-      result = result and _CheckDiskConsistencyInner(lu, instance, child, node,
-                                                     on_primary)
+      result = result and _CheckDiskConsistencyInner(lu, instance, child,
+                                                     node_uuid, on_primary)
 
   return result
 
 
-def CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
+def CheckDiskConsistency(lu, instance, dev, node_uuid, on_primary, ldisk=False):
   """Wrapper around L{_CheckDiskConsistencyInner}.
 
   """
   (disk,) = AnnotateDiskParams(instance, [dev], lu.cfg)
-  return _CheckDiskConsistencyInner(lu, instance, disk, node, on_primary,
+  return _CheckDiskConsistencyInner(lu, instance, disk, node_uuid, on_primary,
                                     ldisk=ldisk)
 
 
-def _BlockdevFind(lu, node, dev, instance):
+def _BlockdevFind(lu, node_uuid, dev, instance):
   """Wrapper around call_blockdev_find to annotate diskparams.
 
   @param lu: A reference to the lu object
-  @param node: The node to call out
+  @param node_uuid: The node to call out
   @param dev: The device to find
   @param instance: The instance object the device belongs to
   @returns The result of the rpc call
 
   """
   (disk,) = AnnotateDiskParams(instance, [dev], lu.cfg)
-  return lu.rpc.call_blockdev_find(node, disk)
+  return lu.rpc.call_blockdev_find(node_uuid, disk)
 
 
 def _GenerateUniqueNames(lu, exts):
@@ -1781,37 +1840,40 @@ class TLReplaceDisks(Tasklet):
   Note: Locking is not within the scope of this class.
 
   """
-  def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
-               disks, early_release, ignore_ipolicy):
+  def __init__(self, lu, instance_uuid, instance_name, mode, iallocator_name,
+               remote_node_uuid, disks, early_release, ignore_ipolicy):
     """Initializes this class.
 
     """
     Tasklet.__init__(self, lu)
 
     # Parameters
+    self.instance_uuid = instance_uuid
     self.instance_name = instance_name
     self.mode = mode
     self.iallocator_name = iallocator_name
-    self.remote_node = remote_node
+    self.remote_node_uuid = remote_node_uuid
     self.disks = disks
     self.early_release = early_release
     self.ignore_ipolicy = ignore_ipolicy
 
     # Runtime data
     self.instance = None
-    self.new_node = None
-    self.target_node = None
-    self.other_node = None
+    self.new_node_uuid = None
+    self.target_node_uuid = None
+    self.other_node_uuid = None
     self.remote_node_info = None
     self.node_secondary_ip = None
 
   @staticmethod
-  def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
+  def _RunAllocator(lu, iallocator_name, instance_uuid,
+                    relocate_from_node_uuids):
     """Compute a new secondary node using an IAllocator.
 
     """
-    req = iallocator.IAReqRelocate(name=instance_name,
-                                   relocate_from=list(relocate_from))
+    req = iallocator.IAReqRelocate(
+          inst_uuid=instance_uuid,
+          relocate_from_node_uuids=list(relocate_from_node_uuids))
     ial = iallocator.IAllocator(lu.cfg, lu.rpc, req)
 
     ial.Run(iallocator_name)
@@ -1822,18 +1884,23 @@ class TLReplaceDisks(Tasklet):
                                  errors.ECODE_NORES)
 
     remote_node_name = ial.result[0]
+    remote_node = lu.cfg.GetNodeInfoByName(remote_node_name)
+
+    if remote_node is None:
+      raise errors.OpPrereqError("Node %s not found in configuration" %
+                                 remote_node_name, errors.ECODE_NOENT)
 
     lu.LogInfo("Selected new secondary for instance '%s': %s",
-               instance_name, remote_node_name)
+               instance_uuid, remote_node_name)
 
-    return remote_node_name
+    return remote_node.uuid
 
-  def _FindFaultyDisks(self, node_name):
+  def _FindFaultyDisks(self, node_uuid):
     """Wrapper for L{FindFaultyInstanceDisks}.
 
     """
     return FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
-                                   node_name, True)
+                                   node_uuid, True)
 
   def _CheckDisksActivated(self, instance):
     """Checks if the instance disks are activated.
@@ -1842,14 +1909,15 @@ class TLReplaceDisks(Tasklet):
     @return: True if they are activated, False otherwise
 
     """
-    nodes = instance.all_nodes
+    node_uuids = instance.all_nodes
 
     for idx, dev in enumerate(instance.disks):
-      for node in nodes:
-        self.lu.LogInfo("Checking disk/%d on %s", idx, node)
-        self.cfg.SetDiskID(dev, node)
+      for node_uuid in node_uuids:
+        self.lu.LogInfo("Checking disk/%d on %s", idx,
+                        self.cfg.GetNodeName(node_uuid))
+        self.cfg.SetDiskID(dev, node_uuid)
 
-        result = _BlockdevFind(self, node, dev, instance)
+        result = _BlockdevFind(self, node_uuid, dev, instance)
 
         if result.offline:
           continue
@@ -1864,44 +1932,44 @@ class TLReplaceDisks(Tasklet):
     This checks that the instance is in the cluster.
 
     """
-    self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
-    assert instance is not None, \
+    self.instance = self.cfg.GetInstanceInfo(self.instance_uuid)
+    assert self.instance is not None, \
       "Cannot retrieve locked instance %s" % self.instance_name
 
-    if instance.disk_template != constants.DT_DRBD8:
+    if self.instance.disk_template != constants.DT_DRBD8:
       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
                                  " instances", errors.ECODE_INVAL)
 
-    if len(instance.secondary_nodes) != 1:
+    if len(self.instance.secondary_nodes) != 1:
       raise errors.OpPrereqError("The instance has a strange layout,"
                                  " expected one secondary but found %d" %
-                                 len(instance.secondary_nodes),
+                                 len(self.instance.secondary_nodes),
                                  errors.ECODE_FAULT)
 
-    instance = self.instance
-    secondary_node = instance.secondary_nodes[0]
+    secondary_node_uuid = self.instance.secondary_nodes[0]
 
     if self.iallocator_name is None:
-      remote_node = self.remote_node
+      remote_node_uuid = self.remote_node_uuid
     else:
-      remote_node = self._RunAllocator(self.lu, self.iallocator_name,
-                                       instance.name, instance.secondary_nodes)
+      remote_node_uuid = self._RunAllocator(self.lu, self.iallocator_name,
+                                            self.instance.uuid,
+                                            self.instance.secondary_nodes)
 
-    if remote_node is None:
+    if remote_node_uuid is None:
       self.remote_node_info = None
     else:
-      assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
-             "Remote node '%s' is not locked" % remote_node
+      assert remote_node_uuid in self.lu.owned_locks(locking.LEVEL_NODE), \
+             "Remote node '%s' is not locked" % remote_node_uuid
 
-      self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
+      self.remote_node_info = self.cfg.GetNodeInfo(remote_node_uuid)
       assert self.remote_node_info is not None, \
-        "Cannot retrieve locked node %s" % remote_node
+        "Cannot retrieve locked node %s" % remote_node_uuid
 
-    if remote_node == self.instance.primary_node:
+    if remote_node_uuid == self.instance.primary_node:
       raise errors.OpPrereqError("The specified node is the primary node of"
                                  " the instance", errors.ECODE_INVAL)
 
-    if remote_node == secondary_node:
+    if remote_node_uuid == secondary_node_uuid:
       raise errors.OpPrereqError("The specified node is already the"
                                  " secondary node of the instance",
                                  errors.ECODE_INVAL)
@@ -1912,12 +1980,12 @@ class TLReplaceDisks(Tasklet):
                                  errors.ECODE_INVAL)
 
     if self.mode == constants.REPLACE_DISK_AUTO:
-      if not self._CheckDisksActivated(instance):
+      if not self._CheckDisksActivated(self.instance):
         raise errors.OpPrereqError("Please run activate-disks on instance %s"
                                    " first" % self.instance_name,
                                    errors.ECODE_STATE)
-      faulty_primary = self._FindFaultyDisks(instance.primary_node)
-      faulty_secondary = self._FindFaultyDisks(secondary_node)
+      faulty_primary = self._FindFaultyDisks(self.instance.primary_node)
+      faulty_secondary = self._FindFaultyDisks(secondary_node_uuid)
 
       if faulty_primary and faulty_secondary:
         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
@@ -1927,14 +1995,14 @@ class TLReplaceDisks(Tasklet):
 
       if faulty_primary:
         self.disks = faulty_primary
-        self.target_node = instance.primary_node
-        self.other_node = secondary_node
-        check_nodes = [self.target_node, self.other_node]
+        self.target_node_uuid = self.instance.primary_node
+        self.other_node_uuid = secondary_node_uuid
+        check_nodes = [self.target_node_uuid, self.other_node_uuid]
       elif faulty_secondary:
         self.disks = faulty_secondary
-        self.target_node = secondary_node
-        self.other_node = instance.primary_node
-        check_nodes = [self.target_node, self.other_node]
+        self.target_node_uuid = secondary_node_uuid
+        self.other_node_uuid = self.instance.primary_node
+        check_nodes = [self.target_node_uuid, self.other_node_uuid]
       else:
         self.disks = []
         check_nodes = []
@@ -1942,31 +2010,31 @@ class TLReplaceDisks(Tasklet):
     else:
       # Non-automatic modes
       if self.mode == constants.REPLACE_DISK_PRI:
-        self.target_node = instance.primary_node
-        self.other_node = secondary_node
-        check_nodes = [self.target_node, self.other_node]
+        self.target_node_uuid = self.instance.primary_node
+        self.other_node_uuid = secondary_node_uuid
+        check_nodes = [self.target_node_uuid, self.other_node_uuid]
 
       elif self.mode == constants.REPLACE_DISK_SEC:
-        self.target_node = secondary_node
-        self.other_node = instance.primary_node
-        check_nodes = [self.target_node, self.other_node]
+        self.target_node_uuid = secondary_node_uuid
+        self.other_node_uuid = self.instance.primary_node
+        check_nodes = [self.target_node_uuid, self.other_node_uuid]
 
       elif self.mode == constants.REPLACE_DISK_CHG:
-        self.new_node = remote_node
-        self.other_node = instance.primary_node
-        self.target_node = secondary_node
-        check_nodes = [self.new_node, self.other_node]
+        self.new_node_uuid = remote_node_uuid
+        self.other_node_uuid = self.instance.primary_node
+        self.target_node_uuid = secondary_node_uuid
+        check_nodes = [self.new_node_uuid, self.other_node_uuid]
 
-        CheckNodeNotDrained(self.lu, remote_node)
-        CheckNodeVmCapable(self.lu, remote_node)
+        CheckNodeNotDrained(self.lu, remote_node_uuid)
+        CheckNodeVmCapable(self.lu, remote_node_uuid)
 
-        old_node_info = self.cfg.GetNodeInfo(secondary_node)
+        old_node_info = self.cfg.GetNodeInfo(secondary_node_uuid)
         assert old_node_info is not None
         if old_node_info.offline and not self.early_release:
           # doesn't make sense to delay the release
           self.early_release = True
           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
-                          " early-release mode", secondary_node)
+                          " early-release mode", secondary_node_uuid)
 
       else:
         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
@@ -1984,16 +2052,17 @@ class TLReplaceDisks(Tasklet):
       cluster = self.cfg.GetClusterInfo()
       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
                                                               new_group_info)
-      CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
-                             self.cfg, ignore=self.ignore_ipolicy)
+      CheckTargetNodeIPolicy(self, ipolicy, self.instance,
+                             self.remote_node_info, self.cfg,
+                             ignore=self.ignore_ipolicy)
 
-    for node in check_nodes:
-      CheckNodeOnline(self.lu, node)
+    for node_uuid in check_nodes:
+      CheckNodeOnline(self.lu, node_uuid)
 
-    touched_nodes = frozenset(node_name for node_name in [self.new_node,
-                                                          self.other_node,
-                                                          self.target_node]
-                              if node_name is not None)
+    touched_nodes = frozenset(node_uuid for node_uuid in [self.new_node_uuid,
+                                                          self.other_node_uuid,
+                                                          self.target_node_uuid]
+                              if node_uuid is not None)
 
     # Release unneeded node and node resource locks
     ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
@@ -2005,10 +2074,10 @@ class TLReplaceDisks(Tasklet):
 
     # Check whether disks are valid
     for disk_idx in self.disks:
-      instance.FindDisk(disk_idx)
+      self.instance.FindDisk(disk_idx)
 
     # Get secondary node IP addresses
-    self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
+    self.node_secondary_ip = dict((uuid, node.secondary_ip) for (uuid, node)
                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
 
   def Exec(self, feedback_fn):
@@ -2041,9 +2110,11 @@ class TLReplaceDisks(Tasklet):
 
     feedback_fn("Replacing disk(s) %s for instance '%s'" %
                 (utils.CommaJoin(self.disks), self.instance.name))
-    feedback_fn("Current primary node: %s" % self.instance.primary_node)
+    feedback_fn("Current primary node: %s" %
+                self.cfg.GetNodeName(self.instance.primary_node))
     feedback_fn("Current seconary node: %s" %
-                utils.CommaJoin(self.instance.secondary_nodes))
+                utils.CommaJoin(self.cfg.GetNodeNames(
+                                  self.instance.secondary_nodes)))
 
     activate_disks = not self.instance.disks_active
 
@@ -2053,7 +2124,7 @@ class TLReplaceDisks(Tasklet):
 
     try:
       # Should we replace the secondary node?
-      if self.new_node is not None:
+      if self.new_node_uuid is not None:
         fn = self._ExecDrbd8Secondary
       else:
         fn = self._ExecDrbd8DiskOnly
@@ -2078,57 +2149,59 @@ class TLReplaceDisks(Tasklet):
 
     return result
 
-  def _CheckVolumeGroup(self, nodes):
+  def _CheckVolumeGroup(self, node_uuids):
     self.lu.LogInfo("Checking volume groups")
 
     vgname = self.cfg.GetVGName()
 
     # Make sure volume group exists on all involved nodes
-    results = self.rpc.call_vg_list(nodes)
+    results = self.rpc.call_vg_list(node_uuids)
     if not results:
       raise errors.OpExecError("Can't list volume groups on the nodes")
 
-    for node in nodes:
-      res = results[node]
-      res.Raise("Error checking node %s" % node)
+    for node_uuid in node_uuids:
+      res = results[node_uuid]
+      res.Raise("Error checking node %s" % self.cfg.GetNodeName(node_uuid))
       if vgname not in res.payload:
         raise errors.OpExecError("Volume group '%s' not found on node %s" %
-                                 (vgname, node))
+                                 (vgname, self.cfg.GetNodeName(node_uuid)))
 
-  def _CheckDisksExistence(self, nodes):
+  def _CheckDisksExistence(self, node_uuids):
     # Check disk existence
     for idx, dev in enumerate(self.instance.disks):
       if idx not in self.disks:
         continue
 
-      for node in nodes:
-        self.lu.LogInfo("Checking disk/%d on %s", idx, node)
-        self.cfg.SetDiskID(dev, node)
+      for node_uuid in node_uuids:
+        self.lu.LogInfo("Checking disk/%d on %s", idx,
+                        self.cfg.GetNodeName(node_uuid))
+        self.cfg.SetDiskID(dev, node_uuid)
 
-        result = _BlockdevFind(self, node, dev, self.instance)
+        result = _BlockdevFind(self, node_uuid, dev, self.instance)
 
         msg = result.fail_msg
         if msg or not result.payload:
           if not msg:
             msg = "disk not found"
           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
-                                   (idx, node, msg))
+                                   (idx, self.cfg.GetNodeName(node_uuid), msg))
 
-  def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
+  def _CheckDisksConsistency(self, node_uuid, on_primary, ldisk):
     for idx, dev in enumerate(self.instance.disks):
       if idx not in self.disks:
         continue
 
       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
-                      (idx, node_name))
+                      (idx, self.cfg.GetNodeName(node_uuid)))
 
-      if not CheckDiskConsistency(self.lu, self.instance, dev, node_name,
+      if not CheckDiskConsistency(self.lu, self.instance, dev, node_uuid,
                                   on_primary, ldisk=ldisk):
         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
                                  " replace disks for instance %s" %
-                                 (node_name, self.instance.name))
+                                 (self.cfg.GetNodeName(node_uuid),
+                                  self.instance.name))
 
-  def _CreateNewStorage(self, node_name):
+  def _CreateNewStorage(self, node_uuid):
     """Create new storage on the primary or secondary node.
 
     This is only used for same-node replaces, not for changing the
@@ -2142,9 +2215,10 @@ class TLReplaceDisks(Tasklet):
       if idx not in self.disks:
         continue
 
-      self.lu.LogInfo("Adding storage on %s for disk/%d", node_name, idx)
+      self.lu.LogInfo("Adding storage on %s for disk/%d",
+                      self.cfg.GetNodeName(node_uuid), idx)
 
-      self.cfg.SetDiskID(dev, node_name)
+      self.cfg.SetDiskID(dev, node_uuid)
 
       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
       names = _GenerateUniqueNames(self.lu, lv_names)
@@ -2163,12 +2237,12 @@ class TLReplaceDisks(Tasklet):
       new_lvs = [lv_data, lv_meta]
       old_lvs = [child.Copy() for child in dev.children]
       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
-      excl_stor = IsExclusiveStorageEnabledNodeName(self.lu.cfg, node_name)
+      excl_stor = IsExclusiveStorageEnabledNodeUuid(self.lu.cfg, node_uuid)
 
       # we pass force_create=True to force the LVM creation
       for new_lv in new_lvs:
         try:
-          _CreateBlockDevInner(self.lu, node_name, self.instance, new_lv, True,
+          _CreateBlockDevInner(self.lu, node_uuid, self.instance, new_lv, True,
                                GetInstanceInfoText(self.instance), False,
                                excl_stor)
         except errors.DeviceCreationError, e:
@@ -2176,11 +2250,11 @@ class TLReplaceDisks(Tasklet):
 
     return iv_names
 
-  def _CheckDevices(self, node_name, iv_names):
+  def _CheckDevices(self, node_uuid, iv_names):
     for name, (dev, _, _) in iv_names.iteritems():
-      self.cfg.SetDiskID(dev, node_name)
+      self.cfg.SetDiskID(dev, node_uuid)
 
-      result = _BlockdevFind(self, node_name, dev, self.instance)
+      result = _BlockdevFind(self, node_uuid, dev, self.instance)
 
       msg = result.fail_msg
       if msg or not result.payload:
@@ -2192,14 +2266,14 @@ class TLReplaceDisks(Tasklet):
       if result.payload.is_degraded:
         raise errors.OpExecError("DRBD device %s is degraded!" % name)
 
-  def _RemoveOldStorage(self, node_name, iv_names):
+  def _RemoveOldStorage(self, node_uuid, iv_names):
     for name, (_, old_lvs, _) in iv_names.iteritems():
       self.lu.LogInfo("Remove logical volumes for %s", name)
 
       for lv in old_lvs:
-        self.cfg.SetDiskID(lv, node_name)
+        self.cfg.SetDiskID(lv, node_uuid)
 
-        msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
+        msg = self.rpc.call_blockdev_remove(node_uuid, lv).fail_msg
         if msg:
           self.lu.LogWarning("Can't remove old LV: %s", msg,
                              hint="remove unused LVs manually")
@@ -2230,28 +2304,29 @@ class TLReplaceDisks(Tasklet):
 
     # Step: check device activation
     self.lu.LogStep(1, steps_total, "Check device existence")
-    self._CheckDisksExistence([self.other_node, self.target_node])
-    self._CheckVolumeGroup([self.target_node, self.other_node])
+    self._CheckDisksExistence([self.other_node_uuid, self.target_node_uuid])
+    self._CheckVolumeGroup([self.target_node_uuid, self.other_node_uuid])
 
     # Step: check other node consistency
     self.lu.LogStep(2, steps_total, "Check peer consistency")
-    self._CheckDisksConsistency(self.other_node,
-                                self.other_node == self.instance.primary_node,
-                                False)
+    self._CheckDisksConsistency(
+      self.other_node_uuid, self.other_node_uuid == self.instance.primary_node,
+      False)
 
     # Step: create new storage
     self.lu.LogStep(3, steps_total, "Allocate new storage")
-    iv_names = self._CreateNewStorage(self.target_node)
+    iv_names = self._CreateNewStorage(self.target_node_uuid)
 
     # Step: for each lv, detach+rename*2+attach
     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
     for dev, old_lvs, new_lvs in iv_names.itervalues():
       self.lu.LogInfo("Detaching %s drbd from local storage", dev.iv_name)
 
-      result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
+      result = self.rpc.call_blockdev_removechildren(self.target_node_uuid, dev,
                                                      old_lvs)
       result.Raise("Can't detach drbd from local storage on node"
-                   " %s for device %s" % (self.target_node, dev.iv_name))
+                   " %s for device %s" %
+                   (self.cfg.GetNodeName(self.target_node_uuid), dev.iv_name))
       #dev.children = []
       #cfg.Update(instance)
 
@@ -2269,44 +2344,47 @@ class TLReplaceDisks(Tasklet):
       # Build the rename list based on what LVs exist on the node
       rename_old_to_new = []
       for to_ren in old_lvs:
-        result = self.rpc.call_blockdev_find(self.target_node, to_ren)
+        result = self.rpc.call_blockdev_find(self.target_node_uuid, to_ren)
         if not result.fail_msg and result.payload:
           # device exists
           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
 
       self.lu.LogInfo("Renaming the old LVs on the target node")
-      result = self.rpc.call_blockdev_rename(self.target_node,
+      result = self.rpc.call_blockdev_rename(self.target_node_uuid,
                                              rename_old_to_new)
-      result.Raise("Can't rename old LVs on node %s" % self.target_node)
+      result.Raise("Can't rename old LVs on node %s" %
+                   self.cfg.GetNodeName(self.target_node_uuid))
 
       # Now we rename the new LVs to the old LVs
       self.lu.LogInfo("Renaming the new LVs on the target node")
       rename_new_to_old = [(new, old.physical_id)
                            for old, new in zip(old_lvs, new_lvs)]
-      result = self.rpc.call_blockdev_rename(self.target_node,
+      result = self.rpc.call_blockdev_rename(self.target_node_uuid,
                                              rename_new_to_old)
-      result.Raise("Can't rename new LVs on node %s" % self.target_node)
+      result.Raise("Can't rename new LVs on node %s" %
+                   self.cfg.GetNodeName(self.target_node_uuid))
 
       # Intermediate steps of in memory modifications
       for old, new in zip(old_lvs, new_lvs):
         new.logical_id = old.logical_id
-        self.cfg.SetDiskID(new, self.target_node)
+        self.cfg.SetDiskID(new, self.target_node_uuid)
 
       # We need to modify old_lvs so that removal later removes the
       # right LVs, not the newly added ones; note that old_lvs is a
       # copy here
       for disk in old_lvs:
         disk.logical_id = ren_fn(disk, temp_suffix)
-        self.cfg.SetDiskID(disk, self.target_node)
+        self.cfg.SetDiskID(disk, self.target_node_uuid)
 
       # Now that the new lvs have the old name, we can add them to the device
-      self.lu.LogInfo("Adding new mirror component on %s", self.target_node)
-      result = self.rpc.call_blockdev_addchildren(self.target_node,
+      self.lu.LogInfo("Adding new mirror component on %s",
+                      self.cfg.GetNodeName(self.target_node_uuid))
+      result = self.rpc.call_blockdev_addchildren(self.target_node_uuid,
                                                   (dev, self.instance), new_lvs)
       msg = result.fail_msg
       if msg:
         for new_lv in new_lvs:
-          msg2 = self.rpc.call_blockdev_remove(self.target_node,
+          msg2 = self.rpc.call_blockdev_remove(self.target_node_uuid,
                                                new_lv).fail_msg
           if msg2:
             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
@@ -2318,7 +2396,7 @@ class TLReplaceDisks(Tasklet):
 
     if self.early_release:
       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
-      self._RemoveOldStorage(self.target_node, iv_names)
+      self._RemoveOldStorage(self.target_node_uuid, iv_names)
       # TODO: Check if releasing locks early still makes sense
       ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
     else:
@@ -2344,7 +2422,7 @@ class TLReplaceDisks(Tasklet):
     # Step: remove old storage
     if not self.early_release:
       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
-      self._RemoveOldStorage(self.target_node, iv_names)
+      self._RemoveOldStorage(self.target_node_uuid, iv_names)
 
   def _ExecDrbd8Secondary(self, feedback_fn):
     """Replace the secondary node for DRBD 8.
@@ -2381,16 +2459,17 @@ class TLReplaceDisks(Tasklet):
     # Step: create new storage
     self.lu.LogStep(3, steps_total, "Allocate new storage")
     disks = AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
-    excl_stor = IsExclusiveStorageEnabledNodeName(self.lu.cfg, self.new_node)
+    excl_stor = IsExclusiveStorageEnabledNodeUuid(self.lu.cfg,
+                                                  self.new_node_uuid)
     for idx, dev in enumerate(disks):
       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
-                      (self.new_node, idx))
+                      (self.cfg.GetNodeName(self.new_node_uuid), idx))
       # we pass force_create=True to force LVM creation
       for new_lv in dev.children:
         try:
-          _CreateBlockDevInner(self.lu, self.new_node, self.instance, new_lv,
-                               True, GetInstanceInfoText(self.instance), False,
-                               excl_stor)
+          _CreateBlockDevInner(self.lu, self.new_node_uuid, self.instance,
+                               new_lv, True, GetInstanceInfoText(self.instance),
+                               False, excl_stor)
         except errors.DeviceCreationError, e:
           raise errors.OpExecError("Can't create block device: %s" % e.message)
 
@@ -2398,15 +2477,15 @@ class TLReplaceDisks(Tasklet):
     # after this, we must manually remove the drbd minors on both the
     # error and the success paths
     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
-    minors = self.cfg.AllocateDRBDMinor([self.new_node
-                                         for dev in self.instance.disks],
-                                        self.instance.name)
+    minors = self.cfg.AllocateDRBDMinor([self.new_node_uuid
+                                         for _ in self.instance.disks],
+                                        self.instance.uuid)
     logging.debug("Allocated minors %r", minors)
 
     iv_names = {}
     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
-                      (self.new_node, idx))
+                      (self.cfg.GetNodeName(self.new_node_uuid), idx))
       # create new devices on new_node; note that we create two IDs:
       # one without port, so the drbd will be activated without
       # networking information on the new node at this stage, and one
@@ -2418,9 +2497,9 @@ class TLReplaceDisks(Tasklet):
         assert self.instance.primary_node == o_node2, "Three-node instance?"
         p_minor = o_minor2
 
-      new_alone_id = (self.instance.primary_node, self.new_node, None,
+      new_alone_id = (self.instance.primary_node, self.new_node_uuid, None,
                       p_minor, new_minor, o_secret)
-      new_net_id = (self.instance.primary_node, self.new_node, o_port,
+      new_net_id = (self.instance.primary_node, self.new_node_uuid, o_port,
                     p_minor, new_minor, o_secret)
 
       iv_names[idx] = (dev, dev.children, new_net_id)
@@ -2434,19 +2513,19 @@ class TLReplaceDisks(Tasklet):
       (anno_new_drbd,) = AnnotateDiskParams(self.instance, [new_drbd],
                                             self.cfg)
       try:
-        CreateSingleBlockDev(self.lu, self.new_node, self.instance,
+        CreateSingleBlockDev(self.lu, self.new_node_uuid, self.instance,
                              anno_new_drbd,
                              GetInstanceInfoText(self.instance), False,
                              excl_stor)
       except errors.GenericError:
-        self.cfg.ReleaseDRBDMinors(self.instance.name)
+        self.cfg.ReleaseDRBDMinors(self.instance.uuid)
         raise
 
     # We have new devices, shutdown the drbd on the old secondary
     for idx, dev in enumerate(self.instance.disks):
       self.lu.LogInfo("Shutting down drbd for disk/%d on old node", idx)
-      self.cfg.SetDiskID(dev, self.target_node)
-      msg = self.rpc.call_blockdev_shutdown(self.target_node,
+      self.cfg.SetDiskID(dev, self.target_node_uuid)
+      msg = self.rpc.call_blockdev_shutdown(self.target_node_uuid,
                                             (dev, self.instance)).fail_msg
       if msg:
         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
@@ -2461,7 +2540,7 @@ class TLReplaceDisks(Tasklet):
     msg = result.fail_msg
     if msg:
       # detaches didn't succeed (unlikely)
-      self.cfg.ReleaseDRBDMinors(self.instance.name)
+      self.cfg.ReleaseDRBDMinors(self.instance.uuid)
       raise errors.OpExecError("Can't detach the disks from the network on"
                                " old node: %s" % (msg,))
 
@@ -2481,7 +2560,7 @@ class TLReplaceDisks(Tasklet):
     self.lu.LogInfo("Attaching primary drbds to new secondary"
                     " (standalone => connected)")
     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
-                                            self.new_node],
+                                            self.new_node_uuid],
                                            self.node_secondary_ip,
                                            (self.instance.disks, self.instance),
                                            self.instance.name,
@@ -2490,7 +2569,7 @@ class TLReplaceDisks(Tasklet):
       msg = to_result.fail_msg
       if msg:
         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
-                           to_node, msg,
+                           self.cfg.GetNodeName(to_node), msg,
                            hint=("please do a gnt-instance info to see the"
                                  " status of disks"))
 
@@ -2498,7 +2577,7 @@ class TLReplaceDisks(Tasklet):
 
     if self.early_release:
       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
-      self._RemoveOldStorage(self.target_node, iv_names)
+      self._RemoveOldStorage(self.target_node_uuid, iv_names)
       # TODO: Check if releasing locks early still makes sense
       ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
     else:
@@ -2521,4 +2600,4 @@ class TLReplaceDisks(Tasklet):
     # Step: remove old storage
     if not self.early_release:
       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
-      self._RemoveOldStorage(self.target_node, iv_names)
+      self._RemoveOldStorage(self.target_node_uuid, iv_names)
index cd126a4..1df01de 100644 (file)
@@ -35,19 +35,19 @@ from ganeti.cmdlib.common import AnnotateDiskParams, \
   ComputeIPolicyInstanceViolation
 
 
-def BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
-                         minmem, maxmem, vcpus, nics, disk_template, disks,
-                         bep, hvp, hypervisor_name, tags):
+def BuildInstanceHookEnv(name, primary_node_name, secondary_node_names, os_type,
+                         status, minmem, maxmem, vcpus, nics, disk_template,
+                         disks, bep, hvp, hypervisor_name, tags):
   """Builds instance related env variables for hooks
 
   This builds the hook environment from individual variables.
 
   @type name: string
   @param name: the name of the instance
-  @type primary_node: string
-  @param primary_node: the name of the instance's primary node
-  @type secondary_nodes: list
-  @param secondary_nodes: list of secondary nodes as strings
+  @type primary_node_name: string
+  @param primary_node_name: the name of the instance's primary node
+  @type secondary_node_names: list
+  @param secondary_node_names: list of secondary nodes as strings
   @type os_type: string
   @param os_type: the name of the instance's OS
   @type status: string
@@ -80,8 +80,8 @@ def BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
   env = {
     "OP_TARGET": name,
     "INSTANCE_NAME": name,
-    "INSTANCE_PRIMARY": primary_node,
-    "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
+    "INSTANCE_PRIMARY": primary_node_name,
+    "INSTANCE_SECONDARIES": " ".join(secondary_node_names),
     "INSTANCE_OS_TYPE": os_type,
     "INSTANCE_STATUS": status,
     "INSTANCE_MINMEM": minmem,
@@ -164,8 +164,8 @@ def BuildInstanceHookEnvByObject(lu, instance, override=None):
   hvp = cluster.FillHV(instance)
   args = {
     "name": instance.name,
-    "primary_node": instance.primary_node,
-    "secondary_nodes": instance.secondary_nodes,
+    "primary_node_name": lu.cfg.GetNodeName(instance.primary_node),
+    "secondary_node_names": lu.cfg.GetNodeNames(instance.secondary_nodes),
     "os_type": instance.os,
     "status": instance.admin_state,
     "maxmem": bep[constants.BE_MAXMEM],
@@ -193,29 +193,29 @@ def GetClusterDomainSecret():
                                strict=True)
 
 
-def CheckNodeNotDrained(lu, node):
+def CheckNodeNotDrained(lu, node_uuid):
   """Ensure that a given node is not drained.
 
   @param lu: the LU on behalf of which we make the check
-  @param node: the node to check
+  @param node_uuid: the node to check
   @raise errors.OpPrereqError: if the node is drained
 
   """
-  if lu.cfg.GetNodeInfo(node).drained:
-    raise errors.OpPrereqError("Can't use drained node %s" % node,
+  if lu.cfg.GetNodeInfo(node_uuid).drained:
+    raise errors.OpPrereqError("Can't use drained node %s" % node_uuid,
                                errors.ECODE_STATE)
 
 
-def CheckNodeVmCapable(lu, node):
+def CheckNodeVmCapable(lu, node_uuid):
   """Ensure that a given node is vm capable.
 
   @param lu: the LU on behalf of which we make the check
-  @param node: the node to check
+  @param node_uuid: the node to check
   @raise errors.OpPrereqError: if the node is not vm capable
 
   """
-  if not lu.cfg.GetNodeInfo(node).vm_capable:
-    raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
+  if not lu.cfg.GetNodeInfo(node_uuid).vm_capable:
+    raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node_uuid,
                                errors.ECODE_STATE)
 
 
@@ -232,7 +232,7 @@ def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
 
   logging.info("Removing instance %s out of cluster config", instance.name)
 
-  lu.cfg.RemoveInstance(instance.name)
+  lu.cfg.RemoveInstance(instance.uuid)
 
   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
     "Instance lock removal conflict"
@@ -241,7 +241,7 @@ def RemoveInstance(lu, feedback_fn, instance, ignore_failures):
   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
 
 
-def RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
+def RemoveDisks(lu, instance, target_node_uuid=None, ignore_failures=False):
   """Remove all disks for an instance.
 
   This abstracts away some work from `AddInstance()` and
@@ -252,8 +252,9 @@ def RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
   @param lu: the logical unit on whose behalf we execute
   @type instance: L{objects.Instance}
   @param instance: the instance whose disks we should remove
-  @type target_node: string
-  @param target_node: used to override the node on which to remove the disks
+  @type target_node_uuid: string
+  @param target_node_uuid: used to override the node on which to remove the
+          disks
   @rtype: boolean
   @return: the success of the removal
 
@@ -264,17 +265,18 @@ def RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
   ports_to_release = set()
   anno_disks = AnnotateDiskParams(instance, instance.disks, lu.cfg)
   for (idx, device) in enumerate(anno_disks):
-    if target_node:
-      edata = [(target_node, device)]
+    if target_node_uuid:
+      edata = [(target_node_uuid, device)]
     else:
       edata = device.ComputeNodeTree(instance.primary_node)
-    for node, disk in edata:
-      lu.cfg.SetDiskID(disk, node)
-      result = lu.rpc.call_blockdev_remove(node, disk)
+    for node_uuid, disk in edata:
+      lu.cfg.SetDiskID(disk, node_uuid)
+      result = lu.rpc.call_blockdev_remove(node_uuid, disk)
       if result.fail_msg:
         lu.LogWarning("Could not remove disk %s on node %s,"
-                      " continuing anyway: %s", idx, node, result.fail_msg)
-        if not (result.offline and node != instance.primary_node):
+                      " continuing anyway: %s", idx,
+                      lu.cfg.GetNodeName(node_uuid), result.fail_msg)
+        if not (result.offline and node_uuid != instance.primary_node):
           all_result = False
 
     # if this is a DRBD disk, return its port to the pool
@@ -287,14 +289,14 @@ def RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
 
   if instance.disk_template in constants.DTS_FILEBASED:
     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
-    if target_node:
-      tgt = target_node
+    if target_node_uuid:
+      tgt = target_node_uuid
     else:
       tgt = instance.primary_node
     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
     if result.fail_msg:
       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
-                    file_storage_dir, instance.primary_node, result.fail_msg)
+                    file_storage_dir, lu.cfg.GetNodeName(tgt), result.fail_msg)
       all_result = False
 
   return all_result
@@ -454,7 +456,7 @@ def GetInstanceInfoText(instance):
   return "originstname+%s" % instance.name
 
 
-def CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
+def CheckNodeFreeMemory(lu, node_uuid, reason, requested, hvname, hvparams):
   """Checks if a node has enough free memory.
 
   This function checks if a given node has the needed amount of free
@@ -464,74 +466,79 @@ def CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
 
   @type lu: C{LogicalUnit}
   @param lu: a logical unit from which we get configuration data
-  @type node: C{str}
-  @param node: the node to check
+  @type node_uuid: C{str}
+  @param node_uuid: the node to check
   @type reason: C{str}
   @param reason: string to use in the error message
   @type requested: C{int}
   @param requested: the amount of memory in MiB to check for
-  @type hypervisor_name: C{str}
-  @param hypervisor_name: the hypervisor to ask for memory stats
+  @type hvname: string
+  @param hvname: the hypervisor's name
+  @type hvparams: dict of strings
+  @param hvparams: the hypervisor's parameters
   @rtype: integer
   @return: node current free memory
   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
       we cannot check the node
 
   """
-  nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name], False)
-  nodeinfo[node].Raise("Can't get data from node %s" % node,
-                       prereq=True, ecode=errors.ECODE_ENVIRON)
-  (_, _, (hv_info, )) = nodeinfo[node].payload
+  node_name = lu.cfg.GetNodeName(node_uuid)
+  nodeinfo = lu.rpc.call_node_info([node_uuid], None, [(hvname, hvparams)],
+                                   False)
+  nodeinfo[node_uuid].Raise("Can't get data from node %s" % node_name,
+                            prereq=True, ecode=errors.ECODE_ENVIRON)
+  (_, _, (hv_info, )) = nodeinfo[node_uuid].payload
 
   free_mem = hv_info.get("memory_free", None)
   if not isinstance(free_mem, int):
     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
-                               " was '%s'" % (node, free_mem),
+                               " was '%s'" % (node_name, free_mem),
                                errors.ECODE_ENVIRON)
   if requested > free_mem:
     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
                                " needed %s MiB, available %s MiB" %
-                               (node, reason, requested, free_mem),
+                               (node_name, reason, requested, free_mem),
                                errors.ECODE_NORES)
   return free_mem
 
 
-def CheckInstanceBridgesExist(lu, instance, node=None):
+def CheckInstanceBridgesExist(lu, instance, node_uuid=None):
   """Check that the brigdes needed by an instance exist.
 
   """
-  if node is None:
-    node = instance.primary_node
-  CheckNicsBridgesExist(lu, instance.nics, node)
+  if node_uuid is None:
+    node_uuid = instance.primary_node
+  CheckNicsBridgesExist(lu, instance.nics, node_uuid)
 
 
-def CheckNicsBridgesExist(lu, target_nics, target_node):
+def CheckNicsBridgesExist(lu, nics, node_uuid):
   """Check that the brigdes needed by a list of nics exist.
 
   """
   cluster = lu.cfg.GetClusterInfo()
-  paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
+  paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in nics]
   brlist = [params[constants.NIC_LINK] for params in paramslist
             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
   if brlist:
-    result = lu.rpc.call_bridges_exist(target_node, brlist)
+    result = lu.rpc.call_bridges_exist(node_uuid, brlist)
     result.Raise("Error checking bridges on destination node '%s'" %
-                 target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
+                 lu.cfg.GetNodeName(node_uuid), prereq=True,
+                 ecode=errors.ECODE_ENVIRON)
 
 
-def CheckNodeHasOS(lu, node, os_name, force_variant):
+def CheckNodeHasOS(lu, node_uuid, os_name, force_variant):
   """Ensure that a node supports a given OS.
 
   @param lu: the LU on behalf of which we make the check
-  @param node: the node to check
+  @param node_uuid: the node to check
   @param os_name: the OS to query about
   @param force_variant: whether to ignore variant errors
   @raise errors.OpPrereqError: if the node is not supporting the OS
 
   """
-  result = lu.rpc.call_os_get(node, os_name)
+  result = lu.rpc.call_os_get(node_uuid, os_name)
   result.Raise("OS '%s' not in supported OS list for node %s" %
-               (os_name, node),
+               (os_name, lu.cfg.GetNodeName(node_uuid)),
                prereq=True, ecode=errors.ECODE_INVAL)
   if not force_variant:
     _CheckOSVariant(result.payload, os_name)
index 893b3f2..9770f1f 100644 (file)
@@ -47,13 +47,14 @@ class LUOobCommand(NoHooksLU):
 
     """
     if self.op.node_names:
-      self.op.node_names = GetWantedNodes(self, self.op.node_names)
-      lock_names = self.op.node_names
+      (self.op.node_uuids, self.op.node_names) = \
+        GetWantedNodes(self, self.op.node_names)
+      lock_node_uuids = self.op.node_uuids
     else:
-      lock_names = locking.ALL_SET
+      lock_node_uuids = locking.ALL_SET
 
     self.needed_locks = {
-      locking.LEVEL_NODE: lock_names,
+      locking.LEVEL_NODE: lock_node_uuids,
       }
 
     self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
@@ -73,54 +74,54 @@ class LUOobCommand(NoHooksLU):
 
     """
     self.nodes = []
-    self.master_node = self.cfg.GetMasterNode()
+    self.master_node_uuid = self.cfg.GetMasterNode()
+    master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid)
 
     assert self.op.power_delay >= 0.0
 
-    if self.op.node_names:
+    if self.op.node_uuids:
       if (self.op.command in self._SKIP_MASTER and
-          self.master_node in self.op.node_names):
-        master_node_obj = self.cfg.GetNodeInfo(self.master_node)
+          master_node_obj.uuid in self.op.node_uuids):
         master_oob_handler = SupportsOob(self.cfg, master_node_obj)
 
         if master_oob_handler:
           additional_text = ("run '%s %s %s' if you want to operate on the"
                              " master regardless") % (master_oob_handler,
                                                       self.op.command,
-                                                      self.master_node)
+                                                      master_node_obj.name)
         else:
           additional_text = "it does not support out-of-band operations"
 
         raise errors.OpPrereqError(("Operating on the master node %s is not"
                                     " allowed for %s; %s") %
-                                   (self.master_node, self.op.command,
+                                   (master_node_obj.name, self.op.command,
                                     additional_text), errors.ECODE_INVAL)
     else:
-      self.op.node_names = self.cfg.GetNodeList()
+      self.op.node_uuids = self.cfg.GetNodeList()
       if self.op.command in self._SKIP_MASTER:
-        self.op.node_names.remove(self.master_node)
+        self.op.node_uuids.remove(master_node_obj.uuid)
 
     if self.op.command in self._SKIP_MASTER:
-      assert self.master_node not in self.op.node_names
+      assert master_node_obj.uuid not in self.op.node_uuids
 
-    for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
+    for node_uuid in self.op.node_uuids:
+      node = self.cfg.GetNodeInfo(node_uuid)
       if node is None:
-        raise errors.OpPrereqError("Node %s not found" % node_name,
+        raise errors.OpPrereqError("Node %s not found" % node_uuid,
                                    errors.ECODE_NOENT)
-      else:
-        self.nodes.append(node)
+
+      self.nodes.append(node)
 
       if (not self.op.ignore_status and
           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
         raise errors.OpPrereqError(("Cannot power off node %s because it is"
-                                    " not marked offline") % node_name,
+                                    " not marked offline") % node.name,
                                    errors.ECODE_STATE)
 
   def Exec(self, feedback_fn):
     """Execute OOB and return result if we expect any.
 
     """
-    master_node = self.master_node
     ret = []
 
     for idx, node in enumerate(utils.NiceSort(self.nodes,
@@ -136,7 +137,7 @@ class LUOobCommand(NoHooksLU):
 
       logging.info("Executing out-of-band command '%s' using '%s' on %s",
                    self.op.command, oob_program, node.name)
-      result = self.rpc.call_run_oob(master_node, oob_program,
+      result = self.rpc.call_run_oob(self.master_node_uuid, oob_program,
                                      self.op.command, node.name,
                                      self.op.timeout)
 
@@ -234,7 +235,7 @@ class ExtStorageQuery(QueryBase):
 
     # The following variables interact with _QueryBase._GetNames
     if self.names:
-      self.wanted = self.names
+      self.wanted = [lu.cfg.GetNodeInfoByName(name).uuid for name in self.names]
     else:
       self.wanted = locking.ALL_SET
 
@@ -247,16 +248,16 @@ class ExtStorageQuery(QueryBase):
   def _DiagnoseByProvider(rlist):
     """Remaps a per-node return list into an a per-provider per-node dictionary
 
-    @param rlist: a map with node names as keys and ExtStorage objects as values
+    @param rlist: a map with node uuids as keys and ExtStorage objects as values
 
     @rtype: dict
     @return: a dictionary with extstorage providers as keys and as
-        value another map, with nodes as keys and tuples of
+        value another map, with node uuids as keys and tuples of
         (path, status, diagnose, parameters) as values, eg::
 
-          {"provider1": {"node1": [(/usr/lib/..., True, "", [])]
-                         "node2": [(/srv/..., False, "missing file")]
-                         "node3": [(/srv/..., True, "", [])]
+          {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])]
+                         "node_uuid2": [(/srv/..., False, "missing file")]
+                         "node_uuid3": [(/srv/..., True, "", [])]
           }
 
     """
@@ -264,9 +265,9 @@ class ExtStorageQuery(QueryBase):
     # we build here the list of nodes that didn't fail the RPC (at RPC
     # level), so that nodes with a non-responding node daemon don't
     # make all OSes invalid
-    good_nodes = [node_name for node_name in rlist
-                  if not rlist[node_name].fail_msg]
-    for node_name, nr in rlist.items():
+    good_nodes = [node_uuid for node_uuid in rlist
+                  if not rlist[node_uuid].fail_msg]
+    for node_uuid, nr in rlist.items():
       if nr.fail_msg or not nr.payload:
         continue
       for (name, path, status, diagnose, params) in nr.payload:
@@ -274,11 +275,11 @@ class ExtStorageQuery(QueryBase):
           # build a list of nodes for this os containing empty lists
           # for each node in node_list
           all_es[name] = {}
-          for nname in good_nodes:
-            all_es[name][nname] = []
+          for nuuid in good_nodes:
+            all_es[name][nuuid] = []
         # convert params from [name, help] to (name, help)
         params = [tuple(v) for v in params]
-        all_es[name][node_name].append((path, status, diagnose, params))
+        all_es[name][node_uuid].append((path, status, diagnose, params))
     return all_es
 
   def _GetQueryData(self, lu):
@@ -291,7 +292,7 @@ class ExtStorageQuery(QueryBase):
                            if level != locking.LEVEL_CLUSTER) or
                 self.do_locking or self.use_locking)
 
-    valid_nodes = [node.name
+    valid_nodes = [node.uuid
                    for node in lu.cfg.GetAllNodesInfo().values()
                    if not node.offline and node.vm_capable]
     pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes))
@@ -382,10 +383,10 @@ class LURestrictedCommand(NoHooksLU):
 
   def ExpandNames(self):
     if self.op.nodes:
-      self.op.nodes = GetWantedNodes(self, self.op.nodes)
+      (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
 
     self.needed_locks = {
-      locking.LEVEL_NODE: self.op.nodes,
+      locking.LEVEL_NODE: self.op.node_uuids,
       }
     self.share_locks = {
       locking.LEVEL_NODE: not self.op.use_locking,
@@ -403,17 +404,19 @@ class LURestrictedCommand(NoHooksLU):
     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
 
     # Check if correct locks are held
-    assert set(self.op.nodes).issubset(owned_nodes)
+    assert set(self.op.node_uuids).issubset(owned_nodes)
 
-    rpcres = self.rpc.call_restricted_command(self.op.nodes, self.op.command)
+    rpcres = self.rpc.call_restricted_command(self.op.node_uuids,
+                                              self.op.command)
 
     result = []
 
-    for node_name in self.op.nodes:
-      nres = rpcres[node_name]
+    for node_uuid in self.op.node_uuids:
+      nres = rpcres[node_uuid]
       if nres.fail_msg:
         msg = ("Command '%s' on node '%s' failed: %s" %
-               (self.op.command, node_name, nres.fail_msg))
+               (self.op.command, self.cfg.GetNodeName(node_uuid),
+                nres.fail_msg))
         result.append((False, msg))
       else:
         result.append((True, nres.payload))
index 2f2dcd7..d4337fa 100644 (file)
@@ -471,7 +471,7 @@ class NetworkQuery(QueryBase):
       for instance in all_instances.values():
         for nic in instance.nics:
           if nic.network in network_uuids:
-            network_to_instances[nic.network].append(instance.name)
+            network_to_instances[nic.network].append(instance.uuid)
             break
 
     if query.NETQ_STATS in self.requested_data:
@@ -541,7 +541,7 @@ def _NetworkConflictCheck(lu, check_fn, action, instances):
   """
   conflicts = []
 
-  for (_, instance) in lu.cfg.GetMultiInstanceInfo(instances):
+  for instance in instances:
     instconflicts = [(idx, nic.ip)
                      for (idx, nic) in enumerate(instance.nics)
                      if check_fn(nic)]
@@ -597,7 +597,8 @@ class LUNetworkConnect(LogicalUnit):
       # been acquired
       if self.op.conflicts_check:
         self.needed_locks[locking.LEVEL_INSTANCE] = \
-            self.cfg.GetNodeGroupInstances(self.group_uuid)
+          self.cfg.GetInstanceNames(
+            self.cfg.GetNodeGroupInstances(self.group_uuid))
 
   def BuildHooksEnv(self):
     ret = {
@@ -608,8 +609,8 @@ class LUNetworkConnect(LogicalUnit):
     return ret
 
   def BuildHooksNodes(self):
-    nodes = self.cfg.GetNodeGroup(self.group_uuid).members
-    return (nodes, nodes)
+    node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
+    return (node_uuids, node_uuids)
 
   def CheckPrereq(self):
     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
@@ -617,9 +618,9 @@ class LUNetworkConnect(LogicalUnit):
     assert self.group_uuid in owned_groups
 
     # Check if locked instances are still correct
-    owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
+    owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
     if self.op.conflicts_check:
-      CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
+      CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
 
     self.netparams = {
       constants.NIC_MODE: self.network_mode,
@@ -640,8 +641,9 @@ class LUNetworkConnect(LogicalUnit):
     elif self.op.conflicts_check:
       pool = network.AddressPool(self.cfg.GetNetwork(self.network_uuid))
 
-      _NetworkConflictCheck(self, lambda nic: pool.Contains(nic.ip),
-                            "connect to", owned_instances)
+      _NetworkConflictCheck(
+        self, lambda nic: pool.Contains(nic.ip), "connect to",
+        self.cfg.GetMultiInstanceInfoByName(owned_instance_names))
 
   def Exec(self, feedback_fn):
     # Connect the network and update the group only if not already connected
@@ -678,7 +680,8 @@ class LUNetworkDisconnect(LogicalUnit):
       # Lock instances optimistically, needs verification once group lock has
       # been acquired
       self.needed_locks[locking.LEVEL_INSTANCE] = \
-        self.cfg.GetNodeGroupInstances(self.group_uuid)
+        self.cfg.GetInstanceNames(
+          self.cfg.GetNodeGroupInstances(self.group_uuid))
 
   def BuildHooksEnv(self):
     ret = {
@@ -708,8 +711,9 @@ class LUNetworkDisconnect(LogicalUnit):
 
     # We need this check only if network is not already connected
     else:
-      _NetworkConflictCheck(self, lambda nic: nic.network == self.network_uuid,
-                            "disconnect from", owned_instances)
+      _NetworkConflictCheck(
+        self, lambda nic: nic.network == self.network_uuid, "disconnect from",
+        self.cfg.GetMultiInstanceInfoByName(owned_instances))
 
   def Exec(self, feedback_fn):
     # Disconnect the network and update the group only if network is connected
index 2ac2e3a..05a72e8 100644 (file)
@@ -41,7 +41,7 @@ from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, QueryBase, \
 from ganeti.cmdlib.common import CheckParamsNotGlobal, \
   MergeAndVerifyHvState, MergeAndVerifyDiskState, \
   IsExclusiveStorageEnabledNode, CheckNodePVs, \
-  RedistributeAncillaryFiles, ExpandNodeName, ShareAll, SupportsOob, \
+  RedistributeAncillaryFiles, ExpandNodeUuidAndName, ShareAll, SupportsOob, \
   CheckInstanceState, INSTANCE_DOWN, GetUpdatedParams, \
   AdjustCandidatePool, CheckIAllocatorOrNode, LoadNodeEvacResult, \
   GetWantedNodes, MapInstanceDisksToNodes, RunPostHook, \
@@ -64,18 +64,21 @@ def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
 
   @type lu: L{LogicalUnit}
   @param lu: the LU on behalf of which we make the check
-  @type node: string
+  @type node: L{objects.Node}
   @param node: the node to check
   @type secondary_ip: string
   @param secondary_ip: the ip to check
   @type prereq: boolean
   @param prereq: whether to throw a prerequisite or an execute error
-  @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
+  @raise errors.OpPrereqError: if the node doesn't have the ip,
+  and prereq=True
   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
 
   """
-  result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
-  result.Raise("Failure checking secondary ip on node %s" % node,
+  # this can be called with a new node, which has no UUID yet, so perform the
+  # RPC call using its name
+  result = lu.rpc.call_node_has_ip_address(node.name, secondary_ip)
+  result.Raise("Failure checking secondary ip on node %s" % node.name,
                prereq=prereq, ecode=errors.ECODE_ENVIRON)
   if not result.payload:
     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
@@ -101,7 +104,7 @@ class LUNodeAdd(LogicalUnit):
                                          family=self.primary_ip_family)
     self.op.node_name = self.hostname.name
 
-    if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
+    if self.op.readd and self.op.node_name == self.cfg.GetMasterNodeName():
       raise errors.OpPrereqError("Cannot readd the master node",
                                  errors.ECODE_STATE)
 
@@ -128,11 +131,14 @@ class LUNodeAdd(LogicalUnit):
     """Build hooks nodes.
 
     """
-    # Exclude added node
-    pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
-    post_nodes = pre_nodes + [self.op.node_name, ]
+    hook_nodes = self.cfg.GetNodeList()
+    new_node_info = self.cfg.GetNodeInfoByName(self.op.node_name)
+    if new_node_info is not None:
+      # Exclude added node
+      hook_nodes = list(set(hook_nodes) - set([new_node_info.uuid]))
 
-    return (pre_nodes, post_nodes)
+    # add the new node as post hook node by name; it does not have an UUID yet
+    return (hook_nodes, hook_nodes, [self.op.node_name, ])
 
   def CheckPrereq(self):
     """Check prerequisites.
@@ -145,45 +151,43 @@ class LUNodeAdd(LogicalUnit):
     Any errors are signaled by raising errors.OpPrereqError.
 
     """
-    cfg = self.cfg
-    hostname = self.hostname
-    node = hostname.name
-    primary_ip = self.op.primary_ip = hostname.ip
+    node_name = self.hostname.name
+    self.op.primary_ip = self.hostname.ip
     if self.op.secondary_ip is None:
       if self.primary_ip_family == netutils.IP6Address.family:
         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
                                    " IPv4 address must be given as secondary",
                                    errors.ECODE_INVAL)
-      self.op.secondary_ip = primary_ip
+      self.op.secondary_ip = self.op.primary_ip
 
     secondary_ip = self.op.secondary_ip
     if not netutils.IP4Address.IsValid(secondary_ip):
       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
                                  " address" % secondary_ip, errors.ECODE_INVAL)
 
-    node_list = cfg.GetNodeList()
-    if not self.op.readd and node in node_list:
+    existing_node_info = self.cfg.GetNodeInfoByName(node_name)
+    if not self.op.readd and existing_node_info is not None:
       raise errors.OpPrereqError("Node %s is already in the configuration" %
-                                 node, errors.ECODE_EXISTS)
-    elif self.op.readd and node not in node_list:
-      raise errors.OpPrereqError("Node %s is not in the configuration" % node,
-                                 errors.ECODE_NOENT)
+                                 node_name, errors.ECODE_EXISTS)
+    elif self.op.readd and existing_node_info is None:
+      raise errors.OpPrereqError("Node %s is not in the configuration" %
+                                 node_name, errors.ECODE_NOENT)
 
     self.changed_primary_ip = False
 
-    for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
-      if self.op.readd and node == existing_node_name:
+    for existing_node in self.cfg.GetAllNodesInfo().values():
+      if self.op.readd and node_name == existing_node.name:
         if existing_node.secondary_ip != secondary_ip:
           raise errors.OpPrereqError("Readded node doesn't have the same IP"
                                      " address configuration as before",
                                      errors.ECODE_INVAL)
-        if existing_node.primary_ip != primary_ip:
+        if existing_node.primary_ip != self.op.primary_ip:
           self.changed_primary_ip = True
 
         continue
 
-      if (existing_node.primary_ip == primary_ip or
-          existing_node.secondary_ip == primary_ip or
+      if (existing_node.primary_ip == self.op.primary_ip or
+          existing_node.secondary_ip == self.op.primary_ip or
           existing_node.primary_ip == secondary_ip or
           existing_node.secondary_ip == secondary_ip):
         raise errors.OpPrereqError("New node ip address(es) conflict with"
@@ -193,29 +197,29 @@ class LUNodeAdd(LogicalUnit):
     # After this 'if' block, None is no longer a valid value for the
     # _capable op attributes
     if self.op.readd:
-      old_node = self.cfg.GetNodeInfo(node)
-      assert old_node is not None, "Can't retrieve locked node %s" % node
+      assert existing_node_info is not None, \
+        "Can't retrieve locked node %s" % node_name
       for attr in self._NFLAGS:
         if getattr(self.op, attr) is None:
-          setattr(self.op, attr, getattr(old_node, attr))
+          setattr(self.op, attr, getattr(existing_node_info, attr))
     else:
       for attr in self._NFLAGS:
         if getattr(self.op, attr) is None:
           setattr(self.op, attr, True)
 
     if self.op.readd and not self.op.vm_capable:
-      pri, sec = cfg.GetNodeInstances(node)
+      pri, sec = self.cfg.GetNodeInstances(existing_node_info.uuid)
       if pri or sec:
         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
                                    " flag set to false, but it already holds"
-                                   " instances" % node,
+                                   " instances" % node_name,
                                    errors.ECODE_STATE)
 
     # check that the type of the node (single versus dual homed) is the
     # same as for the master
-    myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
+    myself = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
     master_singlehomed = myself.secondary_ip == myself.primary_ip
-    newbie_singlehomed = secondary_ip == primary_ip
+    newbie_singlehomed = secondary_ip == self.op.primary_ip
     if master_singlehomed != newbie_singlehomed:
       if master_singlehomed:
         raise errors.OpPrereqError("The master has no secondary ip but the"
@@ -227,7 +231,7 @@ class LUNodeAdd(LogicalUnit):
                                    errors.ECODE_INVAL)
 
     # checks reachability
-    if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
+    if not netutils.TcpPing(self.op.primary_ip, constants.DEFAULT_NODED_PORT):
       raise errors.OpPrereqError("Node not reachable by ping",
                                  errors.ECODE_ENVIRON)
 
@@ -240,7 +244,7 @@ class LUNodeAdd(LogicalUnit):
                                    errors.ECODE_ENVIRON)
 
     if self.op.readd:
-      exceptions = [node]
+      exceptions = [existing_node_info.uuid]
     else:
       exceptions = []
 
@@ -250,11 +254,11 @@ class LUNodeAdd(LogicalUnit):
       self.master_candidate = False
 
     if self.op.readd:
-      self.new_node = old_node
+      self.new_node = existing_node_info
     else:
-      node_group = cfg.LookupNodeGroup(self.op.group)
-      self.new_node = objects.Node(name=node,
-                                   primary_ip=primary_ip,
+      node_group = self.cfg.LookupNodeGroup(self.op.group)
+      self.new_node = objects.Node(name=node_name,
+                                   primary_ip=self.op.primary_ip,
                                    secondary_ip=secondary_ip,
                                    master_candidate=self.master_candidate,
                                    offline=False, drained=False,
@@ -274,23 +278,25 @@ class LUNodeAdd(LogicalUnit):
     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
     #       it a property on the base class.
     rpcrunner = rpc.DnsOnlyRunner()
-    result = rpcrunner.call_version([node])[node]
-    result.Raise("Can't get version information from node %s" % node)
+    result = rpcrunner.call_version([node_name])[node_name]
+    result.Raise("Can't get version information from node %s" % node_name)
     if constants.PROTOCOL_VERSION == result.payload:
       logging.info("Communication to node %s fine, sw version %s match",
-                   node, result.payload)
+                   node_name, result.payload)
     else:
       raise errors.OpPrereqError("Version mismatch master version %s,"
                                  " node version %s" %
                                  (constants.PROTOCOL_VERSION, result.payload),
                                  errors.ECODE_ENVIRON)
 
-    vg_name = cfg.GetVGName()
+    vg_name = self.cfg.GetVGName()
     if vg_name is not None:
       vparams = {constants.NV_PVLIST: [vg_name]}
-      excl_stor = IsExclusiveStorageEnabledNode(cfg, self.new_node)
+      excl_stor = IsExclusiveStorageEnabledNode(self.cfg, self.new_node)
       cname = self.cfg.GetClusterName()
-      result = rpcrunner.call_node_verify_light([node], vparams, cname)[node]
+      result = rpcrunner.call_node_verify_light(
+          [node_name], vparams, cname,
+          self.cfg.GetClusterInfo().hvparams)[node_name]
       (errmsgs, _) = CheckNodePVs(result.payload, excl_stor)
       if errmsgs:
         raise errors.OpPrereqError("Checks on node PVs failed: %s" %
@@ -300,68 +306,66 @@ class LUNodeAdd(LogicalUnit):
     """Adds the new node to the cluster.
 
     """
-    new_node = self.new_node
-    node = new_node.name
-
     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
       "Not owning BGL"
 
     # We adding a new node so we assume it's powered
-    new_node.powered = True
+    self.new_node.powered = True
 
     # for re-adds, reset the offline/drained/master-candidate flags;
     # we need to reset here, otherwise offline would prevent RPC calls
     # later in the procedure; this also means that if the re-add
     # fails, we are left with a non-offlined, broken node
     if self.op.readd:
-      new_node.drained = new_node.offline = False # pylint: disable=W0201
+      self.new_node.drained = False
       self.LogInfo("Readding a node, the offline/drained flags were reset")
       # if we demote the node, we do cleanup later in the procedure
-      new_node.master_candidate = self.master_candidate
+      self.new_node.master_candidate = self.master_candidate
       if self.changed_primary_ip:
-        new_node.primary_ip = self.op.primary_ip
+        self.new_node.primary_ip = self.op.primary_ip
 
     # copy the master/vm_capable flags
     for attr in self._NFLAGS:
-      setattr(new_node, attr, getattr(self.op, attr))
+      setattr(self.new_node, attr, getattr(self.op, attr))
 
     # notify the user about any possible mc promotion
-    if new_node.master_candidate:
+    if self.new_node.master_candidate:
       self.LogInfo("Node will be a master candidate")
 
     if self.op.ndparams:
-      new_node.ndparams = self.op.ndparams
+      self.new_node.ndparams = self.op.ndparams
     else:
-      new_node.ndparams = {}
+      self.new_node.ndparams = {}
 
     if self.op.hv_state:
-      new_node.hv_state_static = self.new_hv_state
+      self.new_node.hv_state_static = self.new_hv_state
 
     if self.op.disk_state:
-      new_node.disk_state_static = self.new_disk_state
+      self.new_node.disk_state_static = self.new_disk_state
 
     # Add node to our /etc/hosts, and add key to known_hosts
     if self.cfg.GetClusterInfo().modify_etc_hosts:
       master_node = self.cfg.GetMasterNode()
-      result = self.rpc.call_etc_hosts_modify(master_node,
-                                              constants.ETC_HOSTS_ADD,
-                                              self.hostname.name,
-                                              self.hostname.ip)
+      result = self.rpc.call_etc_hosts_modify(
+                 master_node, constants.ETC_HOSTS_ADD, self.hostname.name,
+                 self.hostname.ip)
       result.Raise("Can't update hosts file with new host data")
 
-    if new_node.secondary_ip != new_node.primary_ip:
-      _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
+    if self.new_node.secondary_ip != self.new_node.primary_ip:
+      _CheckNodeHasSecondaryIP(self, self.new_node, self.new_node.secondary_ip,
                                False)
 
-    node_verify_list = [self.cfg.GetMasterNode()]
+    node_verifier_uuids = [self.cfg.GetMasterNode()]
     node_verify_param = {
-      constants.NV_NODELIST: ([node], {}),
+      constants.NV_NODELIST: ([self.new_node.name], {}),
       # TODO: do a node-net-test as well?
     }
 
-    result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
-                                       self.cfg.GetClusterName())
-    for verifier in node_verify_list:
+    result = self.rpc.call_node_verify(
+               node_verifier_uuids, node_verify_param,
+               self.cfg.GetClusterName(),
+               self.cfg.GetClusterInfo().hvparams)
+    for verifier in node_verifier_uuids:
       result[verifier].Raise("Cannot communicate with node %s" % verifier)
       nl_payload = result[verifier].payload[constants.NV_NODELIST]
       if nl_payload:
@@ -372,21 +376,18 @@ class LUNodeAdd(LogicalUnit):
         raise errors.OpExecError("ssh/hostname verification failed")
 
     if self.op.readd:
+      self.context.ReaddNode(self.new_node)
       RedistributeAncillaryFiles(self)
-      self.context.ReaddNode(new_node)
       # make sure we redistribute the config
-      self.cfg.Update(new_node, feedback_fn)
+      self.cfg.Update(self.new_node, feedback_fn)
       # and make sure the new node will not have old files around
-      if not new_node.master_candidate:
-        result = self.rpc.call_node_demote_from_mc(new_node.name)
-        msg = result.fail_msg
-        if msg:
-          self.LogWarning("Node failed to demote itself from master"
-                          " candidate status: %s" % msg)
+      if not self.new_node.master_candidate:
+        result = self.rpc.call_node_demote_from_mc(self.new_node.uuid)
+        result.Warn("Node failed to demote itself from master candidate status",
+                    self.LogWarning)
     else:
-      RedistributeAncillaryFiles(self, additional_nodes=[node],
-                                 additional_vm=self.op.vm_capable)
-      self.context.AddNode(new_node, self.proc.GetECId())
+      self.context.AddNode(self.new_node, self.proc.GetECId())
+      RedistributeAncillaryFiles(self)
 
 
 class LUNodeSetParams(LogicalUnit):
@@ -412,7 +413,8 @@ class LUNodeSetParams(LogicalUnit):
   _FLAGS = ["master_candidate", "drained", "offline"]
 
   def CheckArguments(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
                 self.op.master_capable, self.op.vm_capable,
                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
@@ -445,7 +447,7 @@ class LUNodeSetParams(LogicalUnit):
 
     """
     return (instance.disk_template in constants.DTS_INT_MIRROR and
-            self.op.node_name in instance.all_nodes)
+            self.op.node_uuid in instance.all_nodes)
 
   def ExpandNames(self):
     if self.lock_all:
@@ -457,7 +459,7 @@ class LUNodeSetParams(LogicalUnit):
         }
     else:
       self.needed_locks = {
-        locking.LEVEL_NODE: self.op.node_name,
+        locking.LEVEL_NODE: self.op.node_uuid,
         }
 
     # Since modifying a node can have severe effects on currently running
@@ -474,7 +476,8 @@ class LUNodeSetParams(LogicalUnit):
 
     if self.lock_instances:
       self.needed_locks[locking.LEVEL_INSTANCE] = \
-        frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
+        self.cfg.GetInstanceNames(
+          self.cfg.GetInstancesInfoByFilter(self._InstanceFilter).keys())
 
   def BuildHooksEnv(self):
     """Build hooks env.
@@ -495,7 +498,7 @@ class LUNodeSetParams(LogicalUnit):
     """Build hooks nodes.
 
     """
-    nl = [self.cfg.GetMasterNode(), self.op.node_name]
+    nl = [self.cfg.GetMasterNode(), self.op.node_uuid]
     return (nl, nl)
 
   def CheckPrereq(self):
@@ -504,23 +507,23 @@ class LUNodeSetParams(LogicalUnit):
     This only checks the instance list against the existing names.
 
     """
-    node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
-
+    node = self.cfg.GetNodeInfo(self.op.node_uuid)
     if self.lock_instances:
       affected_instances = \
         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
 
       # Verify instance locks
-      owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
-      wanted_instances = frozenset(affected_instances.keys())
-      if wanted_instances - owned_instances:
+      owned_instance_names = self.owned_locks(locking.LEVEL_INSTANCE)
+      wanted_instance_names = frozenset([inst.name for inst in
+                                         affected_instances.values()])
+      if wanted_instance_names - owned_instance_names:
         raise errors.OpPrereqError("Instances affected by changing node %s's"
                                    " secondary IP address have changed since"
                                    " locks were acquired, wanted '%s', have"
                                    " '%s'; retry the operation" %
-                                   (self.op.node_name,
-                                    utils.CommaJoin(wanted_instances),
-                                    utils.CommaJoin(owned_instances)),
+                                   (node.name,
+                                    utils.CommaJoin(wanted_instance_names),
+                                    utils.CommaJoin(owned_instance_names)),
                                    errors.ECODE_STATE)
     else:
       affected_instances = None
@@ -529,7 +532,7 @@ class LUNodeSetParams(LogicalUnit):
         self.op.drained is not None or
         self.op.offline is not None):
       # we can't change the master's node flags
-      if self.op.node_name == self.cfg.GetMasterNode():
+      if node.uuid == self.cfg.GetMasterNode():
         raise errors.OpPrereqError("The master role can be changed"
                                    " only via master-failover",
                                    errors.ECODE_INVAL)
@@ -540,7 +543,7 @@ class LUNodeSetParams(LogicalUnit):
                                  errors.ECODE_STATE)
 
     if self.op.vm_capable is False:
-      (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
+      (ipri, isec) = self.cfg.GetNodeInstances(node.uuid)
       if ipri or isec:
         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
                                    " the vm_capable flag" % node.name,
@@ -551,7 +554,7 @@ class LUNodeSetParams(LogicalUnit):
       # check if after removing the current node, we're missing master
       # candidates
       (mc_remaining, mc_should, _) = \
-          self.cfg.GetMasterCandidateStats(exceptions=[node.name])
+          self.cfg.GetMasterCandidateStats(exceptions=[node.uuid])
       if mc_remaining < mc_should:
         raise errors.OpPrereqError("Not enough master candidates, please"
                                    " pass auto promote option to allow"
@@ -565,7 +568,7 @@ class LUNodeSetParams(LogicalUnit):
 
     # Check for ineffective changes
     for attr in self._FLAGS:
-      if (getattr(self.op, attr) is False and getattr(node, attr) is False):
+      if getattr(self.op, attr) is False and getattr(node, attr) is False:
         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
         setattr(self.op, attr, None)
 
@@ -616,7 +619,7 @@ class LUNodeSetParams(LogicalUnit):
 
     if old_role == self._ROLE_OFFLINE and new_role != old_role:
       # Trying to transition out of offline status
-      result = self.rpc.call_version([node.name])[node.name]
+      result = self.rpc.call_version([node.uuid])[node.uuid]
       if result.fail_msg:
         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
                                    " to report its version: %s" %
@@ -635,7 +638,7 @@ class LUNodeSetParams(LogicalUnit):
       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
       master_singlehomed = master.secondary_ip == master.primary_ip
       if master_singlehomed and self.op.secondary_ip != node.primary_ip:
-        if self.op.force and node.name == master.name:
+        if self.op.force and node.uuid == master.uuid:
           self.LogWarning("Transitioning from single-homed to multi-homed"
                           " cluster; all nodes will require a secondary IP"
                           " address")
@@ -646,7 +649,7 @@ class LUNodeSetParams(LogicalUnit):
                                      " target node to be the master",
                                      errors.ECODE_INVAL)
       elif not master_singlehomed and self.op.secondary_ip == node.primary_ip:
-        if self.op.force and node.name == master.name:
+        if self.op.force and node.uuid == master.uuid:
           self.LogWarning("Transitioning from multi-homed to single-homed"
                           " cluster; secondary IP addresses will have to be"
                           " removed")
@@ -657,14 +660,15 @@ class LUNodeSetParams(LogicalUnit):
                                      " passed, and the target node is the"
                                      " master", errors.ECODE_INVAL)
 
-      assert not (frozenset(affected_instances) -
+      assert not (set([inst.name for inst in affected_instances.values()]) -
                   self.owned_locks(locking.LEVEL_INSTANCE))
 
       if node.offline:
         if affected_instances:
           msg = ("Cannot change secondary IP address: offline node has"
                  " instances (%s) configured to use it" %
-                 utils.CommaJoin(affected_instances.keys()))
+                 utils.CommaJoin(
+                   [inst.name for inst in affected_instances.values()]))
           raise errors.OpPrereqError(msg, errors.ECODE_STATE)
       else:
         # On online nodes, check that no instances are running, and that
@@ -673,8 +677,8 @@ class LUNodeSetParams(LogicalUnit):
           CheckInstanceState(self, instance, INSTANCE_DOWN,
                              msg="cannot change secondary ip")
 
-        _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
-        if master.name != node.name:
+        _CheckNodeHasSecondaryIP(self, node, self.op.secondary_ip, True)
+        if master.uuid != node.uuid:
           # check reachability from master secondary ip to new secondary ip
           if not netutils.TcpPing(self.op.secondary_ip,
                                   constants.DEFAULT_NODED_PORT,
@@ -684,7 +688,7 @@ class LUNodeSetParams(LogicalUnit):
                                        errors.ECODE_ENVIRON)
 
     if self.op.ndparams:
-      new_ndparams = GetUpdatedParams(self.node.ndparams, self.op.ndparams)
+      new_ndparams = GetUpdatedParams(node.ndparams, self.op.ndparams)
       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
       CheckParamsNotGlobal(self.op.ndparams, constants.NDC_GLOBALS, "node",
                            "node", "cluster or group")
@@ -692,21 +696,17 @@ class LUNodeSetParams(LogicalUnit):
 
     if self.op.hv_state:
       self.new_hv_state = MergeAndVerifyHvState(self.op.hv_state,
-                                                self.node.hv_state_static)
+                                                node.hv_state_static)
 
     if self.op.disk_state:
       self.new_disk_state = \
-        MergeAndVerifyDiskState(self.op.disk_state,
-                                self.node.disk_state_static)
+        MergeAndVerifyDiskState(self.op.disk_state, node.disk_state_static)
 
   def Exec(self, feedback_fn):
     """Modifies a node.
 
     """
-    node = self.node
-    old_role = self.old_role
-    new_role = self.new_role
-
+    node = self.cfg.GetNodeInfo(self.op.node_uuid)
     result = []
 
     if self.op.ndparams:
@@ -727,14 +727,15 @@ class LUNodeSetParams(LogicalUnit):
         setattr(node, attr, val)
         result.append((attr, str(val)))
 
-    if new_role != old_role:
+    if self.new_role != self.old_role:
       # Tell the node to demote itself, if no longer MC and not offline
-      if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
+      if self.old_role == self._ROLE_CANDIDATE and \
+          self.new_role != self._ROLE_OFFLINE:
         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
         if msg:
           self.LogWarning("Node failed to demote itself: %s", msg)
 
-      new_flags = self._R2F[new_role]
+      new_flags = self._R2F[self.new_role]
       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
         if of != nf:
           result.append((desc, str(nf)))
@@ -742,7 +743,7 @@ class LUNodeSetParams(LogicalUnit):
 
       # we locked all nodes, we adjust the CP before updating this node
       if self.lock_all:
-        AdjustCandidatePool(self, [node.name])
+        AdjustCandidatePool(self, [node.uuid])
 
     if self.op.secondary_ip:
       node.secondary_ip = self.op.secondary_ip
@@ -753,7 +754,7 @@ class LUNodeSetParams(LogicalUnit):
 
     # this will trigger job queue propagation or cleanup if the mc
     # flag changed
-    if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
+    if [self.old_role, self.new_role].count(self._ROLE_CANDIDATE) == 1:
       self.context.ReaddNode(node)
 
     return result
@@ -766,8 +767,10 @@ class LUNodePowercycle(NoHooksLU):
   REQ_BGL = False
 
   def CheckArguments(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
-    if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
+
+    if self.op.node_uuid == self.cfg.GetMasterNode() and not self.op.force:
       raise errors.OpPrereqError("The node is the master and the force"
                                  " parameter was not set",
                                  errors.ECODE_INVAL)
@@ -785,8 +788,11 @@ class LUNodePowercycle(NoHooksLU):
     """Reboots a node.
 
     """
-    result = self.rpc.call_node_powercycle(self.op.node_name,
-                                           self.cfg.GetHypervisorType())
+    default_hypervisor = self.cfg.GetHypervisorType()
+    hvparams = self.cfg.GetClusterInfo().hvparams[default_hypervisor]
+    result = self.rpc.call_node_powercycle(self.op.node_uuid,
+                                           default_hypervisor,
+                                           hvparams)
     result.Raise("Failed to schedule the reboot")
     return result.payload
 
@@ -795,28 +801,28 @@ def _GetNodeInstancesInner(cfg, fn):
   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
 
 
-def _GetNodePrimaryInstances(cfg, node_name):
+def _GetNodePrimaryInstances(cfg, node_uuid):
   """Returns primary instances on a node.
 
   """
   return _GetNodeInstancesInner(cfg,
-                                lambda inst: node_name == inst.primary_node)
+                                lambda inst: node_uuid == inst.primary_node)
 
 
-def _GetNodeSecondaryInstances(cfg, node_name):
+def _GetNodeSecondaryInstances(cfg, node_uuid):
   """Returns secondary instances on a node.
 
   """
   return _GetNodeInstancesInner(cfg,
-                                lambda inst: node_name in inst.secondary_nodes)
+                                lambda inst: node_uuid in inst.secondary_nodes)
 
 
-def _GetNodeInstances(cfg, node_name):
+def _GetNodeInstances(cfg, node_uuid):
   """Returns a list of all primary and secondary instances on a node.
 
   """
 
-  return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
+  return _GetNodeInstancesInner(cfg, lambda inst: node_uuid in inst.all_nodes)
 
 
 class LUNodeEvacuate(NoHooksLU):
@@ -838,13 +844,16 @@ class LUNodeEvacuate(NoHooksLU):
     CheckIAllocatorOrNode(self, "iallocator", "remote_node")
 
   def ExpandNames(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
 
     if self.op.remote_node is not None:
-      self.op.remote_node = ExpandNodeName(self.cfg, self.op.remote_node)
+      (self.op.remote_node_uuid, self.op.remote_node) = \
+        ExpandNodeUuidAndName(self.cfg, self.op.remote_node_uuid,
+                              self.op.remote_node)
       assert self.op.remote_node
 
-      if self.op.remote_node == self.op.node_name:
+      if self.op.node_uuid == self.op.remote_node_uuid:
         raise errors.OpPrereqError("Can not use evacuated node as a new"
                                    " secondary node", errors.ECODE_INVAL)
 
@@ -866,17 +875,17 @@ class LUNodeEvacuate(NoHooksLU):
     self.lock_nodes = self._DetermineNodes()
 
   def _DetermineNodes(self):
-    """Gets the list of nodes to operate on.
+    """Gets the list of node UUIDs to operate on.
 
     """
     if self.op.remote_node is None:
       # Iallocator will choose any node(s) in the same group
-      group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
+      group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_uuid])
     else:
-      group_nodes = frozenset([self.op.remote_node])
+      group_nodes = frozenset([self.op.remote_node_uuid])
 
     # Determine nodes to be locked
-    return set([self.op.node_name]) | group_nodes
+    return set([self.op.node_uuid]) | group_nodes
 
   def _DetermineInstances(self):
     """Builds list of instances to operate on.
@@ -905,7 +914,7 @@ class LUNodeEvacuate(NoHooksLU):
                                  " instances",
                                  errors.ECODE_INVAL)
 
-    return inst_fn(self.cfg, self.op.node_name)
+    return inst_fn(self.cfg, self.op.node_uuid)
 
   def DeclareLocks(self, level):
     if level == locking.LEVEL_INSTANCE:
@@ -925,7 +934,7 @@ class LUNodeEvacuate(NoHooksLU):
 
   def CheckPrereq(self):
     # Verify locks
-    owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
+    owned_instance_names = self.owned_locks(locking.LEVEL_INSTANCE)
     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
 
@@ -953,13 +962,13 @@ class LUNodeEvacuate(NoHooksLU):
     self.instances = self._DetermineInstances()
     self.instance_names = [i.name for i in self.instances]
 
-    if set(self.instance_names) != owned_instances:
+    if set(self.instance_names) != owned_instance_names:
       raise errors.OpExecError("Instances on node '%s' changed since locks"
                                " were acquired, current instances are '%s',"
                                " used to be '%s'; retry the operation" %
                                (self.op.node_name,
                                 utils.CommaJoin(self.instance_names),
-                                utils.CommaJoin(owned_instances)))
+                                utils.CommaJoin(owned_instance_names)))
 
     if self.instance_names:
       self.LogInfo("Evacuating instances from node '%s': %s",
@@ -971,7 +980,7 @@ class LUNodeEvacuate(NoHooksLU):
 
     if self.op.remote_node is not None:
       for i in self.instances:
-        if i.primary_node == self.op.remote_node:
+        if i.primary_node == self.op.remote_node_uuid:
           raise errors.OpPrereqError("Node %s is the primary node of"
                                      " instance %s, cannot use it as"
                                      " secondary" %
@@ -1030,11 +1039,12 @@ class LUNodeMigrate(LogicalUnit):
     pass
 
   def ExpandNames(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
 
     self.share_locks = ShareAll()
     self.needed_locks = {
-      locking.LEVEL_NODE: [self.op.node_name],
+      locking.LEVEL_NODE: [self.op.node_uuid],
       }
 
   def BuildHooksEnv(self):
@@ -1060,16 +1070,16 @@ class LUNodeMigrate(LogicalUnit):
 
   def Exec(self, feedback_fn):
     # Prepare jobs for migration instances
-    allow_runtime_changes = self.op.allow_runtime_changes
     jobs = [
-      [opcodes.OpInstanceMigrate(instance_name=inst.name,
-                                 mode=self.op.mode,
-                                 live=self.op.live,
-                                 iallocator=self.op.iallocator,
-                                 target_node=self.op.target_node,
-                                 allow_runtime_changes=allow_runtime_changes,
-                                 ignore_ipolicy=self.op.ignore_ipolicy)]
-      for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)]
+      [opcodes.OpInstanceMigrate(
+        instance_name=inst.name,
+        mode=self.op.mode,
+        live=self.op.live,
+        iallocator=self.op.iallocator,
+        target_node=self.op.target_node,
+        allow_runtime_changes=self.op.allow_runtime_changes,
+        ignore_ipolicy=self.op.ignore_ipolicy)]
+      for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_uuid)]
 
     # TODO: Run iallocator in this opcode and pass correct placement options to
     # OpInstanceMigrate. Since other jobs can modify the cluster between
@@ -1077,7 +1087,7 @@ class LUNodeMigrate(LogicalUnit):
     # will have to be found.
 
     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
-            frozenset([self.op.node_name]))
+            frozenset([self.op.node_uuid]))
 
     return ResultWithJobs(jobs)
 
@@ -1101,7 +1111,8 @@ class LUNodeModifyStorage(NoHooksLU):
   REQ_BGL = False
 
   def CheckArguments(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
 
     storage_type = self.op.storage_type
 
@@ -1121,7 +1132,7 @@ class LUNodeModifyStorage(NoHooksLU):
 
   def ExpandNames(self):
     self.needed_locks = {
-      locking.LEVEL_NODE: self.op.node_name,
+      locking.LEVEL_NODE: self.op.node_uuid,
       }
 
   def Exec(self, feedback_fn):
@@ -1129,7 +1140,7 @@ class LUNodeModifyStorage(NoHooksLU):
 
     """
     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
-    result = self.rpc.call_storage_modify(self.op.node_name,
+    result = self.rpc.call_storage_modify(self.op.node_uuid,
                                           self.op.storage_type, st_args,
                                           self.op.name, self.op.changes)
     result.Raise("Failed to modify storage unit '%s' on %s" %
@@ -1144,7 +1155,7 @@ class NodeQuery(QueryBase):
     lu.share_locks = ShareAll()
 
     if self.names:
-      self.wanted = GetWantedNodes(lu, self.names)
+      (self.wanted, _) = GetWantedNodes(lu, self.names)
     else:
       self.wanted = locking.ALL_SET
 
@@ -1165,41 +1176,56 @@ class NodeQuery(QueryBase):
     """
     all_info = lu.cfg.GetAllNodesInfo()
 
-    nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
+    node_uuids = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
 
     # Gather data as requested
     if query.NQ_LIVE in self.requested_data:
       # filter out non-vm_capable nodes
-      toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
-
-      es_flags = rpc.GetExclusiveStorageForNodeNames(lu.cfg, toquery_nodes)
-      node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
-                                        [lu.cfg.GetHypervisorType()], es_flags)
-      live_data = dict((name, rpc.MakeLegacyNodeInfo(nresult.payload))
-                       for (name, nresult) in node_data.items()
-                       if not nresult.fail_msg and nresult.payload)
+      toquery_node_uuids = [node.uuid for node in all_info.values()
+                            if node.vm_capable and node.uuid in node_uuids]
+
+      es_flags = rpc.GetExclusiveStorageForNodes(lu.cfg, toquery_node_uuids)
+      # FIXME: This currently maps everything to lvm, this should be more
+      # flexible
+      lvm_enabled = utils.storage.IsLvmEnabled(
+          lu.cfg.GetClusterInfo().enabled_disk_templates)
+      storage_units = utils.storage.GetStorageUnitsOfCluster(
+          lu.cfg, include_spindles=True)
+      default_hypervisor = lu.cfg.GetHypervisorType()
+      hvparams = lu.cfg.GetClusterInfo().hvparams[default_hypervisor]
+      hvspecs = [(default_hypervisor, hvparams)]
+      node_data = lu.rpc.call_node_info(toquery_node_uuids, storage_units,
+                                        hvspecs, es_flags)
+      live_data = dict(
+          (uuid, rpc.MakeLegacyNodeInfo(nresult.payload,
+                                        require_vg_info=lvm_enabled))
+          for (uuid, nresult) in node_data.items()
+          if not nresult.fail_msg and nresult.payload)
     else:
       live_data = None
 
     if query.NQ_INST in self.requested_data:
-      node_to_primary = dict([(name, set()) for name in nodenames])
-      node_to_secondary = dict([(name, set()) for name in nodenames])
+      node_to_primary = dict([(uuid, set()) for uuid in node_uuids])
+      node_to_secondary = dict([(uuid, set()) for uuid in node_uuids])
 
       inst_data = lu.cfg.GetAllInstancesInfo()
+      inst_uuid_to_inst_name = {}
 
       for inst in inst_data.values():
+        inst_uuid_to_inst_name[inst.uuid] = inst.name
         if inst.primary_node in node_to_primary:
-          node_to_primary[inst.primary_node].add(inst.name)
+          node_to_primary[inst.primary_node].add(inst.uuid)
         for secnode in inst.secondary_nodes:
           if secnode in node_to_secondary:
-            node_to_secondary[secnode].add(inst.name)
+            node_to_secondary[secnode].add(inst.uuid)
     else:
       node_to_primary = None
       node_to_secondary = None
+      inst_uuid_to_inst_name = None
 
     if query.NQ_OOB in self.requested_data:
-      oob_support = dict((name, bool(SupportsOob(lu.cfg, node)))
-                         for name, node in all_info.iteritems())
+      oob_support = dict((uuid, bool(SupportsOob(lu.cfg, node)))
+                         for uuid, node in all_info.iteritems())
     else:
       oob_support = None
 
@@ -1208,10 +1234,11 @@ class NodeQuery(QueryBase):
     else:
       groups = {}
 
-    return query.NodeQueryData([all_info[name] for name in nodenames],
+    return query.NodeQueryData([all_info[uuid] for uuid in node_uuids],
                                live_data, lu.cfg.GetMasterNode(),
-                               node_to_primary, node_to_secondary, groups,
-                               oob_support, lu.cfg.GetClusterInfo())
+                               node_to_primary, node_to_secondary,
+                               inst_uuid_to_inst_name, groups, oob_support,
+                               lu.cfg.GetClusterInfo())
 
 
 class LUNodeQuery(NoHooksLU):
@@ -1272,7 +1299,7 @@ class LUNodeQueryvols(NoHooksLU):
 
     if self.op.nodes:
       self.needed_locks = {
-        locking.LEVEL_NODE: GetWantedNodes(self, self.op.nodes),
+        locking.LEVEL_NODE: GetWantedNodes(self, self.op.nodes)[0],
         }
     else:
       self.needed_locks = {
@@ -1284,20 +1311,21 @@ class LUNodeQueryvols(NoHooksLU):
     """Computes the list of nodes and their attributes.
 
     """
-    nodenames = self.owned_locks(locking.LEVEL_NODE)
-    volumes = self.rpc.call_node_volumes(nodenames)
+    node_uuids = self.owned_locks(locking.LEVEL_NODE)
+    volumes = self.rpc.call_node_volumes(node_uuids)
 
     ilist = self.cfg.GetAllInstancesInfo()
     vol2inst = MapInstanceDisksToNodes(ilist.values())
 
     output = []
-    for node in nodenames:
-      nresult = volumes[node]
+    for node_uuid in node_uuids:
+      nresult = volumes[node_uuid]
       if nresult.offline:
         continue
       msg = nresult.fail_msg
       if msg:
-        self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
+        self.LogWarning("Can't compute volume data on node %s: %s",
+                        self.cfg.GetNodeName(node_uuid), msg)
         continue
 
       node_vols = sorted(nresult.payload,
@@ -1307,7 +1335,7 @@ class LUNodeQueryvols(NoHooksLU):
         node_output = []
         for field in self.op.output_fields:
           if field == "node":
-            val = node
+            val = self.cfg.GetNodeName(node_uuid)
           elif field == "phys":
             val = vol["dev"]
           elif field == "vg":
@@ -1317,7 +1345,7 @@ class LUNodeQueryvols(NoHooksLU):
           elif field == "size":
             val = int(float(vol["size"]))
           elif field == "instance":
-            val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
+            val = vol2inst.get((node_uuid, vol["vg"] + "/" + vol["name"]), "-")
           else:
             raise errors.ParameterError(field)
           node_output.append(str(val))
@@ -1344,7 +1372,7 @@ class LUNodeQueryStorage(NoHooksLU):
 
     if self.op.nodes:
       self.needed_locks = {
-        locking.LEVEL_NODE: GetWantedNodes(self, self.op.nodes),
+        locking.LEVEL_NODE: GetWantedNodes(self, self.op.nodes)[0],
         }
     else:
       self.needed_locks = {
@@ -1356,7 +1384,7 @@ class LUNodeQueryStorage(NoHooksLU):
     """Computes the list of nodes and their attributes.
 
     """
-    self.nodes = self.owned_locks(locking.LEVEL_NODE)
+    self.node_uuids = self.owned_locks(locking.LEVEL_NODE)
 
     # Always get name to sort by
     if constants.SF_NAME in self.op.output_fields:
@@ -1373,20 +1401,22 @@ class LUNodeQueryStorage(NoHooksLU):
     name_idx = field_idx[constants.SF_NAME]
 
     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
-    data = self.rpc.call_storage_list(self.nodes,
+    data = self.rpc.call_storage_list(self.node_uuids,
                                       self.op.storage_type, st_args,
                                       self.op.name, fields)
 
     result = []
 
-    for node in utils.NiceSort(self.nodes):
-      nresult = data[node]
+    for node_uuid in utils.NiceSort(self.node_uuids):
+      node_name = self.cfg.GetNodeName(node_uuid)
+      nresult = data[node_uuid]
       if nresult.offline:
         continue
 
       msg = nresult.fail_msg
       if msg:
-        self.LogWarning("Can't get storage data from node %s: %s", node, msg)
+        self.LogWarning("Can't get storage data from node %s: %s",
+                        node_name, msg)
         continue
 
       rows = dict([(row[name_idx], row) for row in nresult.payload])
@@ -1398,7 +1428,7 @@ class LUNodeQueryStorage(NoHooksLU):
 
         for field in self.op.output_fields:
           if field == constants.SF_NODE:
-            val = node
+            val = node_name
           elif field == constants.SF_TYPE:
             val = self.op.storage_type
           elif field in field_idx:
@@ -1438,7 +1468,7 @@ class LUNodeRemove(LogicalUnit):
     """
     all_nodes = self.cfg.GetNodeList()
     try:
-      all_nodes.remove(self.op.node_name)
+      all_nodes.remove(self.op.node_uuid)
     except ValueError:
       pass
     return (all_nodes, all_nodes)
@@ -1454,19 +1484,20 @@ class LUNodeRemove(LogicalUnit):
     Any errors are signaled by raising errors.OpPrereqError.
 
     """
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
-    node = self.cfg.GetNodeInfo(self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
+    node = self.cfg.GetNodeInfo(self.op.node_uuid)
     assert node is not None
 
     masternode = self.cfg.GetMasterNode()
-    if node.name == masternode:
+    if node.uuid == masternode:
       raise errors.OpPrereqError("Node is the master node, failover to another"
                                  " node is required", errors.ECODE_INVAL)
 
-    for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
-      if node.name in instance.all_nodes:
+    for _, instance in self.cfg.GetAllInstancesInfo().items():
+      if node.uuid in instance.all_nodes:
         raise errors.OpPrereqError("Instance %s is still running on the node,"
-                                   " please remove first" % instance_name,
+                                   " please remove first" % instance.name,
                                    errors.ECODE_INVAL)
     self.op.node_name = node.name
     self.node = node
@@ -1475,9 +1506,8 @@ class LUNodeRemove(LogicalUnit):
     """Removes the node from the cluster.
 
     """
-    node = self.node
     logging.info("Stopping the node daemon and removing configs from node %s",
-                 node.name)
+                 self.node.name)
 
     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
 
@@ -1485,13 +1515,15 @@ class LUNodeRemove(LogicalUnit):
       "Not owning BGL"
 
     # Promote nodes to master candidate as needed
-    AdjustCandidatePool(self, exceptions=[node.name])
-    self.context.RemoveNode(node.name)
+    AdjustCandidatePool(self, exceptions=[self.node.uuid])
+    self.context.RemoveNode(self.node)
 
     # Run post hooks on the node before it's removed
-    RunPostHook(self, node.name)
+    RunPostHook(self, self.node.name)
 
-    result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
+    # we have to call this by name rather than by UUID, as the node is no longer
+    # in the config
+    result = self.rpc.call_node_leave_cluster(self.node.name, modify_ssh_setup)
     msg = result.fail_msg
     if msg:
       self.LogWarning("Errors encountered on the remote node while leaving"
@@ -1499,10 +1531,10 @@ class LUNodeRemove(LogicalUnit):
 
     # Remove node from our /etc/hosts
     if self.cfg.GetClusterInfo().modify_etc_hosts:
-      master_node = self.cfg.GetMasterNode()
-      result = self.rpc.call_etc_hosts_modify(master_node,
+      master_node_uuid = self.cfg.GetMasterNode()
+      result = self.rpc.call_etc_hosts_modify(master_node_uuid,
                                               constants.ETC_HOSTS_REMOVE,
-                                              node.name, None)
+                                              self.node.name, None)
       result.Raise("Can't update hosts file with new host data")
       RedistributeAncillaryFiles(self)
 
@@ -1514,7 +1546,8 @@ class LURepairNodeStorage(NoHooksLU):
   REQ_BGL = False
 
   def CheckArguments(self):
-    self.op.node_name = ExpandNodeName(self.cfg, self.op.node_name)
+    (self.op.node_uuid, self.op.node_name) = \
+      ExpandNodeUuidAndName(self.cfg, self.op.node_uuid, self.op.node_name)
 
     storage_type = self.op.storage_type
 
@@ -1526,16 +1559,18 @@ class LURepairNodeStorage(NoHooksLU):
 
   def ExpandNames(self):
     self.needed_locks = {
-      locking.LEVEL_NODE: [self.op.node_name],
+      locking.LEVEL_NODE: [self.op.node_uuid],
       }
 
-  def _CheckFaultyDisks(self, instance, node_name):
+  def _CheckFaultyDisks(self, instance, node_uuid):
     """Ensure faulty disks abort the opcode or at least warn."""
     try:
       if FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
-                                 node_name, True):
+                                 node_uuid, True):
         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
-                                   " node '%s'" % (instance.name, node_name),
+                                   " node '%s'" %
+                                   (instance.name,
+                                    self.cfg.GetNodeName(node_uuid)),
                                    errors.ECODE_STATE)
     except errors.OpPrereqError, err:
       if self.op.ignore_consistency:
@@ -1548,20 +1583,20 @@ class LURepairNodeStorage(NoHooksLU):
 
     """
     # Check whether any instance on this node has faulty disks
-    for inst in _GetNodeInstances(self.cfg, self.op.node_name):
+    for inst in _GetNodeInstances(self.cfg, self.op.node_uuid):
       if not inst.disks_active:
         continue
       check_nodes = set(inst.all_nodes)
-      check_nodes.discard(self.op.node_name)
-      for inst_node_name in check_nodes:
-        self._CheckFaultyDisks(inst, inst_node_name)
+      check_nodes.discard(self.op.node_uuid)
+      for inst_node_uuid in check_nodes:
+        self._CheckFaultyDisks(inst, inst_node_uuid)
 
   def Exec(self, feedback_fn):
     feedback_fn("Repairing storage unit '%s' on %s ..." %
                 (self.op.name, self.op.node_name))
 
     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
-    result = self.rpc.call_storage_execute(self.op.node_name,
+    result = self.rpc.call_storage_execute(self.op.node_uuid,
                                            self.op.storage_type, st_args,
                                            self.op.name,
                                            constants.SO_FIX_CONSISTENCY)
index ea4bca5..31501c6 100644 (file)
@@ -58,12 +58,12 @@ class OsQuery(QueryBase):
 
     @rtype: dict
     @return: a dictionary with osnames as keys and as value another
-        map, with nodes as keys and tuples of (path, status, diagnose,
+        map, with node UUIDs as keys and tuples of (path, status, diagnose,
         variants, parameters, api_versions) as values, eg::
 
-          {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
-                                     (/srv/..., False, "invalid api")],
-                           "node2": [(/srv/..., True, "", [], [])]}
+          {"debian-etch": {"node1-uuid": [(/usr/lib/..., True, "", [], []),
+                                          (/srv/..., False, "invalid api")],
+                           "node2-uuid": [(/srv/..., True, "", [], [])]}
           }
 
     """
@@ -71,9 +71,9 @@ class OsQuery(QueryBase):
     # we build here the list of nodes that didn't fail the RPC (at RPC
     # level), so that nodes with a non-responding node daemon don't
     # make all OSes invalid
-    good_nodes = [node_name for node_name in rlist
-                  if not rlist[node_name].fail_msg]
-    for node_name, nr in rlist.items():
+    good_node_uuids = [node_uuid for node_uuid in rlist
+                       if not rlist[node_uuid].fail_msg]
+    for node_uuid, nr in rlist.items():
       if nr.fail_msg or not nr.payload:
         continue
       for (name, path, status, diagnose, variants,
@@ -82,11 +82,11 @@ class OsQuery(QueryBase):
           # build a list of nodes for this os containing empty lists
           # for each node in node_list
           all_os[name] = {}
-          for nname in good_nodes:
-            all_os[name][nname] = []
+          for nuuid in good_node_uuids:
+            all_os[name][nuuid] = []
         # convert params from [name, help] to (name, help)
         params = [tuple(v) for v in params]
-        all_os[name][node_name].append((path, status, diagnose,
+        all_os[name][node_uuid].append((path, status, diagnose,
                                         variants, params, api_versions))
     return all_os
 
@@ -100,10 +100,10 @@ class OsQuery(QueryBase):
                            if level != locking.LEVEL_CLUSTER) or
                 self.do_locking or self.use_locking)
 
-    valid_nodes = [node.name
-                   for node in lu.cfg.GetAllNodesInfo().values()
-                   if not node.offline and node.vm_capable]
-    pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
+    valid_node_uuids = [node.uuid
+                        for node in lu.cfg.GetAllNodesInfo().values()
+                        if not node.offline and node.vm_capable]
+    pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_node_uuids))
     cluster = lu.cfg.GetClusterInfo()
 
     data = {}
index 726e3a1..91b52fe 100644 (file)
@@ -29,7 +29,8 @@ from ganeti import locking
 from ganeti import objects
 from ganeti import utils
 from ganeti.cmdlib.base import NoHooksLU
-from ganeti.cmdlib.common import ExpandNodeName, ExpandInstanceName, ShareAll
+from ganeti.cmdlib.common import ExpandNodeUuidAndName, \
+  ExpandInstanceUuidAndName, ShareAll
 
 
 class TagsLU(NoHooksLU): # pylint: disable=W0223
@@ -43,13 +44,15 @@ class TagsLU(NoHooksLU): # pylint: disable=W0223
     self.needed_locks = {}
 
     if self.op.kind == constants.TAG_NODE:
-      self.op.name = ExpandNodeName(self.cfg, self.op.name)
+      (self.node_uuid, _) = \
+        ExpandNodeUuidAndName(self.cfg, None, self.op.name)
       lock_level = locking.LEVEL_NODE
-      lock_name = self.op.name
+      lock_name = self.node_uuid
     elif self.op.kind == constants.TAG_INSTANCE:
-      self.op.name = ExpandInstanceName(self.cfg, self.op.name)
+      (self.inst_uuid, inst_name) = \
+        ExpandInstanceUuidAndName(self.cfg, None, self.op.name)
       lock_level = locking.LEVEL_INSTANCE
-      lock_name = self.op.name
+      lock_name = inst_name
     elif self.op.kind == constants.TAG_NODEGROUP:
       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
       lock_level = locking.LEVEL_NODEGROUP
@@ -75,9 +78,9 @@ class TagsLU(NoHooksLU): # pylint: disable=W0223
     if self.op.kind == constants.TAG_CLUSTER:
       self.target = self.cfg.GetClusterInfo()
     elif self.op.kind == constants.TAG_NODE:
-      self.target = self.cfg.GetNodeInfo(self.op.name)
+      self.target = self.cfg.GetNodeInfo(self.node_uuid)
     elif self.op.kind == constants.TAG_INSTANCE:
-      self.target = self.cfg.GetInstanceInfo(self.op.name)
+      self.target = self.cfg.GetInstanceInfo(self.inst_uuid)
     elif self.op.kind == constants.TAG_NODEGROUP:
       self.target = self.cfg.GetNodeGroup(self.group_uuid)
     elif self.op.kind == constants.TAG_NETWORK:
@@ -131,14 +134,13 @@ class LUTagsSearch(NoHooksLU):
     """Returns the tag list.
 
     """
-    cfg = self.cfg
-    tgts = [("/cluster", cfg.GetClusterInfo())]
-    ilist = cfg.GetAllInstancesInfo().values()
+    tgts = [("/cluster", self.cfg.GetClusterInfo())]
+    ilist = self.cfg.GetAllInstancesInfo().values()
     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
-    nlist = cfg.GetAllNodesInfo().values()
+    nlist = self.cfg.GetAllNodesInfo().values()
     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
     tgts.extend(("/nodegroup/%s" % n.name, n)
-                for n in cfg.GetAllNodeGroupsInfo().values())
+                for n in self.cfg.GetAllNodeGroupsInfo().values())
     results = []
     for path, target in tgts:
       for tag in target.GetTags():
index 52787c3..4b02898 100644 (file)
@@ -33,7 +33,7 @@ from ganeti import locking
 from ganeti import utils
 from ganeti.masterd import iallocator
 from ganeti.cmdlib.base import NoHooksLU
-from ganeti.cmdlib.common import ExpandInstanceName, GetWantedNodes, \
+from ganeti.cmdlib.common import ExpandInstanceUuidAndName, GetWantedNodes, \
   GetWantedInstances
 
 
@@ -57,8 +57,9 @@ class LUTestDelay(NoHooksLU):
       # _GetWantedNodes can be used here, but is not always appropriate to use
       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
       # more information.
-      self.op.on_nodes = GetWantedNodes(self, self.op.on_nodes)
-      self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
+      (self.op.on_node_uuids, self.op.on_nodes) = \
+        GetWantedNodes(self, self.op.on_nodes)
+      self.needed_locks[locking.LEVEL_NODE] = self.op.on_node_uuids
 
   def _TestDelay(self):
     """Do the actual sleep.
@@ -67,10 +68,11 @@ class LUTestDelay(NoHooksLU):
     if self.op.on_master:
       if not utils.TestDelay(self.op.duration):
         raise errors.OpExecError("Error during master delay test")
-    if self.op.on_nodes:
-      result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
-      for node, node_result in result.items():
-        node_result.Raise("Failure during rpc call to node %s" % node)
+    if self.op.on_node_uuids:
+      result = self.rpc.call_test_delay(self.op.on_node_uuids, self.op.duration)
+      for node_uuid, node_result in result.items():
+        node_result.Raise("Failure during rpc call to node %s" %
+                          self.cfg.GetNodeName(node_uuid))
 
   def Exec(self, feedback_fn):
     """Execute the test delay opcode, with the wanted repetitions.
@@ -240,7 +242,7 @@ class LUTestAllocator(NoHooksLU):
         if not hasattr(self.op, attr):
           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
                                      attr, errors.ECODE_INVAL)
-      iname = self.cfg.ExpandInstanceName(self.op.name)
+      (self.inst_uuid, iname) = self.cfg.ExpandInstanceName(self.op.name)
       if iname is not None:
         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
                                    iname, errors.ECODE_EXISTS)
@@ -261,15 +263,15 @@ class LUTestAllocator(NoHooksLU):
       if self.op.hypervisor is None:
         self.op.hypervisor = self.cfg.GetHypervisorType()
     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
-      fname = ExpandInstanceName(self.cfg, self.op.name)
+      (fuuid, fname) = ExpandInstanceUuidAndName(self.cfg, None, self.op.name)
       self.op.name = fname
-      self.relocate_from = \
-          list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
+      self.relocate_from_node_uuids = \
+          list(self.cfg.GetInstanceInfo(fuuid).secondary_nodes)
     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
                           constants.IALLOCATOR_MODE_NODE_EVAC):
       if not self.op.instances:
         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
-      self.op.instances = GetWantedInstances(self, self.op.instances)
+      (_, self.op.instances) = GetWantedInstances(self, self.op.instances)
     else:
       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
                                  self.op.mode, errors.ECODE_INVAL)
@@ -299,8 +301,9 @@ class LUTestAllocator(NoHooksLU):
                                           hypervisor=self.op.hypervisor,
                                           node_whitelist=None)
     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
-      req = iallocator.IAReqRelocate(name=self.op.name,
-                                     relocate_from=list(self.relocate_from))
+      req = iallocator.IAReqRelocate(
+            inst_uuid=self.inst_uuid,
+            relocate_from_node_uuids=list(self.relocate_from_node_uuids))
     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
       req = iallocator.IAReqGroupChange(instances=self.op.instances,
                                         target_groups=self.op.target_groups)
index ed693a7..a99d8a2 100644 (file)
@@ -665,22 +665,22 @@ class ConfigWriter:
     _helper_ipolicy("cluster", cluster.ipolicy, True)
 
     # per-instance checks
-    for instance_name in data.instances:
-      instance = data.instances[instance_name]
-      if instance.name != instance_name:
-        result.append("instance '%s' is indexed by wrong name '%s'" %
-                      (instance.name, instance_name))
+    for instance_uuid in data.instances:
+      instance = data.instances[instance_uuid]
+      if instance.uuid != instance_uuid:
+        result.append("instance '%s' is indexed by wrong UUID '%s'" %
+                      (instance.name, instance_uuid))
       if instance.primary_node not in data.nodes:
         result.append("instance '%s' has invalid primary node '%s'" %
-                      (instance_name, instance.primary_node))
+                      (instance.name, instance.primary_node))
       for snode in instance.secondary_nodes:
         if snode not in data.nodes:
           result.append("instance '%s' has invalid secondary node '%s'" %
-                        (instance_name, snode))
+                        (instance.name, snode))
       for idx, nic in enumerate(instance.nics):
         if nic.mac in seen_macs:
           result.append("instance '%s' has NIC %d mac %s duplicate" %
-                        (instance_name, idx, nic.mac))
+                        (instance.name, idx, nic.mac))
         else:
           seen_macs.append(nic.mac)
         if nic.nicparams:
@@ -693,7 +693,7 @@ class ConfigWriter:
       # disk template checks
       if not instance.disk_template in data.cluster.enabled_disk_templates:
         result.append("instance '%s' uses the disabled disk template '%s'." %
-                      (instance_name, instance.disk_template))
+                      (instance.name, instance.disk_template))
 
       # parameter checks
       if instance.beparams:
@@ -760,10 +760,10 @@ class ConfigWriter:
                     (mc_now, mc_max))
 
     # node checks
-    for node_name, node in data.nodes.items():
-      if node.name != node_name:
-        result.append("Node '%s' is indexed by wrong name '%s'" %
-                      (node.name, node_name))
+    for node_uuid, node in data.nodes.items():
+      if node.uuid != node_uuid:
+        result.append("Node '%s' is indexed by wrong UUID '%s'" %
+                      (node.name, node_uuid))
       if [node.master_candidate, node.drained, node.offline].count(True) > 1:
         result.append("Node %s state is invalid: master_candidate=%s,"
                       " drain=%s, offline=%s" %
@@ -862,7 +862,7 @@ class ConfigWriter:
     """
     return self._UnlockedVerifyConfig()
 
-  def _UnlockedSetDiskID(self, disk, node_name):
+  def _UnlockedSetDiskID(self, disk, node_uuid):
     """Convert the unique ID to the ID needed on the target nodes.
 
     This is used only for drbd, which needs ip/port configuration.
@@ -876,15 +876,15 @@ class ConfigWriter:
     """
     if disk.children:
       for child in disk.children:
-        self._UnlockedSetDiskID(child, node_name)
+        self._UnlockedSetDiskID(child, node_uuid)
 
     if disk.logical_id is None and disk.physical_id is not None:
       return
     if disk.dev_type == constants.LD_DRBD8:
       pnode, snode, port, pminor, sminor, secret = disk.logical_id
-      if node_name not in (pnode, snode):
+      if node_uuid not in (pnode, snode):
         raise errors.ConfigurationError("DRBD device not knowing node %s" %
-                                        node_name)
+                                        node_uuid)
       pnode_info = self._UnlockedGetNodeInfo(pnode)
       snode_info = self._UnlockedGetNodeInfo(snode)
       if pnode_info is None or snode_info is None:
@@ -892,7 +892,7 @@ class ConfigWriter:
                                         " for %s" % str(disk))
       p_data = (pnode_info.secondary_ip, port)
       s_data = (snode_info.secondary_ip, port)
-      if pnode == node_name:
+      if pnode == node_uuid:
         disk.physical_id = p_data + s_data + (pminor, secret)
       else: # it must be secondary, we tested above
         disk.physical_id = s_data + p_data + (sminor, secret)
@@ -901,7 +901,7 @@ class ConfigWriter:
     return
 
   @locking.ssynchronized(_config_lock)
-  def SetDiskID(self, disk, node_name):
+  def SetDiskID(self, disk, node_uuid):
     """Convert the unique ID to the ID needed on the target nodes.
 
     This is used only for drbd, which needs ip/port configuration.
@@ -911,7 +911,7 @@ class ConfigWriter:
     node.
 
     """
-    return self._UnlockedSetDiskID(disk, node_name)
+    return self._UnlockedSetDiskID(disk, node_uuid)
 
   @locking.ssynchronized(_config_lock)
   def AddTcpUdpPort(self, port):
@@ -961,39 +961,44 @@ class ConfigWriter:
     """Compute the used DRBD minor/nodes.
 
     @rtype: (dict, list)
-    @return: dictionary of node_name: dict of minor: instance_name;
+    @return: dictionary of node_uuid: dict of minor: instance_uuid;
         the returned dict will have all the nodes in it (even if with
         an empty list), and a list of duplicates; if the duplicates
         list is not empty, the configuration is corrupted and its caller
         should raise an exception
 
     """
-    def _AppendUsedPorts(instance_name, disk, used):
+    def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
       duplicates = []
       if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
         node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
-        for node, port in ((node_a, minor_a), (node_b, minor_b)):
-          assert node in used, ("Node '%s' of instance '%s' not found"
-                                " in node list" % (node, instance_name))
-          if port in used[node]:
-            duplicates.append((node, port, instance_name, used[node][port]))
+        for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
+          assert node_uuid in used, \
+            ("Node '%s' of instance '%s' not found in node list" %
+             (get_node_name_fn(node_uuid), instance.name))
+          if minor in used[node_uuid]:
+            duplicates.append((node_uuid, minor, instance.uuid,
+                               used[node_uuid][minor]))
           else:
-            used[node][port] = instance_name
+            used[node_uuid][minor] = instance.uuid
       if disk.children:
         for child in disk.children:
-          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
+          duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
+                                              used))
       return duplicates
 
     duplicates = []
-    my_dict = dict((node, {}) for node in self._config_data.nodes)
+    my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
     for instance in self._config_data.instances.itervalues():
       for disk in instance.disks:
-        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
-    for (node, minor), instance in self._temporary_drbds.iteritems():
-      if minor in my_dict[node] and my_dict[node][minor] != instance:
-        duplicates.append((node, minor, instance, my_dict[node][minor]))
+        duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
+                                            instance, disk, my_dict))
+    for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
+      if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
+        duplicates.append((node_uuid, minor, inst_uuid,
+                           my_dict[node_uuid][minor]))
       else:
-        my_dict[node][minor] = instance
+        my_dict[node_uuid][minor] = inst_uuid
     return my_dict, duplicates
 
   @locking.ssynchronized(_config_lock)
@@ -1002,7 +1007,7 @@ class ConfigWriter:
 
     This is just a wrapper over L{_UnlockedComputeDRBDMap}.
 
-    @return: dictionary of node_name: dict of minor: instance_name;
+    @return: dictionary of node_uuid: dict of minor: instance_uuid;
         the returned dict will have all the nodes in it (even if with
         an empty list).
 
@@ -1014,7 +1019,7 @@ class ConfigWriter:
     return d_map
 
   @locking.ssynchronized(_config_lock)
-  def AllocateDRBDMinor(self, nodes, instance):
+  def AllocateDRBDMinor(self, node_uuids, inst_uuid):
     """Allocate a drbd minor.
 
     The free minor will be automatically computed from the existing
@@ -1022,25 +1027,25 @@ class ConfigWriter:
     multiple minors. The result is the list of minors, in the same
     order as the passed nodes.
 
-    @type instance: string
-    @param instance: the instance for which we allocate minors
+    @type inst_uuid: string
+    @param inst_uuid: the instance for which we allocate minors
 
     """
-    assert isinstance(instance, basestring), \
-           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
+    assert isinstance(inst_uuid, basestring), \
+           "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
 
     d_map, duplicates = self._UnlockedComputeDRBDMap()
     if duplicates:
       raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
                                       str(duplicates))
     result = []
-    for nname in nodes:
-      ndata = d_map[nname]
+    for nuuid in node_uuids:
+      ndata = d_map[nuuid]
       if not ndata:
         # no minors used, we can start at 0
         result.append(0)
-        ndata[0] = instance
-        self._temporary_drbds[(nname, 0)] = instance
+        ndata[0] = inst_uuid
+        self._temporary_drbds[(nuuid, 0)] = inst_uuid
         continue
       keys = ndata.keys()
       keys.sort()
@@ -1052,39 +1057,39 @@ class ConfigWriter:
       else:
         minor = ffree
       # double-check minor against current instances
-      assert minor not in d_map[nname], \
+      assert minor not in d_map[nuuid], \
              ("Attempt to reuse allocated DRBD minor %d on node %s,"
               " already allocated to instance %s" %
-              (minor, nname, d_map[nname][minor]))
-      ndata[minor] = instance
+              (minor, nuuid, d_map[nuuid][minor]))
+      ndata[minor] = inst_uuid
       # double-check minor against reservation
-      r_key = (nname, minor)
+      r_key = (nuuid, minor)
       assert r_key not in self._temporary_drbds, \
              ("Attempt to reuse reserved DRBD minor %d on node %s,"
               " reserved for instance %s" %
-              (minor, nname, self._temporary_drbds[r_key]))
-      self._temporary_drbds[r_key] = instance
+              (minor, nuuid, self._temporary_drbds[r_key]))
+      self._temporary_drbds[r_key] = inst_uuid
       result.append(minor)
     logging.debug("Request to allocate drbd minors, input: %s, returning %s",
-                  nodes, result)
+                  node_uuids, result)
     return result
 
-  def _UnlockedReleaseDRBDMinors(self, instance):
+  def _UnlockedReleaseDRBDMinors(self, inst_uuid):
     """Release temporary drbd minors allocated for a given instance.
 
-    @type instance: string
-    @param instance: the instance for which temporary minors should be
-                     released
+    @type inst_uuid: string
+    @param inst_uuid: the instance for which temporary minors should be
+                      released
 
     """
-    assert isinstance(instance, basestring), \
+    assert isinstance(inst_uuid, basestring), \
            "Invalid argument passed to ReleaseDRBDMinors"
-    for key, name in self._temporary_drbds.items():
-      if name == instance:
+    for key, uuid in self._temporary_drbds.items():
+      if uuid == inst_uuid:
         del self._temporary_drbds[key]
 
   @locking.ssynchronized(_config_lock)
-  def ReleaseDRBDMinors(self, instance):
+  def ReleaseDRBDMinors(self, inst_uuid):
     """Release temporary drbd minors allocated for a given instance.
 
     This should be called on the error paths, on the success paths
@@ -1093,12 +1098,12 @@ class ConfigWriter:
 
     This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
 
-    @type instance: string
-    @param instance: the instance for which temporary minors should be
-                     released
+    @type inst_uuid: string
+    @param inst_uuid: the instance for which temporary minors should be
+                      released
 
     """
-    self._UnlockedReleaseDRBDMinors(instance)
+    self._UnlockedReleaseDRBDMinors(inst_uuid)
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetConfigVersion(self):
@@ -1120,14 +1125,23 @@ class ConfigWriter:
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetMasterNode(self):
-    """Get the hostname of the master node for this cluster.
+    """Get the UUID of the master node for this cluster.
 
-    @return: Master hostname
+    @return: Master node UUID
 
     """
     return self._config_data.cluster.master_node
 
   @locking.ssynchronized(_config_lock, shared=1)
+  def GetMasterNodeName(self):
+    """Get the hostname of the master node for this cluster.
+
+    @return: Master node hostname
+
+    """
+    return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetMasterIP(self):
     """Get the IP of the master node for this cluster.
 
@@ -1214,7 +1228,7 @@ class ConfigWriter:
     """
     cluster = self._config_data.cluster
     result = objects.MasterNetworkParameters(
-      name=cluster.master_node, ip=cluster.master_ip,
+      uuid=cluster.master_node, ip=cluster.master_ip,
       netmask=cluster.master_netmask, netdev=cluster.master_netdev,
       ip_family=cluster.primary_ip_family)
 
@@ -1372,11 +1386,11 @@ class ConfigWriter:
     """Get nodes which are member in the same nodegroups as the given nodes.
 
     """
-    ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
-    return frozenset(member_name
-                     for node_name in nodes
-                     for member_name in
-                       self._UnlockedGetNodeGroup(ngfn(node_name)).members)
+    ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
+    return frozenset(member_uuid
+                     for node_uuid in nodes
+                     for member_uuid in
+                       self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetMultiNodeGroupInfo(self, group_uuids):
@@ -1413,13 +1427,13 @@ class ConfigWriter:
                                         " MAC address '%s' already in use." %
                                         (instance.name, nic.mac))
 
-    self._EnsureUUID(instance, ec_id)
+    self._CheckUniqueUUID(instance, include_temporary=False)
 
     instance.serial_no = 1
     instance.ctime = instance.mtime = time.time()
-    self._config_data.instances[instance.name] = instance
+    self._config_data.instances[instance.uuid] = instance
     self._config_data.cluster.serial_no += 1
-    self._UnlockedReleaseDRBDMinors(instance.name)
+    self._UnlockedReleaseDRBDMinors(instance.uuid)
     self._UnlockedCommitTemporaryIps(ec_id)
     self._WriteConfig()
 
@@ -1432,18 +1446,32 @@ class ConfigWriter:
     """
     if not item.uuid:
       item.uuid = self._GenerateUniqueID(ec_id)
-    elif item.uuid in self._AllIDs(include_temporary=True):
+    else:
+      self._CheckUniqueUUID(item, include_temporary=True)
+
+  def _CheckUniqueUUID(self, item, include_temporary):
+    """Checks that the UUID of the given object is unique.
+
+    @param item: the instance or node to be checked
+    @param include_temporary: whether temporarily generated UUID's should be
+              included in the check. If the UUID of the item to be checked is
+              a temporarily generated one, this has to be C{False}.
+
+    """
+    if not item.uuid:
+      raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
+    if item.uuid in self._AllIDs(include_temporary=include_temporary):
       raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
                                       " in use" % (item.name, item.uuid))
 
-  def _SetInstanceStatus(self, instance_name, status, disks_active):
+  def _SetInstanceStatus(self, inst_uuid, status, disks_active):
     """Set the instance's status to a given value.
 
     """
-    if instance_name not in self._config_data.instances:
+    if inst_uuid not in self._config_data.instances:
       raise errors.ConfigurationError("Unknown instance '%s'" %
-                                      instance_name)
-    instance = self._config_data.instances[instance_name]
+                                      inst_uuid)
+    instance = self._config_data.instances[inst_uuid]
 
     if status is None:
       status = instance.admin_state
@@ -1462,51 +1490,51 @@ class ConfigWriter:
       self._WriteConfig()
 
   @locking.ssynchronized(_config_lock)
-  def MarkInstanceUp(self, instance_name):
+  def MarkInstanceUp(self, inst_uuid):
     """Mark the instance status to up in the config.
 
     This also sets the instance disks active flag.
 
     """
-    self._SetInstanceStatus(instance_name, constants.ADMINST_UP, True)
+    self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
 
   @locking.ssynchronized(_config_lock)
-  def MarkInstanceOffline(self, instance_name):
+  def MarkInstanceOffline(self, inst_uuid):
     """Mark the instance status to down in the config.
 
     This also clears the instance disks active flag.
 
     """
-    self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE, False)
+    self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
 
   @locking.ssynchronized(_config_lock)
-  def RemoveInstance(self, instance_name):
+  def RemoveInstance(self, inst_uuid):
     """Remove the instance from the configuration.
 
     """
-    if instance_name not in self._config_data.instances:
-      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
+    if inst_uuid not in self._config_data.instances:
+      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
 
     # If a network port has been allocated to the instance,
     # return it to the pool of free ports.
-    inst = self._config_data.instances[instance_name]
+    inst = self._config_data.instances[inst_uuid]
     network_port = getattr(inst, "network_port", None)
     if network_port is not None:
       self._config_data.cluster.tcpudp_port_pool.add(network_port)
 
-    instance = self._UnlockedGetInstanceInfo(instance_name)
+    instance = self._UnlockedGetInstanceInfo(inst_uuid)
 
     for nic in instance.nics:
       if nic.network and nic.ip:
         # Return all IP addresses to the respective address pools
         self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
 
-    del self._config_data.instances[instance_name]
+    del self._config_data.instances[inst_uuid]
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
 
   @locking.ssynchronized(_config_lock)
-  def RenameInstance(self, old_name, new_name):
+  def RenameInstance(self, inst_uuid, new_name):
     """Rename an instance.
 
     This needs to be done in ConfigWriter and not by RemoveInstance
@@ -1514,11 +1542,10 @@ class ConfigWriter:
     rename.
 
     """
-    if old_name not in self._config_data.instances:
-      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
+    if inst_uuid not in self._config_data.instances:
+      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
 
-    # Operate on a copy to not loose instance object in case of a failure
-    inst = self._config_data.instances[old_name].Copy()
+    inst = self._config_data.instances[inst_uuid]
     inst.name = new_name
 
     for (idx, disk) in enumerate(inst.disks):
@@ -1530,38 +1557,34 @@ class ConfigWriter:
                                           "disk%s" % idx))
         disk.physical_id = disk.logical_id
 
-    # Actually replace instance object
-    del self._config_data.instances[old_name]
-    self._config_data.instances[inst.name] = inst
-
     # Force update of ssconf files
     self._config_data.cluster.serial_no += 1
 
     self._WriteConfig()
 
   @locking.ssynchronized(_config_lock)
-  def MarkInstanceDown(self, instance_name):
+  def MarkInstanceDown(self, inst_uuid):
     """Mark the status of an instance to down in the configuration.
 
     This does not touch the instance disks active flag, as shut down instances
     can still have active disks.
 
     """
-    self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN, None)
+    self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
 
   @locking.ssynchronized(_config_lock)
-  def MarkInstanceDisksActive(self, instance_name):
+  def MarkInstanceDisksActive(self, inst_uuid):
     """Mark the status of instance disks active.
 
     """
-    self._SetInstanceStatus(instance_name, None, True)
+    self._SetInstanceStatus(inst_uuid, None, True)
 
   @locking.ssynchronized(_config_lock)
-  def MarkInstanceDisksInactive(self, instance_name):
+  def MarkInstanceDisksInactive(self, inst_uuid):
     """Mark the status of instance disks inactive.
 
     """
-    self._SetInstanceStatus(instance_name, None, False)
+    self._SetInstanceStatus(inst_uuid, None, False)
 
   def _UnlockedGetInstanceList(self):
     """Get the list of instances.
@@ -1575,8 +1598,7 @@ class ConfigWriter:
   def GetInstanceList(self):
     """Get the list of instances.
 
-    @return: array of instances, ex. ['instance2.example.com',
-        'instance1.example.com']
+    @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
 
     """
     return self._UnlockedGetInstanceList()
@@ -1585,65 +1607,73 @@ class ConfigWriter:
     """Attempt to expand an incomplete instance name.
 
     """
-    # Locking is done in L{ConfigWriter.GetInstanceList}
-    return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
+    # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
+    all_insts = self.GetAllInstancesInfo().values()
+    expanded_name = _MatchNameComponentIgnoreCase(
+                      short_name, [inst.name for inst in all_insts])
 
-  def _UnlockedGetInstanceInfo(self, instance_name):
+    if expanded_name is not None:
+      # there has to be exactly one instance with that name
+      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
+      return (inst.uuid, inst.name)
+    else:
+      return None
+
+  def _UnlockedGetInstanceInfo(self, inst_uuid):
     """Returns information about an instance.
 
     This function is for internal use, when the config lock is already held.
 
     """
-    if instance_name not in self._config_data.instances:
+    if inst_uuid not in self._config_data.instances:
       return None
 
-    return self._config_data.instances[instance_name]
+    return self._config_data.instances[inst_uuid]
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, inst_uuid):
     """Returns information about an instance.
 
     It takes the information from the configuration file. Other information of
     an instance are taken from the live systems.
 
-    @param instance_name: name of the instance, e.g.
-        I{instance1.example.com}
+    @param inst_uuid: UUID of the instance
 
     @rtype: L{objects.Instance}
     @return: the instance object
 
     """
-    return self._UnlockedGetInstanceInfo(instance_name)
+    return self._UnlockedGetInstanceInfo(inst_uuid)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetInstanceNodeGroups(self, instance_name, primary_only=False):
+  def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
     """Returns set of node group UUIDs for instance's nodes.
 
     @rtype: frozenset
 
     """
-    instance = self._UnlockedGetInstanceInfo(instance_name)
+    instance = self._UnlockedGetInstanceInfo(inst_uuid)
     if not instance:
-      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
+      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
 
     if primary_only:
       nodes = [instance.primary_node]
     else:
       nodes = instance.all_nodes
 
-    return frozenset(self._UnlockedGetNodeInfo(node_name).group
-                     for node_name in nodes)
+    return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
+                     for node_uuid in nodes)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetInstanceNetworks(self, instance_name):
+  def GetInstanceNetworks(self, inst_uuid):
     """Returns set of network UUIDs for instance's nics.
 
     @rtype: frozenset
 
     """
-    instance = self._UnlockedGetInstanceInfo(instance_name)
+    instance = self._UnlockedGetInstanceInfo(inst_uuid)
     if not instance:
-      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
+      raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
 
     networks = set()
     for nic in instance.nics:
@@ -1653,17 +1683,34 @@ class ConfigWriter:
     return frozenset(networks)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetMultiInstanceInfo(self, instances):
+  def GetMultiInstanceInfo(self, inst_uuids):
     """Get the configuration of multiple instances.
 
-    @param instances: list of instance names
+    @param inst_uuids: list of instance UUIDs
+    @rtype: list
+    @return: list of tuples (instance UUID, instance_info), where
+        instance_info is what would GetInstanceInfo return for the
+        node, while keeping the original order
+
+    """
+    return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetMultiInstanceInfoByName(self, inst_names):
+    """Get the configuration of multiple instances.
+
+    @param inst_names: list of instance names
     @rtype: list
     @return: list of tuples (instance, instance_info), where
         instance_info is what would GetInstanceInfo return for the
         node, while keeping the original order
 
     """
-    return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
+    result = []
+    for name in inst_names:
+      instance = self._UnlockedGetInstanceInfoByName(name)
+      result.append((instance.uuid, instance))
+    return result
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetAllInstancesInfo(self):
@@ -1674,8 +1721,11 @@ class ConfigWriter:
               would GetInstanceInfo return for the node
 
     """
-    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
-                    for instance in self._UnlockedGetInstanceList()])
+    return self._UnlockedGetAllInstancesInfo()
+
+  def _UnlockedGetAllInstancesInfo(self):
+    my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
+                    for inst_uuid in self._UnlockedGetInstanceList()])
     return my_dict
 
   @locking.ssynchronized(_config_lock, shared=1)
@@ -1690,10 +1740,61 @@ class ConfigWriter:
       other functions and just compares instance attributes.
 
     """
-    return dict((name, inst)
-                for (name, inst) in self._config_data.instances.items()
+    return dict((uuid, inst)
+                for (uuid, inst) in self._config_data.instances.items()
                 if filter_fn(inst))
 
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetInstanceInfoByName(self, inst_name):
+    """Get the L{objects.Instance} object for a named instance.
+
+    @param inst_name: name of the instance to get information for
+    @type inst_name: string
+    @return: the corresponding L{objects.Instance} instance or None if no
+          information is available
+
+    """
+    return self._UnlockedGetInstanceInfoByName(inst_name)
+
+  def _UnlockedGetInstanceInfoByName(self, inst_name):
+    for inst in self._UnlockedGetAllInstancesInfo().values():
+      if inst.name == inst_name:
+        return inst
+    return None
+
+  def _UnlockedGetInstanceName(self, inst_uuid):
+    inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
+    if inst_info is None:
+      raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
+    return inst_info.name
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetInstanceName(self, inst_uuid):
+    """Gets the instance name for the passed instance.
+
+    @param inst_uuid: instance UUID to get name for
+    @type inst_uuid: string
+    @rtype: string
+    @return: instance name
+
+    """
+    return self._UnlockedGetInstanceName(inst_uuid)
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetInstanceNames(self, inst_uuids):
+    """Gets the instance names for the passed list of nodes.
+
+    @param inst_uuids: list of instance UUIDs to get names for
+    @type inst_uuids: list of strings
+    @rtype: list of strings
+    @return: list of instance names
+
+    """
+    return self._UnlockedGetInstanceNames(inst_uuids)
+
+  def _UnlockedGetInstanceNames(self, inst_uuids):
+    return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
+
   @locking.ssynchronized(_config_lock)
   def AddNode(self, node, ec_id):
     """Add a node to the configuration.
@@ -1708,69 +1809,78 @@ class ConfigWriter:
 
     node.serial_no = 1
     node.ctime = node.mtime = time.time()
-    self._UnlockedAddNodeToGroup(node.name, node.group)
-    self._config_data.nodes[node.name] = node
+    self._UnlockedAddNodeToGroup(node.uuid, node.group)
+    self._config_data.nodes[node.uuid] = node
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
 
   @locking.ssynchronized(_config_lock)
-  def RemoveNode(self, node_name):
+  def RemoveNode(self, node_uuid):
     """Remove a node from the configuration.
 
     """
-    logging.info("Removing node %s from configuration", node_name)
+    logging.info("Removing node %s from configuration", node_uuid)
 
-    if node_name not in self._config_data.nodes:
-      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
+    if node_uuid not in self._config_data.nodes:
+      raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
 
-    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
-    del self._config_data.nodes[node_name]
+    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
+    del self._config_data.nodes[node_uuid]
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
 
   def ExpandNodeName(self, short_name):
-    """Attempt to expand an incomplete node name.
+    """Attempt to expand an incomplete node name into a node UUID.
 
     """
-    # Locking is done in L{ConfigWriter.GetNodeList}
-    return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
+    # Locking is done in L{ConfigWriter.GetAllNodesInfo}
+    all_nodes = self.GetAllNodesInfo().values()
+    expanded_name = _MatchNameComponentIgnoreCase(
+                      short_name, [node.name for node in all_nodes])
 
-  def _UnlockedGetNodeInfo(self, node_name):
+    if expanded_name is not None:
+      # there has to be exactly one node with that name
+      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
+      return (node.uuid, node.name)
+    else:
+      return None
+
+  def _UnlockedGetNodeInfo(self, node_uuid):
     """Get the configuration of a node, as stored in the config.
 
     This function is for internal use, when the config lock is already
     held.
 
-    @param node_name: the node name, e.g. I{node1.example.com}
+    @param node_uuid: the node UUID
 
     @rtype: L{objects.Node}
     @return: the node object
 
     """
-    if node_name not in self._config_data.nodes:
+    if node_uuid not in self._config_data.nodes:
       return None
 
-    return self._config_data.nodes[node_name]
+    return self._config_data.nodes[node_uuid]
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetNodeInfo(self, node_name):
+  def GetNodeInfo(self, node_uuid):
     """Get the configuration of a node, as stored in the config.
 
     This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
 
-    @param node_name: the node name, e.g. I{node1.example.com}
+    @param node_uuid: the node UUID
 
     @rtype: L{objects.Node}
     @return: the node object
 
     """
-    return self._UnlockedGetNodeInfo(node_name)
+    return self._UnlockedGetNodeInfo(node_uuid)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetNodeInstances(self, node_name):
+  def GetNodeInstances(self, node_uuid):
     """Get the instances of a node, as stored in the config.
 
-    @param node_name: the node name, e.g. I{node1.example.com}
+    @param node_uuid: the node UUID
 
     @rtype: (list, list)
     @return: a tuple with two lists: the primary and the secondary instances
@@ -1779,10 +1889,10 @@ class ConfigWriter:
     pri = []
     sec = []
     for inst in self._config_data.instances.values():
-      if inst.primary_node == node_name:
-        pri.append(inst.name)
-      if node_name in inst.secondary_nodes:
-        sec.append(inst.name)
+      if inst.primary_node == node_uuid:
+        pri.append(inst.uuid)
+      if node_uuid in inst.secondary_nodes:
+        sec.append(inst.uuid)
     return (pri, sec)
 
   @locking.ssynchronized(_config_lock, shared=1)
@@ -1792,7 +1902,7 @@ class ConfigWriter:
     @param uuid: Node group UUID
     @param primary_only: Whether to only consider primary nodes
     @rtype: frozenset
-    @return: List of instance names in node group
+    @return: List of instance UUIDs in node group
 
     """
     if primary_only:
@@ -1800,10 +1910,36 @@ class ConfigWriter:
     else:
       nodes_fn = lambda inst: inst.all_nodes
 
-    return frozenset(inst.name
+    return frozenset(inst.uuid
                      for inst in self._config_data.instances.values()
-                     for node_name in nodes_fn(inst)
-                     if self._UnlockedGetNodeInfo(node_name).group == uuid)
+                     for node_uuid in nodes_fn(inst)
+                     if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
+
+  def _UnlockedGetHvparamsString(self, hvname):
+    """Return the string representation of the list of hyervisor parameters of
+    the given hypervisor.
+
+    @see: C{GetHvparams}
+
+    """
+    result = ""
+    hvparams = self._config_data.cluster.hvparams[hvname]
+    for key in hvparams:
+      result += "%s=%s\n" % (key, hvparams[key])
+    return result
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetHvparamsString(self, hvname):
+    """Return the hypervisor parameters of the given hypervisor.
+
+    @type hvname: string
+    @param hvname: name of a hypervisor
+    @rtype: string
+    @return: string containing key-value-pairs, one pair on each line;
+      format: KEY=VALUE
+
+    """
+    return self._UnlockedGetHvparamsString(hvname)
 
   def _UnlockedGetNodeList(self):
     """Return the list of nodes which are in the configuration.
@@ -1829,7 +1965,7 @@ class ConfigWriter:
     """
     all_nodes = [self._UnlockedGetNodeInfo(node)
                  for node in self._UnlockedGetNodeList()]
-    return [node.name for node in all_nodes if not node.offline]
+    return [node.uuid for node in all_nodes if not node.offline]
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetOnlineNodeList(self):
@@ -1845,7 +1981,7 @@ class ConfigWriter:
     """
     all_nodes = [self._UnlockedGetNodeInfo(node)
                  for node in self._UnlockedGetNodeList()]
-    return [node.name for node in all_nodes if node.vm_capable]
+    return [node.uuid for node in all_nodes if node.vm_capable]
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetNonVmCapableNodeList(self):
@@ -1854,20 +1990,29 @@ class ConfigWriter:
     """
     all_nodes = [self._UnlockedGetNodeInfo(node)
                  for node in self._UnlockedGetNodeList()]
-    return [node.name for node in all_nodes if not node.vm_capable]
+    return [node.uuid for node in all_nodes if not node.vm_capable]
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetMultiNodeInfo(self, nodes):
+  def GetMultiNodeInfo(self, node_uuids):
     """Get the configuration of multiple nodes.
 
-    @param nodes: list of node names
+    @param node_uuids: list of node UUIDs
     @rtype: list
     @return: list of tuples of (node, node_info), where node_info is
         what would GetNodeInfo return for the node, in the original
         order
 
     """
-    return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
+    return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
+
+  def _UnlockedGetAllNodesInfo(self):
+    """Gets configuration of all nodes.
+
+    @note: See L{GetAllNodesInfo}
+
+    """
+    return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
+                 for node_uuid in self._UnlockedGetNodeList()])
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetAllNodesInfo(self):
@@ -1880,25 +2025,73 @@ class ConfigWriter:
     """
     return self._UnlockedGetAllNodesInfo()
 
-  def _UnlockedGetAllNodesInfo(self):
-    """Gets configuration of all nodes.
+  def _UnlockedGetNodeInfoByName(self, node_name):
+    for node in self._UnlockedGetAllNodesInfo().values():
+      if node.name == node_name:
+        return node
+    return None
 
-    @note: See L{GetAllNodesInfo}
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetNodeInfoByName(self, node_name):
+    """Get the L{objects.Node} object for a named node.
+
+    @param node_name: name of the node to get information for
+    @type node_name: string
+    @return: the corresponding L{objects.Node} instance or None if no
+          information is available
+
+    """
+    return self._UnlockedGetNodeInfoByName(node_name)
+
+  def _UnlockedGetNodeName(self, node_spec):
+    if isinstance(node_spec, objects.Node):
+      return node_spec.name
+    elif isinstance(node_spec, basestring):
+      node_info = self._UnlockedGetNodeInfo(node_spec)
+      if node_info is None:
+        raise errors.OpExecError("Unknown node: %s" % node_spec)
+      return node_info.name
+    else:
+      raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetNodeName(self, node_spec):
+    """Gets the node name for the passed node.
+
+    @param node_spec: node to get names for
+    @type node_spec: either node UUID or a L{objects.Node} object
+    @rtype: string
+    @return: node name
+
+    """
+    return self._UnlockedGetNodeName(node_spec)
+
+  def _UnlockedGetNodeNames(self, node_specs):
+    return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetNodeNames(self, node_specs):
+    """Gets the node names for the passed list of nodes.
+
+    @param node_specs: list of nodes to get names for
+    @type node_specs: list of either node UUIDs or L{objects.Node} objects
+    @rtype: list of strings
+    @return: list of node names
 
     """
-    return dict([(node, self._UnlockedGetNodeInfo(node))
-                 for node in self._UnlockedGetNodeList()])
+    return self._UnlockedGetNodeNames(node_specs)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetNodeGroupsFromNodes(self, nodes):
+  def GetNodeGroupsFromNodes(self, node_uuids):
     """Returns groups for a list of nodes.
 
-    @type nodes: list of string
-    @param nodes: List of node names
+    @type node_uuids: list of string
+    @param node_uuids: List of node UUIDs
     @rtype: frozenset
 
     """
-    return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
+    return frozenset(self._UnlockedGetNodeInfo(uuid).group
+                     for uuid in node_uuids)
 
   def _UnlockedGetMasterCandidateStats(self, exceptions=None):
     """Get the number of current and maximum desired and possible candidates.
@@ -1911,7 +2104,7 @@ class ConfigWriter:
     """
     mc_now = mc_should = mc_max = 0
     for node in self._config_data.nodes.values():
-      if exceptions and node.name in exceptions:
+      if exceptions and node.uuid in exceptions:
         continue
       if not (node.offline or node.drained) and node.master_capable:
         mc_max += 1
@@ -1935,26 +2128,27 @@ class ConfigWriter:
     return self._UnlockedGetMasterCandidateStats(exceptions)
 
   @locking.ssynchronized(_config_lock)
-  def MaintainCandidatePool(self, exceptions):
+  def MaintainCandidatePool(self, exception_node_uuids):
     """Try to grow the candidate pool to the desired size.
 
-    @type exceptions: list
-    @param exceptions: if passed, list of nodes that should be ignored
+    @type exception_node_uuids: list
+    @param exception_node_uuids: if passed, list of nodes that should be ignored
     @rtype: list
     @return: list with the adjusted nodes (L{objects.Node} instances)
 
     """
-    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
+    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
+                          exception_node_uuids)
     mod_list = []
     if mc_now < mc_max:
       node_list = self._config_data.nodes.keys()
       random.shuffle(node_list)
-      for name in node_list:
+      for uuid in node_list:
         if mc_now >= mc_max:
           break
-        node = self._config_data.nodes[name]
+        node = self._config_data.nodes[uuid]
         if (node.master_candidate or node.offline or node.drained or
-            node.name in exceptions or not node.master_capable):
+            node.uuid in exception_node_uuids or not node.master_capable):
           continue
         mod_list.append(node)
         node.master_candidate = True
@@ -1970,7 +2164,7 @@ class ConfigWriter:
 
     return mod_list
 
-  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
+  def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
     """Add a given node to the specified group.
 
     """
@@ -1980,8 +2174,8 @@ class ConfigWriter:
       # the meantime. It's ok though, as we'll fail cleanly if the node group
       # is not found anymore.
       raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
-    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
-      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
+    if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
+      self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
 
   def _UnlockedRemoveNodeFromGroup(self, node):
     """Remove a given node from its group.
@@ -1990,13 +2184,13 @@ class ConfigWriter:
     nodegroup = node.group
     if nodegroup not in self._config_data.nodegroups:
       logging.warning("Warning: node '%s' has unknown node group '%s'"
-                      " (while being removed from it)", node.name, nodegroup)
+                      " (while being removed from it)", node.uuid, nodegroup)
     nodegroup_obj = self._config_data.nodegroups[nodegroup]
-    if node.name not in nodegroup_obj.members:
+    if node.uuid not in nodegroup_obj.members:
       logging.warning("Warning: node '%s' not a member of its node group '%s'"
-                      " (while being removed from it)", node.name, nodegroup)
+                      " (while being removed from it)", node.uuid, nodegroup)
     else:
-      nodegroup_obj.members.remove(node.name)
+      nodegroup_obj.members.remove(node.uuid)
 
   @locking.ssynchronized(_config_lock)
   def AssignGroupNodes(self, mods):
@@ -2011,17 +2205,17 @@ class ConfigWriter:
 
     resmod = []
 
-    # Try to resolve names/UUIDs first
-    for (node_name, new_group_uuid) in mods:
+    # Try to resolve UUIDs first
+    for (node_uuid, new_group_uuid) in mods:
       try:
-        node = nodes[node_name]
+        node = nodes[node_uuid]
       except KeyError:
-        raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
+        raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
 
       if node.group == new_group_uuid:
         # Node is being assigned to its current group
         logging.debug("Node '%s' was assigned to its current group (%s)",
-                      node_name, node.group)
+                      node_uuid, node.group)
         continue
 
       # Try to find current group of node
@@ -2038,12 +2232,12 @@ class ConfigWriter:
         raise errors.ConfigurationError("Unable to find new group '%s'" %
                                         new_group_uuid)
 
-      assert node.name in old_group.members, \
+      assert node.uuid in old_group.members, \
         ("Inconsistent configuration: node '%s' not listed in members for its"
-         " old group '%s'" % (node.name, old_group.uuid))
-      assert node.name not in new_group.members, \
+         " old group '%s'" % (node.uuid, old_group.uuid))
+      assert node.uuid not in new_group.members, \
         ("Inconsistent configuration: node '%s' already listed in members for"
-         " its new group '%s'" % (node.name, new_group.uuid))
+         " its new group '%s'" % (node.uuid, new_group.uuid))
 
       resmod.append((node, old_group, new_group))
 
@@ -2055,10 +2249,10 @@ class ConfigWriter:
       node.group = new_group.uuid
 
       # Update members of involved groups
-      if node.name in old_group.members:
-        old_group.members.remove(node.name)
-      if node.name not in new_group.members:
-        new_group.members.append(node.name)
+      if node.uuid in old_group.members:
+        old_group.members.remove(node.uuid)
+      if node.uuid not in new_group.members:
+        new_group.members.append(node.uuid)
 
     # Update timestamps and serials (only once per node/group object)
     now = time.time()
@@ -2109,11 +2303,18 @@ class ConfigWriter:
       raise errors.ConfigurationError("Incomplete configuration"
                                       " (missing cluster.rsahostkeypub)")
 
-    if data.cluster.master_node != self._my_hostname and not accept_foreign:
+    if not data.cluster.master_node in data.nodes:
+      msg = ("The configuration denotes node %s as master, but does not"
+             " contain information about this node" %
+             data.cluster.master_node)
+      raise errors.ConfigurationError(msg)
+
+    master_info = data.nodes[data.cluster.master_node]
+    if master_info.name != self._my_hostname and not accept_foreign:
       msg = ("The configuration denotes node %s as master, while my"
              " hostname is %s; opening a foreign configuration is only"
              " possible in accept_foreign mode" %
-             (data.cluster.master_node, self._my_hostname))
+             (master_info.name, self._my_hostname))
       raise errors.ConfigurationError(msg)
 
     self._config_data = data
@@ -2162,7 +2363,7 @@ class ConfigWriter:
       # nodegroups are being added, and upon normally loading the config,
       # because the members list of a node group is discarded upon
       # serializing/deserializing the object.
-      self._UnlockedAddNodeToGroup(node.name, node.group)
+      self._UnlockedAddNodeToGroup(node.uuid, node.group)
 
     modified = (oldconf != self._config_data.ToDict())
     if modified:
@@ -2196,11 +2397,9 @@ class ConfigWriter:
     # since the node list comes from _UnlocketGetNodeList, and we are
     # called with the lock held, so no modifications should take place
     # in between
-    for node_name in self._UnlockedGetNodeList():
-      if node_name == myhostname:
-        continue
-      node_info = self._UnlockedGetNodeInfo(node_name)
-      if not node_info.master_candidate:
+    for node_uuid in self._UnlockedGetNodeList():
+      node_info = self._UnlockedGetNodeInfo(node_uuid)
+      if node_info.name == myhostname or not node_info.master_candidate:
         continue
       node_list.append(node_info.name)
       addr_list.append(node_info.primary_ip)
@@ -2267,7 +2466,7 @@ class ConfigWriter:
     if self._last_cluster_serial < self._config_data.cluster.serial_no:
       if not self._offline:
         result = self._GetRpc(None).call_write_ssconf_files(
-          self._UnlockedGetOnlineNodeList(),
+          self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
           self._UnlockedGetSsconfValues())
 
         for nname, nresu in result.items():
@@ -2282,6 +2481,40 @@ class ConfigWriter:
 
       self._last_cluster_serial = self._config_data.cluster.serial_no
 
+  def _GetAllHvparamsStrings(self, hypervisors):
+    """Get the hvparams of all given hypervisors from the config.
+
+    @type hypervisors: list of string
+    @param hypervisors: list of hypervisor names
+    @rtype: dict of strings
+    @returns: dictionary mapping the hypervisor name to a string representation
+      of the hypervisor's hvparams
+
+    """
+    hvparams = {}
+    for hv in hypervisors:
+      hvparams[hv] = self._UnlockedGetHvparamsString(hv)
+    return hvparams
+
+  @staticmethod
+  def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
+    """Extends the ssconf_values dictionary by hvparams.
+
+    @type ssconf_values: dict of strings
+    @param ssconf_values: dictionary mapping ssconf_keys to strings
+      representing the content of ssconf files
+    @type all_hvparams: dict of strings
+    @param all_hvparams: dictionary mapping hypervisor names to a string
+      representation of their hvparams
+    @rtype: same as ssconf_values
+    @returns: the ssconf_values dictionary extended by hvparams
+
+    """
+    for hv in all_hvparams:
+      ssconf_key = constants.SS_HVPARAMS_PREF + hv
+      ssconf_values[ssconf_key] = all_hvparams[hv]
+    return ssconf_values
+
   def _UnlockedGetSsconfValues(self):
     """Return the values needed by ssconf.
 
@@ -2291,19 +2524,21 @@ class ConfigWriter:
 
     """
     fn = "\n".join
-    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
-    node_names = utils.NiceSort(self._UnlockedGetNodeList())
-    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
+    instance_names = utils.NiceSort(
+                       [inst.name for inst in
+                        self._UnlockedGetAllInstancesInfo().values()])
+    node_infos = self._UnlockedGetAllNodesInfo().values()
+    node_names = [node.name for node in node_infos]
     node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
-                    for ninfo in node_info]
+                    for ninfo in node_infos]
     node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
-                    for ninfo in node_info]
+                    for ninfo in node_infos]
 
     instance_data = fn(instance_names)
-    off_data = fn(node.name for node in node_info if node.offline)
-    on_data = fn(node.name for node in node_info if not node.offline)
-    mc_data = fn(node.name for node in node_info if node.master_candidate)
-    mc_ips_data = fn(node.primary_ip for node in node_info
+    off_data = fn(node.name for node in node_infos if node.offline)
+    on_data = fn(node.name for node in node_infos if not node.offline)
+    mc_data = fn(node.name for node in node_infos if node.master_candidate)
+    mc_ips_data = fn(node.primary_ip for node in node_infos
                      if node.master_candidate)
     node_data = fn(node_names)
     node_pri_ips_data = fn(node_pri_ips)
@@ -2313,6 +2548,7 @@ class ConfigWriter:
     cluster_tags = fn(cluster.GetTags())
 
     hypervisor_list = fn(cluster.enabled_hypervisors)
+    all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
 
     uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
 
@@ -2333,7 +2569,7 @@ class ConfigWriter:
       constants.SS_MASTER_IP: cluster.master_ip,
       constants.SS_MASTER_NETDEV: cluster.master_netdev,
       constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
-      constants.SS_MASTER_NODE: cluster.master_node,
+      constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
       constants.SS_NODE_LIST: node_data,
       constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
       constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
@@ -2348,6 +2584,8 @@ class ConfigWriter:
       constants.SS_NODEGROUPS: nodegroups_data,
       constants.SS_NETWORKS: networks_data,
       }
+    ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
+                                                     all_hvparams)
     bad_values = [(k, v) for k, v in ssconf_values.items()
                   if not isinstance(v, (str, basestring))]
     if bad_values:
@@ -2465,7 +2703,7 @@ class ConfigWriter:
       self._config_data.cluster.mtime = now
 
     if isinstance(target, objects.Instance):
-      self._UnlockedReleaseDRBDMinors(target.name)
+      self._UnlockedReleaseDRBDMinors(target.uuid)
 
     if ec_id is not None:
       # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
@@ -2616,34 +2854,34 @@ class ConfigWriter:
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
 
-  def _UnlockedGetGroupNetParams(self, net_uuid, node):
+  def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
     """Get the netparams (mode, link) of a network.
 
     Get a network's netparams for a given node.
 
     @type net_uuid: string
     @param net_uuid: network uuid
-    @type node: string
-    @param node: node name
+    @type node_uuid: string
+    @param node_uuid: node UUID
     @rtype: dict or None
     @return: netparams
 
     """
-    node_info = self._UnlockedGetNodeInfo(node)
+    node_info = self._UnlockedGetNodeInfo(node_uuid)
     nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
     netparams = nodegroup_info.networks.get(net_uuid, None)
 
     return netparams
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetGroupNetParams(self, net_uuid, node):
+  def GetGroupNetParams(self, net_uuid, node_uuid):
     """Locking wrapper of _UnlockedGetGroupNetParams()
 
     """
-    return self._UnlockedGetGroupNetParams(net_uuid, node)
+    return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def CheckIPInNodeGroup(self, ip, node):
+  def CheckIPInNodeGroup(self, ip, node_uuid):
     """Check IP uniqueness in nodegroup.
 
     Check networks that are connected in the node's node group
@@ -2652,15 +2890,15 @@ class ConfigWriter:
 
     @type ip: string
     @param ip: ip address
-    @type node: string
-    @param node: node name
+    @type node_uuid: string
+    @param node_uuid: node UUID
     @rtype: (string, dict) or (None, None)
     @return: (network name, netparams)
 
     """
     if ip is None:
       return (None, None)
-    node_info = self._UnlockedGetNodeInfo(node)
+    node_info = self._UnlockedGetNodeInfo(node_uuid)
     nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
     for net_uuid in nodegroup_info.networks.keys():
       net_info = self._UnlockedGetNetwork(net_uuid)
index 1eaf88c..1e43c5c 100644 (file)
@@ -250,8 +250,6 @@ XEN_KERNEL = _autoconf.XEN_KERNEL
 XEN_INITRD = _autoconf.XEN_INITRD
 XEN_CMD_XM = "xm"
 XEN_CMD_XL = "xl"
-# FIXME: This will be made configurable using hvparams in Ganeti 2.7
-XEN_CMD = _autoconf.XEN_CMD
 
 KNOWN_XEN_COMMANDS = compat.UniqueFrozenset([
   XEN_CMD_XM,
@@ -430,6 +428,12 @@ VALID_STORAGE_OPERATIONS = {
  LDS_UNKNOWN,
  LDS_FAULTY) = range(1, 4)
 
+LDS_NAMES = {
+  LDS_OKAY: "ok",
+  LDS_UNKNOWN: "unknown",
+  LDS_FAULTY: "faulty",
+}
+
 # disk template types
 DT_BLOCK = "blockdev"
 DT_DISKLESS = "diskless"
@@ -467,10 +471,10 @@ DISK_TEMPLATES = compat.UniqueFrozenset([
   ])
 
 # disk templates that are enabled by default
-DEFAULT_ENABLED_DISK_TEMPLATES = compat.UniqueFrozenset([
+DEFAULT_ENABLED_DISK_TEMPLATES = [
   DT_DRBD8,
   DT_PLAIN,
-  ])
+  ]
 
 # mapping of disk templates to storage types
 DISK_TEMPLATES_STORAGE_TYPE = {
@@ -944,6 +948,7 @@ HV_KVM_EXTRA = "kvm_extra"
 HV_KVM_MACHINE_VERSION = "machine_version"
 HV_KVM_PATH = "kvm_path"
 HV_VIF_TYPE = "vif_type"
+HV_XEN_CMD = "xen_cmd"
 
 
 HVS_PARAMETER_TYPES = {
@@ -1014,6 +1019,7 @@ HVS_PARAMETER_TYPES = {
   HV_KVM_EXTRA: VTYPE_STRING,
   HV_KVM_MACHINE_VERSION: VTYPE_STRING,
   HV_VIF_TYPE: VTYPE_STRING,
+  HV_XEN_CMD: VTYPE_STRING,
   }
 
 HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys())
@@ -1326,6 +1332,7 @@ NICS_PARAMETERS = frozenset(NICS_PARAMETER_TYPES.keys())
 
 # IDISK_* constants are used in opcodes, to create/change disks
 IDISK_SIZE = "size"
+IDISK_SPINDLES = "spindles"
 IDISK_MODE = "mode"
 IDISK_ADOPT = "adopt"
 IDISK_VG = "vg"
@@ -1334,6 +1341,7 @@ IDISK_PROVIDER = "provider"
 IDISK_NAME = "name"
 IDISK_PARAMS_TYPES = {
   IDISK_SIZE: VTYPE_SIZE,
+  IDISK_SPINDLES: VTYPE_INT,
   IDISK_MODE: VTYPE_STRING,
   IDISK_ADOPT: VTYPE_STRING,
   IDISK_VG: VTYPE_STRING,
@@ -1587,8 +1595,13 @@ CV_EINSTANCEPOLICY = \
 CV_EINSTANCEUNSUITABLENODE = \
   (CV_TINSTANCE, "EINSTANCEUNSUITABLENODE",
    "Instance running on nodes that are not suitable for it")
+CV_EINSTANCEMISSINGCFGPARAMETER = \
+  (CV_TINSTANCE, "EINSTANCEMISSINGCFGPARAMETER",
+   "A configuration parameter for an instance is missing")
 CV_ENODEDRBD = \
   (CV_TNODE, "ENODEDRBD", "Error parsing the DRBD status file")
+CV_ENODEDRBDVERSION = \
+  (CV_TNODE, "ENODEDRBDVERSION", "DRBD version mismatch within a node group")
 CV_ENODEDRBDHELPER = \
   (CV_TNODE, "ENODEDRBDHELPER", "Error caused by the DRBD helper")
 CV_ENODEFILECHECK = \
@@ -1670,6 +1683,7 @@ CV_ALL_ECODES_STRINGS = \
 # Node verify constants
 NV_BRIDGES = "bridges"
 NV_DRBDHELPER = "drbd-helper"
+NV_DRBDVERSION = "drbd-version"
 NV_DRBDLIST = "drbd-list"
 NV_EXCLUSIVEPVS = "exclusive-pvs"
 NV_FILELIST = "filelist"
@@ -1997,6 +2011,26 @@ SS_UID_POOL = "uid_pool"
 SS_NODEGROUPS = "nodegroups"
 SS_NETWORKS = "networks"
 
+# This is not a complete SSCONF key, but the prefix for the hypervisor keys
+SS_HVPARAMS_PREF = "hvparams_"
+
+# Hvparams keys:
+SS_HVPARAMS_XEN_PVM = SS_HVPARAMS_PREF + HT_XEN_PVM
+SS_HVPARAMS_XEN_FAKE = SS_HVPARAMS_PREF + HT_FAKE
+SS_HVPARAMS_XEN_HVM = SS_HVPARAMS_PREF + HT_XEN_HVM
+SS_HVPARAMS_XEN_KVM = SS_HVPARAMS_PREF + HT_KVM
+SS_HVPARAMS_XEN_CHROOT = SS_HVPARAMS_PREF + HT_CHROOT
+SS_HVPARAMS_XEN_LXC = SS_HVPARAMS_PREF + HT_LXC
+
+VALID_SS_HVPARAMS_KEYS = compat.UniqueFrozenset([
+  SS_HVPARAMS_XEN_PVM,
+  SS_HVPARAMS_XEN_FAKE,
+  SS_HVPARAMS_XEN_HVM,
+  SS_HVPARAMS_XEN_KVM,
+  SS_HVPARAMS_XEN_CHROOT,
+  SS_HVPARAMS_XEN_LXC,
+  ])
+
 SS_FILE_PERMS = 0444
 
 # cluster wide default parameters
@@ -2018,6 +2052,7 @@ HVC_DEFAULTS = {
     HV_CPU_MASK: CPU_PINNING_ALL,
     HV_CPU_CAP: 0,
     HV_CPU_WEIGHT: 256,
+    HV_XEN_CMD: XEN_CMD_XM,
     },
   HT_XEN_HVM: {
     HV_BOOT_ORDER: "cd",
@@ -2040,6 +2075,7 @@ HVC_DEFAULTS = {
     HV_CPU_CAP: 0,
     HV_CPU_WEIGHT: 256,
     HV_VIF_TYPE: HT_HVM_VIF_IOEMU,
+    HV_XEN_CMD: XEN_CMD_XM,
     },
   HT_KVM: {
     HV_KVM_PATH: KVM_PATH,
@@ -2099,7 +2135,9 @@ HVC_DEFAULTS = {
     HV_KVM_EXTRA: "",
     HV_KVM_MACHINE_VERSION: "",
     },
-  HT_FAKE: {},
+  HT_FAKE: {
+    HV_MIGRATION_MODE: HT_MIGRATION_LIVE,
+  },
   HT_CHROOT: {
     HV_INIT_SCRIPT: "/ganeti-chroot",
     },
@@ -2112,6 +2150,7 @@ HVC_GLOBALS = compat.UniqueFrozenset([
   HV_MIGRATION_PORT,
   HV_MIGRATION_BANDWIDTH,
   HV_MIGRATION_MODE,
+  HV_XEN_CMD,
   ])
 
 BEC_DEFAULTS = {
@@ -2466,5 +2505,7 @@ OPCODE_REASON_SOURCES = compat.UniqueFrozenset([
   OPCODE_REASON_SRC_USER,
   ])
 
+DISKSTATS_FILE = "/proc/diskstats"
+
 # Do not re-export imported modules
 del re, _vcsversion, _autoconf, socket, pathutils, compat
index 62ea1f4..0ef1f6e 100644 (file)
@@ -712,12 +712,12 @@ def GenericMain(daemon_name, optionparser,
                           default=constants.SYSLOG_USAGE,
                           choices=["no", "yes", "only"])
 
+  family = ssconf.SimpleStore().GetPrimaryIPFamily()
+  # family will default to AF_INET if there is no ssconf file (e.g. when
+  # upgrading a cluster from 2.2 -> 2.3. This is intended, as Ganeti clusters
+  # <= 2.2 can not be AF_INET6
   if daemon_name in constants.DAEMONS_PORTS:
     default_bind_address = constants.IP4_ADDRESS_ANY
-    family = ssconf.SimpleStore().GetPrimaryIPFamily()
-    # family will default to AF_INET if there is no ssconf file (e.g. when
-    # upgrading a cluster from 2.2 -> 2.3. This is intended, as Ganeti clusters
-    # <= 2.2 can not be AF_INET6
     if family == netutils.IP6Address.family:
       default_bind_address = constants.IP6_ADDRESS_ANY
 
@@ -731,6 +731,8 @@ def GenericMain(daemon_name, optionparser,
                             help=("Bind address (default: '%s')" %
                                   default_bind_address),
                             default=default_bind_address, metavar="ADDRESS")
+    optionparser.add_option("-i", "--interface", dest="bind_interface",
+                            help=("Bind interface"), metavar="INTERFACE")
 
   if default_ssl_key is not None and default_ssl_cert is not None:
     optionparser.add_option("--no-ssl", dest="ssl",
@@ -753,6 +755,24 @@ def GenericMain(daemon_name, optionparser,
 
   options, args = optionparser.parse_args()
 
+  if getattr(options, "bind_interface", None) is not None:
+    if options.bind_address != default_bind_address:
+      msg = ("Can't specify both, bind address (%s) and bind interface (%s)" %
+             (options.bind_address, options.bind_interface))
+      print >> sys.stderr, msg
+      sys.exit(constants.EXIT_FAILURE)
+    interface_ip_addresses = \
+      netutils.GetInterfaceIpAddresses(options.bind_interface)
+    if family == netutils.IP6Address.family:
+      if_addresses = interface_ip_addresses[constants.IP6_VERSION]
+    else:
+      if_addresses = interface_ip_addresses[constants.IP4_VERSION]
+    if len(if_addresses) < 1:
+      msg = "Failed to find IP for interface %s" % options.bind_interace
+      print >> sys.stderr, msg
+      sys.exit(constants.EXIT_FAILURE)
+    options.bind_address = if_addresses[0]
+
   if getattr(options, "ssl", False):
     ssl_paths = {
       "certificate": options.ssl_cert,
index 9f40087..515a2a8 100644 (file)
@@ -171,40 +171,41 @@ class HooksMaster(object):
 
     return self.hooks_execution_fn(node_list, hpath, phase, env)
 
-  def RunPhase(self, phase, nodes=None):
+  def RunPhase(self, phase, node_names=None):
     """Run all the scripts for a phase.
 
     This is the main function of the HookMaster.
     It executes self.hooks_execution_fn, and after running
-    self.hooks_results_adapt_fn on its results it expects them to be in the form
-    {node_name: (fail_msg, [(script, result, output), ...]}).
+    self.hooks_results_adapt_fn on its results it expects them to be in the
+    form {node_name: (fail_msg, [(script, result, output), ...]}).
 
     @param phase: one of L{constants.HOOKS_PHASE_POST} or
         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
-    @param nodes: overrides the predefined list of nodes for the given phase
+    @param node_names: overrides the predefined list of nodes for the given
+        phase
     @return: the processed results of the hooks multi-node rpc call
     @raise errors.HooksFailure: on communication failure to the nodes
     @raise errors.HooksAbort: on failure of one of the hooks
 
     """
     if phase == constants.HOOKS_PHASE_PRE:
-      if nodes is None:
-        nodes = self.pre_nodes
+      if node_names is None:
+        node_names = self.pre_nodes
       env = self.pre_env
     elif phase == constants.HOOKS_PHASE_POST:
-      if nodes is None:
-        nodes = self.post_nodes
+      if node_names is None:
+        node_names = self.post_nodes
       env = self._BuildEnv(phase)
     else:
       raise AssertionError("Unknown phase '%s'" % phase)
 
-    if not nodes:
+    if not node_names:
       # empty node list, we should not attempt to run this as either
       # we're in the cluster init phase and the rpc client part can't
       # even attempt to run, or this LU doesn't do hooks at all
       return
 
-    results = self._RunWrapper(nodes, self.hooks_path, phase, env)
+    results = self._RunWrapper(node_names, self.hooks_path, phase, env)
     if not results:
       msg = "Communication Failure"
       if phase == constants.HOOKS_PHASE_PRE:
@@ -258,11 +259,20 @@ class HooksMaster(object):
     if lu.HPATH is None:
       nodes = (None, None)
     else:
-      nodes = map(frozenset, lu.BuildHooksNodes())
+      hooks_nodes = lu.BuildHooksNodes()
+      to_name = lambda node_uuids: frozenset(lu.cfg.GetNodeNames(node_uuids))
+      if len(hooks_nodes) == 2:
+        nodes = (to_name(hooks_nodes[0]), to_name(hooks_nodes[1]))
+      elif len(hooks_nodes) == 3:
+        nodes = (to_name(hooks_nodes[0]),
+                 to_name(hooks_nodes[1]) | frozenset(hooks_nodes[2]))
+      else:
+        raise errors.ProgrammerError(
+          "LogicalUnit.BuildHooksNodes must return a 2- or 3-tuple")
 
     master_name = cluster_name = None
     if lu.cfg:
-      master_name = lu.cfg.GetMasterNode()
+      master_name = lu.cfg.GetMasterNodeName()
       cluster_name = lu.cfg.GetClusterName()
 
     return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
index 574ddc9..b6f0504 100644 (file)
@@ -159,7 +159,7 @@ class _HttpClientToServerMessageReader(http.HttpMessageReader):
 
   """
   # Length limits
-  START_LINE_LENGTH_MAX = 4096
+  START_LINE_LENGTH_MAX = 8192
   HEADER_LENGTH_MAX = 4096
 
   def ParseStartLine(self, start_line):
index c723a4d..f202386 100644 (file)
@@ -206,32 +206,39 @@ class BaseHypervisor(object):
     """Reboot an instance."""
     raise NotImplementedError
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances."""
     raise NotImplementedError
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
 
     @return: tuple (name, id, memory, vcpus, state, times)
 
     """
     raise NotImplementedError
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: list of tuples (name, id, memory, vcpus, stat, times)
 
     """
     raise NotImplementedError
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
@@ -241,7 +248,7 @@ class BaseHypervisor(object):
     raise NotImplementedError
 
   @classmethod
-  def GetInstanceConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
     """Return information for connecting to the console of an instance.
 
     """
@@ -263,9 +270,12 @@ class BaseHypervisor(object):
 
     return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
@@ -326,9 +336,11 @@ class BaseHypervisor(object):
     """
     pass
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance.
 
+    @type cluster_name: string
+    @param cluster_name: name of the cluster
     @type instance: L{objects.Instance}
     @param instance: the instance to be migrated
     @type target: string
@@ -365,7 +377,7 @@ class BaseHypervisor(object):
     """
     raise NotImplementedError
 
-  def _InstanceStartupMemory(self, instance):
+  def _InstanceStartupMemory(self, instance, hvparams=None):
     """Get the correct startup memory for an instance
 
     This function calculates how much memory an instance should be started
@@ -378,7 +390,7 @@ class BaseHypervisor(object):
     @return: memory the instance should be started with
 
     """
-    free_memory = self.GetNodeInfo()["memory_free"]
+    free_memory = self.GetNodeInfo(hvparams=hvparams)["memory_free"]
     max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
     start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
     return start_mem
@@ -436,13 +448,16 @@ class BaseHypervisor(object):
                                      (name, errstr, value))
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """Hard powercycle a node using hypervisor specific methods.
 
     This method should hard powercycle the node, using whatever
     methods the hypervisor provides. Note that this means that all
     instances running on the node must be stopped too.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     raise NotImplementedError
 
index 5d88447..c3b96e6 100644 (file)
@@ -102,18 +102,20 @@ class ChrootManager(hv_base.BaseHypervisor):
     """
     return utils.PathJoin(cls._ROOT_DIR, instance_name)
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     """
     return [name for name in os.listdir(self._ROOT_DIR)
             if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
 
     @return: (name, id, memory, vcpus, stat, times)
 
@@ -123,9 +125,11 @@ class ChrootManager(hv_base.BaseHypervisor):
       raise HypervisorError("Instance %s is not running" % instance_name)
     return (instance_name, 0, 0, 0, 0, 0)
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: [(name, id, memory, vcpus, stat, times),...]
 
     """
@@ -246,11 +250,14 @@ class ChrootManager(hv_base.BaseHypervisor):
     # Currently chroots don't have memory limits
     pass
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
     This is just a wrapper over the base GetLinuxNodeInfo method.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters, not used in this class
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
@@ -260,7 +267,7 @@ class ChrootManager(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
+  def GetInstanceConsole(cls, instance, primary_node, # pylint: disable=W0221
                          hvparams, beparams, root_dir=None):
     """Return information for connecting to the console of an instance.
 
@@ -272,15 +279,19 @@ class ChrootManager(hv_base.BaseHypervisor):
 
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_SSH,
-                                   host=instance.primary_node,
+                                   host=primary_node.name,
                                    user=constants.SSH_CONSOLE_USER,
                                    command=["chroot", root_dir])
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     For the chroot manager, it just checks the existence of the base dir.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against, not used
+      in for chroot
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
@@ -290,15 +301,20 @@ class ChrootManager(hv_base.BaseHypervisor):
       return "The required directory '%s' does not exist" % self._ROOT_DIR
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """Chroot powercycle, just a wrapper over Linux powercycle.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     cls.LinuxPowercycle()
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance.
 
+    @type cluster_name: string
+    @param cluster_name: name of the cluster
     @type instance: L{objects.Instance}
     @param instance: the instance to be migrated
     @type target: string
index c99e822..46cdb8f 100644 (file)
@@ -42,6 +42,10 @@ class FakeHypervisor(hv_base.BaseHypervisor):
   a real virtualisation software installed.
 
   """
+  PARAMETERS = {
+    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
+    }
+
   CAN_MIGRATE = True
 
   _ROOT_DIR = pathutils.RUN_DIR + "/fake-hypervisor"
@@ -50,16 +54,19 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     hv_base.BaseHypervisor.__init__(self)
     utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     """
     return os.listdir(self._ROOT_DIR)
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
+    @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
 
     @return: tuple of (name, id, memory, vcpus, stat, times)
 
@@ -82,9 +89,11 @@ class FakeHypervisor(hv_base.BaseHypervisor):
       raise errors.HypervisorError("Failed to list instance %s: %s" %
                                    (instance_name, err))
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: list of tuples (name, id, memory, vcpus, stat, times)
 
     """
@@ -205,11 +214,14 @@ class FakeHypervisor(hv_base.BaseHypervisor):
       raise errors.HypervisorError("Failed to balloon memory for %s: %s" %
                                    (instance.name, utils.ErrnoOrStr(err)))
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
     This is just a wrapper over the base GetLinuxNodeInfo method.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters, not used in this class
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
@@ -224,7 +236,7 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     return result
 
   @classmethod
-  def GetInstanceConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
     """Return information for connecting to the console of an instance.
 
     """
@@ -233,12 +245,16 @@ class FakeHypervisor(hv_base.BaseHypervisor):
                                    message=("Console not available for fake"
                                             " hypervisor"))
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     For the fake hypervisor, it just checks the existence of the base
     dir.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against; not used
+      for fake hypervisors
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
@@ -248,9 +264,12 @@ class FakeHypervisor(hv_base.BaseHypervisor):
       return "The required directory '%s' does not exist" % self._ROOT_DIR
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """Fake hypervisor powercycle, just a wrapper over Linux powercycle.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     cls.LinuxPowercycle()
 
@@ -268,9 +287,11 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     if self._IsAlive(instance.name):
       raise errors.HypervisorError("Can't accept instance, already running")
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance.
 
+    @type cluster_name: string
+    @param cluster_name: name of the cluster
     @type instance: L{objects.Instance}
     @param instance: the instance to be migrated
     @type target: string
index 897d1c8..6a1a55d 100644 (file)
@@ -460,11 +460,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     constants.HV_ACPI: hv_base.NO_CHECK,
     constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
     constants.HV_SERIAL_SPEED: hv_base.NO_CHECK,
-    constants.HV_VNC_BIND_ADDRESS:
-      (False, lambda x: (netutils.IP4Address.IsValid(x) or
-                         utils.IsNormAbsPath(x)),
-       "The VNC bind address must be either a valid IP address or an absolute"
-       " pathname", None, None),
+    constants.HV_VNC_BIND_ADDRESS: hv_base.NO_CHECK, # will be checked later
     constants.HV_VNC_TLS: hv_base.NO_CHECK,
     constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
     constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
@@ -571,6 +567,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
   # dashes not preceeded by a new line (which would mean another option
   # different than -drive is starting)
   _BOOT_RE = re.compile(r"^-drive\s([^-]|(?<!^)-)*,boot=on\|off", re.M | re.S)
+  _UUID_RE = re.compile(r"^-uuid\s", re.M)
 
   ANCILLARY_FILES = [
     _KVM_NETWORK_SCRIPT,
@@ -960,7 +957,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     # Run CPU pinning, based on configured mask
     self._AssignCpuAffinity(cpu_mask, pid, thread_dict)
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     We can do this by listing our live instances directory and
@@ -973,11 +970,13 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         result.append(name)
     return result
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
     @rtype: tuple of strings
     @return: (name, id, memory, vcpus, stat, times)
 
@@ -1003,9 +1002,11 @@ class KVMHypervisor(hv_base.BaseHypervisor):
 
     return (instance_name, pid, memory, vcpus, istat, times)
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: list of tuples (name, id, memory, vcpus, stat, times)
 
     """
@@ -1225,6 +1226,14 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       kvm_cmd.extend(["-usbdevice", constants.HT_MOUSE_TABLET])
 
     if vnc_bind_address:
+      if netutils.IsValidInterface(vnc_bind_address):
+        if_addresses = netutils.GetInterfaceIpAddresses(vnc_bind_address)
+        if_ip4_addresses = if_addresses[constants.IP4_VERSION]
+        if len(if_ip4_addresses) < 1:
+          logging.error("Could not determine IPv4 address of interface %s",
+                        vnc_bind_address)
+        else:
+          vnc_bind_address = if_ip4_addresses[0]
       if netutils.IP4Address.IsValid(vnc_bind_address):
         if instance.network_port > constants.VNC_BASE_PORT:
           display = instance.network_port - constants.VNC_BASE_PORT
@@ -1386,6 +1395,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       for dev in hvp[constants.HV_USB_DEVICES].split(","):
         kvm_cmd.extend(["-usbdevice", dev])
 
+    # Set system UUID to instance UUID
+    if self._UUID_RE.search(kvmhelp):
+      kvm_cmd.extend(["-uuid", instance.uuid])
+
     if hvp[constants.HV_KVM_EXTRA]:
       kvm_cmd.extend(hvp[constants.HV_KVM_EXTRA].split(" "))
 
@@ -1881,12 +1894,14 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     else:
       self.StopInstance(instance, force=True)
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance to a target node.
 
     The migration will not be attempted if the instance is not
     currently running.
 
+    @type cluster_name: string
+    @param cluster_name: name of the cluster
     @type instance: L{objects.Instance}
     @param instance: the instance to be migrated
     @type target: string
@@ -1982,9 +1997,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """
     self._CallMonitorCommand(instance.name, "balloon %d" % mem)
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters, not used in this class
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
@@ -2002,7 +2020,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     return result
 
   @classmethod
-  def GetInstanceConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
@@ -2014,7 +2032,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
              "UNIX-CONNECT:%s" % cls._InstanceSerial(instance.name)]
       return objects.InstanceConsole(instance=instance.name,
                                      kind=constants.CONS_SSH,
-                                     host=instance.primary_node,
+                                     host=primary_node.name,
                                      user=constants.SSH_CONSOLE_USER,
                                      command=cmd)
 
@@ -2039,11 +2057,14 @@ class KVMHypervisor(hv_base.BaseHypervisor):
                                    message=("No serial shell for instance %s" %
                                             instance.name))
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     Check that the required binaries exist.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against, not used here
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
@@ -2153,6 +2174,16 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       except KeyError:
         raise errors.HypervisorError("Unknown security domain user %s"
                                      % username)
+    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
+    if vnc_bind_address:
+      bound_to_addr = netutils.IP4Address.IsValid(vnc_bind_address)
+      is_interface = netutils.IsValidInterface(vnc_bind_address)
+      is_path = utils.IsNormAbsPath(vnc_bind_address)
+      if not bound_to_addr and not is_interface and not is_path:
+        raise errors.HypervisorError("VNC: The %s parameter must be either"
+                                     " a valid IP address, an interface name,"
+                                     " or an absolute path" %
+                                     constants.HV_KVM_SPICE_BIND)
 
     spice_bind = hvparams[constants.HV_KVM_SPICE_BIND]
     if spice_bind:
@@ -2184,8 +2215,11 @@ class KVMHypervisor(hv_base.BaseHypervisor):
                                      machine_version)
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """KVM powercycle, just a wrapper over Linux powercycle.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     cls.LinuxPowercycle()
index b7054f5..65dd64f 100644 (file)
@@ -157,17 +157,19 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
     return memory
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     """
     return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
     @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: hvparams to be used with this instance
     @rtype: tuple of strings
     @return: (name, id, memory, vcpus, stat, times)
 
@@ -189,9 +191,11 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2)
     return (instance_name, 0, memory, len(cpu_list), 0, 0)
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameter
     @return: [(name, id, memory, vcpus, stat, times),...]
 
     """
@@ -393,11 +397,14 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     # Currently lxc instances don't have memory limits
     pass
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
     This is just a wrapper over the base GetLinuxNodeInfo method.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters, not used in this class
+
     @return: a dict with the following keys (values in MiB):
           - memory_total: the total memory size on the node
           - memory_free: the available memory on the node for instances
@@ -407,21 +414,24 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetInstanceConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, primary_node, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_SSH,
-                                   host=instance.primary_node,
+                                   host=primary_node.name,
                                    user=constants.SSH_CONSOLE_USER,
                                    command=["lxc-console", "-n", instance.name])
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     For the LXC manager, it just checks the existence of the base dir.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against; not used here
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
@@ -439,15 +449,20 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     return self._FormatVerifyResults(msgs)
 
   @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(cls, hvparams=None):
     """LXC powercycle, just a wrapper over Linux powercycle.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     cls.LinuxPowercycle()
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance.
 
+    @type cluster_name: string
+    @param cluster_name: name of the cluster
     @type instance: L{objects.Instance}
     @param instance: the instance to be migrated
     @type target: string
index daf0b3f..00bd633 100644 (file)
@@ -34,7 +34,6 @@ from ganeti.hypervisor import hv_base
 from ganeti import netutils
 from ganeti import objects
 from ganeti import pathutils
-from ganeti import ssconf
 
 
 XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
@@ -82,32 +81,33 @@ def _CreateConfigCpus(cpu_mask):
     return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
 
 
-def _RunXmList(fn, xmllist_errors):
-  """Helper function for L{_GetXmList} to run "xm list".
+def _RunInstanceList(fn, instance_list_errors):
+  """Helper function for L{_GetInstanceList} to retrieve the list of instances
+  from xen.
 
   @type fn: callable
-  @param fn: Function returning result of running C{xm list}
-  @type xmllist_errors: list
-  @param xmllist_errors: Error list
+  @param fn: Function to query xen for the list of instances
+  @type instance_list_errors: list
+  @param instance_list_errors: Error list
   @rtype: list
 
   """
   result = fn()
   if result.failed:
-    logging.error("xm list failed (%s): %s", result.fail_reason,
-                  result.output)
-    xmllist_errors.append(result)
+    logging.error("Retrieving the instance list from xen failed (%s): %s",
+                  result.fail_reason, result.output)
+    instance_list_errors.append(result)
     raise utils.RetryAgain()
 
   # skip over the heading
   return result.stdout.splitlines()
 
 
-def _ParseXmList(lines, include_node):
-  """Parses the output of C{xm list}.
+def _ParseInstanceList(lines, include_node):
+  """Parses the output of listing instances by xen.
 
   @type lines: list
-  @param lines: Output lines of C{xm list}
+  @param lines: Result of retrieving the instance list from xen
   @type include_node: boolean
   @param include_node: If True, return information for Dom0
   @return: list of tuple containing (name, id, memory, vcpus, state, time
@@ -123,7 +123,7 @@ def _ParseXmList(lines, include_node):
     # Domain-0   0  3418     4 r-----    266.2
     data = line.split()
     if len(data) != 6:
-      raise errors.HypervisorError("Can't parse output of xm list,"
+      raise errors.HypervisorError("Can't parse instance list,"
                                    " line: %s" % line)
     try:
       data[1] = int(data[1])
@@ -131,7 +131,7 @@ def _ParseXmList(lines, include_node):
       data[3] = int(data[3])
       data[5] = float(data[5])
     except (TypeError, ValueError), err:
-      raise errors.HypervisorError("Can't parse output of xm list,"
+      raise errors.HypervisorError("Can't parse instance list,"
                                    " line: %s, error: %s" % (line, err))
 
     # skip the Domain-0 (optional)
@@ -141,28 +141,28 @@ def _ParseXmList(lines, include_node):
   return result
 
 
-def _GetXmList(fn, include_node, _timeout=5):
+def _GetInstanceList(fn, include_node, _timeout=5):
   """Return the list of running instances.
 
-  See L{_RunXmList} and L{_ParseXmList} for parameter details.
+  See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
 
   """
-  xmllist_errors = []
+  instance_list_errors = []
   try:
-    lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
-                        args=(fn, xmllist_errors))
+    lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
+                        args=(fn, instance_list_errors))
   except utils.RetryTimeout:
-    if xmllist_errors:
-      xmlist_result = xmllist_errors.pop()
+    if instance_list_errors:
+      instance_list_result = instance_list_errors.pop()
 
-      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
-                (xmlist_result.fail_reason, xmlist_result.output))
+      errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
+                (instance_list_result.fail_reason, instance_list_result.output))
     else:
-      errmsg = "xm list failed"
+      errmsg = "listing instances failed"
 
     raise errors.HypervisorError(errmsg)
 
-  return _ParseXmList(lines, include_node)
+  return _ParseInstanceList(lines, include_node)
 
 
 def _ParseNodeInfo(info):
@@ -224,19 +224,19 @@ def _ParseNodeInfo(info):
   return result
 
 
-def _MergeInstanceInfo(info, fn):
+def _MergeInstanceInfo(info, instance_list):
   """Updates node information from L{_ParseNodeInfo} with instance info.
 
   @type info: dict
   @param info: Result from L{_ParseNodeInfo}
-  @type fn: callable
-  @param fn: Function returning result of running C{xm list}
+  @type instance_list: list of tuples
+  @param instance_list: list of instance information; one tuple per instance
   @rtype: dict
 
   """
   total_instmem = 0
 
-  for (name, _, mem, vcpus, _, _) in fn(True):
+  for (name, _, mem, vcpus, _, _) in instance_list:
     if name == _DOM0_NAME:
       info["memory_dom0"] = mem
       info["dom0_cpus"] = vcpus
@@ -254,11 +254,14 @@ def _MergeInstanceInfo(info, fn):
   return info
 
 
-def _GetNodeInfo(info, fn):
+def _GetNodeInfo(info, instance_list):
   """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
 
+  @type instance_list: list of tuples
+  @param instance_list: list of instance information; one tuple per instance
+
   """
-  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
+  return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
 
 
 def _GetConfigFileDiskData(block_devices, blockdev_prefix,
@@ -270,7 +273,7 @@ def _GetConfigFileDiskData(block_devices, blockdev_prefix,
 
   @param block_devices: list of tuples (cfdev, rldev):
       - cfdev: dict containing ganeti config disk part
-      - rldev: ganeti.bdev.BlockDev object
+      - rldev: ganeti.block.bdev.BlockDev object
   @param blockdev_prefix: a string containing blockdevice prefix,
                           e.g. "sd" for /dev/sda
 
@@ -335,13 +338,18 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
     self._cmd = _cmd
 
-  def _GetCommand(self):
+  def _GetCommand(self, hvparams):
     """Returns Xen command to use.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters
+
     """
     if self._cmd is None:
-      # TODO: Make command a hypervisor parameter
-      cmd = constants.XEN_CMD
+      if hvparams is None or constants.HV_XEN_CMD not in hvparams:
+        raise errors.HypervisorError("Cannot determine xen command.")
+      else:
+        cmd = hvparams[constants.HV_XEN_CMD]
     else:
       cmd = self._cmd
 
@@ -350,13 +358,15 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
     return cmd
 
-  def _RunXen(self, args):
+  def _RunXen(self, args, hvparams):
     """Wrapper around L{utils.process.RunCmd} to run Xen command.
 
+    @type hvparams: dict of strings
+    @param hvparams: dictionary of hypervisor params
     @see: L{utils.process.RunCmd}
 
     """
-    cmd = [self._GetCommand()]
+    cmd = [self._GetCommand(hvparams)]
     cmd.extend(args)
 
     return self._run_cmd_fn(cmd)
@@ -425,44 +435,52 @@ class XenHypervisor(hv_base.BaseHypervisor):
     utils.RenameFile(old_filename, new_filename)
     return new_filename
 
-  def _GetXmList(self, include_node):
-    """Wrapper around module level L{_GetXmList}.
+  def _GetInstanceList(self, include_node, hvparams):
+    """Wrapper around module level L{_GetInstanceList}.
+
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be used on this node
 
     """
-    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
+    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
+                            include_node)
 
-  def ListInstances(self):
+  def ListInstances(self, hvparams=None):
     """Get the list of running instances.
 
     """
-    xm_list = self._GetXmList(False)
-    names = [info[0] for info in xm_list]
+    instance_list = self._GetInstanceList(False, hvparams)
+    names = [info[0] for info in instance_list]
     return names
 
-  def GetInstanceInfo(self, instance_name):
+  def GetInstanceInfo(self, instance_name, hvparams=None):
     """Get instance properties.
 
+    @type instance_name: string
     @param instance_name: the instance name
+    @type hvparams: dict of strings
+    @param hvparams: the instance's hypervisor params
 
     @return: tuple (name, id, memory, vcpus, stat, times)
 
     """
-    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
+    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
     result = None
-    for data in xm_list:
+    for data in instance_list:
       if data[0] == instance_name:
         result = data
         break
     return result
 
-  def GetAllInstancesInfo(self):
+  def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters
     @return: list of tuples (name, id, memory, vcpus, stat, times)
 
     """
-    xm_list = self._GetXmList(False)
-    return xm_list
+    return self._GetInstanceList(False, hvparams)
 
   def _MakeConfigFile(self, instance, startup_memory, block_devices):
     """Gather configuration details and write to disk.
@@ -482,7 +500,8 @@ class XenHypervisor(hv_base.BaseHypervisor):
     """Start an instance.
 
     """
-    startup_memory = self._InstanceStartupMemory(instance)
+    startup_memory = self._InstanceStartupMemory(instance,
+                                                 hvparams=instance.hvparams)
 
     self._MakeConfigFile(instance, startup_memory, block_devices)
 
@@ -491,7 +510,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
       cmd.append("-p")
     cmd.append(self._ConfigFileName(instance.name))
 
-    result = self._RunXen(cmd)
+    result = self._RunXen(cmd, instance.hvparams)
     if result.failed:
       # Move the Xen configuration file to the log directory to avoid
       # leaving a stale config file behind.
@@ -508,18 +527,25 @@ class XenHypervisor(hv_base.BaseHypervisor):
     if name is None:
       name = instance.name
 
-    return self._StopInstance(name, force)
+    return self._StopInstance(name, force, instance.hvparams)
 
-  def _StopInstance(self, name, force):
+  def _StopInstance(self, name, force, hvparams):
     """Stop an instance.
 
+    @type name: string
+    @param name: name of the instance to be shutdown
+    @type force: boolean
+    @param force: flag specifying whether shutdown should be forced
+    @type hvparams: dict of string
+    @param hvparams: hypervisor parameters of the instance
+
     """
     if force:
       action = "destroy"
     else:
       action = "shutdown"
 
-    result = self._RunXen([action, name])
+    result = self._RunXen([action, name], hvparams)
     if result.failed:
       raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
                                    (name, result.fail_reason, result.output))
@@ -531,20 +557,20 @@ class XenHypervisor(hv_base.BaseHypervisor):
     """Reboot an instance.
 
     """
-    ini_info = self.GetInstanceInfo(instance.name)
+    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
 
     if ini_info is None:
       raise errors.HypervisorError("Failed to reboot instance %s,"
                                    " not running" % instance.name)
 
-    result = self._RunXen(["reboot", instance.name])
+    result = self._RunXen(["reboot", instance.name], instance.hvparams)
     if result.failed:
       raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
                                    (instance.name, result.fail_reason,
                                     result.output))
 
     def _CheckInstance():
-      new_info = self.GetInstanceInfo(instance.name)
+      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
 
       # check if the domain ID has changed or the run time has decreased
       if (new_info is not None and
@@ -570,7 +596,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
     @param mem: actual memory size to use for instance runtime
 
     """
-    result = self._RunXen(["mem-set", instance.name, mem])
+    result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
     if result.failed:
       raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
                                    (instance.name, result.fail_reason,
@@ -586,43 +612,60 @@ class XenHypervisor(hv_base.BaseHypervisor):
                                    (instance.name, result.fail_reason,
                                     result.output))
 
-  def GetNodeInfo(self):
+  def GetNodeInfo(self, hvparams=None):
     """Return information about the node.
 
     @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
 
     """
-    result = self._RunXen(["info"])
+    result = self._RunXen(["info"], hvparams)
     if result.failed:
-      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
-                    result.output)
+      logging.error("Can't retrieve xen hypervisor information (%s): %s",
+                    result.fail_reason, result.output)
       return None
 
-    return _GetNodeInfo(result.stdout, self._GetXmList)
+    instance_list = self._GetInstanceList(True, hvparams)
+    return _GetNodeInfo(result.stdout, instance_list)
 
-  @classmethod
-  def GetInstanceConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
+    xen_cmd = self._GetCommand(hvparams)
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_SSH,
-                                   host=instance.primary_node,
+                                   host=primary_node.name,
                                    user=constants.SSH_CONSOLE_USER,
                                    command=[pathutils.XEN_CONSOLE_WRAPPER,
-                                            constants.XEN_CMD, instance.name])
+                                            xen_cmd, instance.name])
 
-  def Verify(self):
+  def Verify(self, hvparams=None):
     """Verify the hypervisor.
 
     For Xen, this verifies that the xend process is running.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor parameters to be verified against
+
     @return: Problem description if something is wrong, C{None} otherwise
 
     """
-    result = self._RunXen(["info"])
+    if hvparams is None:
+      return "Could not verify the hypervisor, because no hvparams were" \
+             " provided."
+
+    if constants.HV_XEN_CMD in hvparams:
+      xen_cmd = hvparams[constants.HV_XEN_CMD]
+      try:
+        self._CheckToolstack(xen_cmd)
+      except errors.HypervisorError:
+        return "The configured xen toolstack '%s' is not available on this" \
+               " node." % xen_cmd
+
+    result = self._RunXen(["info"], hvparams)
     if result.failed:
-      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
+      return "Retrieving information from xen failed: %s, %s" % \
+        (result.fail_reason, result.output)
 
     return None
 
@@ -667,7 +710,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
     if success:
       self._WriteConfigFile(instance.name, info)
 
-  def MigrateInstance(self, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live):
     """Migrate an instance to a target node.
 
     The migration will not be attempted if the instance is not
@@ -683,23 +726,23 @@ class XenHypervisor(hv_base.BaseHypervisor):
     """
     port = instance.hvparams[constants.HV_MIGRATION_PORT]
 
-    # TODO: Pass cluster name via RPC
-    cluster_name = ssconf.SimpleStore().GetClusterName()
-
     return self._MigrateInstance(cluster_name, instance.name, target, port,
-                                 live)
+                                 live, instance.hvparams)
 
   def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
-                       _ping_fn=netutils.TcpPing):
+                       hvparams, _ping_fn=netutils.TcpPing):
     """Migrate an instance to a target node.
 
     @see: L{MigrateInstance} for details
 
     """
-    if self.GetInstanceInfo(instance_name) is None:
+    if hvparams is None:
+      raise errors.HypervisorError("No hvparams provided.")
+
+    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
       raise errors.HypervisorError("Instance not running, cannot migrate")
 
-    cmd = self._GetCommand()
+    cmd = self._GetCommand(hvparams)
 
     if (cmd == constants.XEN_CMD_XM and
         not _ping_fn(target, port, live_port_needed=True)):
@@ -724,7 +767,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
     args.extend([instance_name, target])
 
-    result = self._RunXen(args)
+    result = self._RunXen(args, hvparams)
     if result.failed:
       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
                                    (instance_name, result.output))
@@ -765,8 +808,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
     """
     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
 
-  @classmethod
-  def PowercycleNode(cls):
+  def PowercycleNode(self, hvparams=None):
     """Xen-specific powercycle.
 
     This first does a Linux reboot (which triggers automatically a Xen
@@ -776,11 +818,57 @@ class XenHypervisor(hv_base.BaseHypervisor):
     won't work in case the root filesystem is broken and/or the xend
     daemon is not working.
 
+    @type hvparams: dict of strings
+    @param hvparams: hypervisor params to be used on this node
+
     """
     try:
-      cls.LinuxPowercycle()
+      self.LinuxPowercycle()
     finally:
-      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
+      xen_cmd = self._GetCommand(hvparams)
+      utils.RunCmd([xen_cmd, "debug", "R"])
+
+  def _CheckToolstack(self, xen_cmd):
+    """Check whether the given toolstack is available on the node.
+
+    @type xen_cmd: string
+    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
+
+    """
+    binary_found = self._CheckToolstackBinary(xen_cmd)
+    if not binary_found:
+      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
+    elif xen_cmd == constants.XEN_CMD_XL:
+      if not self._CheckToolstackXlConfigured():
+        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
+                                     "node." % xen_cmd)
+
+  def _CheckToolstackBinary(self, xen_cmd):
+    """Checks whether the xen command's binary is found on the machine.
+
+    """
+    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
+      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
+    result = self._run_cmd_fn(["which", xen_cmd])
+    return not result.failed
+
+  def _CheckToolstackXlConfigured(self):
+    """Checks whether xl is enabled on an xl-capable node.
+
+    @rtype: bool
+    @returns: C{True} if 'xl' is enabled, C{False} otherwise
+
+    """
+    result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
+    if not result.failed:
+      return True
+    elif result.failed:
+      if "toolstack" in result.stderr:
+        return False
+      # xl fails for some other reason than the toolstack
+      else:
+        raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
+                                     % (constants.XEN_CMD_XL, result.stderr))
 
 
 class XenPvmHypervisor(XenHypervisor):
@@ -804,6 +892,8 @@ class XenPvmHypervisor(XenHypervisor):
     constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
     constants.HV_CPU_WEIGHT:
       (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
+    constants.HV_XEN_CMD:
+      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
     }
 
   def _GetConfig(self, instance, startup_memory, block_devices):
@@ -926,6 +1016,8 @@ class XenHvmHypervisor(XenHypervisor):
       (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
     constants.HV_VIF_TYPE:
       hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
+    constants.HV_XEN_CMD:
+      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
     }
 
   def _GetConfig(self, instance, startup_memory, block_devices):
index abcf22b..7a23af6 100644 (file)
@@ -1636,15 +1636,15 @@ class GanetiLockManager:
   """
   _instance = None
 
-  def __init__(self, nodes, nodegroups, instances, networks):
+  def __init__(self, node_uuids, nodegroups, instance_names, networks):
     """Constructs a new GanetiLockManager object.
 
     There should be only a GanetiLockManager object at any time, so this
     function raises an error if this is not the case.
 
-    @param nodes: list of node names
+    @param node_uuids: list of node UUIDs
     @param nodegroups: list of nodegroup uuids
-    @param instances: list of instance names
+    @param instance_names: list of instance names
 
     """
     assert self.__class__._instance is None, \
@@ -1658,10 +1658,11 @@ class GanetiLockManager:
     # locking order.
     self.__keyring = {
       LEVEL_CLUSTER: LockSet([BGL], "cluster", monitor=self._monitor),
-      LEVEL_NODE: LockSet(nodes, "node", monitor=self._monitor),
-      LEVEL_NODE_RES: LockSet(nodes, "node-res", monitor=self._monitor),
+      LEVEL_NODE: LockSet(node_uuids, "node", monitor=self._monitor),
+      LEVEL_NODE_RES: LockSet(node_uuids, "node-res", monitor=self._monitor),
       LEVEL_NODEGROUP: LockSet(nodegroups, "nodegroup", monitor=self._monitor),
-      LEVEL_INSTANCE: LockSet(instances, "instance", monitor=self._monitor),
+      LEVEL_INSTANCE: LockSet(instance_names, "instance",
+                              monitor=self._monitor),
       LEVEL_NETWORK: LockSet(networks, "network", monitor=self._monitor),
       LEVEL_NODE_ALLOC: LockSet([NAL], "node-alloc", monitor=self._monitor),
       }
index 4790d46..d5ccc72 100644 (file)
@@ -58,6 +58,7 @@ _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
                         ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
 
 _INST_NAME = ("name", ht.TNonEmptyString)
+_INST_UUID = ("inst_uuid", ht.TNonEmptyString)
 
 
 class _AutoReqParam(outils.AutoSlots):
@@ -233,8 +234,8 @@ class IAReqRelocate(IARequestBase):
   # pylint: disable=E1101
   MODE = constants.IALLOCATOR_MODE_RELOC
   REQ_PARAMS = [
-    _INST_NAME,
-    ("relocate_from", _STRING_LIST),
+    _INST_UUID,
+    ("relocate_from_node_uuids", _STRING_LIST),
     ]
   REQ_RESULT = ht.TList
 
@@ -245,10 +246,10 @@ class IAReqRelocate(IARequestBase):
     done.
 
     """
-    instance = cfg.GetInstanceInfo(self.name)
+    instance = cfg.GetInstanceInfo(self.inst_uuid)
     if instance is None:
       raise errors.ProgrammerError("Unknown instance '%s' passed to"
-                                   " IAllocator" % self.name)
+                                   " IAllocator" % self.inst_uuid)
 
     if instance.disk_template not in constants.DTS_MIRRORED:
       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
@@ -263,10 +264,10 @@ class IAReqRelocate(IARequestBase):
     disk_space = gmi.ComputeDiskSize(instance.disk_template, disk_sizes)
 
     return {
-      "name": self.name,
+      "name": instance.name,
       "disk_space_total": disk_space,
       "required_nodes": 1,
-      "relocate_from": self.relocate_from,
+      "relocate_from": cfg.GetNodeNames(self.relocate_from_node_uuids),
       }
 
   def ValidateResult(self, ia, result):
@@ -281,13 +282,14 @@ class IAReqRelocate(IARequestBase):
     fn = compat.partial(self._NodesToGroups, node2group,
                         ia.in_data["nodegroups"])
 
-    instance = ia.cfg.GetInstanceInfo(self.name)
-    request_groups = fn(self.relocate_from + [instance.primary_node])
-    result_groups = fn(result + [instance.primary_node])
+    instance = ia.cfg.GetInstanceInfo(self.inst_uuid)
+    request_groups = fn(ia.cfg.GetNodeNames(self.relocate_from_node_uuids) +
+                        ia.cfg.GetNodeNames([instance.primary_node]))
+    result_groups = fn(result + ia.cfg.GetNodeNames([instance.primary_node]))
 
     if ia.success and not set(result_groups).issubset(request_groups):
       raise errors.ResultValidationError("Groups of nodes returned by"
-                                         "iallocator (%s) differ from original"
+                                         " iallocator (%s) differ from original"
                                          " groups (%s)" %
                                          (utils.CommaJoin(result_groups),
                                           utils.CommaJoin(request_groups)))
@@ -403,56 +405,54 @@ class IAllocator(object):
     This is the data that is independent of the actual operation.
 
     """
-    cfg = self.cfg
-    cluster_info = cfg.GetClusterInfo()
+    cluster_info = self.cfg.GetClusterInfo()
     # cluster data
     data = {
       "version": constants.IALLOCATOR_VERSION,
-      "cluster_name": cfg.GetClusterName(),
+      "cluster_name": self.cfg.GetClusterName(),
       "cluster_tags": list(cluster_info.GetTags()),
       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
       "ipolicy": cluster_info.ipolicy,
       }
-    ninfo = cfg.GetAllNodesInfo()
-    iinfo = cfg.GetAllInstancesInfo().values()
+    ninfo = self.cfg.GetAllNodesInfo()
+    iinfo = self.cfg.GetAllInstancesInfo().values()
     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
 
     # node data
-    node_list = [n.name for n in ninfo.values() if n.vm_capable]
+    node_list = [n.uuid for n in ninfo.values() if n.vm_capable]
 
     if isinstance(self.req, IAReqInstanceAlloc):
       hypervisor_name = self.req.hypervisor
       node_whitelist = self.req.node_whitelist
     elif isinstance(self.req, IAReqRelocate):
-      hypervisor_name = cfg.GetInstanceInfo(self.req.name).hypervisor
+      hypervisor_name = self.cfg.GetInstanceInfo(self.req.inst_uuid).hypervisor
       node_whitelist = None
     else:
       hypervisor_name = cluster_info.primary_hypervisor
       node_whitelist = None
 
-    es_flags = rpc.GetExclusiveStorageForNodeNames(cfg, node_list)
-    vg_name = cfg.GetVGName()
-    if vg_name is not None:
-      has_lvm = True
-      vg_req = [vg_name]
-    else:
-      has_lvm = False
-      vg_req = []
-    node_data = self.rpc.call_node_info(node_list, vg_req,
-                                        [hypervisor_name], es_flags)
+    es_flags = rpc.GetExclusiveStorageForNodes(self.cfg, node_list)
+    storage_units = utils.storage.GetStorageUnitsOfCluster(
+        self.cfg, include_spindles=True)
+    has_lvm = utils.storage.IsLvmEnabled(cluster_info.enabled_disk_templates)
+    hvspecs = [(hypervisor_name, cluster_info.hvparams[hypervisor_name])]
+    node_data = self.rpc.call_node_info(node_list, storage_units,
+                                        hvspecs, es_flags)
     node_iinfo = \
       self.rpc.call_all_instances_info(node_list,
-                                       cluster_info.enabled_hypervisors)
+                                       cluster_info.enabled_hypervisors,
+                                       cluster_info.hvparams)
 
-    data["nodegroups"] = self._ComputeNodeGroupData(cfg)
+    data["nodegroups"] = self._ComputeNodeGroupData(self.cfg)
 
-    config_ndata = self._ComputeBasicNodeData(cfg, ninfo, node_whitelist)
+    config_ndata = self._ComputeBasicNodeData(self.cfg, ninfo, node_whitelist)
     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
                                                  i_list, config_ndata, has_lvm)
     assert len(data["nodes"]) == len(ninfo), \
         "Incomplete node data computed"
 
-    data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
+    data["instances"] = self._ComputeInstanceData(self.cfg, cluster_info,
+                                                  i_list)
 
     self.in_data = data
 
@@ -511,26 +511,27 @@ class IAllocator(object):
     #TODO(dynmem): compute the right data on MAX and MIN memory
     # make a copy of the current dict
     node_results = dict(node_results)
-    for nname, nresult in node_data.items():
-      assert nname in node_results, "Missing basic data for node %s" % nname
-      ninfo = node_cfg[nname]
+    for nuuid, nresult in node_data.items():
+      ninfo = node_cfg[nuuid]
+      assert ninfo.name in node_results, "Missing basic data for node %s" % \
+                                         ninfo.name
 
       if not (ninfo.offline or ninfo.drained):
-        nresult.Raise("Can't get data for node %s" % nname)
-        node_iinfo[nname].Raise("Can't get node instance info from node %s" %
-                                nname)
+        nresult.Raise("Can't get data for node %s" % ninfo.name)
+        node_iinfo[nuuid].Raise("Can't get node instance info from node %s" %
+                                ninfo.name)
         remote_info = rpc.MakeLegacyNodeInfo(nresult.payload,
                                              require_vg_info=has_lvm)
 
         def get_attr(attr):
           if attr not in remote_info:
             raise errors.OpExecError("Node '%s' didn't return attribute"
-                                     " '%s'" % (nname, attr))
+                                     " '%s'" % (ninfo.name, attr))
           value = remote_info[attr]
           if not isinstance(value, int):
             raise errors.OpExecError("Node '%s' returned invalid value"
                                      " for '%s': %s" %
-                                     (nname, attr, value))
+                                     (ninfo.name, attr, value))
           return value
 
         mem_free = get_attr("memory_free")
@@ -538,12 +539,12 @@ class IAllocator(object):
         # compute memory used by primary instances
         i_p_mem = i_p_up_mem = 0
         for iinfo, beinfo in i_list:
-          if iinfo.primary_node == nname:
+          if iinfo.primary_node == nuuid:
             i_p_mem += beinfo[constants.BE_MAXMEM]
-            if iinfo.name not in node_iinfo[nname].payload:
+            if iinfo.name not in node_iinfo[nuuid].payload:
               i_used_mem = 0
             else:
-              i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
+              i_used_mem = int(node_iinfo[nuuid].payload[iinfo.name]["memory"])
             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
             mem_free -= max(0, i_mem_diff)
 
@@ -552,11 +553,14 @@ class IAllocator(object):
 
         # TODO: replace this with proper storage reporting
         if has_lvm:
-          total_disk = get_attr("vg_size")
-          free_disk = get_attr("vg_free")
+          total_disk = get_attr("storage_size")
+          free_disk = get_attr("storage_free")
+          total_spindles = get_attr("spindles_total")
+          free_spindles = get_attr("spindles_free")
         else:
           # we didn't even ask the node for VG status, so use zeros
           total_disk = free_disk = 0
+          total_spindles = free_spindles = 0
 
         # compute memory used by instances
         pnr_dyn = {
@@ -565,17 +569,19 @@ class IAllocator(object):
           "free_memory": mem_free,
           "total_disk": total_disk,
           "free_disk": free_disk,
+          "total_spindles": total_spindles,
+          "free_spindles": free_spindles,
           "total_cpus": get_attr("cpu_total"),
           "i_pri_memory": i_p_mem,
           "i_pri_up_memory": i_p_up_mem,
           }
-        pnr_dyn.update(node_results[nname])
-        node_results[nname] = pnr_dyn
+        pnr_dyn.update(node_results[ninfo.name])
+        node_results[ninfo.name] = pnr_dyn
 
     return node_results
 
   @staticmethod
-  def _ComputeInstanceData(cluster_info, i_list):
+  def _ComputeInstanceData(cfg, cluster_info, i_list):
     """Compute global instance data.
 
     """
@@ -600,10 +606,12 @@ class IAllocator(object):
         "memory": beinfo[constants.BE_MAXMEM],
         "spindle_use": beinfo[constants.BE_SPINDLE_USE],
         "os": iinfo.os,
-        "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
+        "nodes": [cfg.GetNodeName(iinfo.primary_node)] +
+                 cfg.GetNodeNames(iinfo.secondary_nodes),
         "nics": nic_data,
         "disks": [{constants.IDISK_SIZE: dsk.size,
-                   constants.IDISK_MODE: dsk.mode}
+                   constants.IDISK_MODE: dsk.mode,
+                   constants.IDISK_SPINDLES: dsk.spindles}
                   for dsk in iinfo.disks],
         "disk_template": iinfo.disk_template,
         "disks_active": iinfo.disks_active,
index 0954011..8bb5e5d 100644 (file)
@@ -134,13 +134,13 @@ class ImportExportCbBase(object):
 class _DiskImportExportBase(object):
   MODE_TEXT = None
 
-  def __init__(self, lu, node_name, opts,
+  def __init__(self, lu, node_uuid, opts,
                instance, component, timeouts, cbs, private=None):
     """Initializes this class.
 
     @param lu: Logical unit instance
-    @type node_name: string
-    @param node_name: Node name for import
+    @type node_uuid: string
+    @param node_uuid: Node UUID for import
     @type opts: L{objects.ImportExportOptions}
     @param opts: Import/export daemon options
     @type instance: L{objects.Instance}
@@ -157,7 +157,8 @@ class _DiskImportExportBase(object):
     assert self.MODE_TEXT
 
     self._lu = lu
-    self.node_name = node_name
+    self.node_uuid = node_uuid
+    self.node_name = lu.cfg.GetNodeName(node_uuid)
     self._opts = opts.Copy()
     self._instance = instance
     self._component = component
@@ -293,12 +294,12 @@ class _DiskImportExportBase(object):
     """
     if self._daemon_name:
       self._lu.LogWarning("Aborting %s '%s' on %s",
-                          self.MODE_TEXT, self._daemon_name, self.node_name)
-      result = self._lu.rpc.call_impexp_abort(self.node_name, self._daemon_name)
+                          self.MODE_TEXT, self._daemon_name, self.node_uuid)
+      result = self._lu.rpc.call_impexp_abort(self.node_uuid, self._daemon_name)
       if result.fail_msg:
         self._lu.LogWarning("Failed to abort %s '%s' on %s: %s",
                             self.MODE_TEXT, self._daemon_name,
-                            self.node_name, result.fail_msg)
+                            self.node_uuid, result.fail_msg)
         return False
 
     return True
@@ -374,7 +375,7 @@ class _DiskImportExportBase(object):
 
       # TODO: Log remote peer
       logging.debug("%s '%s' on %s is now connected",
-                    self.MODE_TEXT, self._daemon_name, self.node_name)
+                    self.MODE_TEXT, self._daemon_name, self.node_uuid)
 
       self._cbs.ReportConnected(self, self._private)
 
@@ -440,14 +441,15 @@ class _DiskImportExportBase(object):
 
     if success:
       logging.info("%s '%s' on %s succeeded", self.MODE_TEXT,
-                   self._daemon_name, self.node_name)
+                   self._daemon_name, self.node_uuid)
     elif self._daemon_name:
       self._lu.LogWarning("%s '%s' on %s failed: %s",
-                          self.MODE_TEXT, self._daemon_name, self.node_name,
+                          self.MODE_TEXT, self._daemon_name,
+                          self._lu.cfg.GetNodeName(self.node_uuid),
                           message)
     else:
       self._lu.LogWarning("%s on %s failed: %s", self.MODE_TEXT,
-                          self.node_name, message)
+                          self._lu.cfg.GetNodeName(self.node_uuid), message)
 
     self._cbs.ReportFinished(self, self._private)
 
@@ -455,7 +457,7 @@ class _DiskImportExportBase(object):
     """Makes the RPC call to finalize this import/export.
 
     """
-    return self._lu.rpc.call_impexp_cleanup(self.node_name, self._daemon_name)
+    return self._lu.rpc.call_impexp_cleanup(self.node_uuid, self._daemon_name)
 
   def Finalize(self, error=None):
     """Finalizes this import/export.
@@ -463,13 +465,13 @@ class _DiskImportExportBase(object):
     """
     if self._daemon_name:
       logging.info("Finalizing %s '%s' on %s",
-                   self.MODE_TEXT, self._daemon_name, self.node_name)
+                   self.MODE_TEXT, self._daemon_name, self.node_uuid)
 
       result = self._Finalize()
       if result.fail_msg:
         self._lu.LogWarning("Failed to finalize %s '%s' on %s: %s",
                             self.MODE_TEXT, self._daemon_name,
-                            self.node_name, result.fail_msg)
+                            self.node_uuid, result.fail_msg)
         return False
 
       # Daemon is no longer running
@@ -485,13 +487,13 @@ class _DiskImportExportBase(object):
 class DiskImport(_DiskImportExportBase):
   MODE_TEXT = "import"
 
-  def __init__(self, lu, node_name, opts, instance, component,
+  def __init__(self, lu, node_uuid, opts, instance, component,
                dest, dest_args, timeouts, cbs, private=None):
     """Initializes this class.
 
     @param lu: Logical unit instance
-    @type node_name: string
-    @param node_name: Node name for import
+    @type node_uuid: string
+    @param node_uuid: Node name for import
     @type opts: L{objects.ImportExportOptions}
     @param opts: Import/export daemon options
     @type instance: L{objects.Instance}
@@ -507,7 +509,7 @@ class DiskImport(_DiskImportExportBase):
     @param private: Private data for callback functions
 
     """
-    _DiskImportExportBase.__init__(self, lu, node_name, opts, instance,
+    _DiskImportExportBase.__init__(self, lu, node_uuid, opts, instance,
                                    component, timeouts, cbs, private)
     self._dest = dest
     self._dest_args = dest_args
@@ -529,7 +531,7 @@ class DiskImport(_DiskImportExportBase):
     """Starts the import daemon.
 
     """
-    return self._lu.rpc.call_import_start(self.node_name, self._opts,
+    return self._lu.rpc.call_import_start(self.node_uuid, self._opts,
                                           self._instance, self._component,
                                           (self._dest, self._dest_args))
 
@@ -550,7 +552,7 @@ class DiskImport(_DiskImportExportBase):
       self._ts_listening = time.time()
 
       logging.debug("Import '%s' on %s is now listening on port %s",
-                    self._daemon_name, self.node_name, port)
+                    self._daemon_name, self.node_uuid, port)
 
       self._cbs.ReportListening(self, self._private, self._component)
 
@@ -576,14 +578,14 @@ class DiskImport(_DiskImportExportBase):
 class DiskExport(_DiskImportExportBase):
   MODE_TEXT = "export"
 
-  def __init__(self, lu, node_name, opts, dest_host, dest_port,
+  def __init__(self, lu, node_uuid, opts, dest_host, dest_port,
                instance, component, source, source_args,
                timeouts, cbs, private=None):
     """Initializes this class.
 
     @param lu: Logical unit instance
-    @type node_name: string
-    @param node_name: Node name for import
+    @type node_uuid: string
+    @param node_uuid: Node UUID for import
     @type opts: L{objects.ImportExportOptions}
     @param opts: Import/export daemon options
     @type dest_host: string
@@ -603,7 +605,7 @@ class DiskExport(_DiskImportExportBase):
     @param private: Private data for callback functions
 
     """
-    _DiskImportExportBase.__init__(self, lu, node_name, opts, instance,
+    _DiskImportExportBase.__init__(self, lu, node_uuid, opts, instance,
                                    component, timeouts, cbs, private)
     self._dest_host = dest_host
     self._dest_port = dest_port
@@ -614,7 +616,7 @@ class DiskExport(_DiskImportExportBase):
     """Starts the export daemon.
 
     """
-    return self._lu.rpc.call_export_start(self.node_name, self._opts,
+    return self._lu.rpc.call_export_start(self.node_uuid, self._opts,
                                           self._dest_host, self._dest_port,
                                           self._instance, self._component,
                                           (self._source, self._source_args))
@@ -823,8 +825,8 @@ class ImportExportLoop:
 
 
 class _TransferInstCbBase(ImportExportCbBase):
-  def __init__(self, lu, feedback_fn, instance, timeouts, src_node, src_cbs,
-               dest_node, dest_ip):
+  def __init__(self, lu, feedback_fn, instance, timeouts, src_node_uuid,
+               src_cbs, dest_node_uuid, dest_ip):
     """Initializes this class.
 
     """
@@ -834,9 +836,9 @@ class _TransferInstCbBase(ImportExportCbBase):
     self.feedback_fn = feedback_fn
     self.instance = instance
     self.timeouts = timeouts
-    self.src_node = src_node
+    self.src_node_uuid = src_node_uuid
     self.src_cbs = src_cbs
-    self.dest_node = dest_node
+    self.dest_node_uuid = dest_node_uuid
     self.dest_ip = dest_ip
 
 
@@ -901,7 +903,7 @@ class _TransferInstDestCb(_TransferInstCbBase):
     self.feedback_fn("%s is now listening, starting export" % dtp.data.name)
 
     # Start export on source node
-    de = DiskExport(self.lu, self.src_node, dtp.export_opts,
+    de = DiskExport(self.lu, self.src_node_uuid, dtp.export_opts,
                     self.dest_ip, ie.listen_port, self.instance,
                     component, dtp.data.src_io, dtp.data.src_ioargs,
                     self.timeouts, self.src_cbs, private=dtp)
@@ -914,7 +916,8 @@ class _TransferInstDestCb(_TransferInstCbBase):
 
     """
     self.feedback_fn("%s is receiving data on %s" %
-                     (dtp.data.name, self.dest_node))
+                     (dtp.data.name,
+                      self.lu.cfg.GetNodeName(self.dest_node_uuid)))
 
   def ReportFinished(self, ie, dtp):
     """Called when a transfer has finished.
@@ -1003,16 +1006,16 @@ def _GetInstDiskMagic(base, instance_name, index):
   return h.hexdigest()
 
 
-def TransferInstanceData(lu, feedback_fn, src_node, dest_node, dest_ip,
-                         instance, all_transfers):
+def TransferInstanceData(lu, feedback_fn, src_node_uuid, dest_node_uuid,
+                         dest_ip, instance, all_transfers):
   """Transfers an instance's data from one node to another.
 
   @param lu: Logical unit instance
   @param feedback_fn: Feedback function
-  @type src_node: string
-  @param src_node: Source node name
-  @type dest_node: string
-  @param dest_node: Destination node name
+  @type src_node_uuid: string
+  @param src_node_uuid: Source node UUID
+  @type dest_node_uuid: string
+  @param dest_node_uuid: Destination node UUID
   @type dest_ip: string
   @param dest_ip: IP address of destination node
   @type instance: L{objects.Instance}
@@ -1027,14 +1030,18 @@ def TransferInstanceData(lu, feedback_fn, src_node, dest_node, dest_ip,
   # Disable compression for all moves as these are all within the same cluster
   compress = constants.IEC_NONE
 
+  src_node_name = lu.cfg.GetNodeName(src_node_uuid)
+  dest_node_name = lu.cfg.GetNodeName(dest_node_uuid)
+
   logging.debug("Source node %s, destination node %s, compression '%s'",
-                src_node, dest_node, compress)
+                src_node_name, dest_node_name, compress)
 
   timeouts = ImportExportTimeouts(constants.DISK_TRANSFER_CONNECT_TIMEOUT)
   src_cbs = _TransferInstSourceCb(lu, feedback_fn, instance, timeouts,
-                                  src_node, None, dest_node, dest_ip)
+                                  src_node_uuid, None, dest_node_uuid, dest_ip)
   dest_cbs = _TransferInstDestCb(lu, feedback_fn, instance, timeouts,
-                                 src_node, src_cbs, dest_node, dest_ip)
+                                 src_node_uuid, src_cbs, dest_node_uuid,
+                                 dest_ip)
 
   all_dtp = []
 
@@ -1045,7 +1052,7 @@ def TransferInstanceData(lu, feedback_fn, src_node, dest_node, dest_ip,
     for idx, transfer in enumerate(all_transfers):
       if transfer:
         feedback_fn("Exporting %s from %s to %s" %
-                    (transfer.name, src_node, dest_node))
+                    (transfer.name, src_node_name, dest_node_name))
 
         magic = _GetInstDiskMagic(base_magic, instance.name, idx)
         opts = objects.ImportExportOptions(key_name=None, ca_pem=None,
@@ -1053,7 +1060,7 @@ def TransferInstanceData(lu, feedback_fn, src_node, dest_node, dest_ip,
 
         dtp = _DiskTransferPrivate(transfer, True, opts)
 
-        di = DiskImport(lu, dest_node, opts, instance, "disk%d" % idx,
+        di = DiskImport(lu, dest_node_uuid, opts, instance, "disk%d" % idx,
                         transfer.dest_io, transfer.dest_ioargs,
                         timeouts, dest_cbs, private=dtp)
         ieloop.Add(di)
@@ -1217,7 +1224,7 @@ class ExportInstanceHelper:
 
     """
     instance = self._instance
-    src_node = instance.primary_node
+    src_node_uuid = instance.primary_node
 
     assert len(self._snap_disks) == len(instance.disks)
 
@@ -1242,14 +1249,14 @@ class ExportInstanceHelper:
 
     # Actually export data
     dresults = TransferInstanceData(self._lu, self._feedback_fn,
-                                    src_node, dest_node.name,
+                                    src_node_uuid, dest_node.uuid,
                                     dest_node.secondary_ip,
                                     instance, transfers)
 
     assert len(dresults) == len(instance.disks)
 
     self._feedback_fn("Finalizing export on %s" % dest_node.name)
-    result = self._lu.rpc.call_finalize_export(dest_node.name, instance,
+    result = self._lu.rpc.call_finalize_export(dest_node.uuid, instance,
                                                self._snap_disks)
     msg = result.fail_msg
     fin_resu = not msg
index 1f5a194..8fdce13 100644 (file)
@@ -358,7 +358,7 @@ class TaggableObject(ConfigObject):
 class MasterNetworkParameters(ConfigObject):
   """Network configuration parameters for the master
 
-  @ivar name: master name
+  @ivar uuid: master nodes UUID
   @ivar ip: master IP
   @ivar netmask: master netmask
   @ivar netdev: master network device
@@ -366,7 +366,7 @@ class MasterNetworkParameters(ConfigObject):
 
   """
   __slots__ = [
-    "name",
+    "uuid",
     "ip",
     "netmask",
     "netdev",
@@ -512,8 +512,9 @@ class NIC(ConfigObject):
 
 class Disk(ConfigObject):
   """Config object representing a block device."""
-  __slots__ = ["name", "dev_type", "logical_id", "physical_id",
-               "children", "iv_name", "size", "mode", "params"] + _UUID
+  __slots__ = (["name", "dev_type", "logical_id", "physical_id",
+                "children", "iv_name", "size", "mode", "params", "spindles"] +
+               _UUID)
 
   def CreateOnSecondary(self):
     """Test if this device needs to be created on a secondary node."""
@@ -577,7 +578,7 @@ class Disk(ConfigObject):
           return True
     return self.dev_type == dev_type
 
-  def GetNodes(self, node):
+  def GetNodes(self, node_uuid):
     """This function returns the nodes this device lives on.
 
     Given the node on which the parent of the device lives on (or, in
@@ -589,26 +590,26 @@ class Disk(ConfigObject):
     if self.dev_type in [constants.LD_LV, constants.LD_FILE,
                          constants.LD_BLOCKDEV, constants.LD_RBD,
                          constants.LD_EXT]:
-      result = [node]
+      result = [node_uuid]
     elif self.dev_type in constants.LDS_DRBD:
       result = [self.logical_id[0], self.logical_id[1]]
-      if node not in result:
+      if node_uuid not in result:
         raise errors.ConfigurationError("DRBD device passed unknown node")
     else:
       raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
     return result
 
-  def ComputeNodeTree(self, parent_node):
+  def ComputeNodeTree(self, parent_node_uuid):
     """Compute the node/disk tree for this disk and its children.
 
     This method, given the node on which the parent disk lives, will
-    return the list of all (node, disk) pairs which describe the disk
+    return the list of all (node UUID, disk) pairs which describe the disk
     tree in the most compact way. For example, a drbd/lvm stack
     will be returned as (primary_node, drbd) and (secondary_node, drbd)
     which represents all the top-level devices on the nodes.
 
     """
-    my_nodes = self.GetNodes(parent_node)
+    my_nodes = self.GetNodes(parent_node_uuid)
     result = [(node, self) for node in my_nodes]
     if not self.children:
       # leaf device
@@ -674,8 +675,8 @@ class Disk(ConfigObject):
       raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
                                    " disk type %s" % self.dev_type)
 
-  def Update(self, size=None, mode=None):
-    """Apply changes to size and mode.
+  def Update(self, size=None, mode=None, spindles=None):
+    """Apply changes to size, spindles and mode.
 
     """
     if self.dev_type == constants.LD_DRBD8:
@@ -688,6 +689,8 @@ class Disk(ConfigObject):
       self.size = size
     if mode is not None:
       self.mode = mode
+    if spindles is not None:
+      self.spindles = spindles
 
   def UnsetSize(self):
     """Sets recursively the size to zero for the disk and its children.
@@ -698,7 +701,7 @@ class Disk(ConfigObject):
         child.UnsetSize()
     self.size = 0
 
-  def SetPhysicalID(self, target_node, nodes_ip):
+  def SetPhysicalID(self, target_node_uuid, nodes_ip):
     """Convert the logical ID to the physical ID.
 
     This is used only for drbd, which needs ip/port configuration.
@@ -708,7 +711,7 @@ class Disk(ConfigObject):
     node.
 
     Arguments:
-      - target_node: the node we wish to configure for
+      - target_node_uuid: the node UUID we wish to configure for
       - nodes_ip: a mapping of node name to ip
 
     The target_node must exist in in nodes_ip, and must be one of the
@@ -718,23 +721,23 @@ class Disk(ConfigObject):
     """
     if self.children:
       for child in self.children:
-        child.SetPhysicalID(target_node, nodes_ip)
+        child.SetPhysicalID(target_node_uuid, nodes_ip)
 
     if self.logical_id is None and self.physical_id is not None:
       return
     if self.dev_type in constants.LDS_DRBD:
-      pnode, snode, port, pminor, sminor, secret = self.logical_id
-      if target_node not in (pnode, snode):
+      pnode_uuid, snode_uuid, port, pminor, sminor, secret = self.logical_id
+      if target_node_uuid not in (pnode_uuid, snode_uuid):
         raise errors.ConfigurationError("DRBD device not knowing node %s" %
-                                        target_node)
-      pnode_ip = nodes_ip.get(pnode, None)
-      snode_ip = nodes_ip.get(snode, None)
+                                        target_node_uuid)
+      pnode_ip = nodes_ip.get(pnode_uuid, None)
+      snode_ip = nodes_ip.get(snode_uuid, None)
       if pnode_ip is None or snode_ip is None:
         raise errors.ConfigurationError("Can't find primary or secondary node"
                                         " for %s" % str(self))
       p_data = (pnode_ip, port)
       s_data = (snode_ip, port)
-      if pnode == target_node:
+      if pnode_uuid == target_node_uuid:
         self.physical_id = p_data + s_data + (pminor, secret)
       else: # it must be secondary, we tested above
         self.physical_id = s_data + p_data + (sminor, secret)
@@ -804,6 +807,8 @@ class Disk(ConfigObject):
       val += ", not visible"
     else:
       val += ", visible as /dev/%s" % self.iv_name
+    if self.spindles is not None:
+      val += ", spindles=%s" % self.spindles
     if isinstance(self.size, int):
       val += ", size=%dm)>" % self.size
     else:
@@ -1113,7 +1118,7 @@ class Instance(TaggableObject):
         'node' : ['lv', ...] data.
 
     @return: None if lvmap arg is given, otherwise, a dictionary of
-        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
+        the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
         volumeN is of the form "vg_name/lv_name", compatible with
         GetVolumeList()
 
index 9cc8028..26e0b06 100644 (file)
@@ -62,6 +62,10 @@ _PForce = ("force", False, ht.TBool, "Whether to force the operation")
 _PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString,
                   "Instance name")
 
+#: a instance UUID (for single-instance LUs)
+_PInstanceUuid = ("instance_uuid", None, ht.TMaybeString,
+                  "Instance UUID")
+
 #: Whether to ignore offline nodes
 _PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool,
                         "Whether to ignore offline nodes")
@@ -69,6 +73,9 @@ _PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool,
 #: a required node name (for single-node LUs)
 _PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString, "Node name")
 
+#: a node UUID (for use with _PNodeName)
+_PNodeUuid = ("node_uuid", None, ht.TMaybeString, "Node UUID")
+
 #: a required node group name (for single-group LUs)
 _PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString, "Group name")
 
@@ -133,6 +140,9 @@ _PNoRemember = ("no_remember", False, ht.TBool,
 _PMigrationTargetNode = ("target_node", None, ht.TMaybeString,
                          "Target node for shared-storage instances")
 
+_PMigrationTargetNodeUuid = ("target_node_uuid", None, ht.TMaybeString,
+                             "Target node UUID for shared-storage instances")
+
 _PStartupPaused = ("startup_paused", False, ht.TBool,
                    "Pause instance at startup")
 
@@ -868,7 +878,7 @@ class OpClusterRepairDiskSizes(OpCode):
   Parameters: optional instances list, in case we want to restrict the
   checks to only a subset of the instances.
 
-  Result: a list of tuples, (instance, disk, new-size) for changed
+  Result: a list of tuples, (instance, disk, parameter, new-size) for changed
   configurations.
 
   In normal operation, the list should be empty.
@@ -880,9 +890,10 @@ class OpClusterRepairDiskSizes(OpCode):
   OP_PARAMS = [
     ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
     ]
-  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(3),
+  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(4),
                                  ht.TItems([ht.TNonEmptyString,
                                             ht.TNonNegativeInt,
+                                            ht.TNonEmptyString,
                                             ht.TNonNegativeInt])))
 
 
@@ -1051,7 +1062,9 @@ class OpOobCommand(OpCode):
   """Interact with OOB."""
   OP_PARAMS = [
     ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
-     "List of nodes to run the OOB command against"),
+     "List of node names to run the OOB command against"),
+    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
+     "List of node UUIDs to run the OOB command against"),
     ("command", ht.NoDefault, ht.TElemOf(constants.OOB_COMMANDS),
      "OOB command to be run"),
     ("timeout", constants.OOB_TIMEOUT, ht.TInt,
@@ -1073,6 +1086,8 @@ class OpRestrictedCommand(OpCode):
     _PUseLocking,
     ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
      "Nodes on which the command should be run (at least one)"),
+    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
+     "Node UUIDs on which the command should be run (at least one)"),
     ("command", ht.NoDefault, ht.TNonEmptyString,
      "Command name (no parameters)"),
     ]
@@ -1100,6 +1115,7 @@ class OpNodeRemove(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid
     ]
   OP_RESULT = ht.TNone
 
@@ -1187,6 +1203,7 @@ class OpNodeModifyStorage(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid,
     _PStorageType,
     _PStorageName,
     ("changes", ht.NoDefault, ht.TDict, "Requested changes"),
@@ -1199,6 +1216,7 @@ class OpRepairNodeStorage(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid,
     _PStorageType,
     _PStorageName,
     _PIgnoreConsistency,
@@ -1211,6 +1229,7 @@ class OpNodeSetParams(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid,
     _PForce,
     _PHvState,
     _PDiskState,
@@ -1240,6 +1259,7 @@ class OpNodePowercycle(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid,
     _PForce,
     ]
   OP_RESULT = ht.TMaybeString
@@ -1250,9 +1270,11 @@ class OpNodeMigrate(OpCode):
   OP_DSC_FIELD = "node_name"
   OP_PARAMS = [
     _PNodeName,
+    _PNodeUuid,
     _PMigrationMode,
     _PMigrationLive,
     _PMigrationTargetNode,
+    _PMigrationTargetNodeUuid,
     _PAllowRuntimeChgs,
     _PIgnoreIpolicy,
     _PIAllocFromDesc("Iallocator for deciding the target node"
@@ -1267,7 +1289,9 @@ class OpNodeEvacuate(OpCode):
   OP_PARAMS = [
     _PEarlyRelease,
     _PNodeName,
+    _PNodeUuid,
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
+    ("remote_node_uuid", None, ht.TMaybeString, "New secondary node UUID"),
     _PIAllocFromDesc("Iallocator for computing solution"),
     ("mode", ht.NoDefault, ht.TElemOf(constants.NODE_EVAC_MODES),
      "Node evacuation mode"),
@@ -1333,7 +1357,9 @@ class OpInstanceCreate(OpCode):
     ("osparams", ht.EmptyDict, ht.TDict, "OS parameters for instance"),
     ("os_type", None, ht.TMaybeString, "Operating system"),
     ("pnode", None, ht.TMaybeString, "Primary node"),
+    ("pnode_uuid", None, ht.TMaybeString, "Primary node UUID"),
     ("snode", None, ht.TMaybeString, "Secondary node"),
+    ("snode_uuid", None, ht.TMaybeString, "Secondary node UUID"),
     ("source_handshake", None, ht.TMaybe(ht.TList),
      "Signed handshake from source (remote import only)"),
     ("source_instance_name", None, ht.TMaybeString,
@@ -1344,6 +1370,7 @@ class OpInstanceCreate(OpCode):
     ("source_x509_ca", None, ht.TMaybeString,
      "Source X509 CA in PEM format (remote import only)"),
     ("src_node", None, ht.TMaybeString, "Source node for import"),
+    ("src_node_uuid", None, ht.TMaybeString, "Source node UUID for import"),
     ("src_path", None, ht.TMaybeString, "Source directory for import"),
     ("start", True, ht.TBool, "Whether to start instance after creation"),
     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Instance tags"),
@@ -1416,6 +1443,7 @@ class OpInstanceReinstall(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PForceVariant,
     ("os_type", None, ht.TMaybeString, "Instance operating system"),
     ("osparams", None, ht.TMaybeDict, "Temporary OS parameters"),
@@ -1428,6 +1456,7 @@ class OpInstanceRemove(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PShutdownTimeout,
     ("ignore_failures", False, ht.TBool,
      "Whether to ignore failures during removal"),
@@ -1439,6 +1468,7 @@ class OpInstanceRename(OpCode):
   """Rename an instance."""
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PNameCheck,
     ("new_name", ht.NoDefault, ht.TNonEmptyString, "New instance name"),
     ("ip_check", False, ht.TBool, _PIpCheckDoc),
@@ -1451,6 +1481,7 @@ class OpInstanceStartup(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PForce,
     _PIgnoreOfflineNodes,
     ("hvparams", ht.EmptyDict, ht.TDict,
@@ -1467,6 +1498,7 @@ class OpInstanceShutdown(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PForce,
     _PIgnoreOfflineNodes,
     ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
@@ -1481,6 +1513,7 @@ class OpInstanceReboot(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PShutdownTimeout,
     ("ignore_secondaries", False, ht.TBool,
      "Whether to start the instance even if secondary disks are failing"),
@@ -1495,6 +1528,7 @@ class OpInstanceReplaceDisks(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PEarlyRelease,
     _PIgnoreIpolicy,
     ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
@@ -1502,6 +1536,7 @@ class OpInstanceReplaceDisks(OpCode):
     ("disks", ht.EmptyList, ht.TListOf(ht.TNonNegativeInt),
      "Disk indexes"),
     ("remote_node", None, ht.TMaybeString, "New secondary node"),
+    ("remote_node_uuid", None, ht.TMaybeString, "New secondary node UUID"),
     _PIAllocFromDesc("Iallocator for deciding new secondary node"),
     ]
   OP_RESULT = ht.TNone
@@ -1512,9 +1547,11 @@ class OpInstanceFailover(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PShutdownTimeout,
     _PIgnoreConsistency,
     _PMigrationTargetNode,
+    _PMigrationTargetNodeUuid,
     _PIgnoreIpolicy,
     _PIAllocFromDesc("Iallocator for deciding the target node for"
                      " shared-storage instances"),
@@ -1535,9 +1572,11 @@ class OpInstanceMigrate(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PMigrationMode,
     _PMigrationLive,
     _PMigrationTargetNode,
+    _PMigrationTargetNodeUuid,
     _PAllowRuntimeChgs,
     _PIgnoreIpolicy,
     ("cleanup", False, ht.TBool,
@@ -1563,9 +1602,11 @@ class OpInstanceMove(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PShutdownTimeout,
     _PIgnoreIpolicy,
     ("target_node", ht.NoDefault, ht.TNonEmptyString, "Target node"),
+    ("target_node_uuid", None, ht.TMaybeString, "Target node UUID"),
     _PIgnoreConsistency,
     ]
   OP_RESULT = ht.TNone
@@ -1576,6 +1617,7 @@ class OpInstanceConsole(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     ]
   OP_RESULT = ht.TDict
 
@@ -1585,6 +1627,7 @@ class OpInstanceActivateDisks(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     ("ignore_size", False, ht.TBool, "Whether to ignore recorded size"),
     _PWaitForSyncFalse,
     ]
@@ -1599,6 +1642,7 @@ class OpInstanceDeactivateDisks(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PForce,
     ]
   OP_RESULT = ht.TNone
@@ -1614,12 +1658,15 @@ class OpInstanceRecreateDisks(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     ("disks", ht.EmptyList,
      ht.TOr(ht.TListOf(ht.TNonNegativeInt), ht.TListOf(_TDiskChanges)),
      "List of disk indexes (deprecated) or a list of tuples containing a disk"
      " index and a possibly empty dictionary with disk parameter changes"),
     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
      "New instance nodes, if relocation is desired"),
+    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
+     "New instance node UUIDs, if relocation is desired"),
     _PIAllocFromDesc("Iallocator for deciding new nodes"),
     ]
   OP_RESULT = ht.TNone
@@ -1684,6 +1731,7 @@ class OpInstanceSetParams(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PForce,
     _PForceVariant,
     _PIgnoreIpolicy,
@@ -1707,8 +1755,11 @@ class OpInstanceSetParams(OpCode):
     ("disk_template", None, ht.TMaybe(_BuildDiskTemplateCheck(False)),
      "Disk template for instance"),
     ("pnode", None, ht.TMaybeString, "New primary node"),
+    ("pnode_uuid", None, ht.TMaybeString, "New primary node UUID"),
     ("remote_node", None, ht.TMaybeString,
      "Secondary node (used when changing disk template)"),
+    ("remote_node_uuid", None, ht.TMaybeString,
+     "Secondary node UUID (used when changing disk template)"),
     ("os_name", None, ht.TMaybeString,
      "Change the instance's OS without reinstalling the instance"),
     ("osparams", None, ht.TMaybeDict, "Per-instance OS parameters"),
@@ -1725,6 +1776,7 @@ class OpInstanceGrowDisk(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PWaitForSync,
     ("disk", ht.NoDefault, ht.TInt, "Disk index"),
     ("amount", ht.NoDefault, ht.TNonNegativeInt,
@@ -1740,6 +1792,7 @@ class OpInstanceChangeGroup(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PEarlyRelease,
     _PIAllocFromDesc("Iallocator for computing solution"),
     _PTargetGroups,
@@ -1773,6 +1826,8 @@ class OpGroupAssignNodes(OpCode):
     _PForce,
     ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
      "List of nodes to assign"),
+    ("node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString),
+     "List of node UUIDs to assign"),
     ]
   OP_RESULT = ht.TNone
 
@@ -1877,6 +1932,7 @@ class OpBackupPrepare(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
      "Export mode"),
     ]
@@ -1903,11 +1959,14 @@ class OpBackupExport(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     _PShutdownTimeout,
     # TODO: Rename target_node as it changes meaning for different export modes
     # (e.g. "destination")
     ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList),
      "Destination information, depends on export mode"),
+    ("target_node_uuid", None, ht.TMaybeString,
+     "Target node UUID (if local export)"),
     ("shutdown", True, ht.TBool, "Whether to shutdown instance before export"),
     ("remove_instance", False, ht.TBool,
      "Whether to remove instance after export"),
@@ -1932,6 +1991,7 @@ class OpBackupRemove(OpCode):
   OP_DSC_FIELD = "instance_name"
   OP_PARAMS = [
     _PInstanceName,
+    _PInstanceUuid,
     ]
   OP_RESULT = ht.TNone
 
@@ -2015,6 +2075,7 @@ class OpTestDelay(OpCode):
     ("duration", ht.NoDefault, ht.TNumber, None),
     ("on_master", True, ht.TBool, None),
     ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
+    ("on_node_uuids", None, ht.TMaybeListOf(ht.TNonEmptyString), None),
     ("repeat", 0, ht.TNonNegativeInt, None),
     ]
 
index 472391b..5cc60ac 100644 (file)
@@ -1091,16 +1091,18 @@ class NodeQueryData:
   """Data container for node data queries.
 
   """
-  def __init__(self, nodes, live_data, master_name, node_to_primary,
-               node_to_secondary, groups, oob_support, cluster):
+  def __init__(self, nodes, live_data, master_uuid, node_to_primary,
+               node_to_secondary, inst_uuid_to_inst_name, groups, oob_support,
+               cluster):
     """Initializes this class.
 
     """
     self.nodes = nodes
     self.live_data = live_data
-    self.master_name = master_name
+    self.master_uuid = master_uuid
     self.node_to_primary = node_to_primary
     self.node_to_secondary = node_to_secondary
+    self.inst_uuid_to_inst_name = inst_uuid_to_inst_name
     self.groups = groups
     self.oob_support = oob_support
     self.cluster = cluster
@@ -1123,7 +1125,7 @@ class NodeQueryData:
       else:
         self.ndparams = self.cluster.FillND(node, group)
       if self.live_data:
-        self.curlive_data = self.live_data.get(node.name, None)
+        self.curlive_data = self.live_data.get(node.uuid, None)
       else:
         self.curlive_data = None
       yield node
@@ -1155,11 +1157,15 @@ _NODE_LIVE_FIELDS = {
   "csockets": ("CSockets", QFT_NUMBER, "cpu_sockets",
                "Number of physical CPU sockets (if exported by hypervisor)"),
   "ctotal": ("CTotal", QFT_NUMBER, "cpu_total", "Number of logical processors"),
-  "dfree": ("DFree", QFT_UNIT, "vg_free",
-            "Available disk space in volume group"),
-  "dtotal": ("DTotal", QFT_UNIT, "vg_size",
-             "Total disk space in volume group used for instance disk"
+  "dfree": ("DFree", QFT_UNIT, "storage_free",
+            "Available storage space in storage unit"),
+  "dtotal": ("DTotal", QFT_UNIT, "storage_size",
+             "Total storage space in storage unit used for instance disk"
              " allocation"),
+  "spfree": ("SpFree", QFT_NUMBER, "spindles_free",
+             "Available spindles in volume group (exclusive storage only)"),
+  "sptotal": ("SpTotal", QFT_NUMBER, "spindles_total",
+              "Total spindles in volume group (exclusive storage only)"),
   "mfree": ("MFree", QFT_UNIT, "memory_free",
             "Memory available for instance allocations"),
   "mnode": ("MNode", QFT_UNIT, "memory_dom0",
@@ -1214,7 +1220,7 @@ def _GetNodePower(ctx, node):
   @param node: Node object
 
   """
-  if ctx.oob_support[node.name]:
+  if ctx.oob_support[node.uuid]:
     return node.powered
 
   return _FS_UNAVAIL
@@ -1325,7 +1331,7 @@ def _BuildNodeFields():
     (_MakeField("tags", "Tags", QFT_OTHER, "Tags"), NQ_CONFIG, 0,
      lambda ctx, node: list(node.GetTags())),
     (_MakeField("master", "IsMaster", QFT_BOOL, "Whether node is master"),
-     NQ_CONFIG, 0, lambda ctx, node: node.name == ctx.master_name),
+     NQ_CONFIG, 0, lambda ctx, node: node.uuid == ctx.master_uuid),
     (_MakeField("group", "Group", QFT_TEXT, "Node group"), NQ_GROUP, 0,
      _GetGroup(_GetNodeGroup)),
     (_MakeField("group.uuid", "GroupUUID", QFT_TEXT, "UUID of node group"),
@@ -1355,14 +1361,16 @@ def _BuildNodeFields():
               " \"%s\" for regular, \"%s\" for drained, \"%s\" for offline" %
               role_values)
   fields.append((_MakeField("role", "Role", QFT_TEXT, role_doc), NQ_CONFIG, 0,
-                 lambda ctx, node: _GetNodeRole(node, ctx.master_name)))
+                 lambda ctx, node: _GetNodeRole(node, ctx.master_uuid)))
   assert set(role_values) == constants.NR_ALL
 
   def _GetLength(getter):
-    return lambda ctx, node: len(getter(ctx)[node.name])
+    return lambda ctx, node: len(getter(ctx)[node.uuid])
 
   def _GetList(getter):
-    return lambda ctx, node: utils.NiceSort(list(getter(ctx)[node.name]))
+    return lambda ctx, node: utils.NiceSort(
+                               [ctx.inst_uuid_to_inst_name[uuid]
+                                for uuid in getter(ctx)[node.uuid]])
 
   # Add fields operating on instance lists
   for prefix, titleprefix, docword, getter in \
@@ -1400,40 +1408,42 @@ class InstanceQueryData:
   """Data container for instance data queries.
 
   """
-  def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes,
-               live_data, wrongnode_inst, console, nodes, groups, networks):
+  def __init__(self, instances, cluster, disk_usage, offline_node_uuids,
+               bad_node_uuids, live_data, wrongnode_inst, console, nodes,
+               groups, networks):
     """Initializes this class.
 
     @param instances: List of instance objects
     @param cluster: Cluster object
-    @type disk_usage: dict; instance name as key
+    @type disk_usage: dict; instance UUID as key
     @param disk_usage: Per-instance disk usage
-    @type offline_nodes: list of strings
-    @param offline_nodes: List of offline nodes
-    @type bad_nodes: list of strings
-    @param bad_nodes: List of faulty nodes
-    @type live_data: dict; instance name as key
+    @type offline_node_uuids: list of strings
+    @param offline_node_uuids: List of offline nodes
+    @type bad_node_uuids: list of strings
+    @param bad_node_uuids: List of faulty nodes
+    @type live_data: dict; instance UUID as key
     @param live_data: Per-instance live data
     @type wrongnode_inst: set
     @param wrongnode_inst: Set of instances running on wrong node(s)
-    @type console: dict; instance name as key
+    @type console: dict; instance UUID as key
     @param console: Per-instance console information
-    @type nodes: dict; node name as key
+    @type nodes: dict; node UUID as key
     @param nodes: Node objects
     @type networks: dict; net_uuid as key
     @param networks: Network objects
 
     """
-    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
+    assert len(set(bad_node_uuids) & set(offline_node_uuids)) == \
+           len(offline_node_uuids), \
            "Offline nodes not included in bad nodes"
-    assert not (set(live_data.keys()) & set(bad_nodes)), \
+    assert not (set(live_data.keys()) & set(bad_node_uuids)), \
            "Found live data for bad or offline nodes"
 
     self.instances = instances
     self.cluster = cluster
     self.disk_usage = disk_usage
-    self.offline_nodes = offline_nodes
-    self.bad_nodes = bad_nodes
+    self.offline_nodes = offline_node_uuids
+    self.bad_nodes = bad_node_uuids
     self.live_data = live_data
     self.wrongnode_inst = wrongnode_inst
     self.console = console
@@ -1477,7 +1487,7 @@ def _GetInstOperState(ctx, inst):
   if inst.primary_node in ctx.bad_nodes:
     return _FS_NODATA
   else:
-    return bool(ctx.live_data.get(inst.name))
+    return bool(ctx.live_data.get(inst.uuid))
 
 
 def _GetInstLiveData(name):
@@ -1501,8 +1511,8 @@ def _GetInstLiveData(name):
       # offline when we actually don't know due to missing data
       return _FS_NODATA
 
-    if inst.name in ctx.live_data:
-      data = ctx.live_data[inst.name]
+    if inst.uuid in ctx.live_data:
+      data = ctx.live_data[inst.uuid]
       if name in data:
         return data[name]
 
@@ -1525,8 +1535,8 @@ def _GetInstStatus(ctx, inst):
   if inst.primary_node in ctx.bad_nodes:
     return constants.INSTST_NODEDOWN
 
-  if bool(ctx.live_data.get(inst.name)):
-    if inst.name in ctx.wrongnode_inst:
+  if bool(ctx.live_data.get(inst.uuid)):
+    if inst.uuid in ctx.wrongnode_inst:
       return constants.INSTST_WRONGNODE
     elif inst.admin_state == constants.ADMINST_UP:
       return constants.INSTST_RUNNING
@@ -1582,6 +1592,19 @@ def _GetInstDiskSize(ctx, _, disk): # pylint: disable=W0613
     return disk.size
 
 
+def _GetInstDiskSpindles(ctx, _, disk): # pylint: disable=W0613
+  """Get a Disk's spindles.
+
+  @type disk: L{objects.Disk}
+  @param disk: The Disk object
+
+  """
+  if disk.spindles is None:
+    return _FS_UNAVAIL
+  else:
+    return disk.spindles
+
+
 def _GetInstDeviceName(ctx, _, device): # pylint: disable=W0613
   """Get a Device's Name.
 
@@ -1865,7 +1888,7 @@ def _GetInstDiskUsage(ctx, inst):
   @param inst: Instance object
 
   """
-  usage = ctx.disk_usage[inst.name]
+  usage = ctx.disk_usage[inst.uuid]
 
   if usage is None:
     usage = 0
@@ -1881,7 +1904,7 @@ def _GetInstanceConsole(ctx, inst):
   @param inst: Instance object
 
   """
-  consinfo = ctx.console[inst.name]
+  consinfo = ctx.console[inst.uuid]
 
   if consinfo is None:
     return _FS_UNAVAIL
@@ -1905,6 +1928,9 @@ def _GetInstanceDiskFields():
      IQ_CONFIG, 0, lambda ctx, inst: len(inst.disks)),
     (_MakeField("disk.sizes", "Disk_sizes", QFT_OTHER, "List of disk sizes"),
      IQ_CONFIG, 0, lambda ctx, inst: [disk.size for disk in inst.disks]),
+    (_MakeField("disk.spindles", "Disk_spindles", QFT_OTHER,
+                "List of disk spindles"),
+     IQ_CONFIG, 0, lambda ctx, inst: [disk.spindles for disk in inst.disks]),
     (_MakeField("disk.names", "Disk_names", QFT_OTHER, "List of disk names"),
      IQ_CONFIG, 0, lambda ctx, inst: [disk.name for disk in inst.disks]),
     (_MakeField("disk.uuids", "Disk_UUIDs", QFT_OTHER, "List of disk UUIDs"),
@@ -1918,6 +1944,9 @@ def _GetInstanceDiskFields():
         (_MakeField("disk.size/%s" % i, "Disk/%s" % i, QFT_UNIT,
                     "Disk size of %s disk" % numtext),
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSize)),
+        (_MakeField("disk.spindles/%s" % i, "DiskSpindles/%s" % i, QFT_NUMBER,
+                    "Spindles of %s disk" % numtext),
+         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDiskSpindles)),
         (_MakeField("disk.name/%s" % i, "DiskName/%s" % i, QFT_TEXT,
                     "Name of %s disk" % numtext),
         IQ_CONFIG, 0, _GetInstDisk(i, _GetInstDeviceName)),
@@ -2000,34 +2029,51 @@ _INST_SIMPLE_FIELDS = {
   }
 
 
-def _GetInstNodeGroup(ctx, default, node_name):
+def _GetNodeName(ctx, default, node_uuid):
+  """Gets node name of a node.
+
+  @type ctx: L{InstanceQueryData}
+  @param default: Default value
+  @type node_uuid: string
+  @param node_uuid: Node UUID
+
+  """
+  try:
+    node = ctx.nodes[node_uuid]
+  except KeyError:
+    return default
+  else:
+    return node.name
+
+
+def _GetInstNodeGroup(ctx, default, node_uuid):
   """Gets group UUID of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
-  @type node_name: string
-  @param node_name: Node name
+  @type node_uuid: string
+  @param node_uuid: Node UUID
 
   """
   try:
-    node = ctx.nodes[node_name]
+    node = ctx.nodes[node_uuid]
   except KeyError:
     return default
   else:
     return node.group
 
 
-def _GetInstNodeGroupName(ctx, default, node_name):
+def _GetInstNodeGroupName(ctx, default, node_uuid):
   """Gets group name of an instance node.
 
   @type ctx: L{InstanceQueryData}
   @param default: Default value
-  @type node_name: string
-  @param node_name: Node name
+  @type node_uuid: string
+  @param node_uuid: Node UUID
 
   """
   try:
-    node = ctx.nodes[node_name]
+    node = ctx.nodes[node_uuid]
   except KeyError:
     return default
 
@@ -2045,7 +2091,8 @@ def _BuildInstanceFields():
   """
   fields = [
     (_MakeField("pnode", "Primary_node", QFT_TEXT, "Primary node"),
-     IQ_CONFIG, QFF_HOSTNAME, _GetItemAttr("primary_node")),
+     IQ_NODES, QFF_HOSTNAME,
+     lambda ctx, inst: _GetNodeName(ctx, None, inst.primary_node)),
     (_MakeField("pnode.group", "PrimaryNodeGroup", QFT_TEXT,
                 "Primary node's group"),
      IQ_NODES, 0,
@@ -2058,7 +2105,9 @@ def _BuildInstanceFields():
     # TODO: Allow filtering by secondary node as hostname
     (_MakeField("snodes", "Secondary_Nodes", QFT_OTHER,
                 "Secondary nodes; usually this will just be one node"),
-     IQ_CONFIG, 0, lambda ctx, inst: list(inst.secondary_nodes)),
+     IQ_NODES, 0,
+     lambda ctx, inst: map(compat.partial(_GetNodeName, ctx, None),
+                           inst.secondary_nodes)),
     (_MakeField("snodes.group", "SecondaryNodesGroups", QFT_OTHER,
                 "Node groups of secondary nodes"),
      IQ_NODES, 0,
@@ -2554,17 +2603,18 @@ _CLUSTER_VERSION_FIELDS = {
 
 _CLUSTER_SIMPLE_FIELDS = {
   "cluster_name": ("Name", QFT_TEXT, QFF_HOSTNAME, "Cluster name"),
-  "master_node": ("Master", QFT_TEXT, QFF_HOSTNAME, "Master node name"),
   "volume_group_name": ("VgName", QFT_TEXT, 0, "LVM volume group name"),
   }
 
 
 class ClusterQueryData:
-  def __init__(self, cluster, drain_flag, watcher_pause):
+  def __init__(self, cluster, nodes, drain_flag, watcher_pause):
     """Initializes this class.
 
     @type cluster: L{objects.Cluster}
     @param cluster: Instance of cluster object
+    @type nodes: dict; node UUID as key
+    @param nodes: Node objects
     @type drain_flag: bool
     @param drain_flag: Whether job queue is drained
     @type watcher_pause: number
@@ -2572,6 +2622,7 @@ class ClusterQueryData:
 
     """
     self._cluster = cluster
+    self.nodes = nodes
     self.drain_flag = drain_flag
     self.watcher_pause = watcher_pause
 
@@ -2605,6 +2656,9 @@ def _BuildClusterFields():
     (_MakeField("watcher_pause", "WatcherPause", QFT_TIMESTAMP,
                 "Until when watcher is paused"), CQ_WATCHER_PAUSE, 0,
      _ClusterWatcherPause),
+    (_MakeField("master_node", "Master", QFT_TEXT, "Master node name"),
+     CQ_CONFIG, QFF_HOSTNAME,
+     lambda ctx, cluster: _GetNodeName(ctx, None, cluster.master_node)),
     ]
 
   # Simple fields
index d35e3aa..aae61df 100644 (file)
@@ -74,14 +74,14 @@ I_FIELDS = ["name", "admin_state", "os",
             "nic.ips", "nic.macs", "nic.modes",
             "nic.links", "nic.networks", "nic.bridges",
             "network_port",
-            "disk.sizes", "disk_usage",
+            "disk.sizes", "disk.spindles", "disk_usage",
             "beparams", "hvparams",
             "oper_state", "oper_ram", "oper_vcpus", "status",
             "custom_hvparams", "custom_beparams", "custom_nicparams",
             ] + _COMMON_FIELDS
 
 N_FIELDS = ["name", "offline", "master_candidate", "drained",
-            "dtotal", "dfree",
+            "dtotal", "dfree", "sptotal", "spfree",
             "mtotal", "mnode", "mfree",
             "pinst_cnt", "sinst_cnt",
             "ctotal", "cnodes", "csockets",
index f6da489..f3480c2 100644 (file)
@@ -230,6 +230,19 @@ class RpcResult(object):
       args = (msg, )
     raise ec(*args) # pylint: disable=W0142
 
+  def Warn(self, msg, feedback_fn):
+    """If the result has failed, call the feedback_fn.
+
+    This is used to in cases were LU wants to warn the
+    user about a failure, but continue anyway.
+
+    """
+    if not self.fail_msg:
+      return
+
+    msg = "%s: %s" % (msg, self.fail_msg)
+    feedback_fn(msg)
+
 
 def _SsconfResolver(ssconf_ips, node_list, _,
                     ssc=ssconf.SimpleStore,
@@ -262,7 +275,7 @@ def _SsconfResolver(ssconf_ips, node_list, _,
     ip = ipmap.get(node)
     if ip is None:
       ip = nslookup_fn(node, family=family)
-    result.append((node, ip))
+    result.append((node, ip, node))
 
   return result
 
@@ -279,54 +292,60 @@ class _StaticResolver:
 
     """
     assert len(hosts) == len(self._addresses)
-    return zip(hosts, self._addresses)
+    return zip(hosts, self._addresses, hosts)
 
 
-def _CheckConfigNode(name, node, accept_offline_node):
+def _CheckConfigNode(node_uuid_or_name, node, accept_offline_node):
   """Checks if a node is online.
 
-  @type name: string
-  @param name: Node name
+  @type node_uuid_or_name: string
+  @param node_uuid_or_name: Node UUID
   @type node: L{objects.Node} or None
   @param node: Node object
 
   """
   if node is None:
-    # Depend on DNS for name resolution
-    ip = name
-  elif node.offline and not accept_offline_node:
-    ip = _OFFLINE
+    # Assume that the passed parameter was actually a node name, so depend on
+    # DNS for name resolution
+    return (node_uuid_or_name, node_uuid_or_name, node_uuid_or_name)
   else:
-    ip = node.primary_ip
-  return (name, ip)
+    if node.offline and not accept_offline_node:
+      ip = _OFFLINE
+    else:
+      ip = node.primary_ip
+    return (node.name, ip, node_uuid_or_name)
 
 
-def _NodeConfigResolver(single_node_fn, all_nodes_fn, hosts, opts):
+def _NodeConfigResolver(single_node_fn, all_nodes_fn, node_uuids, opts):
   """Calculate node addresses using configuration.
 
+  Note that strings in node_uuids are treated as node names if the UUID is not
+  found in the configuration.
+
   """
   accept_offline_node = (opts is rpc_defs.ACCEPT_OFFLINE_NODE)
 
   assert accept_offline_node or opts is None, "Unknown option"
 
   # Special case for single-host lookups
-  if len(hosts) == 1:
-    (name, ) = hosts
-    return [_CheckConfigNode(name, single_node_fn(name), accept_offline_node)]
+  if len(node_uuids) == 1:
+    (uuid, ) = node_uuids
+    return [_CheckConfigNode(uuid, single_node_fn(uuid), accept_offline_node)]
   else:
     all_nodes = all_nodes_fn()
-    return [_CheckConfigNode(name, all_nodes.get(name, None),
+    return [_CheckConfigNode(uuid, all_nodes.get(uuid, None),
                              accept_offline_node)
-            for name in hosts]
+            for uuid in node_uuids]
 
 
 class _RpcProcessor:
   def __init__(self, resolver, port, lock_monitor_cb=None):
     """Initializes this class.
 
-    @param resolver: callable accepting a list of hostnames, returning a list
-      of tuples containing name and IP address (IP address can be the name or
-      the special value L{_OFFLINE} to mark offline machines)
+    @param resolver: callable accepting a list of node UUIDs or hostnames,
+      returning a list of tuples containing name, IP address and original name
+      of the resolved node. IP address can be the name or the special value
+      L{_OFFLINE} to mark offline machines.
     @type port: int
     @param port: TCP port
     @param lock_monitor_cb: Callable for registering with lock monitor
@@ -350,19 +369,21 @@ class _RpcProcessor:
     assert isinstance(body, dict)
     assert len(body) == len(hosts)
     assert compat.all(isinstance(v, str) for v in body.values())
-    assert frozenset(map(compat.fst, hosts)) == frozenset(body.keys()), \
+    assert frozenset(map(lambda x: x[2], hosts)) == frozenset(body.keys()), \
         "%s != %s" % (hosts, body.keys())
 
-    for (name, ip) in hosts:
+    for (name, ip, original_name) in hosts:
       if ip is _OFFLINE:
         # Node is marked as offline
-        results[name] = RpcResult(node=name, offline=True, call=procedure)
+        results[original_name] = RpcResult(node=name,
+                                           offline=True,
+                                           call=procedure)
       else:
-        requests[name] = \
+        requests[original_name] = \
           http.client.HttpClientRequest(str(ip), port,
                                         http.HTTP_POST, str("/%s" % procedure),
                                         headers=_RPC_CLIENT_HEADERS,
-                                        post_data=body[name],
+                                        post_data=body[original_name],
                                         read_timeout=read_timeout,
                                         nicename="%s/%s" % (name, procedure),
                                         curl_config_fn=_ConfigRpcCurl)
@@ -393,12 +414,12 @@ class _RpcProcessor:
 
     return results
 
-  def __call__(self, hosts, procedure, body, read_timeout, resolver_opts,
+  def __call__(self, nodes, procedure, body, read_timeout, resolver_opts,
                _req_process_fn=None):
     """Makes an RPC request to a number of nodes.
 
-    @type hosts: sequence
-    @param hosts: Hostnames
+    @type nodes: sequence
+    @param nodes: node UUIDs or Hostnames
     @type procedure: string
     @param procedure: Request path
     @type body: dictionary
@@ -416,7 +437,7 @@ class _RpcProcessor:
       _req_process_fn = http.client.ProcessRequests
 
     (results, requests) = \
-      self._PrepareRequests(self._resolver(hosts, resolver_opts), self._port,
+      self._PrepareRequests(self._resolver(nodes, resolver_opts), self._port,
                             procedure, body, read_timeout)
 
     _req_process_fn(requests.values(), lock_monitor_cb=self._lock_monitor_cb)
@@ -571,6 +592,58 @@ def _EncodeBlockdevRename(value):
   return [(d.ToDict(), uid) for d, uid in value]
 
 
+def _AddSpindlesToLegacyNodeInfo(result, space_info):
+  """Extracts the spindle information from the space info and adds
+  it to the result dictionary.
+
+  @type result: dict of strings
+  @param result: dictionary holding the result of the legacy node info
+  @type space_info: list of dicts of strings
+  @param space_info: list, each row holding space information of one storage
+    unit
+  @rtype: None
+  @return: does not return anything, manipulates the C{result} variable
+
+  """
+  lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
+      space_info, constants.ST_LVM_PV)
+  if lvm_pv_info:
+    result["spindles_free"] = lvm_pv_info["storage_free"]
+    result["spindles_total"] = lvm_pv_info["storage_size"]
+
+
+def _AddDefaultStorageInfoToLegacyNodeInfo(result, space_info,
+                                           require_vg_info=True):
+  """Extracts the storage space information of the default storage type from
+  the space info and adds it to the result dictionary.
+
+  @see: C{_AddSpindlesToLegacyNodeInfo} for parameter information.
+  @type require_vg_info: boolean
+  @param require_vg_info: indicates whether volume group information is
+    required or not
+
+  """
+  # Check if there is at least one row for non-spindle storage info.
+  no_defaults = (len(space_info) < 1) or \
+      (space_info[0]["type"] == constants.ST_LVM_PV and len(space_info) == 1)
+
+  default_space_info = None
+  if no_defaults:
+    logging.warning("No storage info provided for default storage type.")
+  else:
+    default_space_info = space_info[0]
+
+  if require_vg_info:
+    if no_defaults or not default_space_info["type"] == constants.ST_LVM_VG:
+      raise errors.OpExecError("LVM volume group info required, but not"
+                               " provided.")
+
+  if default_space_info:
+    result["name"] = default_space_info["name"]
+    result["storage_free"] = default_space_info["storage_free"]
+    result["storage_size"] = default_space_info["storage_size"]
+
+
 def MakeLegacyNodeInfo(data, require_vg_info=True):
   """Formats the data returned by L{rpc.RpcRunner.call_node_info}.
 
@@ -581,13 +654,13 @@ def MakeLegacyNodeInfo(data, require_vg_info=True):
       doesn't have any values
 
   """
-  (bootid, vgs_info, (hv_info, )) = data
+  (bootid, space_info, (hv_info, )) = data
 
   ret = utils.JoinDisjointDicts(hv_info, {"bootid": bootid})
 
-  if require_vg_info or vgs_info:
-    (vg0_info, ) = vgs_info
-    ret = utils.JoinDisjointDicts(vg0_info, ret)
+  _AddSpindlesToLegacyNodeInfo(ret, space_info)
+  _AddDefaultStorageInfoToLegacyNodeInfo(ret, space_info,
+                                         require_vg_info=require_vg_info)
 
   return ret
 
@@ -638,29 +711,30 @@ def AnnotateDiskParams(template, disks, disk_params):
   return [annotation_fn(disk.Copy(), ld_params) for disk in disks]
 
 
-def _GetESFlag(cfg, nodename):
-  ni = cfg.GetNodeInfo(nodename)
+def _GetESFlag(cfg, node_uuid):
+  ni = cfg.GetNodeInfo(node_uuid)
   if ni is None:
-    raise errors.OpPrereqError("Invalid node name %s" % nodename,
+    raise errors.OpPrereqError("Invalid node name %s" % node_uuid,
                                errors.ECODE_NOENT)
   return cfg.GetNdParams(ni)[constants.ND_EXCLUSIVE_STORAGE]
 
 
-def GetExclusiveStorageForNodeNames(cfg, nodelist):
+def GetExclusiveStorageForNodes(cfg, node_uuids):
   """Return the exclusive storage flag for all the given nodes.
 
   @type cfg: L{config.ConfigWriter}
   @param cfg: cluster configuration
-  @type nodelist: list or tuple
-  @param nodelist: node names for which to read the flag
+  @type node_uuids: list or tuple
+  @param node_uuids: node UUIDs for which to read the flag
   @rtype: dict
   @return: mapping from node names to exclusive storage flags
-  @raise errors.OpPrereqError: if any given node name has no corresponding node
+  @raise errors.OpPrereqError: if any given node name has no corresponding
+  node
 
   """
   getflag = lambda n: _GetESFlag(cfg, n)
-  flags = map(getflag, nodelist)
-  return dict(zip(nodelist, flags))
+  flags = map(getflag, node_uuids)
+  return dict(zip(node_uuids, flags))
 
 
 #: Generic encoders
index c6f9a09..07b4645 100644 (file)
@@ -142,6 +142,11 @@ def _NodeInfoPreProc(node, args):
     return args
 
 
+def _DrbdCallsPreProc(node, args):
+  """Add the target node UUID as additional field for DRBD related calls."""
+  return args + [node]
+
+
 def _OsGetPostProc(result):
   """Post-processor for L{rpc.RpcRunner.call_os_get}.
 
@@ -218,13 +223,16 @@ _INSTANCE_CALLS = [
   ("instance_info", SINGLE, None, constants.RPC_TMO_URGENT, [
     ("instance", None, "Instance name"),
     ("hname", None, "Hypervisor type"),
+    ("hvparams", None, "Hypervisor parameters"),
     ], None, None, "Returns information about a single instance"),
   ("all_instances_info", MULTI, None, constants.RPC_TMO_URGENT, [
     ("hypervisor_list", None, "Hypervisors to query for instances"),
+    ("all_hvparams", None, "Dictionary mapping hypervisor names to hvparams"),
     ], None, None,
    "Returns information about all instances on the given nodes"),
   ("instance_list", MULTI, None, constants.RPC_TMO_URGENT, [
     ("hypervisor_list", None, "Hypervisors to query for instances"),
+    ("hvparams", None, "Hvparams of all hypervisors"),
     ], None, None, "Returns the list of running instances on the given nodes"),
   ("instance_reboot", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("inst", ED_INST_DICT, "Instance object"),
@@ -264,6 +272,7 @@ _INSTANCE_CALLS = [
     ("success", None, "Whether the migration was a success or failure"),
     ], None, None, "Finalize any target-node migration specific operation"),
   ("instance_migrate", SINGLE, None, constants.RPC_TMO_SLOW, [
+    ("cluster_name", None, "Cluster name"),
     ("instance", ED_INST_DICT, "Instance object"),
     ("target", None, "Target node name"),
     ("live", None, "Whether the migration should be done live or not"),
@@ -384,23 +393,24 @@ _BLOCKDEV_CALLS = [
     ("instance_name", None, None),
     ("disks", ED_OBJECT_DICT_LIST, None),
     ], None, None, "Closes the given block devices"),
-  ("blockdev_getsize", SINGLE, None, constants.RPC_TMO_NORMAL, [
+  ("blockdev_getdimensions", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("disks", ED_OBJECT_DICT_LIST, None),
-    ], None, None, "Returns the size of the given disks"),
+    ], None, None, "Returns size and spindles of the given disks"),
   ("drbd_disconnect_net", MULTI, None, constants.RPC_TMO_NORMAL, [
     ("nodes_ip", None, None),
     ("disks", ED_OBJECT_DICT_LIST, None),
-    ], None, None, "Disconnects the network of the given drbd devices"),
+    ], _DrbdCallsPreProc, None,
+   "Disconnects the network of the given drbd devices"),
   ("drbd_attach_net", MULTI, None, constants.RPC_TMO_NORMAL, [
     ("nodes_ip", None, None),
     ("disks", ED_DISKS_DICT_DP, None),
     ("instance_name", None, None),
     ("multimaster", None, None),
-    ], None, None, "Connects the given DRBD devices"),
+    ], _DrbdCallsPreProc, None, "Connects the given DRBD devices"),
   ("drbd_wait_sync", MULTI, None, constants.RPC_TMO_SLOW, [
     ("nodes_ip", None, None),
     ("disks", ED_DISKS_DICT_DP, None),
-    ], None, None,
+    ], _DrbdCallsPreProc, None,
    "Waits for the synchronization of drbd devices is complete"),
   ("blockdev_grow", SINGLE, None, constants.RPC_TMO_NORMAL, [
     ("cf_bdev", ED_SINGLE_DISK_DICT_DP, None),
@@ -464,16 +474,19 @@ _NODE_CALLS = [
     ("address", None, "IP address"),
     ], None, None, "Checks if a node has the given IP address"),
   ("node_info", MULTI, None, constants.RPC_TMO_URGENT, [
-    ("vg_names", None,
-     "Names of the volume groups to ask for disk space information"),
-    ("hv_names", None,
-     "Names of the hypervisors to ask for node information"),
+    ("storage_units", None,
+     "List of tuples '<storage_type>,<key>' to ask for disk space"
+     " information"),
+    ("hv_specs", None,
+     "List of hypervisor specification (name, hvparams) to ask for node "
+     "information"),
     ("exclusive_storage", None,
      "Whether exclusive storage is enabled"),
     ], _NodeInfoPreProc, None, "Return node information"),
   ("node_verify", MULTI, None, constants.RPC_TMO_NORMAL, [
-    ("checkdict", None, None),
-    ("cluster_name", None, None),
+    ("checkdict", None, "What to verify"),
+    ("cluster_name", None, "Cluster name"),
+    ("all_hvparams", None, "Dictionary mapping hypervisor names to hvparams"),
     ], None, None, "Request verification of given parameters"),
   ("node_volumes", MULTI, None, constants.RPC_TMO_FAST, [], None, None,
    "Gets all volumes on node(s)"),
@@ -481,6 +494,7 @@ _NODE_CALLS = [
    "Demote a node from the master candidate role"),
   ("node_powercycle", SINGLE, ACCEPT_OFFLINE_NODE, constants.RPC_TMO_NORMAL, [
     ("hypervisor", None, "Hypervisor type"),
+    ("hvparams", None, "Hypervisor parameters"),
     ], None, None, "Tries to powercycle a node"),
   ]
 
@@ -588,8 +602,9 @@ CALLS = {
     ("version", MULTI, ACCEPT_OFFLINE_NODE, constants.RPC_TMO_URGENT, [], None,
      None, "Query node version"),
     ("node_verify_light", MULTI, None, constants.RPC_TMO_NORMAL, [
-      ("checkdict", None, None),
-      ("cluster_name", None, None),
+      ("checkdict", None, "What to verify"),
+      ("cluster_name", None, "Cluster name"),
+      ("hvparams", None, "Dictionary mapping hypervisor names to hvparams"),
       ], None, None, "Request verification of given parameters"),
     ]),
   "RpcClientConfig": _Prepare([
index 8b52c35..febf934 100644 (file)
@@ -487,7 +487,7 @@ class GanetiContext(object):
     self.glm = locking.GanetiLockManager(
       self.cfg.GetNodeList(),
       self.cfg.GetNodeGroupList(),
-      self.cfg.GetInstanceList(),
+      [inst.name for inst in self.cfg.GetAllInstancesInfo().values()],
       self.cfg.GetNetworkList())
 
     self.cfg.SetContext(self)
@@ -519,8 +519,8 @@ class GanetiContext(object):
     self.jobqueue.AddNode(node)
 
     # Add the new node to the Ganeti Lock Manager
-    self.glm.add(locking.LEVEL_NODE, node.name)
-    self.glm.add(locking.LEVEL_NODE_RES, node.name)
+    self.glm.add(locking.LEVEL_NODE, node.uuid)
+    self.glm.add(locking.LEVEL_NODE_RES, node.uuid)
 
   def ReaddNode(self, node):
     """Updates a node that's already in the configuration
@@ -529,19 +529,19 @@ class GanetiContext(object):
     # Synchronize the queue again
     self.jobqueue.AddNode(node)
 
-  def RemoveNode(self, name):
+  def RemoveNode(self, node):
     """Removes a node from the configuration and lock manager.
 
     """
     # Remove node from configuration
-    self.cfg.RemoveNode(name)
+    self.cfg.RemoveNode(node.uuid)
 
     # Notify job queue
-    self.jobqueue.RemoveNode(name)
+    self.jobqueue.RemoveNode(node.name)
 
     # Remove the node from the Ganeti Lock Manager
-    self.glm.remove(locking.LEVEL_NODE, name)
-    self.glm.remove(locking.LEVEL_NODE_RES, name)
+    self.glm.remove(locking.LEVEL_NODE, node.uuid)
+    self.glm.remove(locking.LEVEL_NODE_RES, node.uuid)
 
 
 def _SetWatcherPause(context, until):
@@ -602,11 +602,11 @@ def CheckAgreement():
   myself = netutils.Hostname.GetSysName()
   #temp instantiation of a config writer, used only to get the node list
   cfg = config.ConfigWriter()
-  node_list = cfg.GetNodeList()
+  node_names = cfg.GetNodeNames(cfg.GetNodeList())
   del cfg
   retries = 6
   while retries > 0:
-    votes = bootstrap.GatherMasterVotes(node_list)
+    votes = bootstrap.GatherMasterVotes(node_names)
     if not votes:
       # empty node list, this is a one node cluster
       return True
@@ -646,8 +646,9 @@ def ActivateMasterIP():
   master_params = cfg.GetMasterNetworkParameters()
   ems = cfg.GetUseExternalMipScript()
   runner = rpc.BootstrapRunner()
-  result = runner.call_node_activate_master_ip(master_params.name,
-                                               master_params, ems)
+  # we use the node name, as the configuration is only available here yet
+  result = runner.call_node_activate_master_ip(
+             cfg.GetNodeName(master_params.uuid), master_params, ems)
 
   msg = result.fail_msg
   if msg:
index a7d7a88..386e568 100644 (file)
@@ -45,7 +45,7 @@ from ganeti import jstore
 from ganeti import daemon
 from ganeti import http
 from ganeti import utils
-from ganeti import storage
+from ganeti.storage import container
 from ganeti import serializer
 from ganeti import netutils
 from ganeti import pathutils
@@ -379,12 +379,12 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     return backend.BlockdevClose(params[0], disks)
 
   @staticmethod
-  def perspective_blockdev_getsize(params):
+  def perspective_blockdev_getdimensions(params):
     """Compute the sizes of the given block devices.
 
     """
     disks = [objects.Disk.FromDict(cf) for cf in params[0]]
-    return backend.BlockdevGetsize(disks)
+    return backend.BlockdevGetdimensions(disks)
 
   @staticmethod
   def perspective_blockdev_export(params):
@@ -414,9 +414,9 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks = params
+    nodes_ip, disks, target_node_uuid = params
     disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdDisconnectNet(nodes_ip, disks)
+    return backend.DrbdDisconnectNet(target_node_uuid, nodes_ip, disks)
 
   @staticmethod
   def perspective_drbd_attach_net(params):
@@ -426,10 +426,10 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks, instance_name, multimaster = params
+    nodes_ip, disks, instance_name, multimaster, target_node_uuid = params
     disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdAttachNet(nodes_ip, disks,
-                                     instance_name, multimaster)
+    return backend.DrbdAttachNet(target_node_uuid, nodes_ip, disks,
+                                 instance_name, multimaster)
 
   @staticmethod
   def perspective_drbd_wait_sync(params):
@@ -439,9 +439,9 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     disk list must all be drbd devices.
 
     """
-    nodes_ip, disks = params
+    nodes_ip, disks, target_node_uuid = params
     disks = [objects.Disk.FromDict(cf) for cf in disks]
-    return backend.DrbdWaitSync(nodes_ip, disks)
+    return backend.DrbdWaitSync(target_node_uuid, nodes_ip, disks)
 
   @staticmethod
   def perspective_drbd_helper(params):
@@ -532,7 +532,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, fields) = params
-    return storage.GetStorage(su_name, *su_args).List(name, fields)
+    return container.GetStorage(su_name, *su_args).List(name, fields)
 
   @staticmethod
   def perspective_storage_modify(params):
@@ -540,7 +540,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, changes) = params
-    return storage.GetStorage(su_name, *su_args).Modify(name, changes)
+    return container.GetStorage(su_name, *su_args).Modify(name, changes)
 
   @staticmethod
   def perspective_storage_execute(params):
@@ -548,7 +548,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (su_name, su_args, name, op) = params
-    return storage.GetStorage(su_name, *su_args).Execute(name, op)
+    return container.GetStorage(su_name, *su_args).Execute(name, op)
 
   # bridge  --------------------------
 
@@ -634,9 +634,9 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Migrates an instance.
 
     """
-    instance, target, live = params
+    cluster_name, instance, target, live = params
     instance = objects.Instance.FromDict(instance)
-    return backend.MigrateInstance(instance, target, live)
+    return backend.MigrateInstance(cluster_name, instance, target, live)
 
   @staticmethod
   def perspective_instance_finalize_migration_src(params):
@@ -682,7 +682,8 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Query instance information.
 
     """
-    return backend.GetInstanceInfo(params[0], params[1])
+    (instance_name, hypervisor_name, hvparams) = params
+    return backend.GetInstanceInfo(instance_name, hypervisor_name, hvparams)
 
   @staticmethod
   def perspective_instance_migratable(params):
@@ -697,14 +698,16 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Query information about all instances.
 
     """
-    return backend.GetAllInstancesInfo(params[0])
+    (hypervisor_list, all_hvparams) = params
+    return backend.GetAllInstancesInfo(hypervisor_list, all_hvparams)
 
   @staticmethod
   def perspective_instance_list(params):
     """Query the list of running instances.
 
     """
-    return backend.GetInstanceList(params[0])
+    (hypervisor_list, hvparams) = params
+    return backend.GetInstanceList(hypervisor_list, hvparams)
 
   # node --------------------------
 
@@ -720,8 +723,8 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Query node information.
 
     """
-    (vg_names, hv_names, excl_stor) = params
-    return backend.GetNodeInfo(vg_names, hv_names, excl_stor)
+    (storage_units, hv_specs, excl_stor) = params
+    return backend.GetNodeInfo(storage_units, hv_specs, excl_stor)
 
   @staticmethod
   def perspective_etc_hosts_modify(params):
@@ -737,7 +740,8 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Run a verify sequence on this node.
 
     """
-    return backend.VerifyNode(params[0], params[1])
+    (what, cluster_name, hvparams) = params
+    return backend.VerifyNode(what, cluster_name, hvparams)
 
   @classmethod
   def perspective_node_verify_light(cls, params):
@@ -811,8 +815,8 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     """Tries to powercycle the nod.
 
     """
-    hypervisor_type = params[0]
-    return backend.PowercycleNode(hypervisor_type)
+    (hypervisor_type, hvparams) = params
+    return backend.PowercycleNode(hypervisor_type, hvparams)
 
   # cluster --------------------------
 
@@ -1177,7 +1181,8 @@ def Main():
 
   """
   parser = OptionParser(description="Ganeti node daemon",
-                        usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
+                        usage="%prog [-f] [-d] [-p port] [-b ADDRESS]\
+                              \ [-i INTERFACE]",
                         version="%%prog (ganeti) %s" %
                         constants.RELEASE_VERSION)
   parser.add_option("--no-mlock", dest="mlock",
index 47ebb5b..e03a93f 100644 (file)
@@ -360,7 +360,8 @@ def Main():
 
   """
   parser = optparse.OptionParser(description="Ganeti Remote API",
-                                 usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
+                                 usage="%prog [-f] [-d] [-p port] [-b ADDRESS]\
+                                       \ [-i INTERFACE]",
                                  version="%%prog (ganeti) %s" %
                                  constants.RELEASE_VERSION)
   parser.add_option("--require-authentication", dest="reqauth",
index fc79422..11af2a2 100644 (file)
@@ -65,6 +65,12 @@ _VALID_KEYS = compat.UniqueFrozenset([
   constants.SS_UID_POOL,
   constants.SS_NODEGROUPS,
   constants.SS_NETWORKS,
+  constants.SS_HVPARAMS_XEN_PVM,
+  constants.SS_HVPARAMS_XEN_FAKE,
+  constants.SS_HVPARAMS_XEN_HVM,
+  constants.SS_HVPARAMS_XEN_KVM,
+  constants.SS_HVPARAMS_XEN_CHROOT,
+  constants.SS_HVPARAMS_XEN_LXC,
   ])
 
 #: Maximum size for ssconf files
@@ -315,6 +321,35 @@ class SimpleStore(object):
     nl = data.splitlines(False)
     return nl
 
+  def GetHvparamsForHypervisor(self, hvname):
+    """Return the hypervisor parameters of the given hypervisor.
+
+    @type hvname: string
+    @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
+    @rtype: dict of strings
+    @returns: dictionary with hypervisor parameters
+
+    """
+    data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
+    lines = data.splitlines(False)
+    hvparams = {}
+    for line in lines:
+      (key, value) = line.split("=")
+      hvparams[key] = value
+    return hvparams
+
+  def GetHvparams(self):
+    """Return the hypervisor parameters of all hypervisors.
+
+    @rtype: dict of dict of strings
+    @returns: dictionary mapping hypervisor names to hvparams
+
+    """
+    all_hvparams = {}
+    for hv in constants.HYPER_TYPES:
+      all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
+    return all_hvparams
+
   def GetMaintainNodeHealth(self):
     """Return the value of the maintain_node_health option.
 
diff --git a/lib/storage/__init__.py b/lib/storage/__init__.py
new file mode 100644 (file)
index 0000000..9b9e38c
--- /dev/null
@@ -0,0 +1,24 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2008 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.
+
+
+"""Block device abstraction
+
+"""
diff --git a/lib/storage/base.py b/lib/storage/base.py
new file mode 100644 (file)
index 0000000..5f58b6e
--- /dev/null
@@ -0,0 +1,390 @@
+#
+#
+
+# 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
+# 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.
+
+
+"""Block device abstraction - base class and utility functions"""
+
+import logging
+
+from ganeti import objects
+from ganeti import constants
+from ganeti import utils
+from ganeti import errors
+
+
+class BlockDev(object):
+  """Block device abstract class.
+
+  A block device can be in the following states:
+    - not existing on the system, and by `Create()` it goes into:
+    - existing but not setup/not active, and by `Assemble()` goes into:
+    - active read-write and by `Open()` it goes into
+    - online (=used, or ready for use)
+
+  A device can also be online but read-only, however we are not using
+  the readonly state (LV has it, if needed in the future) and we are
+  usually looking at this like at a stack, so it's easier to
+  conceptualise the transition from not-existing to online and back
+  like a linear one.
+
+  The many different states of the device are due to the fact that we
+  need to cover many device types:
+    - logical volumes are created, lvchange -a y $lv, and used
+    - drbd devices are attached to a local disk/remote peer and made primary
+
+  A block device is identified by three items:
+    - the /dev path of the device (dynamic)
+    - a unique ID of the device (static)
+    - it's major/minor pair (dynamic)
+
+  Not all devices implement both the first two as distinct items. LVM
+  logical volumes have their unique ID (the pair volume group, logical
+  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
+  the /dev path is again dynamic and the unique id is the pair (host1,
+  dev1), (host2, dev2).
+
+  You can get to a device in two ways:
+    - creating the (real) device, which returns you
+      an attached instance (lvcreate)
+    - attaching of a python instance to an existing (real) device
+
+  The second point, the attachment to a device, is different
+  depending on whether the device is assembled or not. At init() time,
+  we search for a device with the same unique_id as us. If found,
+  good. It also means that the device is already assembled. If not,
+  after assembly we'll have our correct major/minor.
+
+  """
+  def __init__(self, unique_id, children, size, params):
+    self._children = children
+    self.dev_path = None
+    self.unique_id = unique_id
+    self.major = None
+    self.minor = None
+    self.attached = False
+    self.size = size
+    self.params = params
+
+  def Assemble(self):
+    """Assemble the device from its components.
+
+    Implementations of this method by child classes must ensure that:
+      - after the device has been assembled, it knows its major/minor
+        numbers; this allows other devices (usually parents) to probe
+        correctly for their children
+      - calling this method on an existing, in-use device is safe
+      - if the device is already configured (and in an OK state),
+        this method is idempotent
+
+    """
+    pass
+
+  def Attach(self):
+    """Find a device which matches our config and attach to it.
+
+    """
+    raise NotImplementedError
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    """
+    raise NotImplementedError
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create the device.
+
+    If the device cannot be created, it will return None
+    instead. Error messages go to the logging system.
+
+    Note that for some devices, the unique_id is used, and for other,
+    the children. The idea is that these two, taken together, are
+    enough for both creation and assembly (later).
+
+    @type unique_id: 2-element tuple or list
+    @param unique_id: unique identifier; the details depend on the actual device
+        type
+    @type children: list of L{BlockDev}
+    @param children: for hierarchical devices, the child devices
+    @type size: float
+    @param size: size in MiB
+    @type spindles: int
+    @param spindles: number of physical disk to dedicate to the device
+    @type params: dict
+    @param params: device-specific options/parameters
+    @type excl_stor: bool
+    @param excl_stor: whether exclusive_storage is active
+    @rtype: L{BlockDev}
+    @return: the created device, or C{None} in case of an error
+
+    """
+    raise NotImplementedError
+
+  def Remove(self):
+    """Remove this device.
+
+    This makes sense only for some of the device types: LV and file
+    storage. Also note that if the device can't attach, the removal
+    can't be completed.
+
+    """
+    raise NotImplementedError
+
+  def Rename(self, new_id):
+    """Rename this device.
+
+    This may or may not make sense for a given device type.
+
+    """
+    raise NotImplementedError
+
+  def Open(self, force=False):
+    """Make the device ready for use.
+
+    This makes the device ready for I/O. For now, just the DRBD
+    devices need this.
+
+    The force parameter signifies that if the device has any kind of
+    --force thing, it should be used, we know what we are doing.
+
+    @type force: boolean
+
+    """
+    raise NotImplementedError
+
+  def Shutdown(self):
+    """Shut down the device, freeing its children.
+
+    This undoes the `Assemble()` work, except for the child
+    assembling; as such, the children on the device are still
+    assembled after this call.
+
+    """
+    raise NotImplementedError
+
+  def SetSyncParams(self, params):
+    """Adjust the synchronization parameters of the mirror.
+
+    In case this is not a mirroring device, this is no-op.
+
+    @param params: dictionary of LD level disk parameters related to the
+    synchronization.
+    @rtype: list
+    @return: a list of error messages, emitted both by the current node and by
+    children. An empty list means no errors.
+
+    """
+    result = []
+    if self._children:
+      for child in self._children:
+        result.extend(child.SetSyncParams(params))
+    return result
+
+  def PauseResumeSync(self, pause):
+    """Pause/Resume the sync of the mirror.
+
+    In case this is not a mirroring device, this is no-op.
+
+    @type pause: boolean
+    @param pause: Whether to pause or resume
+
+    """
+    result = True
+    if self._children:
+      for child in self._children:
+        result = result and child.PauseResumeSync(pause)
+    return result
+
+  def GetSyncStatus(self):
+    """Returns the sync status of the device.
+
+    If this device is a mirroring device, this function returns the
+    status of the mirror.
+
+    If sync_percent is None, it means the device is not syncing.
+
+    If estimated_time is None, it means we can't estimate
+    the time needed, otherwise it's the time left in seconds.
+
+    If is_degraded is True, it means the device is missing
+    redundancy. This is usually a sign that something went wrong in
+    the device setup, if sync_percent is None.
+
+    The ldisk parameter represents the degradation of the local
+    data. This is only valid for some devices, the rest will always
+    return False (not degraded).
+
+    @rtype: objects.BlockDevStatus
+
+    """
+    return objects.BlockDevStatus(dev_path=self.dev_path,
+                                  major=self.major,
+                                  minor=self.minor,
+                                  sync_percent=None,
+                                  estimated_time=None,
+                                  is_degraded=False,
+                                  ldisk_status=constants.LDS_OKAY)
+
+  def CombinedSyncStatus(self):
+    """Calculate the mirror status recursively for our children.
+
+    The return value is the same as for `GetSyncStatus()` except the
+    minimum percent and maximum time are calculated across our
+    children.
+
+    @rtype: objects.BlockDevStatus
+
+    """
+    status = self.GetSyncStatus()
+
+    min_percent = status.sync_percent
+    max_time = status.estimated_time
+    is_degraded = status.is_degraded
+    ldisk_status = status.ldisk_status
+
+    if self._children:
+      for child in self._children:
+        child_status = child.GetSyncStatus()
+
+        if min_percent is None:
+          min_percent = child_status.sync_percent
+        elif child_status.sync_percent is not None:
+          min_percent = min(min_percent, child_status.sync_percent)
+
+        if max_time is None:
+          max_time = child_status.estimated_time
+        elif child_status.estimated_time is not None:
+          max_time = max(max_time, child_status.estimated_time)
+
+        is_degraded = is_degraded or child_status.is_degraded
+
+        if ldisk_status is None:
+          ldisk_status = child_status.ldisk_status
+        elif child_status.ldisk_status is not None:
+          ldisk_status = max(ldisk_status, child_status.ldisk_status)
+
+    return objects.BlockDevStatus(dev_path=self.dev_path,
+                                  major=self.major,
+                                  minor=self.minor,
+                                  sync_percent=min_percent,
+                                  estimated_time=max_time,
+                                  is_degraded=is_degraded,
+                                  ldisk_status=ldisk_status)
+
+  def SetInfo(self, text):
+    """Update metadata with info text.
+
+    Only supported for some device types.
+
+    """
+    for child in self._children:
+      child.SetInfo(text)
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the block device.
+
+    @type amount: integer
+    @param amount: the amount (in mebibytes) to grow with
+    @type dryrun: boolean
+    @param dryrun: whether to execute the operation in simulation mode
+        only, without actually increasing the size
+    @param backingstore: whether to execute the operation on backing storage
+        only, or on "logical" storage only; e.g. DRBD is logical storage,
+        whereas LVM, file, RBD are backing storage
+
+    """
+    raise NotImplementedError
+
+  def GetActualSize(self):
+    """Return the actual disk size.
+
+    @note: the device needs to be active when this is called
+
+    """
+    assert self.attached, "BlockDevice not attached in GetActualSize()"
+    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
+    if result.failed:
+      ThrowError("blockdev failed (%s): %s",
+                  result.fail_reason, result.output)
+    try:
+      sz = int(result.output.strip())
+    except (ValueError, TypeError), err:
+      ThrowError("Failed to parse blockdev output: %s", str(err))
+    return sz
+
+  def GetActualSpindles(self):
+    """Return the actual number of spindles used.
+
+    This is not supported by all devices; if not supported, C{None} is returned.
+
+    @note: the device needs to be active when this is called
+
+    """
+    assert self.attached, "BlockDevice not attached in GetActualSpindles()"
+    return None
+
+  def GetActualDimensions(self):
+    """Return the actual disk size and number of spindles used.
+
+    @rtype: tuple
+    @return: (size, spindles); spindles is C{None} when they are not supported
+
+    @note: the device needs to be active when this is called
+
+    """
+    return (self.GetActualSize(), self.GetActualSpindles())
+
+  def __repr__(self):
+    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
+            (self.__class__, self.unique_id, self._children,
+             self.major, self.minor, self.dev_path))
+
+
+def ThrowError(msg, *args):
+  """Log an error to the node daemon and the raise an exception.
+
+  @type msg: string
+  @param msg: the text of the exception
+  @raise errors.BlockDeviceError
+
+  """
+  if args:
+    msg = msg % args
+  logging.error(msg)
+  raise errors.BlockDeviceError(msg)
+
+
+def IgnoreError(fn, *args, **kwargs):
+  """Executes the given function, ignoring BlockDeviceErrors.
+
+  This is used in order to simplify the execution of cleanup or
+  rollback functions.
+
+  @rtype: boolean
+  @return: True when fn didn't raise an exception, False otherwise
+
+  """
+  try:
+    fn(*args, **kwargs)
+    return True
+  except errors.BlockDeviceError, err:
+    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
+    return False
diff --git a/lib/storage/bdev.py b/lib/storage/bdev.py
new file mode 100644 (file)
index 0000000..dfb1acc
--- /dev/null
@@ -0,0 +1,1887 @@
+#
+#
+
+# 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
+# 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.
+
+
+"""Block device abstraction"""
+
+import re
+import errno
+import stat
+import os
+import logging
+import math
+
+from ganeti import utils
+from ganeti import errors
+from ganeti import constants
+from ganeti import objects
+from ganeti import compat
+from ganeti import pathutils
+from ganeti import serializer
+from ganeti.storage import drbd
+from ganeti.storage import base
+
+
+class RbdShowmappedJsonError(Exception):
+  """`rbd showmmapped' JSON formatting error Exception class.
+
+  """
+  pass
+
+
+def _CheckResult(result):
+  """Throws an error if the given result is a failed one.
+
+  @param result: result from RunCmd
+
+  """
+  if result.failed:
+    base.ThrowError("Command: %s error: %s - %s",
+                    result.cmd, result.fail_reason, result.output)
+
+
+def _GetForbiddenFileStoragePaths():
+  """Builds a list of path prefixes which shouldn't be used for file storage.
+
+  @rtype: frozenset
+
+  """
+  paths = set([
+    "/boot",
+    "/dev",
+    "/etc",
+    "/home",
+    "/proc",
+    "/root",
+    "/sys",
+    ])
+
+  for prefix in ["", "/usr", "/usr/local"]:
+    paths.update(map(lambda s: "%s/%s" % (prefix, s),
+                     ["bin", "lib", "lib32", "lib64", "sbin"]))
+
+  return compat.UniqueFrozenset(map(os.path.normpath, paths))
+
+
+def _ComputeWrongFileStoragePaths(paths,
+                                  _forbidden=_GetForbiddenFileStoragePaths()):
+  """Cross-checks a list of paths for prefixes considered bad.
+
+  Some paths, e.g. "/bin", should not be used for file storage.
+
+  @type paths: list
+  @param paths: List of paths to be checked
+  @rtype: list
+  @return: Sorted list of paths for which the user should be warned
+
+  """
+  def _Check(path):
+    return (not os.path.isabs(path) or
+            path in _forbidden or
+            filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
+
+  return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
+
+
+def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
+  """Returns a list of file storage paths whose prefix is considered bad.
+
+  See L{_ComputeWrongFileStoragePaths}.
+
+  """
+  return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
+
+
+def _CheckFileStoragePath(path, allowed):
+  """Checks if a path is in a list of allowed paths for file storage.
+
+  @type path: string
+  @param path: Path to check
+  @type allowed: list
+  @param allowed: List of allowed paths
+  @raise errors.FileStoragePathError: If the path is not allowed
+
+  """
+  if not os.path.isabs(path):
+    raise errors.FileStoragePathError("File storage path must be absolute,"
+                                      " got '%s'" % path)
+
+  for i in allowed:
+    if not os.path.isabs(i):
+      logging.info("Ignoring relative path '%s' for file storage", i)
+      continue
+
+    if utils.IsBelowDir(i, path):
+      break
+  else:
+    raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
+                                      " storage" % path)
+
+
+def _LoadAllowedFileStoragePaths(filename):
+  """Loads file containing allowed file storage paths.
+
+  @rtype: list
+  @return: List of allowed paths (can be an empty list)
+
+  """
+  try:
+    contents = utils.ReadFile(filename)
+  except EnvironmentError:
+    return []
+  else:
+    return utils.FilterEmptyLinesAndComments(contents)
+
+
+def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
+  """Checks if a path is allowed for file storage.
+
+  @type path: string
+  @param path: Path to check
+  @raise errors.FileStoragePathError: If the path is not allowed
+
+  """
+  allowed = _LoadAllowedFileStoragePaths(_filename)
+
+  if _ComputeWrongFileStoragePaths([path]):
+    raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
+                                      path)
+
+  _CheckFileStoragePath(path, allowed)
+
+
+class LogicalVolume(base.BlockDev):
+  """Logical Volume block device.
+
+  """
+  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
+  _PARSE_PV_DEV_RE = re.compile("^([^ ()]+)\([0-9]+\)$")
+  _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
+  _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
+
+  def __init__(self, unique_id, children, size, params):
+    """Attaches to a LV device.
+
+    The unique_id is a tuple (vg_name, lv_name)
+
+    """
+    super(LogicalVolume, self).__init__(unique_id, children, size, params)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    self._vg_name, self._lv_name = unique_id
+    self._ValidateName(self._vg_name)
+    self._ValidateName(self._lv_name)
+    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
+    self._degraded = True
+    self.major = self.minor = self.pe_size = self.stripe_count = None
+    self.pv_names = None
+    self.Attach()
+
+  @staticmethod
+  def _GetStdPvSize(pvs_info):
+    """Return the the standard PV size (used with exclusive storage).
+
+    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
+    @rtype: float
+    @return: size in MiB
+
+    """
+    assert len(pvs_info) > 0
+    smallest = min([pv.size for pv in pvs_info])
+    return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
+
+  @staticmethod
+  def _ComputeNumPvs(size, pvs_info):
+    """Compute the number of PVs needed for an LV (with exclusive storage).
+
+    @type size: float
+    @param size: LV size in MiB
+    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
+    @rtype: integer
+    @return: number of PVs needed
+    """
+    assert len(pvs_info) > 0
+    pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
+    return int(math.ceil(float(size) / pv_size))
+
+  @staticmethod
+  def _GetEmptyPvNames(pvs_info, max_pvs=None):
+    """Return a list of empty PVs, by name.
+
+    """
+    empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
+    if max_pvs is not None:
+      empty_pvs = empty_pvs[:max_pvs]
+    return map((lambda pv: pv.name), empty_pvs)
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new logical volume.
+
+    """
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise errors.ProgrammerError("Invalid configuration data %s" %
+                                   str(unique_id))
+    vg_name, lv_name = unique_id
+    cls._ValidateName(vg_name)
+    cls._ValidateName(lv_name)
+    pvs_info = cls.GetPVInfo([vg_name])
+    if not pvs_info:
+      if excl_stor:
+        msg = "No (empty) PVs found"
+      else:
+        msg = "Can't compute PV info for vg %s" % vg_name
+      base.ThrowError(msg)
+    pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
+
+    pvlist = [pv.name for pv in pvs_info]
+    if compat.any(":" in v for v in pvlist):
+      base.ThrowError("Some of your PVs have the invalid character ':' in their"
+                      " name, this is not supported - please filter them out"
+                      " in lvm.conf using either 'filter' or 'preferred_names'")
+
+    current_pvs = len(pvlist)
+    desired_stripes = params[constants.LDP_STRIPES]
+    stripes = min(current_pvs, desired_stripes)
+
+    if excl_stor:
+      if spindles is None:
+        base.ThrowError("Unspecified number of spindles: this is required"
+                        "when exclusive storage is enabled, try running"
+                        " gnt-cluster repair-disk-sizes")
+      (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
+      if err_msgs:
+        for m in err_msgs:
+          logging.warning(m)
+      req_pvs = cls._ComputeNumPvs(size, pvs_info)
+      if spindles < req_pvs:
+        base.ThrowError("Requested number of spindles (%s) is not enough for"
+                        " a disk of %d MB (at least %d spindles needed)",
+                        spindles, size, req_pvs)
+      else:
+        req_pvs = spindles
+      pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
+      current_pvs = len(pvlist)
+      if current_pvs < req_pvs:
+        base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d"
+                        " MB: %d available, %d needed",
+                        size, current_pvs, req_pvs)
+      assert current_pvs == len(pvlist)
+      # We must update stripes to be sure to use all the desired spindles
+      stripes = current_pvs
+      if stripes > desired_stripes:
+        # Don't warn when lowering stripes, as it's no surprise
+        logging.warning("Using %s stripes instead of %s, to be able to use"
+                        " %s spindles", stripes, desired_stripes, current_pvs)
+
+    else:
+      if stripes < desired_stripes:
+        logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
+                        " available.", desired_stripes, vg_name, current_pvs)
+      free_size = sum([pv.free for pv in pvs_info])
+      # The size constraint should have been checked from the master before
+      # calling the create function.
+      if free_size < size:
+        base.ThrowError("Not enough free space: required %s,"
+                        " available %s", size, free_size)
+
+    # If the free space is not well distributed, we won't be able to
+    # create an optimally-striped volume; in that case, we want to try
+    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
+    # stripes
+    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
+    for stripes_arg in range(stripes, 0, -1):
+      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
+      if not result.failed:
+        break
+    if result.failed:
+      base.ThrowError("LV create failed (%s): %s",
+                      result.fail_reason, result.output)
+    return LogicalVolume(unique_id, children, size, params)
+
+  @staticmethod
+  def _GetVolumeInfo(lvm_cmd, fields):
+    """Returns LVM Volume infos using lvm_cmd
+
+    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
+    @param fields: Fields to return
+    @return: A list of dicts each with the parsed fields
+
+    """
+    if not fields:
+      raise errors.ProgrammerError("No fields specified")
+
+    sep = "|"
+    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
+           "--separator=%s" % sep, "-o%s" % ",".join(fields)]
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      raise errors.CommandError("Can't get the volume information: %s - %s" %
+                                (result.fail_reason, result.output))
+
+    data = []
+    for line in result.stdout.splitlines():
+      splitted_fields = line.strip().split(sep)
+
+      if len(fields) != len(splitted_fields):
+        raise errors.CommandError("Can't parse %s output: line '%s'" %
+                                  (lvm_cmd, line))
+
+      data.append(splitted_fields)
+
+    return data
+
+  @classmethod
+  def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
+    """Get the free space info for PVs in a volume group.
+
+    @param vg_names: list of volume group names, if empty all will be returned
+    @param filter_allocatable: whether to skip over unallocatable PVs
+    @param include_lvs: whether to include a list of LVs hosted on each PV
+
+    @rtype: list
+    @return: list of objects.LvmPvInfo objects
+
+    """
+    # We request "lv_name" field only if we care about LVs, so we don't get
+    # a long list of entries with many duplicates unless we really have to.
+    # The duplicate "pv_name" field will be ignored.
+    if include_lvs:
+      lvfield = "lv_name"
+    else:
+      lvfield = "pv_name"
+    try:
+      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
+                                        "pv_attr", "pv_size", lvfield])
+    except errors.GenericError, err:
+      logging.error("Can't get PV information: %s", err)
+      return None
+
+    # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
+    # pair. We sort entries by PV name and then LV name, so it's easy to weed
+    # out duplicates.
+    if include_lvs:
+      info.sort(key=(lambda i: (i[0], i[5])))
+    data = []
+    lastpvi = None
+    for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
+      # (possibly) skip over pvs which are not allocatable
+      if filter_allocatable and pv_attr[0] != "a":
+        continue
+      # (possibly) skip over pvs which are not in the right volume group(s)
+      if vg_names and vg_name not in vg_names:
+        continue
+      # Beware of duplicates (check before inserting)
+      if lastpvi and lastpvi.name == pv_name:
+        if include_lvs and lv_name:
+          if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
+            lastpvi.lv_list.append(lv_name)
+      else:
+        if include_lvs and lv_name:
+          lvl = [lv_name]
+        else:
+          lvl = []
+        lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
+                                    size=float(pv_size), free=float(pv_free),
+                                    attributes=pv_attr, lv_list=lvl)
+        data.append(lastpvi)
+
+    return data
+
+  @classmethod
+  def _GetRawFreePvInfo(cls, vg_name):
+    """Return info (size/free) about PVs.
+
+    @type vg_name: string
+    @param vg_name: VG name
+    @rtype: tuple
+    @return: (standard_pv_size_in_MiB, number_of_free_pvs, total_number_of_pvs)
+
+    """
+    pvs_info = cls.GetPVInfo([vg_name])
+    if not pvs_info:
+      pv_size = 0.0
+      free_pvs = 0
+      num_pvs = 0
+    else:
+      pv_size = cls._GetStdPvSize(pvs_info)
+      free_pvs = len(cls._GetEmptyPvNames(pvs_info))
+      num_pvs = len(pvs_info)
+    return (pv_size, free_pvs, num_pvs)
+
+  @classmethod
+  def _GetExclusiveStorageVgFree(cls, vg_name):
+    """Return the free disk space in the given VG, in exclusive storage mode.
+
+    @type vg_name: string
+    @param vg_name: VG name
+    @rtype: float
+    @return: free space in MiB
+    """
+    (pv_size, free_pvs, _) = cls._GetRawFreePvInfo(vg_name)
+    return pv_size * free_pvs
+
+  @classmethod
+  def GetVgSpindlesInfo(cls, vg_name):
+    """Get the free space info for specific VGs.
+
+    @param vg_name: volume group name
+    @rtype: tuple
+    @return: (free_spindles, total_spindles)
+
+    """
+    (_, free_pvs, num_pvs) = cls._GetRawFreePvInfo(vg_name)
+    return (free_pvs, num_pvs)
+
+  @classmethod
+  def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
+    """Get the free space info for specific VGs.
+
+    @param vg_names: list of volume group names, if empty all will be returned
+    @param excl_stor: whether exclusive_storage is enabled
+    @param filter_readonly: whether to skip over readonly VGs
+
+    @rtype: list
+    @return: list of tuples (free_space, total_size, name) with free_space in
+             MiB
+
+    """
+    try:
+      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
+                                        "vg_size"])
+    except errors.GenericError, err:
+      logging.error("Can't get VG information: %s", err)
+      return None
+
+    data = []
+    for vg_name, vg_free, vg_attr, vg_size in info:
+      # (possibly) skip over vgs which are not writable
+      if filter_readonly and vg_attr[0] == "r":
+        continue
+      # (possibly) skip over vgs which are not in the right volume group(s)
+      if vg_names and vg_name not in vg_names:
+        continue
+      # Exclusive storage needs a different concept of free space
+      if excl_stor:
+        es_free = cls._GetExclusiveStorageVgFree(vg_name)
+        assert es_free <= vg_free
+        vg_free = es_free
+      data.append((float(vg_free), float(vg_size), vg_name))
+
+    return data
+
+  @classmethod
+  def _ValidateName(cls, name):
+    """Validates that a given name is valid as VG or LV name.
+
+    The list of valid characters and restricted names is taken out of
+    the lvm(8) manpage, with the simplification that we enforce both
+    VG and LV restrictions on the names.
+
+    """
+    if (not cls._VALID_NAME_RE.match(name) or
+        name in cls._INVALID_NAMES or
+        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
+      base.ThrowError("Invalid LVM name '%s'", name)
+
+  def Remove(self):
+    """Remove this logical volume.
+
+    """
+    if not self.minor and not self.Attach():
+      # the LV does not exist
+      return
+    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
+                           (self._vg_name, self._lv_name)])
+    if result.failed:
+      base.ThrowError("Can't lvremove: %s - %s",
+                      result.fail_reason, result.output)
+
+  def Rename(self, new_id):
+    """Rename this logical volume.
+
+    """
+    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
+      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
+    new_vg, new_name = new_id
+    if new_vg != self._vg_name:
+      raise errors.ProgrammerError("Can't move a logical volume across"
+                                   " volume groups (from %s to to %s)" %
+                                   (self._vg_name, new_vg))
+    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
+    if result.failed:
+      base.ThrowError("Failed to rename the logical volume: %s", result.output)
+    self._lv_name = new_name
+    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
+
+  @classmethod
+  def _ParseLvInfoLine(cls, line, sep):
+    """Parse one line of the lvs output used in L{_GetLvInfo}.
+
+    """
+    elems = line.strip().rstrip(sep).split(sep)
+    if len(elems) != 6:
+      base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
+
+    (status, major, minor, pe_size, stripes, pvs) = elems
+    if len(status) < 6:
+      base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
+
+    try:
+      major = int(major)
+      minor = int(minor)
+    except (TypeError, ValueError), err:
+      base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
+
+    try:
+      pe_size = int(float(pe_size))
+    except (TypeError, ValueError), err:
+      base.ThrowError("Can't parse vg extent size: %s", err)
+
+    try:
+      stripes = int(stripes)
+    except (TypeError, ValueError), err:
+      base.ThrowError("Can't parse the number of stripes: %s", err)
+
+    pv_names = []
+    for pv in pvs.split(","):
+      m = re.match(cls._PARSE_PV_DEV_RE, pv)
+      if not m:
+        base.ThrowError("Can't parse this device list: %s", pvs)
+      pv_names.append(m.group(1))
+    assert len(pv_names) > 0
+
+    return (status, major, minor, pe_size, stripes, pv_names)
+
+  @classmethod
+  def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
+    """Get info about the given existing LV to be used.
+
+    """
+    sep = "|"
+    result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
+                       "--units=k", "--nosuffix",
+                       "-olv_attr,lv_kernel_major,lv_kernel_minor,"
+                       "vg_extent_size,stripes,devices", dev_path])
+    if result.failed:
+      base.ThrowError("Can't find LV %s: %s, %s",
+                      dev_path, result.fail_reason, result.output)
+    # the output can (and will) have multiple lines for multi-segment
+    # LVs, as the 'stripes' parameter is a segment one, so we take
+    # only the last entry, which is the one we're interested in; note
+    # that with LVM2 anyway the 'stripes' value must be constant
+    # across segments, so this is a no-op actually
+    out = result.stdout.splitlines()
+    if not out: # totally empty result? splitlines() returns at least
+                # one line for any non-empty string
+      base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
+    pv_names = set()
+    for line in out:
+      (status, major, minor, pe_size, stripes, more_pvs) = \
+        cls._ParseLvInfoLine(line, sep)
+      pv_names.update(more_pvs)
+    return (status, major, minor, pe_size, stripes, pv_names)
+
+  def Attach(self):
+    """Attach to an existing LV.
+
+    This method will try to see if an existing and active LV exists
+    which matches our name. If so, its major/minor will be
+    recorded.
+
+    """
+    self.attached = False
+    try:
+      (status, major, minor, pe_size, stripes, pv_names) = \
+        self._GetLvInfo(self.dev_path)
+    except errors.BlockDeviceError:
+      return False
+
+    self.major = major
+    self.minor = minor
+    self.pe_size = pe_size
+    self.stripe_count = stripes
+    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
+                                      # storage
+    self.pv_names = pv_names
+    self.attached = True
+    return True
+
+  def Assemble(self):
+    """Assemble the device.
+
+    We always run `lvchange -ay` on the LV to ensure it's active before
+    use, as there were cases when xenvg was not active after boot
+    (also possibly after disk issues).
+
+    """
+    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
+    if result.failed:
+      base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    This is a no-op for the LV device type, as we don't deactivate the
+    volumes on shutdown.
+
+    """
+    pass
+
+  def GetSyncStatus(self):
+    """Returns the sync status of the device.
+
+    If this device is a mirroring device, this function returns the
+    status of the mirror.
+
+    For logical volumes, sync_percent and estimated_time are always
+    None (no recovery in progress, as we don't handle the mirrored LV
+    case). The is_degraded parameter is the inverse of the ldisk
+    parameter.
+
+    For the ldisk parameter, we check if the logical volume has the
+    'virtual' type, which means it's not backed by existing storage
+    anymore (read from it return I/O error). This happens after a
+    physical disk failure and subsequent 'vgreduce --removemissing' on
+    the volume group.
+
+    The status was already read in Attach, so we just return it.
+
+    @rtype: objects.BlockDevStatus
+
+    """
+    if self._degraded:
+      ldisk_status = constants.LDS_FAULTY
+    else:
+      ldisk_status = constants.LDS_OKAY
+
+    return objects.BlockDevStatus(dev_path=self.dev_path,
+                                  major=self.major,
+                                  minor=self.minor,
+                                  sync_percent=None,
+                                  estimated_time=None,
+                                  is_degraded=self._degraded,
+                                  ldisk_status=ldisk_status)
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    This is a no-op for the LV device type.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    This is a no-op for the LV device type.
+
+    """
+    pass
+
+  def Snapshot(self, size):
+    """Create a snapshot copy of an lvm block device.
+
+    @returns: tuple (vg, lv)
+
+    """
+    snap_name = self._lv_name + ".snap"
+
+    # remove existing snapshot if found
+    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
+    base.IgnoreError(snap.Remove)
+
+    vg_info = self.GetVGInfo([self._vg_name], False)
+    if not vg_info:
+      base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
+    free_size, _, _ = vg_info[0]
+    if free_size < size:
+      base.ThrowError("Not enough free space: required %s,"
+                      " available %s", size, free_size)
+
+    _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
+                               "-n%s" % snap_name, self.dev_path]))
+
+    return (self._vg_name, snap_name)
+
+  def _RemoveOldInfo(self):
+    """Try to remove old tags from the lv.
+
+    """
+    result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
+                           self.dev_path])
+    _CheckResult(result)
+
+    raw_tags = result.stdout.strip()
+    if raw_tags:
+      for tag in raw_tags.split(","):
+        _CheckResult(utils.RunCmd(["lvchange", "--deltag",
+                                   tag.strip(), self.dev_path]))
+
+  def SetInfo(self, text):
+    """Update metadata with info text.
+
+    """
+    base.BlockDev.SetInfo(self, text)
+
+    self._RemoveOldInfo()
+
+    # Replace invalid characters
+    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
+    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
+
+    # Only up to 128 characters are allowed
+    text = text[:128]
+
+    _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the logical volume.
+
+    """
+    if not backingstore:
+      return
+    if self.pe_size is None or self.stripe_count is None:
+      if not self.Attach():
+        base.ThrowError("Can't attach to LV during Grow()")
+    full_stripe_size = self.pe_size * self.stripe_count
+    # pe_size is in KB
+    amount *= 1024
+    rest = amount % full_stripe_size
+    if rest != 0:
+      amount += full_stripe_size - rest
+    cmd = ["lvextend", "-L", "+%dk" % amount]
+    if dryrun:
+      cmd.append("--test")
+    # we try multiple algorithms since the 'best' ones might not have
+    # space available in the right place, but later ones might (since
+    # they have less constraints); also note that only recent LVM
+    # supports 'cling'
+    for alloc_policy in "contiguous", "cling", "normal":
+      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
+      if not result.failed:
+        return
+    base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
+
+  def GetActualSpindles(self):
+    """Return the number of spindles used.
+
+    """
+    assert self.attached, "BlockDevice not attached in GetActualSpindles()"
+    return len(self.pv_names)
+
+
+class FileStorage(base.BlockDev):
+  """File device.
+
+  This class represents the a file storage backend device.
+
+  The unique_id for the file device is a (file_driver, file_path) tuple.
+
+  """
+  def __init__(self, unique_id, children, size, params):
+    """Initalizes a file device backend.
+
+    """
+    if children:
+      raise errors.BlockDeviceError("Invalid setup for file device")
+    super(FileStorage, self).__init__(unique_id, children, size, params)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    self.driver = unique_id[0]
+    self.dev_path = unique_id[1]
+
+    CheckFileStoragePath(self.dev_path)
+
+    self.Attach()
+
+  def Assemble(self):
+    """Assemble the device.
+
+    Checks whether the file device exists, raises BlockDeviceError otherwise.
+
+    """
+    if not os.path.exists(self.dev_path):
+      base.ThrowError("File device '%s' does not exist" % self.dev_path)
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    This is a no-op for the file type, as we don't deactivate
+    the file on shutdown.
+
+    """
+    pass
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    This is a no-op for the file type.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    This is a no-op for the file type.
+
+    """
+    pass
+
+  def Remove(self):
+    """Remove the file backing the block device.
+
+    @rtype: boolean
+    @return: True if the removal was successful
+
+    """
+    try:
+      os.remove(self.dev_path)
+    except OSError, err:
+      if err.errno != errno.ENOENT:
+        base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
+
+  def Rename(self, new_id):
+    """Renames the file.
+
+    """
+    # TODO: implement rename for file-based storage
+    base.ThrowError("Rename is not supported for file-based storage")
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the file
+
+    @param amount: the amount (in mebibytes) to grow with
+
+    """
+    if not backingstore:
+      return
+    # Check that the file exists
+    self.Assemble()
+    current_size = self.GetActualSize()
+    new_size = current_size + amount * 1024 * 1024
+    assert new_size > current_size, "Cannot Grow with a negative amount"
+    # We can't really simulate the growth
+    if dryrun:
+      return
+    try:
+      f = open(self.dev_path, "a+")
+      f.truncate(new_size)
+      f.close()
+    except EnvironmentError, err:
+      base.ThrowError("Error in file growth: %", str(err))
+
+  def Attach(self):
+    """Attach to an existing file.
+
+    Check if this file already exists.
+
+    @rtype: boolean
+    @return: True if file exists
+
+    """
+    self.attached = os.path.exists(self.dev_path)
+    return self.attached
+
+  def GetActualSize(self):
+    """Return the actual disk size.
+
+    @note: the device needs to be active when this is called
+
+    """
+    assert self.attached, "BlockDevice not attached in GetActualSize()"
+    try:
+      st = os.stat(self.dev_path)
+      return st.st_size
+    except OSError, err:
+      base.ThrowError("Can't stat %s: %s", self.dev_path, err)
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new file.
+
+    @param size: the size of file in MiB
+
+    @rtype: L{bdev.FileStorage}
+    @return: an instance of FileStorage
+
+    """
+    if excl_stor:
+      raise errors.ProgrammerError("FileStorage device requested with"
+                                   " exclusive_storage")
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+
+    dev_path = unique_id[1]
+
+    CheckFileStoragePath(dev_path)
+
+    try:
+      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
+      f = os.fdopen(fd, "w")
+      f.truncate(size * 1024 * 1024)
+      f.close()
+    except EnvironmentError, err:
+      if err.errno == errno.EEXIST:
+        base.ThrowError("File already existing: %s", dev_path)
+      base.ThrowError("Error in file creation: %", str(err))
+
+    return FileStorage(unique_id, children, size, params)
+
+
+class PersistentBlockDevice(base.BlockDev):
+  """A block device with persistent node
+
+  May be either directly attached, or exposed through DM (e.g. dm-multipath).
+  udev helpers are probably required to give persistent, human-friendly
+  names.
+
+  For the time being, pathnames are required to lie under /dev.
+
+  """
+  def __init__(self, unique_id, children, size, params):
+    """Attaches to a static block device.
+
+    The unique_id is a path under /dev.
+
+    """
+    super(PersistentBlockDevice, self).__init__(unique_id, children, size,
+                                                params)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    self.dev_path = unique_id[1]
+    if not os.path.realpath(self.dev_path).startswith("/dev/"):
+      raise ValueError("Full path '%s' lies outside /dev" %
+                              os.path.realpath(self.dev_path))
+    # TODO: this is just a safety guard checking that we only deal with devices
+    # we know how to handle. In the future this will be integrated with
+    # external storage backends and possible values will probably be collected
+    # from the cluster configuration.
+    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
+      raise ValueError("Got persistent block device of invalid type: %s" %
+                       unique_id[0])
+
+    self.major = self.minor = None
+    self.Attach()
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new device
+
+    This is a noop, we only return a PersistentBlockDevice instance
+
+    """
+    if excl_stor:
+      raise errors.ProgrammerError("Persistent block device requested with"
+                                   " exclusive_storage")
+    return PersistentBlockDevice(unique_id, children, 0, params)
+
+  def Remove(self):
+    """Remove a device
+
+    This is a noop
+
+    """
+    pass
+
+  def Rename(self, new_id):
+    """Rename this device.
+
+    """
+    base.ThrowError("Rename is not supported for PersistentBlockDev storage")
+
+  def Attach(self):
+    """Attach to an existing block device.
+
+
+    """
+    self.attached = False
+    try:
+      st = os.stat(self.dev_path)
+    except OSError, err:
+      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
+      return False
+
+    if not stat.S_ISBLK(st.st_mode):
+      logging.error("%s is not a block device", self.dev_path)
+      return False
+
+    self.major = os.major(st.st_rdev)
+    self.minor = os.minor(st.st_rdev)
+    self.attached = True
+
+    return True
+
+  def Assemble(self):
+    """Assemble the device.
+
+    """
+    pass
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    """
+    pass
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    """
+    pass
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the logical volume.
+
+    """
+    base.ThrowError("Grow is not supported for PersistentBlockDev storage")
+
+
+class RADOSBlockDevice(base.BlockDev):
+  """A RADOS Block Device (rbd).
+
+  This class implements the RADOS Block Device for the backend. You need
+  the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
+  this to be functional.
+
+  """
+  def __init__(self, unique_id, children, size, params):
+    """Attaches to an rbd device.
+
+    """
+    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+
+    self.driver, self.rbd_name = unique_id
+
+    self.major = self.minor = None
+    self.Attach()
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new rbd device.
+
+    Provision a new rbd volume inside a RADOS pool.
+
+    """
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise errors.ProgrammerError("Invalid configuration data %s" %
+                                   str(unique_id))
+    if excl_stor:
+      raise errors.ProgrammerError("RBD device requested with"
+                                   " exclusive_storage")
+    rbd_pool = params[constants.LDP_POOL]
+    rbd_name = unique_id[1]
+
+    # Provision a new rbd volume (Image) inside the RADOS cluster.
+    cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
+           rbd_name, "--size", "%s" % size]
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("rbd creation failed (%s): %s",
+                      result.fail_reason, result.output)
+
+    return RADOSBlockDevice(unique_id, children, size, params)
+
+  def Remove(self):
+    """Remove the rbd device.
+
+    """
+    rbd_pool = self.params[constants.LDP_POOL]
+    rbd_name = self.unique_id[1]
+
+    if not self.minor and not self.Attach():
+      # The rbd device doesn't exist.
+      return
+
+    # First shutdown the device (remove mappings).
+    self.Shutdown()
+
+    # Remove the actual Volume (Image) from the RADOS cluster.
+    cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
+                      result.fail_reason, result.output)
+
+  def Rename(self, new_id):
+    """Rename this device.
+
+    """
+    pass
+
+  def Attach(self):
+    """Attach to an existing rbd device.
+
+    This method maps the rbd volume that matches our name with
+    an rbd device and then attaches to this device.
+
+    """
+    self.attached = False
+
+    # Map the rbd volume to a block device under /dev
+    self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
+
+    try:
+      st = os.stat(self.dev_path)
+    except OSError, err:
+      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
+      return False
+
+    if not stat.S_ISBLK(st.st_mode):
+      logging.error("%s is not a block device", self.dev_path)
+      return False
+
+    self.major = os.major(st.st_rdev)
+    self.minor = os.minor(st.st_rdev)
+    self.attached = True
+
+    return True
+
+  def _MapVolumeToBlockdev(self, unique_id):
+    """Maps existing rbd volumes to block devices.
+
+    This method should be idempotent if the mapping already exists.
+
+    @rtype: string
+    @return: the block device path that corresponds to the volume
+
+    """
+    pool = self.params[constants.LDP_POOL]
+    name = unique_id[1]
+
+    # Check if the mapping already exists.
+    rbd_dev = self._VolumeToBlockdev(pool, name)
+    if rbd_dev:
+      # The mapping exists. Return it.
+      return rbd_dev
+
+    # The mapping doesn't exist. Create it.
+    map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
+    result = utils.RunCmd(map_cmd)
+    if result.failed:
+      base.ThrowError("rbd map failed (%s): %s",
+                      result.fail_reason, result.output)
+
+    # Find the corresponding rbd device.
+    rbd_dev = self._VolumeToBlockdev(pool, name)
+    if not rbd_dev:
+      base.ThrowError("rbd map succeeded, but could not find the rbd block"
+                      " device in output of showmapped, for volume: %s", name)
+
+    # The device was successfully mapped. Return it.
+    return rbd_dev
+
+  @classmethod
+  def _VolumeToBlockdev(cls, pool, volume_name):
+    """Do the 'volume name'-to-'rbd block device' resolving.
+
+    @type pool: string
+    @param pool: RADOS pool to use
+    @type volume_name: string
+    @param volume_name: the name of the volume whose device we search for
+    @rtype: string or None
+    @return: block device path if the volume is mapped, else None
+
+    """
+    try:
+      # Newer versions of the rbd tool support json output formatting. Use it
+      # if available.
+      showmap_cmd = [
+        constants.RBD_CMD,
+        "showmapped",
+        "-p",
+        pool,
+        "--format",
+        "json"
+        ]
+      result = utils.RunCmd(showmap_cmd)
+      if result.failed:
+        logging.error("rbd JSON output formatting returned error (%s): %s,"
+                      "falling back to plain output parsing",
+                      result.fail_reason, result.output)
+        raise RbdShowmappedJsonError
+
+      return cls._ParseRbdShowmappedJson(result.output, volume_name)
+    except RbdShowmappedJsonError:
+      # For older versions of rbd, we have to parse the plain / text output
+      # manually.
+      showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
+      result = utils.RunCmd(showmap_cmd)
+      if result.failed:
+        base.ThrowError("rbd showmapped failed (%s): %s",
+                        result.fail_reason, result.output)
+
+      return cls._ParseRbdShowmappedPlain(result.output, volume_name)
+
+  @staticmethod
+  def _ParseRbdShowmappedJson(output, volume_name):
+    """Parse the json output of `rbd showmapped'.
+
+    This method parses the json output of `rbd showmapped' and returns the rbd
+    block device path (e.g. /dev/rbd0) that matches the given rbd volume.
+
+    @type output: string
+    @param output: the json output of `rbd showmapped'
+    @type volume_name: string
+    @param volume_name: the name of the volume whose device we search for
+    @rtype: string or None
+    @return: block device path if the volume is mapped, else None
+
+    """
+    try:
+      devices = serializer.LoadJson(output)
+    except ValueError, err:
+      base.ThrowError("Unable to parse JSON data: %s" % err)
+
+    rbd_dev = None
+    for d in devices.values(): # pylint: disable=E1103
+      try:
+        name = d["name"]
+      except KeyError:
+        base.ThrowError("'name' key missing from json object %s", devices)
+
+      if name == volume_name:
+        if rbd_dev is not None:
+          base.ThrowError("rbd volume %s is mapped more than once", volume_name)
+
+        rbd_dev = d["device"]
+
+    return rbd_dev
+
+  @staticmethod
+  def _ParseRbdShowmappedPlain(output, volume_name):
+    """Parse the (plain / text) output of `rbd showmapped'.
+
+    This method parses the output of `rbd showmapped' and returns
+    the rbd block device path (e.g. /dev/rbd0) that matches the
+    given rbd volume.
+
+    @type output: string
+    @param output: the plain text output of `rbd showmapped'
+    @type volume_name: string
+    @param volume_name: the name of the volume whose device we search for
+    @rtype: string or None
+    @return: block device path if the volume is mapped, else None
+
+    """
+    allfields = 5
+    volumefield = 2
+    devicefield = 4
+
+    lines = output.splitlines()
+
+    # Try parsing the new output format (ceph >= 0.55).
+    splitted_lines = map(lambda l: l.split(), lines)
+
+    # Check for empty output.
+    if not splitted_lines:
+      return None
+
+    # Check showmapped output, to determine number of fields.
+    field_cnt = len(splitted_lines[0])
+    if field_cnt != allfields:
+      # Parsing the new format failed. Fallback to parsing the old output
+      # format (< 0.55).
+      splitted_lines = map(lambda l: l.split("\t"), lines)
+      if field_cnt != allfields:
+        base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
+                        " found %s", allfields, field_cnt)
+
+    matched_lines = \
+      filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
+             splitted_lines)
+
+    if len(matched_lines) > 1:
+      base.ThrowError("rbd volume %s mapped more than once", volume_name)
+
+    if matched_lines:
+      # rbd block device found. Return it.
+      rbd_dev = matched_lines[0][devicefield]
+      return rbd_dev
+
+    # The given volume is not mapped.
+    return None
+
+  def Assemble(self):
+    """Assemble the device.
+
+    """
+    pass
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    """
+    if not self.minor and not self.Attach():
+      # The rbd device doesn't exist.
+      return
+
+    # Unmap the block device from the Volume.
+    self._UnmapVolumeFromBlockdev(self.unique_id)
+
+    self.minor = None
+    self.dev_path = None
+
+  def _UnmapVolumeFromBlockdev(self, unique_id):
+    """Unmaps the rbd device from the Volume it is mapped.
+
+    Unmaps the rbd device from the Volume it was previously mapped to.
+    This method should be idempotent if the Volume isn't mapped.
+
+    """
+    pool = self.params[constants.LDP_POOL]
+    name = unique_id[1]
+
+    # Check if the mapping already exists.
+    rbd_dev = self._VolumeToBlockdev(pool, name)
+
+    if rbd_dev:
+      # The mapping exists. Unmap the rbd device.
+      unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
+      result = utils.RunCmd(unmap_cmd)
+      if result.failed:
+        base.ThrowError("rbd unmap failed (%s): %s",
+                        result.fail_reason, result.output)
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    """
+    pass
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the Volume.
+
+    @type amount: integer
+    @param amount: the amount (in mebibytes) to grow with
+    @type dryrun: boolean
+    @param dryrun: whether to execute the operation in simulation mode
+        only, without actually increasing the size
+
+    """
+    if not backingstore:
+      return
+    if not self.Attach():
+      base.ThrowError("Can't attach to rbd device during Grow()")
+
+    if dryrun:
+      # the rbd tool does not support dry runs of resize operations.
+      # Since rbd volumes are thinly provisioned, we assume
+      # there is always enough free space for the operation.
+      return
+
+    rbd_pool = self.params[constants.LDP_POOL]
+    rbd_name = self.unique_id[1]
+    new_size = self.size + amount
+
+    # Resize the rbd volume (Image) inside the RADOS cluster.
+    cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
+           rbd_name, "--size", "%s" % new_size]
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("rbd resize failed (%s): %s",
+                      result.fail_reason, result.output)
+
+
+class ExtStorageDevice(base.BlockDev):
+  """A block device provided by an ExtStorage Provider.
+
+  This class implements the External Storage Interface, which means
+  handling of the externally provided block devices.
+
+  """
+  def __init__(self, unique_id, children, size, params):
+    """Attaches to an extstorage block device.
+
+    """
+    super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+
+    self.driver, self.vol_name = unique_id
+    self.ext_params = params
+
+    self.major = self.minor = None
+    self.Attach()
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new extstorage device.
+
+    Provision a new volume using an extstorage provider, which will
+    then be mapped to a block device.
+
+    """
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+      raise errors.ProgrammerError("Invalid configuration data %s" %
+                                   str(unique_id))
+    if excl_stor:
+      raise errors.ProgrammerError("extstorage device requested with"
+                                   " exclusive_storage")
+
+    # Call the External Storage's create script,
+    # to provision a new Volume inside the External Storage
+    _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
+                      params, str(size))
+
+    return ExtStorageDevice(unique_id, children, size, params)
+
+  def Remove(self):
+    """Remove the extstorage device.
+
+    """
+    if not self.minor and not self.Attach():
+      # The extstorage device doesn't exist.
+      return
+
+    # First shutdown the device (remove mappings).
+    self.Shutdown()
+
+    # Call the External Storage's remove script,
+    # to remove the Volume from the External Storage
+    _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id,
+                      self.ext_params)
+
+  def Rename(self, new_id):
+    """Rename this device.
+
+    """
+    pass
+
+  def Attach(self):
+    """Attach to an existing extstorage device.
+
+    This method maps the extstorage volume that matches our name with
+    a corresponding block device and then attaches to this device.
+
+    """
+    self.attached = False
+
+    # Call the External Storage's attach script,
+    # to attach an existing Volume to a block device under /dev
+    self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
+                                      self.unique_id, self.ext_params)
+
+    try:
+      st = os.stat(self.dev_path)
+    except OSError, err:
+      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
+      return False
+
+    if not stat.S_ISBLK(st.st_mode):
+      logging.error("%s is not a block device", self.dev_path)
+      return False
+
+    self.major = os.major(st.st_rdev)
+    self.minor = os.minor(st.st_rdev)
+    self.attached = True
+
+    return True
+
+  def Assemble(self):
+    """Assemble the device.
+
+    """
+    pass
+
+  def Shutdown(self):
+    """Shutdown the device.
+
+    """
+    if not self.minor and not self.Attach():
+      # The extstorage device doesn't exist.
+      return
+
+    # Call the External Storage's detach script,
+    # to detach an existing Volume from it's block device under /dev
+    _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
+                      self.ext_params)
+
+    self.minor = None
+    self.dev_path = None
+
+  def Open(self, force=False):
+    """Make the device ready for I/O.
+
+    """
+    pass
+
+  def Close(self):
+    """Notifies that the device will no longer be used for I/O.
+
+    """
+    pass
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Grow the Volume.
+
+    @type amount: integer
+    @param amount: the amount (in mebibytes) to grow with
+    @type dryrun: boolean
+    @param dryrun: whether to execute the operation in simulation mode
+        only, without actually increasing the size
+
+    """
+    if not backingstore:
+      return
+    if not self.Attach():
+      base.ThrowError("Can't attach to extstorage device during Grow()")
+
+    if dryrun:
+      # we do not support dry runs of resize operations for now.
+      return
+
+    new_size = self.size + amount
+
+    # Call the External Storage's grow script,
+    # to grow an existing Volume inside the External Storage
+    _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
+                      self.ext_params, str(self.size), grow=str(new_size))
+
+  def SetInfo(self, text):
+    """Update metadata with info text.
+
+    """
+    # Replace invalid characters
+    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
+    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
+
+    # Only up to 128 characters are allowed
+    text = text[:128]
+
+    # Call the External Storage's setinfo script,
+    # to set metadata for an existing Volume inside the External Storage
+    _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id,
+                      self.ext_params, metadata=text)
+
+
+def _ExtStorageAction(action, unique_id, ext_params,
+                      size=None, grow=None, metadata=None):
+  """Take an External Storage action.
+
+  Take an External Storage action concerning or affecting
+  a specific Volume inside the External Storage.
+
+  @type action: string
+  @param action: which action to perform. One of:
+                 create / remove / grow / attach / detach
+  @type unique_id: tuple (driver, vol_name)
+  @param unique_id: a tuple containing the type of ExtStorage (driver)
+                    and the Volume name
+  @type ext_params: dict
+  @param ext_params: ExtStorage parameters
+  @type size: integer
+  @param size: the size of the Volume in mebibytes
+  @type grow: integer
+  @param grow: the new size in mebibytes (after grow)
+  @type metadata: string
+  @param metadata: metadata info of the Volume, for use by the provider
+  @rtype: None or a block device path (during attach)
+
+  """
+  driver, vol_name = unique_id
+
+  # Create an External Storage instance of type `driver'
+  status, inst_es = ExtStorageFromDisk(driver)
+  if not status:
+    base.ThrowError("%s" % inst_es)
+
+  # Create the basic environment for the driver's scripts
+  create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
+                                      grow, metadata)
+
+  # Do not use log file for action `attach' as we need
+  # to get the output from RunResult
+  # TODO: find a way to have a log file for attach too
+  logfile = None
+  if action is not constants.ES_ACTION_ATTACH:
+    logfile = _VolumeLogName(action, driver, vol_name)
+
+  # Make sure the given action results in a valid script
+  if action not in constants.ES_SCRIPTS:
+    base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
+                    action)
+
+  # Find out which external script to run according the given action
+  script_name = action + "_script"
+  script = getattr(inst_es, script_name)
+
+  # Run the external script
+  result = utils.RunCmd([script], env=create_env,
+                        cwd=inst_es.path, output=logfile,)
+  if result.failed:
+    logging.error("External storage's %s command '%s' returned"
+                  " error: %s, logfile: %s, output: %s",
+                  action, result.cmd, result.fail_reason,
+                  logfile, result.output)
+
+    # If logfile is 'None' (during attach), it breaks TailFile
+    # TODO: have a log file for attach too
+    if action is not constants.ES_ACTION_ATTACH:
+      lines = [utils.SafeEncode(val)
+               for val in utils.TailFile(logfile, lines=20)]
+    else:
+      lines = result.output[-20:]
+
+    base.ThrowError("External storage's %s script failed (%s), last"
+                    " lines of output:\n%s",
+                    action, result.fail_reason, "\n".join(lines))
+
+  if action == constants.ES_ACTION_ATTACH:
+    return result.stdout
+
+
+def ExtStorageFromDisk(name, base_dir=None):
+  """Create an ExtStorage instance from disk.
+
+  This function will return an ExtStorage instance
+  if the given name is a valid ExtStorage name.
+
+  @type base_dir: string
+  @keyword base_dir: Base directory containing ExtStorage installations.
+                     Defaults to a search in all the ES_SEARCH_PATH dirs.
+  @rtype: tuple
+  @return: True and the ExtStorage instance if we find a valid one, or
+      False and the diagnose message on error
+
+  """
+  if base_dir is None:
+    es_base_dir = pathutils.ES_SEARCH_PATH
+  else:
+    es_base_dir = [base_dir]
+
+  es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
+
+  if es_dir is None:
+    return False, ("Directory for External Storage Provider %s not"
+                   " found in search path" % name)
+
+  # ES Files dictionary, we will populate it with the absolute path
+  # names; if the value is True, then it is a required file, otherwise
+  # an optional one
+  es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
+
+  es_files[constants.ES_PARAMETERS_FILE] = True
+
+  for (filename, _) in es_files.items():
+    es_files[filename] = utils.PathJoin(es_dir, filename)
+
+    try:
+      st = os.stat(es_files[filename])
+    except EnvironmentError, err:
+      return False, ("File '%s' under path '%s' is missing (%s)" %
+                     (filename, es_dir, utils.ErrnoOrStr(err)))
+
+    if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
+      return False, ("File '%s' under path '%s' is not a regular file" %
+                     (filename, es_dir))
+
+    if filename in constants.ES_SCRIPTS:
+      if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
+        return False, ("File '%s' under path '%s' is not executable" %
+                       (filename, es_dir))
+
+  parameters = []
+  if constants.ES_PARAMETERS_FILE in es_files:
+    parameters_file = es_files[constants.ES_PARAMETERS_FILE]
+    try:
+      parameters = utils.ReadFile(parameters_file).splitlines()
+    except EnvironmentError, err:
+      return False, ("Error while reading the EXT parameters file at %s: %s" %
+                     (parameters_file, utils.ErrnoOrStr(err)))
+    parameters = [v.split(None, 1) for v in parameters]
+
+  es_obj = \
+    objects.ExtStorage(name=name, path=es_dir,
+                       create_script=es_files[constants.ES_SCRIPT_CREATE],
+                       remove_script=es_files[constants.ES_SCRIPT_REMOVE],
+                       grow_script=es_files[constants.ES_SCRIPT_GROW],
+                       attach_script=es_files[constants.ES_SCRIPT_ATTACH],
+                       detach_script=es_files[constants.ES_SCRIPT_DETACH],
+                       setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
+                       verify_script=es_files[constants.ES_SCRIPT_VERIFY],
+                       supported_parameters=parameters)
+  return True, es_obj
+
+
+def _ExtStorageEnvironment(unique_id, ext_params,
+                           size=None, grow=None, metadata=None):
+  """Calculate the environment for an External Storage script.
+
+  @type unique_id: tuple (driver, vol_name)
+  @param unique_id: ExtStorage pool and name of the Volume
+  @type ext_params: dict
+  @param ext_params: the EXT parameters
+  @type size: string
+  @param size: size of the Volume (in mebibytes)
+  @type grow: string
+  @param grow: new size of Volume after grow (in mebibytes)
+  @type metadata: string
+  @param metadata: metadata info of the Volume
+  @rtype: dict
+  @return: dict of environment variables
+
+  """
+  vol_name = unique_id[1]
+
+  result = {}
+  result["VOL_NAME"] = vol_name
+
+  # EXT params
+  for pname, pvalue in ext_params.items():
+    result["EXTP_%s" % pname.upper()] = str(pvalue)
+
+  if size is not None:
+    result["VOL_SIZE"] = size
+
+  if grow is not None:
+    result["VOL_NEW_SIZE"] = grow
+
+  if metadata is not None:
+    result["VOL_METADATA"] = metadata
+
+  return result
+
+
+def _VolumeLogName(kind, es_name, volume):
+  """Compute the ExtStorage log filename for a given Volume and operation.
+
+  @type kind: string
+  @param kind: the operation type (e.g. create, remove etc.)
+  @type es_name: string
+  @param es_name: the ExtStorage name
+  @type volume: string
+  @param volume: the name of the Volume inside the External Storage
+
+  """
+  # Check if the extstorage log dir is a valid dir
+  if not os.path.isdir(pathutils.LOG_ES_DIR):
+    base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
+
+  # TODO: Use tempfile.mkstemp to create unique filename
+  basename = ("%s-%s-%s-%s.log" %
+              (kind, es_name, volume, utils.TimestampForFilename()))
+  return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
+
+
+DEV_MAP = {
+  constants.LD_LV: LogicalVolume,
+  constants.LD_DRBD8: drbd.DRBD8Dev,
+  constants.LD_BLOCKDEV: PersistentBlockDevice,
+  constants.LD_RBD: RADOSBlockDevice,
+  constants.LD_EXT: ExtStorageDevice,
+  }
+
+if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
+  DEV_MAP[constants.LD_FILE] = FileStorage
+
+
+def _VerifyDiskType(dev_type):
+  if dev_type not in DEV_MAP:
+    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
+
+
+def _VerifyDiskParams(disk):
+  """Verifies if all disk parameters are set.
+
+  """
+  missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
+  if missing:
+    raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
+                                 missing)
+
+
+def FindDevice(disk, children):
+  """Search for an existing, assembled device.
+
+  This will succeed only if the device exists and is assembled, but it
+  does not do any actions in order to activate the device.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk object to find
+  @type children: list of L{bdev.BlockDev}
+  @param children: the list of block devices that are children of the device
+                  represented by the disk parameter
+
+  """
+  _VerifyDiskType(disk.dev_type)
+  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
+                                  disk.params)
+  if not device.attached:
+    return None
+  return device
+
+
+def Assemble(disk, children):
+  """Try to attach or assemble an existing device.
+
+  This will attach to assemble the device, as needed, to bring it
+  fully up. It must be safe to run on already-assembled devices.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk object to assemble
+  @type children: list of L{bdev.BlockDev}
+  @param children: the list of block devices that are children of the device
+                  represented by the disk parameter
+
+  """
+  _VerifyDiskType(disk.dev_type)
+  _VerifyDiskParams(disk)
+  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
+                                  disk.params)
+  device.Assemble()
+  return device
+
+
+def Create(disk, children, excl_stor):
+  """Create a device.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk object to create
+  @type children: list of L{bdev.BlockDev}
+  @param children: the list of block devices that are children of the device
+                  represented by the disk parameter
+  @type excl_stor: boolean
+  @param excl_stor: Whether exclusive_storage is active
+  @rtype: L{bdev.BlockDev}
+  @return: the created device, or C{None} in case of an error
+
+  """
+  _VerifyDiskType(disk.dev_type)
+  _VerifyDiskParams(disk)
+  device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
+                                         disk.spindles, disk.params, excl_stor)
+  return device
similarity index 100%
rename from lib/storage.py
rename to lib/storage/container.py
diff --git a/lib/storage/drbd.py b/lib/storage/drbd.py
new file mode 100644 (file)
index 0000000..14f1a1f
--- /dev/null
@@ -0,0 +1,1053 @@
+#
+#
+
+# 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
+# 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.
+
+
+"""DRBD block device related functionality"""
+
+import errno
+import logging
+import time
+
+from ganeti import constants
+from ganeti import utils
+from ganeti import errors
+from ganeti import netutils
+from ganeti import objects
+from ganeti.storage import base
+from ganeti.storage.drbd_info import DRBD8Info
+from ganeti.storage import drbd_info
+from ganeti.storage import drbd_cmdgen
+
+
+# Size of reads in _CanReadDevice
+
+_DEVICE_READ_SIZE = 128 * 1024
+
+
+class DRBD8(object):
+  """Various methods to deals with the DRBD system as a whole.
+
+  This class provides a set of methods to deal with the DRBD installation on
+  the node or with uninitialized devices as opposed to a DRBD device.
+
+  """
+  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
+
+  _MAX_MINORS = 255
+
+  @staticmethod
+  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
+    """Returns DRBD usermode_helper currently set.
+
+    @type filename: string
+    @param filename: the filename to read the usermode helper from
+    @rtype: string
+    @return: the currently configured DRBD usermode helper
+
+    """
+    try:
+      helper = utils.ReadFile(filename).splitlines()[0]
+    except EnvironmentError, err:
+      if err.errno == errno.ENOENT:
+        base.ThrowError("The file %s cannot be opened, check if the module"
+                        " is loaded (%s)", filename, str(err))
+      else:
+        base.ThrowError("Can't read DRBD helper file %s: %s",
+                        filename, str(err))
+    if not helper:
+      base.ThrowError("Can't read any data from %s", filename)
+    return helper
+
+  @staticmethod
+  def GetProcInfo():
+    """Reads and parses information from /proc/drbd.
+
+    @rtype: DRBD8Info
+    @return: a L{DRBD8Info} instance containing the current /proc/drbd info
+
+    """
+    return DRBD8Info.CreateFromFile()
+
+  @staticmethod
+  def GetUsedDevs():
+    """Compute the list of used DRBD minors.
+
+    @rtype: list of ints
+
+    """
+    info = DRBD8.GetProcInfo()
+    return filter(lambda m: not info.GetMinorStatus(m).is_unconfigured,
+                  info.GetMinors())
+
+  @staticmethod
+  def FindUnusedMinor():
+    """Find an unused DRBD device.
+
+    This is specific to 8.x as the minors are allocated dynamically,
+    so non-existing numbers up to a max minor count are actually free.
+
+    @rtype: int
+
+    """
+    highest = None
+    info = DRBD8.GetProcInfo()
+    for minor in info.GetMinors():
+      status = info.GetMinorStatus(minor)
+      if not status.is_in_use:
+        return minor
+      highest = max(highest, minor)
+
+    if highest is None: # there are no minors in use at all
+      return 0
+    if highest >= DRBD8._MAX_MINORS:
+      logging.error("Error: no free drbd minors!")
+      raise errors.BlockDeviceError("Can't find a free DRBD minor")
+
+    return highest + 1
+
+  @staticmethod
+  def GetCmdGenerator(info):
+    """Creates a suitable L{BaseDRBDCmdGenerator} based on the given info.
+
+    @type info: DRBD8Info
+    @rtype: BaseDRBDCmdGenerator
+
+    """
+    version = info.GetVersion()
+    if version["k_minor"] <= 3:
+      return drbd_cmdgen.DRBD83CmdGenerator(version)
+    else:
+      return drbd_cmdgen.DRBD84CmdGenerator(version)
+
+  @staticmethod
+  def ShutdownAll(minor):
+    """Deactivate the device.
+
+    This will, of course, fail if the device is in use.
+
+    @type minor: int
+    @param minor: the minor to shut down
+
+    """
+    info = DRBD8.GetProcInfo()
+    cmd_gen = DRBD8.GetCmdGenerator(info)
+
+    cmd = cmd_gen.GenDownCmd(minor)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
+                      minor, result.output)
+
+
+class DRBD8Dev(base.BlockDev):
+  """DRBD v8.x block device.
+
+  This implements the local host part of the DRBD device, i.e. it
+  doesn't do anything to the supposed peer. If you need a fully
+  connected DRBD pair, you need to use this class on both hosts.
+
+  The unique_id for the drbd device is a (local_ip, local_port,
+  remote_ip, remote_port, local_minor, secret) tuple, and it must have
+  two children: the data device and the meta_device. The meta device
+  is checked for valid size and is zeroed on create.
+
+  """
+  _DRBD_MAJOR = 147
+
+  # timeout constants
+  _NET_RECONFIG_TIMEOUT = 60
+
+  def __init__(self, unique_id, children, size, params):
+    if children and children.count(None) > 0:
+      children = []
+    if len(children) not in (0, 2):
+      raise ValueError("Invalid configuration data %s" % str(children))
+    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
+      raise ValueError("Invalid configuration data %s" % str(unique_id))
+    (self._lhost, self._lport,
+     self._rhost, self._rport,
+     self._aminor, self._secret) = unique_id
+    if children:
+      if not _CanReadDevice(children[1].dev_path):
+        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
+        children = []
+    super(DRBD8Dev, self).__init__(unique_id, children, size, params)
+    self.major = self._DRBD_MAJOR
+
+    info = DRBD8.GetProcInfo()
+    version = info.GetVersion()
+    if version["k_major"] != 8:
+      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
+                      " usage: kernel is %s.%s, ganeti wants 8.x",
+                      version["k_major"], version["k_minor"])
+
+    if version["k_minor"] <= 3:
+      self._show_info_cls = drbd_info.DRBD83ShowInfo
+    else:
+      self._show_info_cls = drbd_info.DRBD84ShowInfo
+
+    self._cmd_gen = DRBD8.GetCmdGenerator(info)
+
+    if (self._lhost is not None and self._lhost == self._rhost and
+            self._lport == self._rport):
+      raise ValueError("Invalid configuration data, same local/remote %s" %
+                       (unique_id,))
+    self.Attach()
+
+  @staticmethod
+  def _DevPath(minor):
+    """Return the path to a drbd device for a given minor.
+
+    @type minor: int
+    @rtype: string
+
+    """
+    return "/dev/drbd%d" % minor
+
+  def _SetFromMinor(self, minor):
+    """Set our parameters based on the given minor.
+
+    This sets our minor variable and our dev_path.
+
+    @type minor: int
+
+    """
+    if minor is None:
+      self.minor = self.dev_path = None
+      self.attached = False
+    else:
+      self.minor = minor
+      self.dev_path = self._DevPath(minor)
+      self.attached = True
+
+  @staticmethod
+  def _CheckMetaSize(meta_device):
+    """Check if the given meta device looks like a valid one.
+
+    This currently only checks the size, which must be around
+    128MiB.
+
+    @type meta_device: string
+    @param meta_device: the path to the device to check
+
+    """
+    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
+    if result.failed:
+      base.ThrowError("Failed to get device size: %s - %s",
+                      result.fail_reason, result.output)
+    try:
+      sectors = int(result.stdout)
+    except (TypeError, ValueError):
+      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
+    num_bytes = sectors * 512
+    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
+      base.ThrowError("Meta device too small (%.2fMib)",
+                      (num_bytes / 1024 / 1024))
+    # the maximum *valid* size of the meta device when living on top
+    # of LVM is hard to compute: it depends on the number of stripes
+    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
+    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
+    # size meta device; as such, we restrict it to 1GB (a little bit
+    # too generous, but making assumptions about PE size is hard)
+    if num_bytes > 1024 * 1024 * 1024:
+      base.ThrowError("Meta device too big (%.2fMiB)",
+                      (num_bytes / 1024 / 1024))
+
+  def _GetShowData(self, minor):
+    """Return the `drbdsetup show` data.
+
+    @type minor: int
+    @param minor: the minor to collect show output for
+    @rtype: string
+
+    """
+    result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
+    if result.failed:
+      logging.error("Can't display the drbd config: %s - %s",
+                    result.fail_reason, result.output)
+      return None
+    return result.stdout
+
+  def _GetShowInfo(self, minor):
+    """Return parsed information from `drbdsetup show`.
+
+    @type minor: int
+    @param minor: the minor to return information for
+    @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
+
+    """
+    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
+
+  def _MatchesLocal(self, info):
+    """Test if our local config matches with an existing device.
+
+    The parameter should be as returned from `_GetShowInfo()`. This
+    method tests if our local backing device is the same as the one in
+    the info parameter, in effect testing if we look like the given
+    device.
+
+    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
+    @rtype: boolean
+
+    """
+    if self._children:
+      backend, meta = self._children
+    else:
+      backend = meta = None
+
+    if backend is not None:
+      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
+    else:
+      retval = ("local_dev" not in info)
+
+    if meta is not None:
+      retval = retval and ("meta_dev" in info and
+                           info["meta_dev"] == meta.dev_path)
+      if "meta_index" in info:
+        retval = retval and info["meta_index"] == 0
+    else:
+      retval = retval and ("meta_dev" not in info and
+                           "meta_index" not in info)
+    return retval
+
+  def _MatchesNet(self, info):
+    """Test if our network config matches with an existing device.
+
+    The parameter should be as returned from `_GetShowInfo()`. This
+    method tests if our network configuration is the same as the one
+    in the info parameter, in effect testing if we look like the given
+    device.
+
+    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
+    @rtype: boolean
+
+    """
+    if (((self._lhost is None and not ("local_addr" in info)) and
+         (self._rhost is None and not ("remote_addr" in info)))):
+      return True
+
+    if self._lhost is None:
+      return False
+
+    if not ("local_addr" in info and
+            "remote_addr" in info):
+      return False
+
+    retval = (info["local_addr"] == (self._lhost, self._lport))
+    retval = (retval and
+              info["remote_addr"] == (self._rhost, self._rport))
+    return retval
+
+  def _AssembleLocal(self, minor, backend, meta, size):
+    """Configure the local part of a DRBD device.
+
+    @type minor: int
+    @param minor: the minor to assemble locally
+    @type backend: string
+    @param backend: path to the data device to use
+    @type meta: string
+    @param meta: path to the meta device to use
+    @type size: int
+    @param size: size in MiB
+
+    """
+    cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
+                                          size, self.params)
+
+    for cmd in cmds:
+      result = utils.RunCmd(cmd)
+      if result.failed:
+        base.ThrowError("drbd%d: can't attach local disk: %s",
+                        minor, result.output)
+
+  def _AssembleNet(self, minor, net_info, protocol,
+                   dual_pri=False, hmac=None, secret=None):
+    """Configure the network part of the device.
+
+    @type minor: int
+    @param minor: the minor to assemble the network for
+    @type net_info: (string, int, string, int)
+    @param net_info: tuple containing the local address, local port, remote
+      address and remote port
+    @type protocol: string
+    @param protocol: either "ipv4" or "ipv6"
+    @type dual_pri: boolean
+    @param dual_pri: whether two primaries should be allowed or not
+    @type hmac: string
+    @param hmac: the HMAC algorithm to use
+    @type secret: string
+    @param secret: the shared secret to use
+
+    """
+    lhost, lport, rhost, rport = net_info
+    if None in net_info:
+      # we don't want network connection and actually want to make
+      # sure its shutdown
+      self._ShutdownNet(minor)
+      return
+
+    # Workaround for a race condition. When DRBD is doing its dance to
+    # establish a connection with its peer, it also sends the
+    # synchronization speed over the wire. In some cases setting the
+    # sync speed only after setting up both sides can race with DRBD
+    # connecting, hence we set it here before telling DRBD anything
+    # about its peer.
+    sync_errors = self._SetMinorSyncParams(minor, self.params)
+    if sync_errors:
+      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
+                      (minor, utils.CommaJoin(sync_errors)))
+
+    family = self._GetNetFamily(minor, lhost, rhost)
+
+    cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
+                                      rhost, rport, protocol,
+                                      dual_pri, hmac, secret, self.params)
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't setup network: %s - %s",
+                      minor, result.fail_reason, result.output)
+
+    def _CheckNetworkConfig():
+      info = self._GetShowInfo(minor)
+      if not "local_addr" in info or not "remote_addr" in info:
+        raise utils.RetryAgain()
+
+      if (info["local_addr"] != (lhost, lport) or
+          info["remote_addr"] != (rhost, rport)):
+        raise utils.RetryAgain()
+
+    try:
+      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
+    except utils.RetryTimeout:
+      base.ThrowError("drbd%d: timeout while configuring network", minor)
+
+  @staticmethod
+  def _GetNetFamily(minor, lhost, rhost):
+    if netutils.IP6Address.IsValid(lhost):
+      if not netutils.IP6Address.IsValid(rhost):
+        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
+                        (minor, lhost, rhost))
+      return "ipv6"
+    elif netutils.IP4Address.IsValid(lhost):
+      if not netutils.IP4Address.IsValid(rhost):
+        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
+                        (minor, lhost, rhost))
+      return "ipv4"
+    else:
+      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
+
+  def AddChildren(self, devices):
+    """Add a disk to the DRBD device.
+
+    @type devices: list of L{BlockDev}
+    @param devices: a list of exactly two L{BlockDev} objects; the first
+      denotes the data device, the second the meta device for this DRBD device
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
+                      self._aminor)
+    if len(devices) != 2:
+      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
+    info = self._GetShowInfo(self.minor)
+    if "local_dev" in info:
+      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
+    backend, meta = devices
+    if backend.dev_path is None or meta.dev_path is None:
+      base.ThrowError("drbd%d: children not ready during AddChildren",
+                      self.minor)
+    backend.Open()
+    meta.Open()
+    self._CheckMetaSize(meta.dev_path)
+    self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
+
+    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
+    self._children = devices
+
+  def RemoveChildren(self, devices):
+    """Detach the drbd device from local storage.
+
+    @type devices: list of L{BlockDev}
+    @param devices: a list of exactly two L{BlockDev} objects; the first
+      denotes the data device, the second the meta device for this DRBD device
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
+                      self._aminor)
+    # early return if we don't actually have backing storage
+    info = self._GetShowInfo(self.minor)
+    if "local_dev" not in info:
+      return
+    if len(self._children) != 2:
+      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
+                      self._children)
+    if self._children.count(None) == 2: # we don't actually have children :)
+      logging.warning("drbd%d: requested detach while detached", self.minor)
+      return
+    if len(devices) != 2:
+      base.ThrowError("drbd%d: we need two children in RemoveChildren",
+                      self.minor)
+    for child, dev in zip(self._children, devices):
+      if dev != child.dev_path:
+        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
+                        " RemoveChildren", self.minor, dev, child.dev_path)
+
+    self._ShutdownLocal(self.minor)
+    self._children = []
+
+  def _SetMinorSyncParams(self, minor, params):
+    """Set the parameters of the DRBD syncer.
+
+    This is the low-level implementation.
+
+    @type minor: int
+    @param minor: the drbd minor whose settings we change
+    @type params: dict
+    @param params: LD level disk parameters related to the synchronization
+    @rtype: list
+    @return: a list of error messages
+
+    """
+    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      msg = ("Can't change syncer rate: %s - %s" %
+             (result.fail_reason, result.output))
+      logging.error(msg)
+      return [msg]
+
+    return []
+
+  def SetSyncParams(self, params):
+    """Set the synchronization parameters of the DRBD syncer.
+
+    See L{BlockDev.SetSyncParams} for parameter description.
+
+    """
+    if self.minor is None:
+      err = "Not attached during SetSyncParams"
+      logging.info(err)
+      return [err]
+
+    children_result = super(DRBD8Dev, self).SetSyncParams(params)
+    children_result.extend(self._SetMinorSyncParams(self.minor, params))
+    return children_result
+
+  def PauseResumeSync(self, pause):
+    """Pauses or resumes the sync of a DRBD device.
+
+    See L{BlockDev.PauseResumeSync} for parameter description.
+
+    """
+    if self.minor is None:
+      logging.info("Not attached during PauseSync")
+      return False
+
+    children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
+
+    if pause:
+      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
+    else:
+      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      logging.error("Can't %s: %s - %s", cmd,
+                    result.fail_reason, result.output)
+    return not result.failed and children_result
+
+  def GetProcStatus(self):
+    """Return the current status data from /proc/drbd for this device.
+
+    @rtype: DRBD8Status
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: GetStats() called while not attached",
+                      self._aminor)
+    info = DRBD8.GetProcInfo()
+    if not info.HasMinorStatus(self.minor):
+      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
+    return info.GetMinorStatus(self.minor)
+
+  def GetSyncStatus(self):
+    """Returns the sync status of the device.
+
+    If sync_percent is None, it means all is ok
+    If estimated_time is None, it means we can't estimate
+    the time needed, otherwise it's the time left in seconds.
+
+    We set the is_degraded parameter to True on two conditions:
+    network not connected or local disk missing.
+
+    We compute the ldisk parameter based on whether we have a local
+    disk or not.
+
+    @rtype: objects.BlockDevStatus
+
+    """
+    if self.minor is None and not self.Attach():
+      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
+
+    stats = self.GetProcStatus()
+    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
+
+    if stats.is_disk_uptodate:
+      ldisk_status = constants.LDS_OKAY
+    elif stats.is_diskless:
+      ldisk_status = constants.LDS_FAULTY
+    else:
+      ldisk_status = constants.LDS_UNKNOWN
+
+    return objects.BlockDevStatus(dev_path=self.dev_path,
+                                  major=self.major,
+                                  minor=self.minor,
+                                  sync_percent=stats.sync_percent,
+                                  estimated_time=stats.est_time,
+                                  is_degraded=is_degraded,
+                                  ldisk_status=ldisk_status)
+
+  def Open(self, force=False):
+    """Make the local state primary.
+
+    If the 'force' parameter is given, DRBD is instructed to switch the device
+    into primary mode. Since this is a potentially dangerous operation, the
+    force flag should be only given after creation, when it actually is
+    mandatory.
+
+    """
+    if self.minor is None and not self.Attach():
+      logging.error("DRBD cannot attach to a device during open")
+      return False
+
+    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
+                      result.output)
+
+  def Close(self):
+    """Make the local state secondary.
+
+    This will, of course, fail if the device is in use.
+
+    """
+    if self.minor is None and not self.Attach():
+      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
+    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
+                      self.minor, result.output)
+
+  def DisconnectNet(self):
+    """Removes network configuration.
+
+    This method shutdowns the network side of the device.
+
+    The method will wait up to a hardcoded timeout for the device to
+    go into standalone after the 'disconnect' command before
+    re-configuring it, as sometimes it takes a while for the
+    disconnect to actually propagate and thus we might issue a 'net'
+    command while the device is still connected. If the device will
+    still be attached to the network and we time out, we raise an
+    exception.
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: disk not attached in re-attach net",
+                      self._aminor)
+
+    if None in (self._lhost, self._lport, self._rhost, self._rport):
+      base.ThrowError("drbd%d: DRBD disk missing network info in"
+                      " DisconnectNet()", self.minor)
+
+    class _DisconnectStatus:
+      def __init__(self, ever_disconnected):
+        self.ever_disconnected = ever_disconnected
+
+    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
+
+    def _WaitForDisconnect():
+      if self.GetProcStatus().is_standalone:
+        return
+
+      # retry the disconnect, it seems possible that due to a well-time
+      # disconnect on the peer, my disconnect command might be ignored and
+      # forgotten
+      dstatus.ever_disconnected = \
+        base.IgnoreError(self._ShutdownNet, self.minor) or \
+        dstatus.ever_disconnected
+
+      raise utils.RetryAgain()
+
+    # Keep start time
+    start_time = time.time()
+
+    try:
+      # Start delay at 100 milliseconds and grow up to 2 seconds
+      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
+                  self._NET_RECONFIG_TIMEOUT)
+    except utils.RetryTimeout:
+      if dstatus.ever_disconnected:
+        msg = ("drbd%d: device did not react to the"
+               " 'disconnect' command in a timely manner")
+      else:
+        msg = "drbd%d: can't shutdown network, even after multiple retries"
+
+      base.ThrowError(msg, self.minor)
+
+    reconfig_time = time.time() - start_time
+    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
+      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
+                   self.minor, reconfig_time)
+
+  def AttachNet(self, multimaster):
+    """Reconnects the network.
+
+    This method connects the network side of the device with a
+    specified multi-master flag. The device needs to be 'Standalone'
+    but have valid network configuration data.
+
+    @type multimaster: boolean
+    @param multimaster: init the network in dual-primary mode
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
+
+    if None in (self._lhost, self._lport, self._rhost, self._rport):
+      base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
+
+    status = self.GetProcStatus()
+
+    if not status.is_standalone:
+      base.ThrowError("drbd%d: device is not standalone in AttachNet",
+                      self.minor)
+
+    self._AssembleNet(self.minor,
+                      (self._lhost, self._lport, self._rhost, self._rport),
+                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
+                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
+
+  def Attach(self):
+    """Check if our minor is configured.
+
+    This doesn't do any device configurations - it only checks if the
+    minor is in a state different from Unconfigured.
+
+    Note that this function will not change the state of the system in
+    any way (except in case of side-effects caused by reading from
+    /proc).
+
+    """
+    used_devs = DRBD8.GetUsedDevs()
+    if self._aminor in used_devs:
+      minor = self._aminor
+    else:
+      minor = None
+
+    self._SetFromMinor(minor)
+    return minor is not None
+
+  def Assemble(self):
+    """Assemble the drbd.
+
+    Method:
+      - if we have a configured device, we try to ensure that it matches
+        our config
+      - if not, we create it from zero
+      - anyway, set the device parameters
+
+    """
+    super(DRBD8Dev, self).Assemble()
+
+    self.Attach()
+    if self.minor is None:
+      # local device completely unconfigured
+      self._FastAssemble()
+    else:
+      # we have to recheck the local and network status and try to fix
+      # the device
+      self._SlowAssemble()
+
+    sync_errors = self.SetSyncParams(self.params)
+    if sync_errors:
+      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
+                      (self.minor, utils.CommaJoin(sync_errors)))
+
+  def _SlowAssemble(self):
+    """Assembles the DRBD device from a (partially) configured device.
+
+    In case of partially attached (local device matches but no network
+    setup), we perform the network attach. If successful, we re-test
+    the attach if can return success.
+
+    """
+    # TODO: Rewrite to not use a for loop just because there is 'break'
+    # pylint: disable=W0631
+    net_data = (self._lhost, self._lport, self._rhost, self._rport)
+    for minor in (self._aminor,):
+      info = self._GetShowInfo(minor)
+      match_l = self._MatchesLocal(info)
+      match_r = self._MatchesNet(info)
+
+      if match_l and match_r:
+        # everything matches
+        break
+
+      if match_l and not match_r and "local_addr" not in info:
+        # disk matches, but not attached to network, attach and recheck
+        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
+                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
+        if self._MatchesNet(self._GetShowInfo(minor)):
+          break
+        else:
+          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
+                          " show' disagrees", minor)
+
+      if match_r and "local_dev" not in info:
+        # no local disk, but network attached and it matches
+        self._AssembleLocal(minor, self._children[0].dev_path,
+                            self._children[1].dev_path, self.size)
+        if self._MatchesLocal(self._GetShowInfo(minor)):
+          break
+        else:
+          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
+                          " show' disagrees", minor)
+
+      # this case must be considered only if we actually have local
+      # storage, i.e. not in diskless mode, because all diskless
+      # devices are equal from the point of view of local
+      # configuration
+      if (match_l and "local_dev" in info and
+          not match_r and "local_addr" in info):
+        # strange case - the device network part points to somewhere
+        # else, even though its local storage is ours; as we own the
+        # drbd space, we try to disconnect from the remote peer and
+        # reconnect to our correct one
+        try:
+          self._ShutdownNet(minor)
+        except errors.BlockDeviceError, err:
+          base.ThrowError("drbd%d: device has correct local storage, wrong"
+                          " remote peer and is unable to disconnect in order"
+                          " to attach to the correct peer: %s", minor, str(err))
+        # note: _AssembleNet also handles the case when we don't want
+        # local storage (i.e. one or more of the _[lr](host|port) is
+        # None)
+        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
+                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
+        if self._MatchesNet(self._GetShowInfo(minor)):
+          break
+        else:
+          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
+                          " show' disagrees", minor)
+
+    else:
+      minor = None
+
+    self._SetFromMinor(minor)
+    if minor is None:
+      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
+                      self._aminor)
+
+  def _FastAssemble(self):
+    """Assemble the drbd device from zero.
+
+    This is run when in Assemble we detect our minor is unused.
+
+    """
+    minor = self._aminor
+    if self._children and self._children[0] and self._children[1]:
+      self._AssembleLocal(minor, self._children[0].dev_path,
+                          self._children[1].dev_path, self.size)
+    if self._lhost and self._lport and self._rhost and self._rport:
+      self._AssembleNet(minor,
+                        (self._lhost, self._lport, self._rhost, self._rport),
+                        constants.DRBD_NET_PROTOCOL,
+                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
+    self._SetFromMinor(minor)
+
+  def _ShutdownLocal(self, minor):
+    """Detach from the local device.
+
+    I/Os will continue to be served from the remote device. If we
+    don't have a remote device, this operation will fail.
+
+    @type minor: int
+    @param minor: the device to detach from the local device
+
+    """
+    cmd = self._cmd_gen.GenDetachCmd(minor)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't detach local disk: %s",
+                      minor, result.output)
+
+  def _ShutdownNet(self, minor):
+    """Disconnect from the remote peer.
+
+    This fails if we don't have a local device.
+
+    @type minor: boolean
+    @param minor: the device to disconnect from the remote peer
+
+    """
+    family = self._GetNetFamily(minor, self._lhost, self._rhost)
+    cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
+                                         self._lhost, self._lport,
+                                         self._rhost, self._rport)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: can't shutdown network: %s",
+                      minor, result.output)
+
+  def Shutdown(self):
+    """Shutdown the DRBD device.
+
+    """
+    if self.minor is None and not self.Attach():
+      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
+      return
+
+    try:
+      DRBD8.ShutdownAll(self.minor)
+    finally:
+      self.minor = None
+      self.dev_path = None
+
+  def Remove(self):
+    """Stub remove for DRBD devices.
+
+    """
+    self.Shutdown()
+
+  def Rename(self, new_id):
+    """Rename a device.
+
+    This is not supported for drbd devices.
+
+    """
+    raise errors.ProgrammerError("Can't rename a drbd device")
+
+  def Grow(self, amount, dryrun, backingstore):
+    """Resize the DRBD device and its backing storage.
+
+    See L{BlockDev.Grow} for parameter description.
+
+    """
+    if self.minor is None:
+      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
+    if len(self._children) != 2 or None in self._children:
+      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
+    self._children[0].Grow(amount, dryrun, backingstore)
+    if dryrun or backingstore:
+      # DRBD does not support dry-run mode and is not backing storage,
+      # so we'll return here
+      return
+    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
+
+  @classmethod
+  def _InitMeta(cls, minor, dev_path):
+    """Initialize a meta device.
+
+    This will not work if the given minor is in use.
+
+    @type minor: int
+    @param minor: the DRBD minor whose (future) meta device should be
+      initialized
+    @type dev_path: string
+    @param dev_path: path to the meta device to initialize
+
+    """
+    # Zero the metadata first, in order to make sure drbdmeta doesn't
+    # try to auto-detect existing filesystems or similar (see
+    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
+    # care about the first 128MB of data in the device, even though it
+    # can be bigger
+    result = utils.RunCmd([constants.DD_CMD,
+                           "if=/dev/zero", "of=%s" % dev_path,
+                           "bs=1048576", "count=128", "oflag=direct"])
+    if result.failed:
+      base.ThrowError("Can't wipe the meta device: %s", result.output)
+
+    info = DRBD8.GetProcInfo()
+    cmd_gen = DRBD8.GetCmdGenerator(info)
+    cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      base.ThrowError("Can't initialize meta device: %s", result.output)
+
+  @classmethod
+  def Create(cls, unique_id, children, size, spindles, params, excl_stor):
+    """Create a new DRBD8 device.
+
+    Since DRBD devices are not created per se, just assembled, this
+    function only initializes the metadata.
+
+    """
+    if len(children) != 2:
+      raise errors.ProgrammerError("Invalid setup for the drbd device")
+    if excl_stor:
+      raise errors.ProgrammerError("DRBD device requested with"
+                                   " exclusive_storage")
+    # check that the minor is unused
+    aminor = unique_id[4]
+
+    info = DRBD8.GetProcInfo()
+    if info.HasMinorStatus(aminor):
+      status = info.GetMinorStatus(aminor)
+      in_use = status.is_in_use
+    else:
+      in_use = False
+    if in_use:
+      base.ThrowError("drbd%d: minor is already in use at Create() time",
+                      aminor)
+    meta = children[1]
+    meta.Assemble()
+    if not meta.Attach():
+      base.ThrowError("drbd%d: can't attach to meta device '%s'",
+                      aminor, meta)
+    cls._CheckMetaSize(meta.dev_path)
+    cls._InitMeta(aminor, meta.dev_path)
+    return cls(unique_id, children, size, params)
+
+
+def _CanReadDevice(path):
+  """Check if we can read from the given device.
+
+  This tries to read the first 128k of the device.
+
+  @type path: string
+
+  """
+  try:
+    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
+    return True
+  except EnvironmentError:
+    logging.warning("Can't read from device %s", path, exc_info=True)
+    return False
diff --git a/lib/storage/drbd_cmdgen.py b/lib/storage/drbd_cmdgen.py
new file mode 100644 (file)
index 0000000..b82aaaa
--- /dev/null
@@ -0,0 +1,451 @@
+#
+#
+
+# 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
+# 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.
+
+
+"""DRBD command generating classes"""
+
+import logging
+import shlex
+
+from ganeti import constants
+from ganeti import errors
+
+
+class BaseDRBDCmdGenerator(object):
+  """Base class for DRBD command generators.
+
+  This class defines the interface for the command generators and holds shared
+  code.
+
+  """
+  def __init__(self, version):
+    self._version = version
+
+  def GenShowCmd(self, minor):
+    raise NotImplementedError
+
+  def GenInitMetaCmd(self, minor, meta_dev):
+    raise NotImplementedError
+
+  def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
+    raise NotImplementedError
+
+  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
+                    dual_pri, hmac, secret, params):
+    raise NotImplementedError
+
+  def GenSyncParamsCmd(self, minor, params):
+    raise NotImplementedError
+
+  def GenPauseSyncCmd(self, minor):
+    raise NotImplementedError
+
+  def GenResumeSyncCmd(self, minor):
+    raise NotImplementedError
+
+  def GenPrimaryCmd(self, minor, force):
+    raise NotImplementedError
+
+  def GenSecondaryCmd(self, minor):
+    raise NotImplementedError
+
+  def GenDetachCmd(self, minor):
+    raise NotImplementedError
+
+  def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
+    raise NotImplementedError
+
+  def GenDownCmd(self, minor):
+    raise NotImplementedError
+
+  def GenResizeCmd(self, minor, size_mb):
+    raise NotImplementedError
+
+  @staticmethod
+  def _DevPath(minor):
+    """Return the path to a drbd device for a given minor.
+
+    """
+    return "/dev/drbd%d" % minor
+
+
+class DRBD83CmdGenerator(BaseDRBDCmdGenerator):
+  """Generates drbdsetup commands suited for the DRBD <= 8.3 syntax.
+
+  """
+  # command line options for barriers
+  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
+  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
+  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
+  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
+
+  def __init__(self, version):
+    super(DRBD83CmdGenerator, self).__init__(version)
+
+  def GenShowCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "show"]
+
+  def GenInitMetaCmd(self, minor, meta_dev):
+    return ["drbdmeta", "--force", self._DevPath(minor),
+            "v08", meta_dev, "0", "create-md"]
+
+  def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
+    args = ["drbdsetup", self._DevPath(minor), "disk",
+            data_dev, meta_dev, "0",
+            "-e", "detach",
+            "--create-device"]
+    if size_mb:
+      args.extend(["-d", "%sm" % size_mb])
+
+    vmaj = self._version["k_major"]
+    vmin = self._version["k_minor"]
+    vrel = self._version["k_point"]
+
+    barrier_args = \
+      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
+                                   params[constants.LDP_BARRIERS],
+                                   params[constants.LDP_NO_META_FLUSH])
+    args.extend(barrier_args)
+
+    if params[constants.LDP_DISK_CUSTOM]:
+      args.extend(shlex.split(params[constants.LDP_DISK_CUSTOM]))
+
+    return [args]
+
+  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
+                    dual_pri, hmac, secret, params):
+    args = ["drbdsetup", self._DevPath(minor), "net",
+            "%s:%s:%s" % (family, lhost, lport),
+            "%s:%s:%s" % (family, rhost, rport), protocol,
+            "-A", "discard-zero-changes",
+            "-B", "consensus",
+            "--create-device",
+            ]
+    if dual_pri:
+      args.append("-m")
+    if hmac and secret:
+      args.extend(["-a", hmac, "-x", secret])
+
+    if params[constants.LDP_NET_CUSTOM]:
+      args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
+
+    return args
+
+  def GenSyncParamsCmd(self, minor, params):
+    args = ["drbdsetup", self._DevPath(minor), "syncer"]
+    if params[constants.LDP_DYNAMIC_RESYNC]:
+      vmin = self._version["k_minor"]
+      vrel = self._version["k_point"]
+
+      # By definition we are using 8.x, so just check the rest of the version
+      # number
+      if vmin != 3 or vrel < 9:
+        msg = ("The current DRBD version (8.%d.%d) does not support the "
+               "dynamic resync speed controller" % (vmin, vrel))
+        logging.error(msg)
+        return [msg]
+
+      if params[constants.LDP_PLAN_AHEAD] == 0:
+        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
+               " controller at DRBD level. If you want to disable it, please"
+               " set the dynamic-resync disk parameter to False.")
+        logging.error(msg)
+        return [msg]
+
+      # add the c-* parameters to args
+      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
+                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
+                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
+                   "--c-max-rate", params[constants.LDP_MAX_RATE],
+                   "--c-min-rate", params[constants.LDP_MIN_RATE],
+                   ])
+
+    else:
+      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
+
+    args.append("--create-device")
+
+    return args
+
+  def GenPauseSyncCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "pause-sync"]
+
+  def GenResumeSyncCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "resume-sync"]
+
+  def GenPrimaryCmd(self, minor, force):
+    cmd = ["drbdsetup", self._DevPath(minor), "primary"]
+
+    if force:
+      cmd.append("-o")
+
+    return cmd
+
+  def GenSecondaryCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "secondary"]
+
+  def GenDetachCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "detach"]
+
+  def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
+    return ["drbdsetup", self._DevPath(minor), "disconnect"]
+
+  def GenDownCmd(self, minor):
+    return ["drbdsetup", self._DevPath(minor), "down"]
+
+  def GenResizeCmd(self, minor, size_mb):
+    return ["drbdsetup", self._DevPath(minor), "resize", "-s", "%dm" % size_mb]
+
+  @classmethod
+  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
+                              disable_meta_flush):
+    """Compute the DRBD command line parameters for disk barriers
+
+    Returns a list of the disk barrier parameters as requested via the
+    disabled_barriers and disable_meta_flush arguments, and according to the
+    supported ones in the DRBD version vmaj.vmin.vrel
+
+    If the desired option is unsupported, raises errors.BlockDeviceError.
+
+    """
+    disabled_barriers_set = frozenset(disabled_barriers)
+    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
+      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
+                                    " barriers" % disabled_barriers)
+
+    args = []
+
+    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
+    # does not exist)
+    if not vmaj == 8 and vmin in (0, 2, 3):
+      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
+                                    (vmaj, vmin, vrel))
+
+    def _AppendOrRaise(option, min_version):
+      """Helper for DRBD options"""
+      if min_version is not None and vrel >= min_version:
+        args.append(option)
+      else:
+        raise errors.BlockDeviceError("Could not use the option %s as the"
+                                      " DRBD version %d.%d.%d does not support"
+                                      " it." % (option, vmaj, vmin, vrel))
+
+    # the minimum version for each feature is encoded via pairs of (minor
+    # version -> x) where x is version in which support for the option was
+    # introduced.
+    meta_flush_supported = disk_flush_supported = {
+      0: 12,
+      2: 7,
+      3: 0,
+      }
+
+    disk_drain_supported = {
+      2: 7,
+      3: 0,
+      }
+
+    disk_barriers_supported = {
+      3: 0,
+      }
+
+    # meta flushes
+    if disable_meta_flush:
+      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
+                     meta_flush_supported.get(vmin, None))
+
+    # disk flushes
+    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
+      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
+                     disk_flush_supported.get(vmin, None))
+
+    # disk drain
+    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
+      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
+                     disk_drain_supported.get(vmin, None))
+
+    # disk barriers
+    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
+      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
+                     disk_barriers_supported.get(vmin, None))
+
+    return args
+
+
+class DRBD84CmdGenerator(BaseDRBDCmdGenerator):
+  """Generates drbdsetup commands suited for the DRBD >= 8.4 syntax.
+
+  """
+  # command line options for barriers
+  _DISABLE_DISK_OPTION = "--disk-barrier=no"
+  _DISABLE_DRAIN_OPTION = "--disk-drain=no"
+  _DISABLE_FLUSH_OPTION = "--disk-flushes=no"
+  _DISABLE_META_FLUSH_OPTION = "--md-flushes=no"
+
+  def __init__(self, version):
+    super(DRBD84CmdGenerator, self).__init__(version)
+
+  def GenShowCmd(self, minor):
+    return ["drbdsetup", "show", minor]
+
+  def GenInitMetaCmd(self, minor, meta_dev):
+    return ["drbdmeta", "--force", self._DevPath(minor),
+            "v08", meta_dev, "flex-external", "create-md"]
+
+  def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
+    cmds = []
+
+    cmds.append(["drbdsetup", "new-resource", self._GetResource(minor)])
+    cmds.append(["drbdsetup", "new-minor", self._GetResource(minor),
+                 str(minor), "0"])
+    # We need to apply the activity log before attaching the disk else drbdsetup
+    # will fail.
+    cmds.append(["drbdmeta", self._DevPath(minor),
+                 "v08", meta_dev, "flex-external", "apply-al"])
+
+    attach_cmd = ["drbdsetup", "attach", minor, data_dev, meta_dev, "flexible",
+                  "--on-io-error=detach"]
+    if size_mb:
+      attach_cmd.extend(["--size", "%sm" % size_mb])
+
+    barrier_args = \
+      self._ComputeDiskBarrierArgs(params[constants.LDP_BARRIERS],
+                                   params[constants.LDP_NO_META_FLUSH])
+    attach_cmd.extend(barrier_args)
+
+    if params[constants.LDP_DISK_CUSTOM]:
+      attach_cmd.extend(shlex.split(params[constants.LDP_DISK_CUSTOM]))
+
+    cmds.append(attach_cmd)
+
+    return cmds
+
+  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
+                    dual_pri, hmac, secret, params):
+    args = ["drbdsetup", "connect", self._GetResource(minor),
+            "%s:%s:%s" % (family, lhost, lport),
+            "%s:%s:%s" % (family, rhost, rport),
+            "--protocol", protocol,
+            "--after-sb-0pri", "discard-zero-changes",
+            "--after-sb-1pri", "consensus"
+            ]
+    if dual_pri:
+      args.append("--allow-two-primaries")
+    if hmac and secret:
+      args.extend(["--cram-hmac-alg", hmac, "--shared-secret", secret])
+
+    if params[constants.LDP_NET_CUSTOM]:
+      args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
+
+    return args
+
+  def GenSyncParamsCmd(self, minor, params):
+    args = ["drbdsetup", "disk-options", minor]
+    if params[constants.LDP_DYNAMIC_RESYNC]:
+      if params[constants.LDP_PLAN_AHEAD] == 0:
+        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
+               " controller at DRBD level. If you want to disable it, please"
+               " set the dynamic-resync disk parameter to False.")
+        logging.error(msg)
+        return [msg]
+
+      # add the c-* parameters to args
+      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
+                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
+                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
+                   "--c-max-rate", params[constants.LDP_MAX_RATE],
+                   "--c-min-rate", params[constants.LDP_MIN_RATE],
+                   ])
+
+    else:
+      args.extend(["--resync-rate", "%d" % params[constants.LDP_RESYNC_RATE]])
+
+    return args
+
+  def GenPauseSyncCmd(self, minor):
+    return ["drbdsetup", "pause-sync", minor]
+
+  def GenResumeSyncCmd(self, minor):
+    return ["drbdsetup", "resume-sync", minor]
+
+  def GenPrimaryCmd(self, minor, force):
+    cmd = ["drbdsetup", "primary", minor]
+
+    if force:
+      cmd.append("--force")
+
+    return cmd
+
+  def GenSecondaryCmd(self, minor):
+    return ["drbdsetup", "secondary", minor]
+
+  def GenDetachCmd(self, minor):
+    return ["drbdsetup", "detach", minor]
+
+  def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
+    return ["drbdsetup", "disconnect",
+            "%s:%s:%s" % (family, lhost, lport),
+            "%s:%s:%s" % (family, rhost, rport)]
+
+  def GenDownCmd(self, minor):
+    return ["drbdsetup", "down", self._GetResource(minor)]
+
+  def GenResizeCmd(self, minor, size_mb):
+    return ["drbdsetup", "resize", minor, "--size", "%dm" % size_mb]
+
+  @staticmethod
+  def _GetResource(minor):
+    """Return the resource name for a given minor.
+
+    Currently we don't support DRBD volumes which share a resource, so we
+    generate the resource name based on the minor the resulting volumes is
+    assigned to.
+
+    """
+    return "resource%d" % minor
+
+  @classmethod
+  def _ComputeDiskBarrierArgs(cls, disabled_barriers, disable_meta_flush):
+    """Compute the DRBD command line parameters for disk barriers
+
+    """
+    disabled_barriers_set = frozenset(disabled_barriers)
+    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
+      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
+                                    " barriers" % disabled_barriers)
+
+    args = []
+
+    # meta flushes
+    if disable_meta_flush:
+      args.append(cls._DISABLE_META_FLUSH_OPTION)
+
+    # disk flushes
+    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
+      args.append(cls._DISABLE_FLUSH_OPTION)
+
+    # disk drain
+    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
+      args.append(cls._DISABLE_DRAIN_OPTION)
+
+    # disk barriers
+    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
+      args.append(cls._DISABLE_DISK_OPTION)
+
+    return args
diff --git a/lib/storage/drbd_info.py b/lib/storage/drbd_info.py
new file mode 100644 (file)
index 0000000..0cd8944
--- /dev/null
@@ -0,0 +1,454 @@
+#
+#
+
+# 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
+# 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.
+
+
+"""DRBD information parsing utilities"""
+
+import errno
+import pyparsing as pyp
+import re
+
+from ganeti import constants
+from ganeti import utils
+from ganeti import errors
+from ganeti import compat
+from ganeti.storage import base
+
+
+class DRBD8Status(object): # pylint: disable=R0902
+  """A DRBD status representation class.
+
+  Note that this class is meant to be used to parse one of the entries returned
+  from L{DRBD8Info._JoinLinesPerMinor}.
+
+  """
+  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
+  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
+                       "\s+ds:([^/]+)/(\S+)\s+.*$")
+  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
+                       # Due to a bug in drbd in the kernel, introduced in
+                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
+                       "(?:\s|M)"
+                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
+
+  CS_UNCONFIGURED = "Unconfigured"
+  CS_STANDALONE = "StandAlone"
+  CS_WFCONNECTION = "WFConnection"
+  CS_WFREPORTPARAMS = "WFReportParams"
+  CS_CONNECTED = "Connected"
+  CS_STARTINGSYNCS = "StartingSyncS"
+  CS_STARTINGSYNCT = "StartingSyncT"
+  CS_WFBITMAPS = "WFBitMapS"
+  CS_WFBITMAPT = "WFBitMapT"
+  CS_WFSYNCUUID = "WFSyncUUID"
+  CS_SYNCSOURCE = "SyncSource"
+  CS_SYNCTARGET = "SyncTarget"
+  CS_PAUSEDSYNCS = "PausedSyncS"
+  CS_PAUSEDSYNCT = "PausedSyncT"
+  CSET_SYNC = compat.UniqueFrozenset([
+    CS_WFREPORTPARAMS,
+    CS_STARTINGSYNCS,
+    CS_STARTINGSYNCT,
+    CS_WFBITMAPS,
+    CS_WFBITMAPT,
+    CS_WFSYNCUUID,
+    CS_SYNCSOURCE,
+    CS_SYNCTARGET,
+    CS_PAUSEDSYNCS,
+    CS_PAUSEDSYNCT,
+    ])
+
+  DS_DISKLESS = "Diskless"
+  DS_ATTACHING = "Attaching" # transient state
+  DS_FAILED = "Failed" # transient state, next: diskless
+  DS_NEGOTIATING = "Negotiating" # transient state
+  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
+  DS_OUTDATED = "Outdated"
+  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
+  DS_CONSISTENT = "Consistent"
+  DS_UPTODATE = "UpToDate" # normal state
+
+  RO_PRIMARY = "Primary"
+  RO_SECONDARY = "Secondary"
+  RO_UNKNOWN = "Unknown"
+
+  def __init__(self, procline):
+    u = self.UNCONF_RE.match(procline)
+    if u:
+      self.cstatus = self.CS_UNCONFIGURED
+      self.lrole = self.rrole = self.ldisk = self.rdisk = None
+    else:
+      m = self.LINE_RE.match(procline)
+      if not m:
+        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
+      self.cstatus = m.group(1)
+      self.lrole = m.group(2)
+      self.rrole = m.group(3)
+      self.ldisk = m.group(4)
+      self.rdisk = m.group(5)
+
+    # end reading of data from the LINE_RE or UNCONF_RE
+
+    self.is_standalone = self.cstatus == self.CS_STANDALONE
+    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
+    self.is_connected = self.cstatus == self.CS_CONNECTED
+    self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED
+    self.is_primary = self.lrole == self.RO_PRIMARY
+    self.is_secondary = self.lrole == self.RO_SECONDARY
+    self.peer_primary = self.rrole == self.RO_PRIMARY
+    self.peer_secondary = self.rrole == self.RO_SECONDARY
+    self.both_primary = self.is_primary and self.peer_primary
+    self.both_secondary = self.is_secondary and self.peer_secondary
+
+    self.is_diskless = self.ldisk == self.DS_DISKLESS
+    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
+
+    self.is_in_resync = self.cstatus in self.CSET_SYNC
+    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
+
+    m = self.SYNC_RE.match(procline)
+    if m:
+      self.sync_percent = float(m.group(1))
+      hours = int(m.group(2))
+      minutes = int(m.group(3))
+      seconds = int(m.group(4))
+      self.est_time = hours * 3600 + minutes * 60 + seconds
+    else:
+      # we have (in this if branch) no percent information, but if
+      # we're resyncing we need to 'fake' a sync percent information,
+      # as this is how cmdlib determines if it makes sense to wait for
+      # resyncing or not
+      if self.is_in_resync:
+        self.sync_percent = 0
+      else:
+        self.sync_percent = None
+      self.est_time = None
+
+
+class DRBD8Info(object):
+  """Represents information DRBD exports (usually via /proc/drbd).
+
+  An instance of this class is created by one of the CreateFrom... methods.
+
+  """
+
+  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.(\d+))?"
+                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
+  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
+
+  def __init__(self, lines):
+    self._version = self._ParseVersion(lines)
+    self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines)
+
+  def GetVersion(self):
+    """Return the DRBD version.
+
+    This will return a dict with keys:
+      - k_major
+      - k_minor
+      - k_point
+      - k_fix (only on some drbd versions)
+      - api
+      - proto
+      - proto2 (only on drbd > 8.2.X)
+
+    """
+    return self._version
+
+  def GetVersionString(self):
+    """Return the DRBD version as a single string.
+
+    """
+    version = self.GetVersion()
+    retval = "%d.%d.%d" % \
+             (version["k_major"], version["k_minor"], version["k_point"])
+    if "k_fix" in version:
+      retval += ".%s" % version["k_fix"]
+
+    retval += " (api:%d/proto:%d" % (version["api"], version["proto"])
+    if "proto2" in version:
+      retval += "-%s" % version["proto2"]
+    retval += ")"
+    return retval
+
+  def GetMinors(self):
+    """Return a list of minor for which information is available.
+
+    This list is ordered in exactly the order which was found in the underlying
+    data.
+
+    """
+    return self._minors
+
+  def HasMinorStatus(self, minor):
+    return minor in self._line_per_minor
+
+  def GetMinorStatus(self, minor):
+    return DRBD8Status(self._line_per_minor[minor])
+
+  def _ParseVersion(self, lines):
+    first_line = lines[0].strip()
+    version = self._VERSION_RE.match(first_line)
+    if not version:
+      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
+                                    first_line)
+
+    values = version.groups()
+    retval = {
+      "k_major": int(values[0]),
+      "k_minor": int(values[1]),
+      "k_point": int(values[2]),
+      "api": int(values[4]),
+      "proto": int(values[5]),
+      }
+    if values[3] is not None:
+      retval["k_fix"] = values[3]
+    if values[6] is not None:
+      retval["proto2"] = values[6]
+
+    return retval
+
+  def _JoinLinesPerMinor(self, lines):
+    """Transform the raw lines into a dictionary based on the minor.
+
+    @return: a dictionary of minor: joined lines from /proc/drbd
+        for that minor
+
+    """
+    minors = []
+    results = {}
+    old_minor = old_line = None
+    for line in lines:
+      if not line: # completely empty lines, as can be returned by drbd8.0+
+        continue
+      lresult = self._VALID_LINE_RE.match(line)
+      if lresult is not None:
+        if old_minor is not None:
+          minors.append(old_minor)
+          results[old_minor] = old_line
+        old_minor = int(lresult.group(1))
+        old_line = line
+      else:
+        if old_minor is not None:
+          old_line += " " + line.strip()
+    # add last line
+    if old_minor is not None:
+      minors.append(old_minor)
+      results[old_minor] = old_line
+    return minors, results
+
+  @staticmethod
+  def CreateFromLines(lines):
+    return DRBD8Info(lines)
+
+  @staticmethod
+  def CreateFromFile(filename=constants.DRBD_STATUS_FILE):
+    try:
+      lines = utils.ReadFile(filename).splitlines()
+    except EnvironmentError, err:
+      if err.errno == errno.ENOENT:
+        base.ThrowError("The file %s cannot be opened, check if the module"
+                        " is loaded (%s)", filename, str(err))
+      else:
+        base.ThrowError("Can't read the DRBD proc file %s: %s",
+                        filename, str(err))
+    if not lines:
+      base.ThrowError("Can't read any data from %s", filename)
+    return DRBD8Info.CreateFromLines(lines)
+
+
+class BaseShowInfo(object):
+  """Base class for parsing the `drbdsetup show` output.
+
+  Holds various common pyparsing expressions which are used by subclasses. Also
+  provides caching of the constructed parser.
+
+  """
+  _PARSE_SHOW = None
+
+  # pyparsing setup
+  _lbrace = pyp.Literal("{").suppress()
+  _rbrace = pyp.Literal("}").suppress()
+  _lbracket = pyp.Literal("[").suppress()
+  _rbracket = pyp.Literal("]").suppress()
+  _semi = pyp.Literal(";").suppress()
+  _colon = pyp.Literal(":").suppress()
+  # this also converts the value to an int
+  _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
+
+  _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
+  _defa = pyp.Literal("_is_default").suppress()
+  _dbl_quote = pyp.Literal('"').suppress()
+
+  _keyword = pyp.Word(pyp.alphanums + "-")
+
+  # value types
+  _value = pyp.Word(pyp.alphanums + "_-/.:")
+  _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote
+  _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
+                pyp.Word(pyp.nums + ".") + _colon + _number)
+  _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
+                pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") +
+                pyp.Optional(_rbracket) + _colon + _number)
+  # meta device, extended syntax
+  _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket)
+  # device name, extended syntax
+  _device_value = pyp.Literal("minor").suppress() + _number
+
+  # a statement
+  _stmt = (~_rbrace + _keyword + ~_lbrace +
+           pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^
+                        _meta_value ^ _device_value) +
+           pyp.Optional(_defa) + _semi +
+           pyp.Optional(pyp.restOfLine).suppress())
+
+  @classmethod
+  def GetDevInfo(cls, show_data):
+    """Parse details about a given DRBD minor.
+
+    This returns, if available, the local backing device (as a path)
+    and the local and remote (ip, port) information from a string
+    containing the output of the `drbdsetup show` command as returned
+    by DRBD8Dev._GetShowData.
+
+    This will return a dict with keys:
+      - local_dev
+      - meta_dev
+      - meta_index
+      - local_addr
+      - remote_addr
+
+    """
+    if not show_data:
+      return {}
+
+    try:
+      # run pyparse
+      results = (cls._GetShowParser()).parseString(show_data)
+    except pyp.ParseException, err:
+      base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
+
+    return cls._TransformParseResult(results)
+
+  @classmethod
+  def _TransformParseResult(cls, parse_result):
+    raise NotImplementedError
+
+  @classmethod
+  def _GetShowParser(cls):
+    """Return a parser for `drbd show` output.
+
+    This will either create or return an already-created parser for the
+    output of the command `drbd show`.
+
+    """
+    if cls._PARSE_SHOW is None:
+      cls._PARSE_SHOW = cls._ConstructShowParser()
+
+    return cls._PARSE_SHOW
+
+  @classmethod
+  def _ConstructShowParser(cls):
+    raise NotImplementedError
+
+
+class DRBD83ShowInfo(BaseShowInfo):
+  @classmethod
+  def _ConstructShowParser(cls):
+    # an entire section
+    section_name = pyp.Word(pyp.alphas + "_")
+    section = section_name + \
+              cls._lbrace + \
+              pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \
+              cls._rbrace
+
+    bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt))
+    bnf.ignore(cls._comment)
+
+    return bnf
+
+  @classmethod
+  def _TransformParseResult(cls, parse_result):
+    retval = {}
+    for section in parse_result:
+      sname = section[0]
+      if sname == "_this_host":
+        for lst in section[1:]:
+          if lst[0] == "disk":
+            retval["local_dev"] = lst[1]
+          elif lst[0] == "meta-disk":
+            retval["meta_dev"] = lst[1]
+            retval["meta_index"] = lst[2]
+          elif lst[0] == "address":
+            retval["local_addr"] = tuple(lst[1:])
+      elif sname == "_remote_host":
+        for lst in section[1:]:
+          if lst[0] == "address":
+            retval["remote_addr"] = tuple(lst[1:])
+    return retval
+
+
+class DRBD84ShowInfo(BaseShowInfo):
+  @classmethod
+  def _ConstructShowParser(cls):
+    # an entire section (sections can be nested in DRBD 8.4, and there exist
+    # sections like "volume 0")
+    section_name = pyp.Word(pyp.alphas + "_") + \
+                   pyp.Optional(pyp.Word(pyp.nums)).suppress() # skip volume idx
+    section = pyp.Forward()
+    # pylint: disable=W0106
+    section << (section_name +
+                cls._lbrace +
+                pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) +
+                cls._rbrace)
+
+    resource_name = pyp.Word(pyp.alphanums + "_-.")
+    resource = (pyp.Literal("resource") + resource_name).suppress() + \
+               cls._lbrace + \
+               pyp.ZeroOrMore(pyp.Group(section)) + \
+               cls._rbrace
+
+    resource.ignore(cls._comment)
+
+    return resource
+
+  @classmethod
+  def _TransformParseResult(cls, parse_result):
+    retval = {}
+    for section in parse_result:
+      sname = section[0]
+      if sname == "_this_host":
+        for lst in section[1:]:
+          if lst[0] == "address":
+            retval["local_addr"] = tuple(lst[1:])
+          elif lst[0] == "volume":
+            for inner in lst[1:]:
+              if inner[0] == "disk" and len(inner) == 2:
+                retval["local_dev"] = inner[1]
+              elif inner[0] == "meta-disk":
+                if len(inner) > 1:
+                  retval["meta_dev"] = inner[1]
+                if len(inner) > 2:
+                  retval["meta_index"] = inner[2]
+      elif sname == "_remote_host":
+        for lst in section[1:]:
+          if lst[0] == "address":
+            retval["remote_addr"] = tuple(lst[1:])
+    return retval
diff --git a/lib/storage/filestorage.py b/lib/storage/filestorage.py
new file mode 100644 (file)
index 0000000..c20a0b8
--- /dev/null
@@ -0,0 +1,52 @@
+#
+#
+
+# 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.
+
+
+"""File storage functions.
+
+"""
+
+import os
+
+from ganeti import constants
+from ganeti import errors
+
+
+def GetFileStorageSpaceInfo(path):
+  """Retrieves the free and total space of the device where the file is
+     located.
+
+     @type path: string
+     @param path: Path of the file whose embracing device's capacity is
+       reported.
+     @return: a dictionary containing 'vg_size' and 'vg_free' given in MebiBytes
+
+  """
+  try:
+    result = os.statvfs(path)
+    free = (result.f_frsize * result.f_bavail) / (1024 * 1024)
+    size = (result.f_frsize * result.f_blocks) / (1024 * 1024)
+    return {"type": constants.ST_FILE,
+            "name": path,
+            "storage_size": size,
+            "storage_free": free}
+  except OSError, e:
+    raise errors.CommandError("Failed to retrieve file system information about"
+                              " path: %s - %s" % (path, e.strerror))
index a2e8863..766d93a 100755 (executable)
@@ -1053,16 +1053,14 @@ class Burner(object):
 
     """
 
-    opts = self.opts
-
     Log("Testing global parameters")
 
     if (len(self.nodes) == 1 and
-        opts.disk_template not in _SINGLE_NODE_DISK_TEMPLATES):
+        self.opts.disk_template not in _SINGLE_NODE_DISK_TEMPLATES):
       Err("When one node is available/selected the disk template must"
           " be one of %s" % utils.CommaJoin(_SINGLE_NODE_DISK_TEMPLATES))
 
-    if opts.do_confd_tests and not constants.ENABLE_CONFD:
+    if self.opts.do_confd_tests and not constants.ENABLE_CONFD:
       Err("You selected confd tests but confd was disabled at configure time")
 
     has_err = True
@@ -1072,68 +1070,70 @@ class Burner(object):
       if self.bep[constants.BE_MINMEM] < self.bep[constants.BE_MAXMEM]:
         self.BurnModifyRuntimeMemory()
 
-      if opts.do_replace1 and opts.disk_template in constants.DTS_INT_MIRROR:
+      if self.opts.do_replace1 and \
+           self.opts.disk_template in constants.DTS_INT_MIRROR:
         self.BurnReplaceDisks1D8()
-      if (opts.do_replace2 and len(self.nodes) > 2 and
-          opts.disk_template in constants.DTS_INT_MIRROR):
+      if (self.opts.do_replace2 and len(self.nodes) > 2 and
+          self.opts.disk_template in constants.DTS_INT_MIRROR):
         self.BurnReplaceDisks2()
 
-      if (opts.disk_template in constants.DTS_GROWABLE and
+      if (self.opts.disk_template in constants.DTS_GROWABLE and
           compat.any(n > 0 for n in self.disk_growth)):
         self.BurnGrowDisks()
 
-      if opts.do_failover and opts.disk_template in constants.DTS_MIRRORED:
+      if self.opts.do_failover and \
+           self.opts.disk_template in constants.DTS_MIRRORED:
         self.BurnFailover()
 
-      if opts.do_migrate:
-        if opts.disk_template not in constants.DTS_MIRRORED:
+      if self.opts.do_migrate:
+        if self.opts.disk_template not in constants.DTS_MIRRORED:
           Log("Skipping migration (disk template %s does not support it)",
-              opts.disk_template)
+              self.opts.disk_template)
         elif not self.hv_can_migrate:
           Log("Skipping migration (hypervisor %s does not support it)",
               self.hypervisor)
         else:
           self.BurnMigrate()
 
-      if (opts.do_move and len(self.nodes) > 1 and
-          opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):
+      if (self.opts.do_move and len(self.nodes) > 1 and
+          self.opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):
         self.BurnMove()
 
-      if (opts.do_importexport and
-          opts.disk_template in _IMPEXP_DISK_TEMPLATES):
+      if (self.opts.do_importexport and
+          self.opts.disk_template in _IMPEXP_DISK_TEMPLATES):
         self.BurnImportExport()
 
-      if opts.do_reinstall:
+      if self.opts.do_reinstall:
         self.BurnReinstall()
 
-      if opts.do_reboot:
+      if self.opts.do_reboot:
         self.BurnReboot()
 
-      if opts.do_renamesame:
+      if self.opts.do_renamesame:
         self.BurnRenameSame()
 
-      if opts.do_addremove_disks:
+      if self.opts.do_addremove_disks:
         self.BurnAddRemoveDisks()
 
       default_nic_mode = self.cluster_default_nicparams[constants.NIC_MODE]
       # Don't add/remove nics in routed mode, as we would need an ip to add
       # them with
-      if opts.do_addremove_nics:
+      if self.opts.do_addremove_nics:
         if default_nic_mode == constants.NIC_MODE_BRIDGED:
           self.BurnAddRemoveNICs()
         else:
           Log("Skipping nic add/remove as the cluster is not in bridged mode")
 
-      if opts.do_activate_disks:
+      if self.opts.do_activate_disks:
         self.BurnActivateDisks()
 
-      if opts.rename:
+      if self.opts.rename:
         self.BurnRename()
 
-      if opts.do_confd_tests:
+      if self.opts.do_confd_tests:
         self.BurnConfd()
 
-      if opts.do_startstop:
+      if self.opts.do_startstop:
         self.BurnStopStart()
 
       has_err = False
index f9468fc..48f1b15 100644 (file)
@@ -53,6 +53,7 @@ from ganeti.utils.mlock import *
 from ganeti.utils.nodesetup import *
 from ganeti.utils.process import *
 from ganeti.utils.retry import *
+from ganeti.utils.storage import *
 from ganeti.utils.text import *
 from ganeti.utils.wrapper import *
 from ganeti.utils.x509 import *
diff --git a/lib/utils/storage.py b/lib/utils/storage.py
new file mode 100644 (file)
index 0000000..02fec19
--- /dev/null
@@ -0,0 +1,162 @@
+#
+#
+
+# 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.
+
+"""Utility functions for storage.
+
+"""
+
+import logging
+
+from ganeti import constants
+from ganeti import pathutils
+
+
+def GetDiskTemplatesOfStorageType(storage_type):
+  """Given the storage type, returns a list of disk templates based on that
+     storage type."""
+  return [dt for dt in constants.DISK_TEMPLATES
+          if constants.DISK_TEMPLATES_STORAGE_TYPE[dt] == storage_type]
+
+
+def GetLvmDiskTemplates():
+  """Returns all disk templates that use LVM."""
+  return GetDiskTemplatesOfStorageType(constants.ST_LVM_VG)
+
+
+def IsLvmEnabled(enabled_disk_templates):
+  """Check whether or not any lvm-based disk templates are enabled."""
+  return len(set(GetLvmDiskTemplates())
+             .intersection(set(enabled_disk_templates))) != 0
+
+
+def LvmGetsEnabled(enabled_disk_templates, new_enabled_disk_templates):
+  """Checks whether lvm was not enabled before, but will be enabled after
+     the operation.
+
+  """
+  if IsLvmEnabled(enabled_disk_templates):
+    return False
+  return set(GetLvmDiskTemplates()).intersection(
+      set(new_enabled_disk_templates))
+
+
+def _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template):
+  """Retrieves the identifier of the default storage entity for the given
+  storage type.
+
+  @type disk_template: string
+  @param disk_template: a disk template, for example 'drbd'
+  @rtype: string
+  @return: identifier for a storage unit, for example the vg_name for lvm
+     storage
+
+  """
+  storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
+  if disk_template in GetLvmDiskTemplates():
+    return (storage_type, cfg.GetVGName())
+  # FIXME: Adjust this, once FILE_STORAGE_DIR and SHARED_FILE_STORAGE_DIR
+  # are not in autoconf anymore.
+  elif disk_template == constants.DT_FILE:
+    return (storage_type, pathutils.DEFAULT_FILE_STORAGE_DIR)
+  elif disk_template == constants.DT_SHARED_FILE:
+    return (storage_type, pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
+  else:
+    return (storage_type, None)
+
+
+def _GetDefaultStorageUnitForSpindles(cfg):
+  """Creates a 'spindle' storage unit, by retrieving the volume group
+  name and associating it to the lvm-pv storage type.
+
+  @rtype: (string, string)
+  @return: tuple (storage_type, storage_key), where storage type is
+    'lvm-pv' and storage_key the name of the default volume group
+
+  """
+  return (constants.ST_LVM_PV, cfg.GetVGName())
+
+
+# List of storage type for which space reporting is implemented.
+# FIXME: Remove this, once the backend is capable to do this for all
+# storage types.
+_DISK_TEMPLATES_SPACE_QUERYABLE = GetLvmDiskTemplates() \
+    + GetDiskTemplatesOfStorageType(constants.ST_FILE)
+
+
+def GetStorageUnitsOfCluster(cfg, include_spindles=False):
+  """Examines the cluster's configuration and returns a list of storage
+  units and their storage keys, ordered by the order in which they
+  are enabled.
+
+  @type cfg: L{config.ConfigWriter}
+  @param cfg: Cluster configuration
+  @type include_spindles: boolean
+  @param include_spindles: flag to include an extra storage unit for physical
+    volumes
+  @rtype: list of tuples (string, string)
+  @return: list of storage units, each storage unit being a tuple of
+    (storage_type, storage_key); storage_type is in
+    C{constants.VALID_STORAGE_TYPES} and the storage_key a string to
+    identify an entity of that storage type, for example a volume group
+    name for LVM storage or a file for file storage.
+
+  """
+  cluster_config = cfg.GetClusterInfo()
+  storage_units = []
+  for disk_template in cluster_config.enabled_disk_templates:
+    if disk_template in _DISK_TEMPLATES_SPACE_QUERYABLE:
+      storage_units.append(
+          _GetDefaultStorageUnitForDiskTemplate(cfg, disk_template))
+  if include_spindles:
+    included_storage_types = set([st for (st, _) in storage_units])
+    if not constants.ST_LVM_PV in included_storage_types:
+      storage_units.append(
+          _GetDefaultStorageUnitForSpindles(cfg))
+
+  return storage_units
+
+
+def LookupSpaceInfoByStorageType(storage_space_info, storage_type):
+  """Looks up the storage space info for a given storage type.
+
+  Note that this lookup can be ambiguous if storage space reporting for several
+  units of the same storage type was requested. This function is only supposed
+  to be used for legacy code in situations where it actually is unambiguous.
+
+  @type storage_space_info: list of dicts
+  @param storage_space_info: result of C{GetNodeInfo}
+  @type storage_type: string
+  @param storage_type: a storage type, which is included in the storage_units
+    list
+  @rtype: tuple
+  @return: returns the element of storage_space_info that matches the given
+    storage type
+
+  """
+  result = None
+  for unit_info in storage_space_info:
+    if unit_info["type"] == storage_type:
+      if result is None:
+        result = unit_info
+      else:
+        # There is more than one storage type in the query, log a warning
+        logging.warning("Storage space information requested for"
+                        " ambiguous storage type '%s'.", storage_type)
+  return result
index 6cb2a48..8228a7c 100644 (file)
@@ -25,7 +25,6 @@
 
 import logging
 
-from ganeti import bdev
 from ganeti import constants
 from ganeti import errors
 from ganeti import hypervisor
@@ -33,6 +32,7 @@ from ganeti import netutils
 from ganeti import ssconf
 from ganeti import utils
 from ganeti import confd
+from ganeti.storage import drbd
 
 import ganeti.confd.client # pylint: disable=W0611
 
@@ -64,11 +64,12 @@ class NodeMaintenance(object):
 
     """
     hyp_list = ssconf.SimpleStore().GetHypervisorList()
+    hvparams = ssconf.SimpleStore().GetHvparams()
     results = []
     for hv_name in hyp_list:
       try:
         hv = hypervisor.GetHypervisor(hv_name)
-        ilist = hv.ListInstances()
+        ilist = hv.ListInstances(hvparams=hvparams)
         results.extend([(iname, hv_name) for iname in ilist])
       except: # pylint: disable=W0702
         logging.error("Error while listing instances for hypervisor %s",
@@ -80,7 +81,7 @@ class NodeMaintenance(object):
     """Get list of used DRBD minors.
 
     """
-    return bdev.DRBD8.GetUsedDevs().keys()
+    return drbd.DRBD8.GetUsedDevs()
 
   @classmethod
   def DoMaintenance(cls, role):
@@ -121,10 +122,7 @@ class NodeMaintenance(object):
       logging.info("Following DRBD minors should not be active,"
                    " shutting them down: %s", utils.CommaJoin(drbd_running))
       for minor in drbd_running:
-        # pylint: disable=W0212
-        # using the private method as is, pending enhancements to the DRBD
-        # interface
-        bdev.DRBD8._ShutdownAll(minor)
+        drbd.DRBD8.ShutdownAll(minor)
 
   def Exec(self):
     """Check node status versus cluster desired state.
index b19271b..3321cb8 100644 (file)
@@ -9,7 +9,8 @@ ganeti-noded - Ganeti node daemon
 Synopsis
 --------
 
-**ganeti-noded** [-f] [-d]
+**ganeti-noded** [-f] [-d] [-p *PORT*] [-b *ADDRESS*] [-i *INTERFACE*]
+[--no-mlock] [--syslog] [--no-ssl] [-K *SSL_KEY_FILE*] [-C *SSL_CERT_FILE*]
 
 DESCRIPTION
 -----------
@@ -34,7 +35,8 @@ The **ganeti-noded** daemon listens to port 1811 TCP, on all
 interfaces, by default. The port can be overridden by an entry in the
 services database (usually ``/etc/services``) or by passing the ``-p``
 option.  The ``-b`` option can be used to specify the address to bind
-to (defaults to ``0.0.0.0``).
+to (defaults to ``0.0.0.0``); alternatively, the ``-i`` option can be
+used to specify the interface to bind do.
 
 Ganeti noded communication is protected via SSL, with a key
 generated at cluster init time. This can be disabled with the
index 7b5ce1f..da515cf 100644 (file)
@@ -9,8 +9,9 @@ ganeti-rapi - Ganeti remote API daemon
 Synopsis
 --------
 
-| **ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*]
-| [-C *SSL_CERT_FILE*] [\--require-authentication]
+| **ganeti-rapi** [-d] [-f] [-p *PORT] [-b *ADDRESS*] [-i *INTERFACE*]
+| [\--no-ssl] [-K *SSL_KEY_FILE*] [-C *SSL_CERT_FILE*]
+| [\--require-authentication]
 
 DESCRIPTION
 -----------
@@ -25,6 +26,10 @@ changed via the ``-C`` option and the key via the ``-K`` option.
 
 The daemon will listen to the "ganeti-rapi" TCP port, as listed in the
 system services database, or if not defined, to port 5080 by default.
+The port can be overridded by passing the ``-p`` option. The ``-b``
+option can be used to specify the address to bind to (defaults to
+``0.0.0.0``); alternatively, the ``-i`` option can be used to specify
+the interface to bind do.
 
 See the *Ganeti remote API* documentation for further information.
 
index e3efff9..153cd23 100644 (file)
@@ -240,7 +240,7 @@ availability for a certain command can be checked by calling the
 command using the ``--help`` option.
 
 | **gnt-...** *command* [\--dry-run] [\--priority {low | normal | high}]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 
 The ``--dry-run`` option can be used to check whether an operation
 would succeed.
@@ -252,6 +252,9 @@ The ``--submit`` option is used to send the job to the master daemon but
 not wait for its completion. The job ID will be shown so that it can be
 examined using **gnt-job info**.
 
+The ``--print-job-id`` option makes the command print the job id as first
+line on stdout, so that it is easy to parse by other programs.
+
 Defaults
 ~~~~~~~~
 
index bb9851f..a17dc51 100644 (file)
@@ -26,6 +26,7 @@ EXPORT
 
 | **export** {-n *node*} [\--shutdown-timeout=*N*] [\--noshutdown]
 | [\--remove-instance] [\--ignore-remove-failures] [\--submit]
+| [\--print-job-id]
 | {*instance*}
 
 Exports an instance to the target node. All the instance data and
@@ -75,7 +76,7 @@ IMPORT
 | [-t [diskless | plain | drbd | file]]
 | [\--identify-defaults]
 | [\--ignore-ipolicy]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | {*instance*}
 
 Imports a new instance from an export residing on *source-node* in
index 9103f60..2f9ac38 100644 (file)
@@ -170,7 +170,6 @@ INIT
 | [\--master-netmask *netmask*]
 | [\--use-external-mip-script {yes \| no}]
 | [{-m|\--mac-prefix} *mac-prefix*]
-| [\--no-lvm-storage]
 | [\--no-etc-hosts]
 | [\--no-ssh-init]
 | [\--file-storage-dir *dir*]
@@ -198,6 +197,7 @@ INIT
 | [\--ipolicy-vcpu-ratio *ratio*]
 | [\--disk-state *diskstate*]
 | [\--hypervisor-state *hvstate*]
+| [\--drbd-usermode-helper *helper*]
 | [\--enabled-disk-templates *template* [,*template*...]]
 | {*clustername*}
 
@@ -235,7 +235,8 @@ the cluster creation and DRBD support is enabled you might have to
 manually modify the metavg as well.
 
 If you don't want to use lvm storage at all use
-the ``--no-lvm-storage`` option. Once the cluster is initialized
+the ``--enabled-disk-template`` option to restrict the set of enabled
+disk templates. Once the cluster is initialized
 you can change this setup with the **modify** command.
 
 The ``--master-netdev`` option is useful for specifying a different
@@ -259,12 +260,6 @@ prefix under which the virtual MAC addresses of your instances will be
 generated. The prefix must be specified in the format ``XX:XX:XX`` and
 the default is ``aa:00:00``.
 
-The ``--no-lvm-storage`` option allows you to initialize the
-cluster without lvm support. This means that only instances using
-files as storage backend will be possible to create. Once the
-cluster is initialized you can change this setup with the
-**modify** command.
-
 The ``--no-etc-hosts`` option allows you to initialize the cluster
 without modifying the /etc/hosts file.
 
@@ -545,6 +540,9 @@ All the instance policy elements can be overridden at group level. Group
 level overrides can be removed by specifying ``default`` as the value of
 an item.
 
+The ``--drbd-usermode-helper`` option can be used to specify a usermode
+helper. Check that this string is the one used by the DRBD kernel.
+
 For details about how to use ``--hypervisor-state`` and ``--disk-state``
 have a look at **ganeti**\(7).
 
@@ -594,10 +592,9 @@ be 1.
 MODIFY
 ~~~~~~
 
-| **modify** [\--submit]
+| **modify** [\--submit] [\--print-job-id]
 | [\--force]
 | [\--vg-name *vg-name*]
-| [\--no-lvm-storage]
 | [\--enabled-hypervisors *hypervisors*]
 | [{-H|\--hypervisor-parameters} *hypervisor*:*hv-param*=*value*[,*hv-param*=*value*...]]
 | [{-B|\--backend-parameters} *be-param*=*value*[,*be-param*=*value*...]]
@@ -623,18 +620,18 @@ MODIFY
 | [\--ipolicy-spindle-ratio *ratio*]
 | [\--ipolicy-vcpu-ratio *ratio*]
 | [\--enabled-disk-templates *template* [,*template*...]]
+| [\--drbd-usermode-helper *helper*]
 
 
 Modify the options for the cluster.
 
-The ``--vg-name``, ``--no-lvm-storage``, ``--enabled-hypervisors``,
-``-H (--hypervisor-parameters)``, ``-B (--backend-parameters)``,
-``-D (--disk-parameters)``, ``--nic-parameters``, ``-C
-(--candidate-pool-size)``, ``--maintain-node-health``,
+The ``--vg-name``, ``--enabled-hypervisors``, ``-H (--hypervisor-parameters)``,
+``-B (--backend-parameters)``, ``-D (--disk-parameters)``, ``--nic-parameters``,
+``-C (--candidate-pool-size)``, ``--maintain-node-health``,
 ``--prealloc-wipe-disks``, ``--uid-pool``, ``--node-parameters``,
 ``--master-netdev``, ``--master-netmask``, ``--use-external-mip-script``,
-and ``--enabled-disk-templates`` options are described in the **init**
-command.
+``--drbd-usermode-helper``, and ``--enabled-disk-templates`` options are
+described in the **init** command.
 
 The ``--hypervisor-state`` and ``--disk-state`` options are described in
 detail in **ganeti**\(7).
@@ -697,7 +694,7 @@ The ``info`` option shows whether the watcher is currently paused.
 REDIST-CONF
 ~~~~~~~~~~~
 
-**redist-conf** [\--submit]
+**redist-conf** [\--submit] [\--print-job-id]
 
 This command forces a full push of configuration files from the
 master node to the other nodes in the cluster. This is normally not
@@ -762,14 +759,15 @@ This command checks that the recorded size of the given instance's
 disks matches the actual size and updates any mismatches found.
 This is needed if the Ganeti configuration is no longer consistent
 with reality, as it will impact some disk operations. If no
-arguments are given, all instances will be checked.
+arguments are given, all instances will be checked. When exclusive
+storage is active, also spindles are updated.
 
 Note that only active disks can be checked by this command; in case
 a disk cannot be activated it's advised to use
 **gnt-instance activate-disks \--ignore-size ...** to force
 activation without regard to the current size.
 
-When the all disk sizes are consistent, the command will return no
+When all the disk sizes are consistent, the command will return no
 output. Otherwise it will log details about the inconsistencies in
 the configuration.
 
index 12b4c84..e100715 100644 (file)
@@ -23,7 +23,7 @@ COMMANDS
 ADD
 ~~~
 
-| **add** [\--submit]
+| **add** [\--submit] [\--print-job-id]
 | [\--node-parameters=*NDPARAMS*]
 | [\--alloc-policy=*POLICY*]
 | [{-D|\--disk-parameters} *disk-template*:*disk-param*=*value*[,*disk-param*=*value*...]]
@@ -75,7 +75,7 @@ ASSIGN-NODES
 ~~~~~~~~~~~~
 
 | **assign-nodes**
-| [\--force] [\--submit]
+| [\--force] [\--submit] [\--print-job-id]
 | {*group*} {*node*...}
 
 Assigns one or more nodes to the specified group, moving them from their
@@ -93,7 +93,7 @@ options.
 MODIFY
 ~~~~~~
 
-| **modify** [\--submit]
+| **modify** [\--submit] [\--print-job-id]
 | [\--node-parameters=*NDPARAMS*]
 | [\--alloc-policy=*POLICY*]
 | [\--hypervisor-state *hvstate*]
@@ -124,7 +124,7 @@ options.
 REMOVE
 ~~~~~~
 
-| **remove** [\--submit] {*group*}
+| **remove** [\--submit] [\--print-job-id] {*group*}
 
 Deletes the indicated node group, which must be empty. There must always be at
 least one group, so the last group cannot be removed.
@@ -176,7 +176,7 @@ List available fields for node groups.
 RENAME
 ~~~~~~
 
-| **rename** [\--submit] {*oldname*} {*newname*}
+| **rename** [\--submit] [\--print-job-id] {*oldname*} {*newname*}
 
 Renames a given group from *oldname* to *newname*.
 
@@ -187,7 +187,8 @@ options.
 EVACUATE
 ~~~~~~~~
 
-**evacuate** [\--submit] [\--iallocator *NAME*] [\--to *GROUP*...] {*group*}
+| **evacuate** [\--submit] [\--print-job-id]
+| [\--iallocator *NAME*] [\--to *GROUP*...] {*group*}
 
 This command will move all instances out of the given node group.
 Instances are placed in a new group by an iallocator, either given on
index 8ce924f..71ebe3d 100644 (file)
@@ -27,8 +27,8 @@ ADD
 ^^^
 
 | **add**
-| {-t|\--disk-template {diskless | file \| plain \| drbd \| rbd}}
-| {\--disk=*N*: {size=*VAL* \| adopt=*LV*}[,options...]
+| {-t|\--disk-template {diskless \| file \| plain \| drbd \| rbd}}
+| {\--disk=*N*: {size=*VAL*[,spindles=*VAL*] \| adopt=*LV*}[,options...]
 |  \| {size=*VAL*,provider=*PROVIDER*}[,param=*value*... ][,options...]
 |  \| {-s|\--os-size} *SIZE*}
 | [\--no-ip-check] [\--no-name-check] [\--no-conflicts-check]
@@ -40,7 +40,7 @@ ADD
 | [\--file-storage-dir *dir\_path*] [\--file-driver {loop \| blktap}]
 | {{-n|\--node} *node[:secondary-node]* \| {-I|\--iallocator} *name*}
 | {{-o|\--os-type} *os-type*}
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | [\--ignore-ipolicy]
 | {*instance*}
 
@@ -57,12 +57,15 @@ given) in mebibytes. You can also use one of the suffixes *m*, *g* or
 mebibytes, gibibytes and tebibytes. Each disk can also take these
 parameters (all optional):
 
+spindles
+  How many spindles (physical disks on the node) the disk should span.
+
 mode
   The access mode. Either ``ro`` (read-only) or the default ``rw``
   (read-write).
 
 name
-   this option specifies a name for the disk, which can be used as a disk
+   This option specifies a name for the disk, which can be used as a disk
    identifier. An instance can not have two disks with the same name.
 
 vg
@@ -944,8 +947,8 @@ follows::
 REMOVE
 ^^^^^^
 
-**remove** [\--ignore-failures] [\--shutdown-timeout=*N*] [\--submit]
-[\--force] {*instance*}
+| **remove** [\--ignore-failures] [\--shutdown-timeout=*N*] [\--submit]
+| [\--print-job-id] [\--force] {*instance*}
 
 Remove an instance. This will remove all data from the instance and
 there is *no way back*. If you are not sure if you use an instance
@@ -1070,12 +1073,12 @@ MODIFY
 |  \--disk *N*:add,size=*SIZE*,provider=*PROVIDER*[,options...][,param=*value*... ] \|
 |  \--disk *ID*:modify[,options...]
 |  \--disk [*ID*:]remove]
-| [{-t|\--disk-template} plain | {-t|\--disk-template} drbd -n *new_secondary*] [\--no-wait-for-sync]
+| [{-t|\--disk-template} plain \| {-t|\--disk-template} drbd -n *new_secondary*] [\--no-wait-for-sync]
 | [\--new-primary=*node*]
 | [\--os-type=*OS* [\--force-variant]]
 | [{-O|\--os-parameters} *param*=*value*... ]
 | [\--offline \| \--online]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | [\--ignore-ipolicy]
 | {*instance*}
 
@@ -1105,22 +1108,22 @@ by ballooning it up or down to the new value.
 The ``--disk add:size=*SIZE*,[options..]`` option adds a disk to the
 instance, and ``--disk *N*:add:size=*SIZE*,[options..]`` will add a disk
 to the the instance at a specific index. The available options are the
-same as in the **add** command(``mode``, ``name``, ``vg``, ``metavg``).
-When adding an ExtStorage disk the ``provider=*PROVIDER*`` option is
-also mandatory and specifies the ExtStorage provider. Also, for
-ExtStorage disks arbitrary parameters can be passed as additional comma
-separated options, same as in the **add** command. -The ``--disk remove``
-option will remove the last disk of the instance. Use
-``--disk `` *ID*``:remove`` to remove a disk by its identifier.  *ID*
-can be the index of the disk, the disks's name or the disks's UUID.  The
-``--disk *ID*:modify[,options...]`` wil change the options of the disk.
+same as in the **add** command(``spindles``, ``mode``, ``name``, ``vg``,
+``metavg``). When adding an ExtStorage disk the ``provider=*PROVIDER*``
+option is also mandatory and specifies the ExtStorage provider. Also,
+for ExtStorage disks arbitrary parameters can be passed as additional
+comma separated options, same as in the **add** command. -The ``--disk
+remove`` option will remove the last disk of the instance. Use ``--disk
+`` *ID*``:remove`` to remove a disk by its identifier. *ID* can be the
+index of the disk, the disks's name or the disks's UUID. The ``--disk
+*ID*:modify[,options...]`` will change the options of the disk.
 Available options are:
 
 mode
   The access mode. Either ``ro`` (read-only) or the default ``rw`` (read-write).
 
 name
-   this option specifies a name for the disk, which can be used as a disk
+   This option specifies a name for the disk, which can be used as a disk
    identifier. An instance can not have two disks with the same name.
 
 The ``--net *N*:add[,options..]`` will add a new network interface to
@@ -1164,7 +1167,8 @@ REINSTALL
 | **reinstall** [{-o|\--os-type} *os-type*] [\--select-os] [-f *force*]
 | [\--force-multiple]
 | [\--instance \| \--node \| \--primary \| \--secondary \| \--all]
-| [{-O|\--os-parameters} *OS\_PARAMETERS*] [\--submit] {*instance*...}
+| [{-O|\--os-parameters} *OS\_PARAMETERS*] [\--submit] [\--print-job-id]
+| {*instance*...}
 
 Reinstalls the operating system on the given instance(s). The
 instance(s) must be stopped when running this command. If the ``-o
@@ -1189,7 +1193,7 @@ options.
 RENAME
 ^^^^^^
 
-| **rename** [\--no-ip-check] [\--no-name-check] [\--submit]
+| **rename** [\--no-ip-check] [\--no-name-check] [\--submit] [\--print-job-id]
 | {*instance*} {*new\_name*}
 
 Renames the given instance. The instance must be stopped when running
@@ -1225,7 +1229,7 @@ STARTUP
 | \--tags \| \--node-tags \| \--pri-node-tags \| \--sec-node-tags]
 | [{-H|\--hypervisor-parameters} ``key=value...``]
 | [{-B|\--backend-parameters} ``key=value...``]
-| [\--submit] [\--paused]
+| [\--submit] [\--print-job-id] [\--paused]
 | {*name*...}
 
 Starts one or more instances, depending on the following options.  The
@@ -1323,7 +1327,7 @@ SHUTDOWN
 | [\--force] [\--force-multiple] [\--ignore-offline] [\--no-remember]
 | [\--instance \| \--node \| \--primary \| \--secondary \| \--all \|
 | \--tags \| \--node-tags \| \--pri-node-tags \| \--sec-node-tags]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | {*name*...}
 
 Stops one or more instances. If the instance cannot be cleanly stopped
@@ -1377,7 +1381,7 @@ REBOOT
 | [\--force-multiple]
 | [\--instance \| \--node \| \--primary \| \--secondary \| \--all \|
 | \--tags \| \--node-tags \| \--pri-node-tags \| \--sec-node-tags]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | [*name*...]
 
 Reboots one or more instances. The type of reboot depends on the value
@@ -1441,17 +1445,18 @@ Disk management
 REPLACE-DISKS
 ^^^^^^^^^^^^^
 
-**replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy] {-p}
-[\--disks *idx*] {*instance*}
+| **replace-disks** [\--submit] [\--print-job-id] [\--early-release]
+| [\--ignore-ipolicy] {-p} [\--disks *idx*] {*instance*}
 
-**replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy] {-s}
-[\--disks *idx*] {*instance*}
+| **replace-disks** [\--submit] [\--print-job-id] [\--early-release]
+| [\--ignore-ipolicy] {-s} [\--disks *idx*] {*instance*}
 
-**replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy]
-{{-I\|\--iallocator} *name* \| {{-n|\--new-secondary} *node* } {*instance*}
+| **replace-disks** [\--submit] [\--print-job-id] [\--early-release]
+| [\--ignore-ipolicy]
+| {{-I\|\--iallocator} *name* \| {{-n|\--new-secondary} *node* } {*instance*}
 
-**replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy]
-{-a\|\--auto} {*instance*}
+| **replace-disks** [\--submit] [\--print-job-id] [\--early-release]
+| [\--ignore-ipolicy] {-a\|\--auto} {*instance*}
 
 This command is a generalized form for replacing disks. It is
 currently only valid for the mirrored (DRBD) disk template.
@@ -1499,7 +1504,8 @@ options.
 ACTIVATE-DISKS
 ^^^^^^^^^^^^^^
 
-**activate-disks** [\--submit] [\--ignore-size] [\--wait-for-sync] {*instance*}
+| **activate-disks** [\--submit] [\--print-job-id] [\--ignore-size]
+| [\--wait-for-sync] {*instance*}
 
 Activates the block devices of the given instance. If successful, the
 command will show the location and name of the block devices::
@@ -1537,7 +1543,7 @@ options.
 DEACTIVATE-DISKS
 ^^^^^^^^^^^^^^^^
 
-**deactivate-disks** [-f] [\--submit] {*instance*}
+**deactivate-disks** [-f] [\--submit] [\--print-job-id] {*instance*}
 
 De-activates the block devices of the given instance. Note that if you
 run this command for an instance with a drbd disk template, while it
@@ -1558,7 +1564,8 @@ options.
 GROW-DISK
 ^^^^^^^^^
 
-| **grow-disk** [\--no-wait-for-sync] [\--submit] [\--absolute]
+| **grow-disk** [\--no-wait-for-sync] [\--submit] [\--print-job-id]
+| [\--absolute]
 | {*instance*} {*disk*} {*amount*}
 
 Grows an instance's disk. This is only possible for instances having a
@@ -1616,9 +1623,9 @@ instance.
 RECREATE-DISKS
 ^^^^^^^^^^^^^^
 
-| **recreate-disks** [\--submit]
+| **recreate-disks** [\--submit] [\--print-job-id]
 | [{-n node1:[node2] \| {-I\|\--iallocator *name*}}]
-| [\--disk=*N*[:[size=*VAL*][,mode=*ro\|rw*]]] {*instance*}
+| [\--disk=*N*[:[size=*VAL*][,spindles=*VAL*][,mode=*ro\|rw*]]] {*instance*}
 
 Recreates all or a subset of disks of the given instance.
 
@@ -1629,10 +1636,10 @@ normal operation and as such the impact of this is low.
 
 If only a subset should be recreated, any number of ``disk`` options can
 be specified. It expects a disk index and an optional list of disk
-parameters to change. Only ``size`` and ``mode`` can be changed while
-recreating disks. To recreate all disks while changing parameters on
-a subset only, a ``--disk`` option must be given for every disk of the
-instance.
+parameters to change. Only ``size``, ``spindles``, and ``mode`` can be
+changed while recreating disks. To recreate all disks while changing
+parameters on a subset only, a ``--disk`` option must be given for every
+disk of the instance.
 
 Optionally the instance's disks can be recreated on different
 nodes. This can be useful if, for example, the original nodes of the
@@ -1660,7 +1667,7 @@ FAILOVER
 | **failover** [-f] [\--ignore-consistency] [\--ignore-ipolicy]
 | [\--shutdown-timeout=*N*]
 | [{-n|\--target-node} *node* \| {-I|\--iallocator} *name*]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | {*instance*}
 
 Failover will stop the instance (if running), change its primary node,
@@ -1714,10 +1721,10 @@ MIGRATE
 
 | **migrate** [-f] [\--allow-failover] [\--non-live]
 | [\--migration-mode=live\|non-live] [\--ignore-ipolicy]
-| [\--no-runtime-changes] [\--submit]
+| [\--no-runtime-changes] [\--submit] [\--print-job-id]
 | [{-n|\--target-node} *node* \| {-I|\--iallocator} *name*] {*instance*}
 
-| **migrate** [-f] \--cleanup [\--submit] {*instance*}
+| **migrate** [-f] \--cleanup [\--submit] [\--print-job-id] {*instance*}
 
 Migrate will move the instance to its secondary node without shutdown.
 As with failover, it works for instances having the drbd disk template
@@ -1807,7 +1814,8 @@ MOVE
 ^^^^
 
 | **move** [-f] [\--ignore-consistency]
-| [-n *node*] [\--shutdown-timeout=*N*] [\--submit] [\--ignore-ipolicy]
+| [-n *node*] [\--shutdown-timeout=*N*] [\--submit] [\--print-job-id]
+| [\--ignore-ipolicy]
 | {*instance*}
 
 Move will move the instance to an arbitrary node in the cluster. This
@@ -1840,7 +1848,7 @@ Example::
 CHANGE-GROUP
 ^^^^^^^^^^^^
 
-| **change-group** [\--submit]
+| **change-group** [\--submit] [\--print-job-id]
 | [\--iallocator *NAME*] [\--to *GROUP*...] {*instance*}
 
 This command moves an instance to another node group. The move is
index 3a88326..7f3c80d 100644 (file)
@@ -126,6 +126,14 @@ LIST-FIELDS
 Lists available fields for jobs.
 
 
+WAIT
+~~~~~
+
+**wait** {id}
+
+Wait for the job by the given *id* to finish; do not produce
+any output.
+
 WATCH
 ~~~~~
 
index c53dbac..26dd168 100644 (file)
@@ -40,7 +40,7 @@ ADD
 | [\--network6=*NETWORK6*]
 | [\--gateway6=*GATEWAY6*]
 | [\--mac-prefix=*MACPREFIX*]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | {*network*}
 
 Creates a new network with the given name. The network will be unused
@@ -76,7 +76,7 @@ MODIFY
 | [\--network6=*NETWORK6*]
 | [\--gateway6=*GATEWAY6*]
 | [\--mac-prefix=*MACPREFIX*]
-| [\--submit]
+| [\--submit] [\--print-job-id]
 | {*network*}
 
 Modifies parameters from the network.
@@ -91,7 +91,7 @@ options.
 REMOVE
 ~~~~~~
 
-| **remove** [\--submit] {*network*}
+| **remove** [\--submit] [\--print-job-id] {*network*}
 
 Deletes the indicated network, which must be not connected to any node group.
 
index 8adda3b..0bb29bc 100644 (file)
@@ -81,7 +81,7 @@ Example::
 EVACUATE
 ~~~~~~~~
 
-| **evacuate** [-f] [\--early-release] [\--submit]
+| **evacuate** [-f] [\--early-release] [\--submit] [\--print-job-id]
 | [{-I|\--iallocator} *NAME* \| {-n|\--new-secondary} *destination\_node*]
 | [{-p|\--primary-only} \| {-s|\--secondary-only} ]
 |  {*node*}
@@ -273,7 +273,7 @@ MIGRATE
 ~~~~~~~
 
 | **migrate** [-f] [\--non-live] [\--migration-mode=live\|non-live]
-| [\--ignore-ipolicy] [\--submit] {*node*}
+| [\--ignore-ipolicy] [\--submit] [\--print-job-id] {*node*}
 
 This command will migrate all instances having the given node as
 primary to their secondary nodes. This works only for instances
@@ -297,7 +297,7 @@ Example::
 MODIFY
 ~~~~~~
 
-| **modify** [-f] [\--submit]
+| **modify** [-f] [\--submit] [\--print-job-id]
 | [{-C|\--master-candidate} ``yes|no``]
 | [{-D|\--drained} ``yes|no``] [{-O|\--offline} ``yes|no``]
 | [\--master-capable=``yes|no``] [\--vm-capable=``yes|no``] [\--auto-promote]
@@ -487,7 +487,7 @@ Example::
 MODIFY-STORAGE
 ~~~~~~~~~~~~~~
 
-| **modify-storage** [\--allocatable={yes|no}] [\--submit]
+| **modify-storage** [\--allocatable={yes|no}] [\--submit] [\--print-job-id]
 | {*node*} {*storage-type*} {*volume-name*}
 
 Modifies storage volumes on a node. Only LVM physical volumes can
@@ -527,7 +527,7 @@ Example::
 POWERCYCLE
 ~~~~~~~~~~
 
-**powercycle** [\--yes] [\--force] [\--submit] {*node*}
+**powercycle** [\--yes] [\--force] [\--submit] [\--print-job-id] {*node*}
 
 This command (tries to) forcefully reboot a node. It is a command
 that can be used if the node environment is broken, such that the
index 2018048..c3392aa 100644 (file)
@@ -56,7 +56,8 @@ documentations, etc.
 MODIFY
 ~~~~~~
 
-| **modify** [\--submit] [-H *HYPERVISOR*:option=*value*[,...]]
+| **modify** [\--submit] [\--print-job-id]
+| [-H *HYPERVISOR*:option=*value*[,...]]
 | [\--hidden=*yes|no*] [\--blacklisted=*yes|no*]
 | {*OS*}
 
index c381a0a..61791e2 100644 (file)
@@ -19,16 +19,24 @@ Backend options:
 { **-m** *cluster* | **-L[** *path* **]** | **-t** *data-file* |
 **-I** *path* }
 
+**[ --force ]**
+
 Algorithm options:
 
 **[ -G *name* ]**
-
 **[ -O *name...* ]**
+**[ --node-tags** *tag,..* **]**
+**[ --skip-non-redundant ]**
+
+**[ --offline-maintenance ]**
+**[ --ignore-non-redundant ]**
 
 Reporting options:
 
 **[ -v... | -q ]**
 **[ -S *file* ]**
+**[ --one-step-only ]**
+**[ --print-moves ]**
 
 DESCRIPTION
 -----------
@@ -39,23 +47,52 @@ having both primary and secondary nodes being rebooted at the same time.
 
 For backends that support identifying the master node (currenlty
 RAPI and LUXI), the master node is scheduled as the last node
-in the last reboot group.
+in the last reboot group. Apart from this restriction, larger reboot
+groups are put first.
 
 ALGORITHM FOR CALCULATING OFFLINE REBOOT GROUPS
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 hroller will view the nodes as vertices of an undirected graph,
-connecting by instances which have both a primary and a secondary node.
-It will then color the graph using a few different heuristics, and
-return the minimum-size color set found. Node with the same color don't
-share an edge, and as such don't have an instance with both primary and
-secondary node on them, so they are safe to be rebooted concurrently.
+with two kind of edges. Firstly, there are edges from the primary
+to the secondary node of every instance. Secondly, two nodes are connected
+by an edge if they are the primary nodes of two instances that have the
+same secondary node. It will then color the graph using a few different
+heuristics, and return the minimum-size color set found. Node with the same
+color can then simultaneously migrate all instance off to their respective
+secondary nodes, and it is safe to reboot them simultaneously.
 
 OPTIONS
 -------
 
-Currently only standard htools options are supported. For a description of them
-check **htools**\(7) and **hbal**\(1).
+For a description of the standard options check **htools**\(7) and
+**hbal**\(1).
+
+\--force
+  Do not fail, even if the master node cannot be determined.
+
+\--node-tags *tag,...*
+  Restrict to nodes having at least one of the given tags.
+
+\--skip-non-redundant
+  Restrict to nodes not hosting any non-redundant instance.
+
+\--offline-maintenance
+  Pretend that all instances are shutdown before the reboots are carried
+  out. I.e., only edges from the primary to the secondary node of an instance
+  are considered.
+
+\--ignore-non-redundnant
+  Pretend that the non-redundant instances do not exist, and only take
+  instances with primary and secondary node into account.
+
+\--one-step-only
+  Restrict to the first reboot group. Output the group one node per line.
+
+\--print-moves
+  After each group list for each affected non-redundant instance a node
+  where it can be evacuated to.
+
 
 BUGS
 ----
@@ -71,22 +108,73 @@ Online rolling maintenances (where instance need not be shut down, but
 are migrated from node to node) are not supported yet. Hroller by design
 should support them both with and without secondary node replacement.
 
-EXAMPLE
--------
+EXAMPLES
+--------
+
+Online Rolling reboots, using tags
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Selecting by tags and getting output for one step only can be used for
+planing the next maintenance step.
+::
+
+   $ hroller --node-tags needsreboot --one-step-only -L
+   'First Reboot Group'
+    node1.example.com
+    node3.example.com
+
+Typically these nodes would be drained and migrated.
+::
+
+   $ GROUP=`hroller --node-tags needsreboot --one-step-only --no-headers -L`
+   $ for node in $GROUP; do gnt-node modify -D yes $node; done
+   $ for node in $GROUP; do gnt-node migrate -f --submit $node; done
+
+After maintenance, the tags would be removed and the nodes undrained.
+
 
 Offline Rolling node reboot output
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-With the default options, the program shows one reboot group per line as
-a comma-separated list.
+If all instances are shut down, usually larger node groups can be found.
 ::
 
-    $ hroller -L
+    $ hroller --offline-maintainance -L
     'Node Reboot Groups'
     node1.example.com,node3.example.com,node5.example.com
     node8.example.com,node6.example.com,node2.example.com
     node7.example.com,node4.example.com
 
+Rolling reboots with non-redundant instances
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, hroller plans capacity to move the non-redundant instances
+out of the nodes to be rebooted. If requested, apropriate locations for
+the non-redundant instances can be shown. The assumption is that instances
+are moved back to their original node after each reboot; these back moves
+are not part of the output.
+::
+
+    $ hroller --print-moves -L
+    'Node Reboot Groups'
+    node-01-002,node-01-003
+      inst-20 node-01-001
+      inst-21 node-01-000
+      inst-30 node-01-005
+      inst-31 node-01-004
+    node-01-004,node-01-005
+      inst-40 node-01-001
+      inst-41 node-01-000
+      inst-50 node-01-003
+      inst-51 node-01-002
+    node-01-001,node-01-000
+      inst-00 node-01-002
+      inst-01 node-01-003
+      inst-10 node-01-005
+      inst-11 node-01-004
+
+
+
 .. vim: set textwidth=72 :
 .. Local Variables:
 .. mode: rst
index 8b83526..eb312b4 100644 (file)
@@ -178,6 +178,9 @@ support all options. Some common options are:
     nodes, and ``M`` for the master node which is always online)
   - group UUID
   - node spindle count
+  - node tags
+  - exclusive storage value (``Y`` if active, ``N`` otherwise)
+  - node free spindles
 
   The third section contains instance data, with the fields:
 
@@ -191,6 +194,9 @@ support all options. Some common options are:
   - instance secondary node(s), if any
   - instance disk type (e.g. ``plain`` or ``drbd``)
   - instance tags
+  - spindle use back-end parameter
+  - actual disk spindles used by the instance (it can be ``-`` when
+    exclusive storage is not active)
 
   The fourth section contains the cluster tags, with one tag per line
   (no columns/no column processing).
index aea8812..d985117 100644 (file)
@@ -24,16 +24,14 @@ collector to be run.
 When executed, ``mon-collector`` will run the specified collector and will
 print its output to stdout, in JSON format.
 
-
-
-
 COLLECTORS
 ----------
 
 DRBD
 ~~~~
 
-| drbd [ [ **-s** | **\--drbd-status** ] = *status-file* ] [ [ **-p** | **\--drbd-pairing**] = *pairing-file* ]
+| drbd [ [ **-s** | **\--drbd-status** ] = *status-file* ] [ [ **-p** |
+  **\--drbd-pairing**] = *pairing-file* ]
 
 Collects the information about the version and status of the DRBD kernel
 module, and of the disks it is managing.
@@ -51,3 +49,21 @@ The options that can be passed to the DRBD collector are as follows:
 -p *pairing-file*, \--drbd-pairing=*pairing-file*
   Read the information about the pairing between instances and DRBD minors
   from the specified file instead of asking the Confd servers for them.
+
+INSTANCE STATUS
+~~~~~~~~~~~~~~~
+
+| inst-status-xen [ [ **-a** | **\--address** ] = *ip-address* ] [ [ **-p** |
+  **\--port** ] = *port-number* ]
+
+Collects the information about the status of the instances of the current node.
+In order to perform this task, it needs to connect to the ConfD daemon to fetch
+some configuration information. The following parameters allow the user to
+specify the position where the daemon is listening, in case it's not the default
+one:
+
+-a *ip-address*, \--address=*ip-address*
+  The IP address the ConfD daemon is listening on.
+
+-p *port-number*, \--port=*port-number*
+  The port the ConfD deamon is listening on.
index c58b959..ab1c1b6 100755 (executable)
@@ -38,6 +38,7 @@ import qa_env
 import qa_error
 import qa_group
 import qa_instance
+import qa_monitoring
 import qa_network
 import qa_node
 import qa_os
@@ -502,6 +503,7 @@ def RunHardwareFailureTests(instance, inodes):
   if len(inodes) >= 2:
     RunTestIf("node-evacuate", qa_node.TestNodeEvacuate, inodes[0], inodes[1])
     RunTestIf("node-failover", qa_node.TestNodeFailover, inodes[0], inodes[1])
+    RunTestIf("node-migrate", qa_node.TestNodeMigrate, inodes[0], inodes[1])
 
 
 def RunExclusiveStorageTests():
@@ -746,6 +748,11 @@ def RunInstanceTests():
       qa_cluster.AssertClusterVerify()
 
 
+def RunMonitoringTests():
+  if qa_config.TestEnabled("mon-collector"):
+    RunTest(qa_monitoring.TestInstStatusCollector)
+
+
 def RunQa():
   """Main QA body.
 
@@ -867,6 +874,8 @@ def RunQa():
       snode.Release()
     qa_cluster.AssertClusterVerify()
 
+  RunMonitoringTests()
+
   RunTestIf("create-cluster", qa_node.TestNodeRemoveAll)
 
   RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy)
index d30316b..b29b687 100644 (file)
   "disks": [
     {
       "size": "1G",
+      "spindles": 2,
       "name": "disk0",
-      "growth": "2G"
+      "growth": "2G",
+      "spindles-growth": 1
     },
     {
       "size": "512M",
+      "spindles": 1,
       "name": "disk1",
-      "growth": "768M"
+      "growth": "768M",
+      "spindles-growth": 0
     }
   ],
 
     "node-modify": true,
     "node-oob": true,
 
-    "# This test needs at least three nodes": null,
+    "# These tests need at least three nodes": null,
     "node-evacuate": false,
+    "node-migrate": false,
 
     "# This test needs at least two nodes": null,
     "node-failover": false,
 
     "# Run instance tests with different cluster configurations": null,
     "default-instance-tests": true,
-    "exclusive-storage-instance-tests": false
+    "exclusive-storage-instance-tests": false,
+
+    "mon-collector": true
   },
 
   "options": {
index cde9db3..0f12b79 100644 (file)
@@ -192,9 +192,13 @@ def TestClusterInit(rapi_user, rapi_secret):
   if master.secondary:
     cmd.append("--secondary-ip=%s" % master.secondary)
 
-  vgname = qa_config.get("vg-name", None)
-  if vgname:
-    cmd.append("--vg-name=%s" % vgname)
+  if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
+    vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
+    if vgname:
+      cmd.append("--vg-name=%s" % vgname)
+    else:
+      raise qa_error.Error("Please specify a volume group if you enable"
+                           " lvm-based disk templates in the QA.")
 
   master_netdev = qa_config.get("master-netdev", None)
   if master_netdev:
@@ -398,6 +402,7 @@ def TestClusterModifyDiskTemplates():
 
   _TestClusterModifyDiskTemplatesArguments(default_disk_template,
                                            enabled_disk_templates)
+  _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
 
   _RestoreEnabledDiskTemplates()
   nodes = qa_config.AcquireManyNodes(2)
@@ -420,10 +425,12 @@ def _RestoreEnabledDiskTemplates():
      other tests.
 
   """
+  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
   AssertCommand(
     ["gnt-cluster", "modify",
-     "--enabled-disk-template=%s" %
-       ",".join(qa_config.GetEnabledDiskTemplates())],
+     "--enabled-disk-templates=%s" %
+       ",".join(qa_config.GetEnabledDiskTemplates()),
+     "--vg-name=%s" % vgname],
     fail=False)
 
 
@@ -434,11 +441,7 @@ def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
      of instances.
 
   """
-  AssertCommand(
-    ["gnt-cluster", "modify",
-     "--enabled-disk-template=%s" %
-       ",".join(enabled_disk_templates)],
-    fail=False)
+  _RestoreEnabledDiskTemplates()
 
   # bogus templates
   AssertCommand(["gnt-cluster", "modify",
@@ -452,6 +455,105 @@ def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
       (default_disk_template, default_disk_template)],
     fail=False)
 
+  # interaction with --drbd-usermode-helper option
+  drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
+  if not drbd_usermode_helper:
+    drbd_usermode_helper = "/bin/true"
+  # specifying a helper when drbd gets disabled
+  AssertCommand(["gnt-cluster", "modify",
+                 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
+                 "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
+                 fail=False)
+  if constants.DT_DRBD8 in enabled_disk_templates:
+    # specifying a vg name when lvm is enabled
+    AssertCommand(["gnt-cluster", "modify",
+                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
+                   "--enabled-disk-templates=%s" %
+                     ",".join(enabled_disk_templates)],
+                  fail=False)
+
+
+def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
+  """Tests argument handling of 'gnt-cluster modify' with respect to
+     the parameter '--enabled-disk-templates' and '--vg-name'. This test is
+     independent of instances.
+
+  """
+  if not utils.IsLvmEnabled(enabled_disk_templates):
+    # These tests only make sense if lvm is enabled for QA
+    return
+
+  # determine an LVM and a non-LVM disk template for the tests
+  non_lvm_templates = list(set(enabled_disk_templates)
+                           - set(utils.GetLvmDiskTemplates()))
+  lvm_template = list(set(enabled_disk_templates)
+                      .intersection(set(utils.GetLvmDiskTemplates())))[0]
+  non_lvm_template = None
+  if non_lvm_templates:
+    non_lvm_template = non_lvm_templates[0]
+  else:
+    # If no non-lvm disk template is available for QA, choose 'diskless' and
+    # hope for the best.
+    non_lvm_template = constants.ST_DISKLESS
+
+  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
+
+  # Clean start: unset volume group name, disable lvm storage
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % non_lvm_template,
+     "--vg-name="],
+    fail=False)
+
+  # Try to enable lvm, when no volume group is given
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % lvm_template],
+    fail=True)
+
+  # Set volume group, with lvm still disabled: just a warning
+  AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
+
+  # Try unsetting vg name and enabling lvm at the same time
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % lvm_template,
+     "--vg-name="],
+    fail=True)
+
+  # Enable lvm with vg name present
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % lvm_template],
+    fail=False)
+
+  # Try unsetting vg name with lvm still enabled
+  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
+
+  # Disable lvm with vg name still set
+  AssertCommand(
+    ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
+    fail=False)
+
+  # Try unsetting vg name with lvm disabled
+  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
+
+  # Set vg name and enable lvm at the same time
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % lvm_template,
+     "--vg-name=%s" % vgname],
+    fail=False)
+
+  # Unset vg name and disable lvm at the same time
+  AssertCommand(
+    ["gnt-cluster", "modify",
+     "--enabled-disk-templates=%s" % non_lvm_template,
+     "--vg-name="],
+    fail=False)
+
+  _RestoreEnabledDiskTemplates()
+
 
 def _TestClusterModifyUsedDiskTemplate(instance_template,
                                        enabled_disk_templates):
index 4e0d5f5..d9e5b0e 100644 (file)
@@ -370,7 +370,7 @@ class _QaConfig(object):
     """
     return self._GetStringListParameter(
       _ENABLED_DISK_TEMPLATES_KEY,
-      list(constants.DEFAULT_ENABLED_DISK_TEMPLATES))
+      constants.DEFAULT_ENABLED_DISK_TEMPLATES)
 
   def GetDefaultDiskTemplate(self):
     """Returns the default disk template to be used.
@@ -418,6 +418,12 @@ class _QaConfig(object):
     return enabled and (not self.GetExclusiveStorage() or
                         templ in constants.DTS_EXCL_STORAGE)
 
+  def AreSpindlesSupported(self):
+    """Are spindles supported by the current configuration?
+
+    """
+    return self.GetExclusiveStorage()
+
   def GetVclusterSettings(self):
     """Returns settings for virtual cluster.
 
@@ -643,6 +649,13 @@ def IsTemplateSupported(templ):
   return GetConfig().IsTemplateSupported(templ)
 
 
+def AreSpindlesSupported():
+  """Wrapper for L{_QaConfig.AreSpindlesSupported}.
+
+  """
+  return GetConfig().AreSpindlesSupported()
+
+
 def _NodeSortKey(node):
   """Returns sort key for a node.
 
index ba861ed..256c133 100644 (file)
@@ -23,7 +23,6 @@
 
 """
 
-import operator
 import os
 import re
 
@@ -36,146 +35,19 @@ import qa_config
 import qa_utils
 import qa_error
 
-from qa_utils import AssertIn, AssertCommand, AssertEqual
+from qa_utils import AssertCommand, AssertEqual
 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
+from qa_instance_utils import CheckSsconfInstanceList, \
+                              CreateInstanceDrbd8, \
+                              CreateInstanceByDiskTemplate, \
+                              CreateInstanceByDiskTemplateOneNode, \
+                              GetGenericAddParameters
 
 
 def _GetDiskStatePath(disk):
   return "/sys/block/%s/device/state" % disk
 
 
-def _GetGenericAddParameters(inst, disk_template, force_mac=None):
-  params = ["-B"]
-  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
-                                 qa_config.get(constants.BE_MINMEM),
-                                 constants.BE_MAXMEM,
-                                 qa_config.get(constants.BE_MAXMEM)))
-
-  if disk_template != constants.DT_DISKLESS:
-    for idx, disk in enumerate(qa_config.GetDiskOptions()):
-      size = disk.get("size")
-      name = disk.get("name")
-      diskparams = "%s:size=%s" % (idx, size)
-      if name:
-        diskparams += ",name=%s" % name
-      params.extend(["--disk", diskparams])
-
-  # Set static MAC address if configured
-  if force_mac:
-    nic0_mac = force_mac
-  else:
-    nic0_mac = inst.GetNicMacAddr(0, None)
-
-  if nic0_mac:
-    params.extend(["--net", "0:mac=%s" % nic0_mac])
-
-  return params
-
-
-def _CreateInstanceByDiskTemplateRaw(nodes_spec, disk_template, fail=False):
-  """Creates an instance with the given disk template on the given nodes(s).
-     Note that this function does not check if enough nodes are given for
-     the respective disk template.
-
-  @type nodes_spec: string
-  @param nodes_spec: string specification of one node (by node name) or several
-                     nodes according to the requirements of the disk template
-  @type disk_template: string
-  @param disk_template: the disk template to be used by the instance
-  @return: the created instance
-
-  """
-  instance = qa_config.AcquireInstance()
-  try:
-    cmd = (["gnt-instance", "add",
-            "--os-type=%s" % qa_config.get("os"),
-            "--disk-template=%s" % disk_template,
-            "--node=%s" % nodes_spec] +
-           _GetGenericAddParameters(instance, disk_template))
-    cmd.append(instance.name)
-
-    AssertCommand(cmd, fail=fail)
-
-    if not fail:
-      _CheckSsconfInstanceList(instance.name)
-      instance.SetDiskTemplate(disk_template)
-
-      return instance
-  except:
-    instance.Release()
-    raise
-
-  # Handle the case where creation is expected to fail
-  assert fail
-  instance.Release()
-  return None
-
-
-def _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=False):
-  """Creates an instance using the given disk template for disk templates
-     for which one given node is sufficient. These templates are for example:
-     plain, diskless, file, sharedfile, blockdev, rados.
-
-  @type nodes: list of nodes
-  @param nodes: a list of nodes, whose first element is used to create the
-                instance
-  @type disk_template: string
-  @param disk_template: the disk template to be used by the instance
-  @return: the created instance
-
-  """
-  assert len(nodes) > 0
-  return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
-                                          fail=fail)
-
-
-def _CreateInstanceDrbd8(nodes, fail=False):
-  """Creates an instance using disk template 'drbd' on the given nodes.
-
-  @type nodes: list of nodes
-  @param nodes: nodes to be used by the instance
-  @return: the created instance
-
-  """
-  assert len(nodes) > 1
-  return _CreateInstanceByDiskTemplateRaw(
-    ":".join(map(operator.attrgetter("primary"), nodes)),
-    constants.DT_DRBD8, fail=fail)
-
-
-def CreateInstanceByDiskTemplate(nodes, disk_template, fail=False):
-  """Given a disk template, this function creates an instance using
-     the template. It uses the required number of nodes depending on
-     the disk template. This function is intended to be used by tests
-     that don't care about the specifics of the instance other than
-     that it uses the given disk template.
-
-     Note: If you use this function, make sure to call
-     'TestInstanceRemove' at the end of your tests to avoid orphaned
-     instances hanging around and interfering with the following tests.
-
-  @type nodes: list of nodes
-  @param nodes: the list of the nodes on which the instance will be placed;
-                it needs to have sufficiently many elements for the given
-                disk template
-  @type disk_template: string
-  @param disk_template: the disk template to be used by the instance
-  @return: the created instance
-
-  """
-  if disk_template == constants.DT_DRBD8:
-    return _CreateInstanceDrbd8(nodes, fail=fail)
-  elif disk_template in [constants.DT_DISKLESS, constants.DT_PLAIN,
-                         constants.DT_FILE]:
-    return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
-  else:
-    # FIXME: This assumes that for all other disk templates, we only need one
-    # node and no disk template specific parameters. This else-branch is
-    # currently only used in cases where we expect failure. Extend it when
-    # QA needs for these templates change.
-    return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
-
-
 def _GetInstanceInfo(instance):
   """Return information about the actual state of an instance.
 
@@ -382,7 +254,7 @@ def IsDiskSupported(instance):
 def TestInstanceAddWithPlainDisk(nodes, fail=False):
   """gnt-instance add -t plain"""
   if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
-    instance = _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
+    instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
                                                     fail=fail)
     if not fail:
       qa_utils.RunInstanceCheck(instance, True)
@@ -393,7 +265,7 @@ def TestInstanceAddWithPlainDisk(nodes, fail=False):
 def TestInstanceAddWithDrbdDisk(nodes):
   """gnt-instance add -t drbd"""
   if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
-    return _CreateInstanceDrbd8(nodes)
+    return CreateInstanceDrbd8(nodes)
 
 
 @InstanceCheck(None, INST_UP, RETURN_VALUE)
@@ -401,15 +273,15 @@ def TestInstanceAddFile(nodes):
   """gnt-instance add -t file"""
   assert len(nodes) == 1
   if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
-    return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
+    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
 
 
 @InstanceCheck(None, INST_UP, RETURN_VALUE)
 def TestInstanceAddDiskless(nodes):
   """gnt-instance add -t diskless"""
   assert len(nodes) == 1
-  if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
-    return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
+  if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
+    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
 
 
 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
@@ -466,32 +338,6 @@ def TestInstanceReinstall(instance):
                 fail=True)
 
 
-def _ReadSsconfInstanceList():
-  """Reads ssconf_instance_list from the master node.
-
-  """
-  master = qa_config.GetMasterNode()
-
-  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
-                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
-
-  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
-
-  return qa_utils.GetCommandOutput(master.primary,
-                                   utils.ShellQuoteArgs(cmd)).splitlines()
-
-
-def _CheckSsconfInstanceList(instance):
-  """Checks if a certain instance is in the ssconf instance list.
-
-  @type instance: string
-  @param instance: Instance name
-
-  """
-  AssertIn(qa_utils.ResolveInstanceName(instance),
-           _ReadSsconfInstanceList())
-
-
 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
 def TestInstanceRenameAndBack(rename_source, rename_target):
   """gnt-instance rename
@@ -500,14 +346,14 @@ def TestInstanceRenameAndBack(rename_source, rename_target):
   name.
 
   """
-  _CheckSsconfInstanceList(rename_source)
+  CheckSsconfInstanceList(rename_source)
 
   # first do a rename to a different actual name, expecting it to fail
   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
   try:
     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
                   fail=True)
-    _CheckSsconfInstanceList(rename_source)
+    CheckSsconfInstanceList(rename_source)
   finally:
     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
 
@@ -530,7 +376,7 @@ def TestInstanceRenameAndBack(rename_source, rename_target):
 
   # and now rename instance to rename_target...
   AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
-  _CheckSsconfInstanceList(rename_target)
+  CheckSsconfInstanceList(rename_target)
   qa_utils.RunInstanceCheck(rename_source, False)
   qa_utils.RunInstanceCheck(rename_target, False)
 
@@ -544,7 +390,7 @@ def TestInstanceRenameAndBack(rename_source, rename_target):
 
   # and back
   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
-  _CheckSsconfInstanceList(rename_source)
+  CheckSsconfInstanceList(rename_source)
   qa_utils.RunInstanceCheck(rename_target, False)
 
   if (rename_source != rename_target and
@@ -784,10 +630,22 @@ def TestInstanceModifyDisks(instance):
     print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
     return
 
-  size = qa_config.GetDiskOptions()[-1].get("size")
+  disk_conf = qa_config.GetDiskOptions()[-1]
+  size = disk_conf.get("size")
   name = instance.name
   build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
-  AssertCommand(build_cmd("add:size=%s" % size))
+  if qa_config.AreSpindlesSupported():
+    spindles = disk_conf.get("spindles")
+    spindles_supported = True
+  else:
+    # Any number is good for spindles in this case
+    spindles = 1
+    spindles_supported = False
+  AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
+                fail=not spindles_supported)
+  AssertCommand(build_cmd("add:size=%s" % size),
+                fail=spindles_supported)
+  # Exactly one of the above commands has succeded, so we need one remove
   AssertCommand(build_cmd("remove"))
 
 
@@ -835,6 +693,8 @@ def TestInstanceDeviceNames(instance):
   for dev_type in ["disk", "net"]:
     if dev_type == "disk":
       options = ",size=512M"
+      if qa_config.AreSpindlesSupported():
+        options += ",spindles=1"
     else:
       options = ""
     # succeed in adding a device named 'test_device'
@@ -960,6 +820,33 @@ def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
     AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
 
 
+def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
+                            spindles_supported):
+  if with_spindles:
+    if spindles_supported:
+      if with_growth:
+        build_spindles_opt = (lambda disk:
+                              ",spindles=%s" %
+                              (disk["spindles"] + disk["spindles-growth"]))
+      else:
+        build_spindles_opt = (lambda disk:
+                              ",spindles=%s" % disk["spindles"])
+    else:
+      build_spindles_opt = (lambda _: ",spindles=1")
+  else:
+    build_spindles_opt = (lambda _: "")
+  if with_growth:
+    build_size_opt = (lambda disk:
+                      "size=%s" % (utils.ParseUnit(disk["size"]) +
+                                   utils.ParseUnit(disk["growth"])))
+  else:
+    build_size_opt = (lambda disk: "size=%s" % disk["size"])
+  build_disk_opt = (lambda (idx, disk):
+                    "--disk=%s:%s%s" % (idx, build_size_opt(disk),
+                                        build_spindles_opt(disk)))
+  return map(build_disk_opt, en_disks)
+
+
 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
 def TestRecreateDisks(instance, inodes, othernodes):
   """gnt-instance recreate-disks
@@ -982,6 +869,10 @@ def TestRecreateDisks(instance, inodes, othernodes):
   AssertCommand(["gnt-instance", "stop", instance.name])
   # Disks exist: this should fail
   _AssertRecreateDisks([], instance, fail=True, destroy=False)
+  # Unsupported spindles parameters: fail
+  if not qa_config.AreSpindlesSupported():
+    _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
+                         fail=True, destroy=False)
   # Recreate disks in place
   _AssertRecreateDisks([], instance)
   # Move disks away
@@ -994,12 +885,32 @@ def TestRecreateDisks(instance, inodes, othernodes):
     _AssertRecreateDisks(["-n", other_seq], instance)
   # Move disks back
   _AssertRecreateDisks(["-n", orig_seq], instance)
-  # Recreate the disks one by one
-  for idx in range(0, len(qa_config.GetDiskOptions())):
+  # Recreate resized disks
+  # One of the two commands fails because either spindles are given when they
+  # should not or vice versa
+  alldisks = qa_config.GetDiskOptions()
+  spindles_supported = qa_config.AreSpindlesSupported()
+  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
+                                      spindles_supported)
+  _AssertRecreateDisks(disk_opts, instance, destroy=True,
+                       fail=not spindles_supported)
+  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
+                                      spindles_supported)
+  _AssertRecreateDisks(disk_opts, instance, destroy=False,
+                       fail=spindles_supported)
+  # Recreate the disks one by one (with the original size)
+  for (idx, disk) in enumerate(alldisks):
     # Only the first call should destroy all the disk
     destroy = (idx == 0)
-    _AssertRecreateDisks(["--disk=%s" % idx], instance, destroy=destroy,
-                         check=False)
+    # Again, one of the two commands is expected to fail
+    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
+                                        spindles_supported)
+    _AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
+                         fail=not spindles_supported)
+    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
+                                        spindles_supported)
+    _AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
+                         fail=spindles_supported)
   # This and InstanceCheck decoration check that the disks are working
   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
   AssertCommand(["gnt-instance", "start", instance.name])
@@ -1036,7 +947,7 @@ def TestInstanceImport(newinst, node, expnode, name):
           "--src-node=%s" % expnode.primary,
           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
           "--node=%s" % node.primary] +
-         _GetGenericAddParameters(newinst, templ,
+         GetGenericAddParameters(newinst, templ,
                                   force_mac=constants.VALUE_GENERATE))
   cmd.append(newinst.name)
   AssertCommand(cmd)
@@ -1077,7 +988,16 @@ def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
     # FIXME: abstract the cleanup inside the disks
     if info["storage-type"] == constants.ST_LVM_VG:
       for minor in info["drbd-minors"][snode.primary]:
-        AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
+        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
+        # relies on the fact that we always create a resources for each minor,
+        # and that this resources is always named resource{minor}.
+        # As 'drbdsetup 0 down' does return success (even though that's invalid
+        # syntax), we always have to perform both commands and ignore the
+        # output.
+        drbd_shutdown_cmd = \
+          "(drbdsetup %d down && drbdsetup down resource%d) || /bin/true" % \
+            (minor, minor)
+        AssertCommand(drbd_shutdown_cmd, node=snode)
       AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
     elif info["storage-type"] == constants.ST_FILE:
       filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
diff --git a/qa/qa_instance_utils.py b/qa/qa_instance_utils.py
new file mode 100644 (file)
index 0000000..72715e9
--- /dev/null
@@ -0,0 +1,204 @@
+#
+#
+
+# 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.
+
+
+"""QA utility functions for managing instances
+
+"""
+
+import operator
+
+from ganeti import utils
+from ganeti import constants
+from ganeti import pathutils
+
+import qa_config
+import qa_error
+import qa_utils
+
+from qa_utils import AssertIn, AssertCommand
+
+
+def RemoveInstance(instance):
+  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
+
+
+def GetGenericAddParameters(inst, disk_template, force_mac=None):
+  params = ["-B"]
+  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
+                                 qa_config.get(constants.BE_MINMEM),
+                                 constants.BE_MAXMEM,
+                                 qa_config.get(constants.BE_MAXMEM)))
+
+  if disk_template != constants.DT_DISKLESS:
+    for idx, disk in enumerate(qa_config.GetDiskOptions()):
+      size = disk.get("size")
+      name = disk.get("name")
+      diskparams = "%s:size=%s" % (idx, size)
+      if name:
+        diskparams += ",name=%s" % name
+      if qa_config.AreSpindlesSupported():
+        spindles = disk.get("spindles")
+        if spindles is None:
+          raise qa_error.Error("'spindles' is a required parameter for disks"
+                               " when you enable exclusive storage tests")
+        diskparams += ",spindles=%s" % spindles
+      params.extend(["--disk", diskparams])
+
+  # Set static MAC address if configured
+  if force_mac:
+    nic0_mac = force_mac
+  else:
+    nic0_mac = inst.GetNicMacAddr(0, None)
+
+  if nic0_mac:
+    params.extend(["--net", "0:mac=%s" % nic0_mac])
+
+  return params
+
+
+def _CreateInstanceByDiskTemplateRaw(nodes_spec, disk_template, fail=False):
+  """Creates an instance with the given disk template on the given nodes(s).
+     Note that this function does not check if enough nodes are given for
+     the respective disk template.
+
+  @type nodes_spec: string
+  @param nodes_spec: string specification of one node (by node name) or several
+                     nodes according to the requirements of the disk template
+  @type disk_template: string
+  @param disk_template: the disk template to be used by the instance
+  @return: the created instance
+
+  """
+  instance = qa_config.AcquireInstance()
+  try:
+    cmd = (["gnt-instance", "add",
+            "--os-type=%s" % qa_config.get("os"),
+            "--disk-template=%s" % disk_template,
+            "--node=%s" % nodes_spec] +
+           GetGenericAddParameters(instance, disk_template))
+    cmd.append(instance.name)
+
+    AssertCommand(cmd, fail=fail)
+
+    if not fail:
+      CheckSsconfInstanceList(instance.name)
+      instance.SetDiskTemplate(disk_template)
+
+      return instance
+  except:
+    instance.Release()
+    raise
+
+  # Handle the case where creation is expected to fail
+  assert fail
+  instance.Release()
+  return None
+
+
+def CreateInstanceDrbd8(nodes, fail=False):
+  """Creates an instance using disk template 'drbd' on the given nodes.
+
+  @type nodes: list of nodes
+  @param nodes: nodes to be used by the instance
+  @return: the created instance
+
+  """
+  assert len(nodes) > 1
+  return _CreateInstanceByDiskTemplateRaw(
+    ":".join(map(operator.attrgetter("primary"), nodes)),
+    constants.DT_DRBD8, fail=fail)
+
+
+def CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=False):
+  """Creates an instance using the given disk template for disk templates
+     for which one given node is sufficient. These templates are for example:
+     plain, diskless, file, sharedfile, blockdev, rados.
+
+  @type nodes: list of nodes
+  @param nodes: a list of nodes, whose first element is used to create the
+                instance
+  @type disk_template: string
+  @param disk_template: the disk template to be used by the instance
+  @return: the created instance
+
+  """
+  assert len(nodes) > 0
+  return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
+                                          fail=fail)
+
+
+def CreateInstanceByDiskTemplate(nodes, disk_template, fail=False):
+  """Given a disk template, this function creates an instance using
+     the template. It uses the required number of nodes depending on
+     the disk template. This function is intended to be used by tests
+     that don't care about the specifics of the instance other than
+     that it uses the given disk template.
+
+     Note: If you use this function, make sure to call
+     'TestInstanceRemove' at the end of your tests to avoid orphaned
+     instances hanging around and interfering with the following tests.
+
+  @type nodes: list of nodes
+  @param nodes: the list of the nodes on which the instance will be placed;
+                it needs to have sufficiently many elements for the given
+                disk template
+  @type disk_template: string
+  @param disk_template: the disk template to be used by the instance
+  @return: the created instance
+
+  """
+  if disk_template == constants.DT_DRBD8:
+    return CreateInstanceDrbd8(nodes, fail=fail)
+  elif disk_template in [constants.DT_DISKLESS, constants.DT_PLAIN,
+                         constants.DT_FILE]:
+    return CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
+  else:
+    # FIXME: This assumes that for all other disk templates, we only need one
+    # node and no disk template specific parameters. This else-branch is
+    # currently only used in cases where we expect failure. Extend it when
+    # QA needs for these templates change.
+    return CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
+
+
+def _ReadSsconfInstanceList():
+  """Reads ssconf_instance_list from the master node.
+
+  """
+  master = qa_config.GetMasterNode()
+
+  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
+                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
+
+  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
+
+  return qa_utils.GetCommandOutput(master.primary,
+                                   utils.ShellQuoteArgs(cmd)).splitlines()
+
+
+def CheckSsconfInstanceList(instance):
+  """Checks if a certain instance is in the ssconf instance list.
+
+  @type instance: string
+  @param instance: Instance name
+
+  """
+  AssertIn(qa_utils.ResolveInstanceName(instance),
+           _ReadSsconfInstanceList())
diff --git a/qa/qa_monitoring.py b/qa/qa_monitoring.py
new file mode 100644 (file)
index 0000000..90bc1b9
--- /dev/null
@@ -0,0 +1,63 @@
+#
+#
+
+# Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Monitoring related QA tests.
+
+"""
+
+from ganeti import _autoconf
+from ganeti import constants
+
+import qa_config
+
+from qa_utils import AssertCommand
+from qa_instance_utils import CreateInstanceByDiskTemplate, \
+                              RemoveInstance
+
+MON_COLLECTOR = _autoconf.PKGLIBDIR + "/mon-collector"
+
+
+def TestInstStatusCollector():
+  """Test the Xen instance status collector.
+
+  """
+  enabled_hypervisors = qa_config.GetEnabledHypervisors()
+
+  is_xen = (constants.HT_XEN_PVM in enabled_hypervisors or
+            constants.HT_XEN_HVM in enabled_hypervisors)
+  if not is_xen:
+    return
+
+  # Execute on master on an empty cluster
+  AssertCommand([MON_COLLECTOR, "inst-status-xen"])
+
+  #Execute on cluster with instances
+  node1 = qa_config.AcquireNode()
+  node2 = qa_config.AcquireNode()
+  template = qa_config.GetDefaultDiskTemplate()
+
+  instance = CreateInstanceByDiskTemplate([node1, node2], template)
+  AssertCommand([MON_COLLECTOR, "inst-status-xen"], node=node1)
+  AssertCommand([MON_COLLECTOR, "inst-status-xen"], node=node2)
+  RemoveInstance(instance)
+
+  node1.Release()
+  node2.Release()
index c9a3685..2d33699 100644 (file)
@@ -201,6 +201,20 @@ def TestNodeFailover(node, node2):
   AssertCommand(["gnt-node", "failover", "-f", node2.primary])
 
 
+def TestNodeMigrate(node, node2):
+  """gnt-node migrate"""
+  if qa_utils.GetNodeInstances(node2, secondaries=False):
+    raise qa_error.UnusableNodeError("Secondary node has at least one"
+                                     " primary instance. This test requires"
+                                     " it to have no primary instances.")
+
+  # Migrate to secondary node
+  AssertCommand(["gnt-node", "migrate", "-f", node.primary])
+
+  # ... and back again.
+  AssertCommand(["gnt-node", "migrate", "-f", node2.primary])
+
+
 def TestNodeEvacuate(node, node2):
   """gnt-node evacuate"""
   node3 = qa_config.AcquireNode(exclude=[node, node2])
@@ -223,13 +237,19 @@ def TestNodeEvacuate(node, node2):
 
 def TestNodeModify(node):
   """gnt-node modify"""
+
+  # make sure enough master candidates will be available by disabling the
+  # master candidate role first with --auto-promote
+  AssertCommand(["gnt-node", "modify", "--master-candidate=no",
+                "--auto-promote", node.primary])
+
+  # now it's save to force-remove the master candidate role
   for flag in ["master-candidate", "drained", "offline"]:
     for value in ["yes", "no"]:
       AssertCommand(["gnt-node", "modify", "--force",
                      "--%s=%s" % (flag, value), node.primary])
 
-  AssertCommand(["gnt-node", "modify", "--master-candidate=yes",
-                 "--auto-promote", node.primary])
+  AssertCommand(["gnt-node", "modify", "--master-candidate=yes", node.primary])
 
   # Test setting secondary IP address
   AssertCommand(["gnt-node", "modify", "--secondary-ip=%s" % node.secondary,
index 0ebe635..c794c75 100644 (file)
@@ -105,12 +105,12 @@ def Setup(username, password):
 
 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
                    "admin_state",
-                   "disk_template", "disk.sizes",
+                   "disk_template", "disk.sizes", "disk.spindles",
                    "nic.ips", "nic.macs", "nic.modes", "nic.links",
                    "beparams", "hvparams",
                    "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
 
-NODE_FIELDS = ("name", "dtotal", "dfree",
+NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
                "mtotal", "mnode", "mfree",
                "pinst_cnt", "sinst_cnt", "tags")
 
index d7a5ea0..8bd7613 100644 (file)
@@ -42,6 +42,7 @@ import Ganeti.Confd.Utils
 import qualified Ganeti.Constants as C
 import Ganeti.Hash
 import Ganeti.Ssconf
+import Ganeti.Utils
 
 -- | Builds a properly initialized ConfdClient.
 -- The parameters (an IP address and the port number for the Confd client
@@ -117,9 +118,11 @@ queryOneServer semaphore answer crType cQuery hmac (host, port) = do
   let signedMsg =
         signMessage hmac timestamp (J.encodeStrict request)
       completeMsg = C.confdMagicFourcc ++ J.encodeStrict signedMsg
-  s <- S.socket S.AF_INET S.Datagram S.defaultProtocol
-  hostAddr <- S.inet_addr host
-  _ <- S.sendTo s completeMsg $ S.SockAddrInet port hostAddr
+  addr <- resolveAddr (fromIntegral port) host
+  (af_family, sockaddr) <-
+    exitIfBad "Unable to resolve the IP address" addr
+  s <- S.socket af_family S.Datagram S.defaultProtocol
+  _ <- S.sendTo s completeMsg sockaddr
   replyMsg <- S.recv s C.maxUdpDataSize
   parsedReply <-
     if C.confdMagicFourcc `isPrefixOf` replyMsg
index 4339ce2..f05d3b5 100644 (file)
@@ -59,6 +59,7 @@ import Ganeti.Logging
 import qualified Ganeti.Constants as C
 import qualified Ganeti.Path as Path
 import Ganeti.Query.Server (prepQueryD, runQueryD)
+import qualified Ganeti.Query.Cluster as QCluster
 import Ganeti.Utils
 
 -- * Types and constants definitions
@@ -137,16 +138,16 @@ gntErrorToResult (Ok x) = Ok x
 
 -- | Computes the node role.
 nodeRole :: ConfigData -> String -> Result ConfdNodeRole
-nodeRole cfg name =
-  let cmaster = clusterMasterNode . configCluster $ cfg
-      mnode = M.lookup name . fromContainer . configNodes $ cfg
-  in case mnode of
-       Nothing -> Bad "Node not found"
-       Just node | cmaster == name -> Ok NodeRoleMaster
-                 | nodeDrained node -> Ok NodeRoleDrained
-                 | nodeOffline node -> Ok NodeRoleOffline
-                 | nodeMasterCandidate node -> Ok NodeRoleCandidate
-       _ -> Ok NodeRoleRegular
+nodeRole cfg name = do
+  cmaster <- errToResult $ QCluster.clusterMasterNodeName cfg
+  mnode <- errToResult $ getNode cfg name
+  let role = case mnode of
+               node | cmaster == name -> NodeRoleMaster
+                    | nodeDrained node -> NodeRoleDrained
+                    | nodeOffline node -> NodeRoleOffline
+                    | nodeMasterCandidate node -> NodeRoleCandidate
+               _ -> NodeRoleRegular
+  return role
 
 -- | Does an instance ip -> instance -> primary node -> primary ip
 -- transformation.
@@ -170,17 +171,19 @@ buildResponse (cfg, _) (ConfdRequest { confdRqType = ReqPing }) =
 
 buildResponse cdata req@(ConfdRequest { confdRqType = ReqClusterMaster }) =
   case confdRqQuery req of
-    EmptyQuery -> return (ReplyStatusOk, J.showJSON master_name)
+    EmptyQuery -> liftM ((,) ReplyStatusOk . J.showJSON) master_name
     PlainQuery _ -> return queryArgumentError
     DictQuery reqq -> do
-      mnode <- gntErrorToResult $ getNode cfg master_name
+      mnode <- gntErrorToResult $ getNode cfg master_uuid
+      mname <- master_name
       let fvals = map (\field -> case field of
-                                   ReqFieldName -> master_name
+                                   ReqFieldName -> mname
                                    ReqFieldIp -> clusterMasterIp cluster
                                    ReqFieldMNodePip -> nodePrimaryIp mnode
                       ) (confdReqQFields reqq)
       return (ReplyStatusOk, J.showJSON fvals)
-    where master_name = clusterMasterNode cluster
+    where master_uuid = clusterMasterNode cluster
+          master_name = errToResult $ QCluster.clusterMasterNodeName cfg
           cluster = configCluster cfg
           cfg = fst cdata
 
index 18dce62..9380362 100644 (file)
@@ -160,17 +160,29 @@ getItem kind name allitems = do
   maybe (err "not found after successfull match?!") Ok $
         M.lookup fullname allitems
 
--- | Looks up a node.
+-- | Looks up a node by name or uuid.
 getNode :: ConfigData -> String -> ErrorResult Node
-getNode cfg name = getItem "Node" name (fromContainer $ configNodes cfg)
+getNode cfg name =
+  let nodes = fromContainer (configNodes cfg)
+  in case getItem "Node" name nodes of
+       -- if not found by uuid, we need to look it up by name
+       Ok node -> Ok node
+       Bad _ -> let by_name = M.mapKeys
+                              (nodeName . (M.!) nodes) nodes
+                in getItem "Node" name by_name
 
--- | Looks up an instance.
+-- | Looks up an instance by name or uuid.
 getInstance :: ConfigData -> String -> ErrorResult Instance
 getInstance cfg name =
-  getItem "Instance" name (fromContainer $ configInstances cfg)
+  let instances = fromContainer (configInstances cfg)
+  in case getItem "Instance" name instances of
+       -- if not found by uuid, we need to look it up by name
+       Ok inst -> Ok inst
+       Bad _ -> let by_name = M.mapKeys
+                              (instName . (M.!) instances) instances
+                in getItem "Instance" name by_name
 
--- | Looks up a node group. This is more tricky than for
--- node/instances since the groups map is indexed by uuid, not name.
+-- | Looks up a node group by name or uuid.
 getGroup :: ConfigData -> String -> ErrorResult NodeGroup
 getGroup cfg name =
   let groups = fromContainer (configNodegroups cfg)
@@ -207,7 +219,7 @@ getGroupNodes cfg gname =
 -- | Get (primary, secondary) instances of a given node group.
 getGroupInstances :: ConfigData -> String -> ([Instance], [Instance])
 getGroupInstances cfg gname =
-  let gnodes = map nodeName (getGroupNodes cfg gname)
+  let gnodes = map nodeUuid (getGroupNodes cfg gname)
       ginsts = map (getNodeInstances cfg) gnodes in
   (concatMap fst ginsts, concatMap snd ginsts)
 
index 73bdb16..e0dfc5f 100644 (file)
@@ -263,20 +263,6 @@ defaultBindAddr port Socket.AF_INET6 =
       Socket.SockAddrInet6 (fromIntegral port) 0 Socket.iN6ADDR_ANY 0)
 defaultBindAddr _ fam = Bad $ "Unsupported address family: " ++ show fam
 
--- | Default hints for the resolver
-resolveAddrHints :: Maybe Socket.AddrInfo
-resolveAddrHints =
-  Just Socket.defaultHints { Socket.addrFlags = [Socket.AI_NUMERICHOST,
-                                                 Socket.AI_NUMERICSERV] }
-
--- | Resolves a numeric address.
-resolveAddr :: Int -> String -> IO (Result (Socket.Family, Socket.SockAddr))
-resolveAddr port str = do
-  resolved <- Socket.getAddrInfo resolveAddrHints (Just str) (Just (show port))
-  return $ case resolved of
-             [] -> Bad "Invalid results from lookup?"
-             best:_ -> Ok (Socket.addrFamily best, Socket.addrAddress best)
-
 -- | Based on the options, compute the socket address to use for the
 -- daemon.
 parseAddress :: DaemonOptions      -- ^ Command line options
index 3c58a3f..db30441 100644 (file)
@@ -39,6 +39,7 @@ module Ganeti.DataCollectors.CLI
   , oNode
   , oConfdAddr
   , oConfdPort
+  , oInputFile
   , genericOptions
   ) where
 
@@ -64,6 +65,8 @@ data Options = Options
   , optConfdAddr   :: Maybe String   -- ^ IP address of the Confd server
   , optConfdPort   :: Maybe Int      -- ^ The port of the Confd server to
                                      -- connect to
+  , optInputFile   :: Maybe FilePath -- ^ Path to the file containing the
+                                     -- information to be parsed
   } deriving Show
 
 -- | Default values for the command line options.
@@ -77,6 +80,7 @@ defaultOptions  = Options
   , optNode        = Nothing
   , optConfdAddr   = Nothing
   , optConfdPort   = Nothing
+  , optInputFile   = Nothing
   }
 
 -- | Abbreviation for the option type.
@@ -127,6 +131,13 @@ oConfdPort =
     "Network port of the Confd server to connect to",
     OptComplInteger)
 
+oInputFile :: OptType
+oInputFile =
+  ( Option "f" ["file"]
+      (ReqArg (\ f o -> Ok o { optInputFile = Just f }) "FILE")
+      "the input FILE",
+    OptComplFile)
+
 -- | Generic options.
 genericOptions :: [GenericOptType Options]
 genericOptions =  [ oShowVer
diff --git a/src/Ganeti/DataCollectors/Diskstats.hs b/src/Ganeti/DataCollectors/Diskstats.hs
new file mode 100644 (file)
index 0000000..92d7ac3
--- /dev/null
@@ -0,0 +1,130 @@
+{-| @/proc/diskstats@ data collector.
+
+-}
+
+{-
+
+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.
+
+-}
+
+module Ganeti.DataCollectors.Diskstats
+  ( main
+  , options
+  , arguments
+  , dcName
+  , dcVersion
+  , dcFormatVersion
+  , dcCategory
+  , dcKind
+  , dcReport
+  ) where
+
+
+import qualified Control.Exception as E
+import Control.Monad
+import Data.Attoparsec.Text.Lazy as A
+import Data.Maybe
+import Data.Text.Lazy (pack, unpack)
+import qualified Text.JSON as J
+
+import qualified Ganeti.BasicTypes as BT
+import qualified Ganeti.Constants as C
+import Ganeti.Storage.Diskstats.Parser(diskstatsParser)
+import Ganeti.Common
+import Ganeti.DataCollectors.CLI
+import Ganeti.DataCollectors.Types
+import Ganeti.Utils
+
+
+-- | The default path of the diskstats status file.
+-- It is hardcoded because it is not likely to change.
+defaultFile :: FilePath
+defaultFile = C.diskstatsFile
+
+-- | The default setting for the maximum amount of not parsed character to
+-- print in case of error.
+-- It is set to use most of the screen estate on a standard 80x25 terminal.
+-- TODO: add the possibility to set this with a command line parameter.
+defaultCharNum :: Int
+defaultCharNum = 80*20
+
+-- | The name of this data collector.
+dcName :: String
+dcName = "diskstats"
+
+-- | The version of this data collector.
+dcVersion :: DCVersion
+dcVersion = DCVerBuiltin
+
+-- | The version number for the data format of this data collector.
+dcFormatVersion :: Int
+dcFormatVersion = 1
+
+-- | The category of this data collector.
+dcCategory :: Maybe DCCategory
+dcCategory = Just DCStorage
+
+-- | The kind of this data collector.
+dcKind :: DCKind
+dcKind = DCKPerf
+
+-- | The data exported by the data collector, taken from the default location.
+dcReport :: IO DCReport
+dcReport = buildDCReport defaultFile
+
+-- * Command line options
+
+options :: IO [OptType]
+options =
+  return
+    [ oInputFile
+    ]
+
+-- | The list of arguments supported by the program.
+arguments :: [ArgCompletion]
+arguments = [ArgCompletion OptComplFile 0 (Just 0)]
+
+-- | This function computes the JSON representation of the diskstats status.
+buildJsonReport :: FilePath -> IO J.JSValue
+buildJsonReport inputFile = do
+  contents <-
+    ((E.try $ readFile inputFile) :: IO (Either IOError String)) >>=
+      exitIfBad "reading from file" . either (BT.Bad . show) BT.Ok
+  diskstatsData <-
+    case A.parse diskstatsParser $ pack contents of
+      A.Fail unparsedText contexts errorMessage -> exitErr $
+        show (Prelude.take defaultCharNum $ unpack unparsedText) ++ "\n"
+          ++ show contexts ++ "\n" ++ errorMessage
+      A.Done _ diskstatsD -> return diskstatsD
+  return $ J.showJSON diskstatsData
+
+-- | This function computes the DCReport for the diskstats status.
+buildDCReport :: FilePath -> IO DCReport
+buildDCReport inputFile =
+  buildJsonReport inputFile >>=
+    buildReport dcName dcVersion dcFormatVersion dcCategory dcKind
+
+-- | Main function.
+main :: Options -> [String] -> IO ()
+main opts args = do
+  let inputFile = fromMaybe defaultFile $ optInputFile opts
+  unless (null args) . exitErr $ "This program takes exactly zero" ++
+                                  " arguments, got '" ++ unwords args ++ "'"
+  report <- buildDCReport inputFile
+  putStrLn $ J.encode report
index 944808c..fd168da 100644 (file)
@@ -47,8 +47,8 @@ import qualified Text.JSON as J
 
 import qualified Ganeti.BasicTypes as BT
 import qualified Ganeti.Constants as C
-import Ganeti.Block.Drbd.Parser(drbdStatusParser)
-import Ganeti.Block.Drbd.Types
+import Ganeti.Storage.Drbd.Parser(drbdStatusParser)
+import Ganeti.Storage.Drbd.Types
 import Ganeti.Common
 import Ganeti.Confd.Client
 import Ganeti.Confd.Types
@@ -137,17 +137,6 @@ computeStatus (DRBDStatus _ devInfos) =
       (code, strList) = foldr mergeStatuses (DCSCOk, [""]) statuses
   in DCStatus code $ intercalate "\n" strList
 
--- | Helper function for merging statuses.
-mergeStatuses :: (DCStatusCode, String) -> (DCStatusCode, [String])
-              -> (DCStatusCode, [String])
-mergeStatuses (newStat, newStr) (storedStat, storedStrs) =
-  let resStat = max newStat storedStat
-      resStrs =
-        if newStr == ""
-          then storedStrs
-          else storedStrs ++ [newStr]
-  in (resStat, resStrs)
-
 -- | Compute the status of a DRBD device and its error message.
 computeDevStatus :: DeviceInfo -> (DCStatusCode, String)
 computeDevStatus (UnconfiguredDevice _) = (DCSCOk, "")
diff --git a/src/Ganeti/DataCollectors/InstStatus.hs b/src/Ganeti/DataCollectors/InstStatus.hs
new file mode 100644 (file)
index 0000000..ec60c0b
--- /dev/null
@@ -0,0 +1,218 @@
+{-| Instance status data collector.
+
+-}
+
+{-
+
+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.
+
+-}
+
+module Ganeti.DataCollectors.InstStatus
+  ( main
+  , options
+  , arguments
+  , dcName
+  , dcVersion
+  , dcFormatVersion
+  , dcCategory
+  , dcKind
+  , dcReport
+  ) where
+
+
+import Control.Exception.Base
+import Data.List
+import Data.Maybe
+import qualified Data.Map as Map
+import Network.BSD (getHostName)
+import qualified Text.JSON as J
+
+import qualified Ganeti.BasicTypes as BT
+import Ganeti.Confd.Client
+import Ganeti.Confd.Types
+import Ganeti.Common
+import Ganeti.DataCollectors.CLI
+import Ganeti.DataCollectors.InstStatusTypes
+import Ganeti.DataCollectors.Types
+import Ganeti.Hypervisor.Xen
+import Ganeti.Hypervisor.Xen.Types
+import Ganeti.Logging
+import Ganeti.Objects
+import Ganeti.Path
+import Ganeti.Types
+import Ganeti.Utils
+
+
+-- | The name of this data collector.
+dcName :: String
+dcName = "inst-status-xen"
+
+-- | The version of this data collector.
+dcVersion :: DCVersion
+dcVersion = DCVerBuiltin
+
+-- | The version number for the data format of this data collector.
+dcFormatVersion :: Int
+dcFormatVersion = 1
+
+-- | The category of this data collector.
+dcCategory :: Maybe DCCategory
+dcCategory = Just DCInstance
+
+-- | The kind of this data collector.
+dcKind :: DCKind
+dcKind = DCKStatus
+
+-- | The report of this data collector.
+dcReport :: IO DCReport
+dcReport = buildInstStatusReport Nothing Nothing
+
+-- * Command line options
+
+options :: IO [OptType]
+options = return
+  [ oConfdAddr
+  , oConfdPort
+  ]
+
+-- | The list of arguments supported by the program.
+arguments :: [ArgCompletion]
+arguments = []
+
+-- | Get the list of instances ([primary], [secondary]) on the given node.
+-- Implemented as a function, even if used a single time, to specify in a
+-- convenient and elegant way the return data type, required in order to
+-- prevent incurring in the monomorphism restriction.
+-- The server address and the server port parameters are mainly intended
+-- for testing purposes. If they are Nothing, the default values will be used.
+getInstances
+  :: String
+  -> Maybe String
+  -> Maybe Int
+  -> IO (BT.Result ([Ganeti.Objects.Instance], [Ganeti.Objects.Instance]))
+getInstances node srvAddr srvPort = do
+  client <- getConfdClient srvAddr srvPort
+  reply <- query client ReqNodeInstances $ PlainQuery node
+  return $
+    case fmap (J.readJSON . confdReplyAnswer) reply of
+      Just (J.Ok instances) -> BT.Ok instances
+      Just (J.Error msg) -> BT.Bad msg
+      Nothing -> BT.Bad "No answer from the Confd server"
+
+-- | Try to get the reason trail for an instance. In case it is not possible,
+-- log the failure and return an empty list instead.
+getReasonTrail :: String -> IO ReasonTrail
+getReasonTrail instanceName = do
+  fileName <- getInstReasonFilename instanceName
+  content <- try $ readFile fileName
+  case content of
+    Left e -> do
+      logWarning $
+        "Unable to open the reason trail for instance " ++ instanceName ++
+        " expected at " ++ fileName ++ ": " ++ show (e :: IOException)
+      return []
+    Right trailString ->
+      case J.decode trailString of
+        J.Ok t -> return t
+        J.Error msg -> do
+          logWarning $ "Unable to parse the reason trail: " ++ msg
+          return []
+
+-- | Determine the value of the status field for the report of one instance
+computeStatusField :: AdminState -> ActualState -> DCStatus
+computeStatusField AdminDown actualState =
+  if actualState `notElem` [ActualShutdown, ActualDying]
+    then DCStatus DCSCBad "The instance is not stopped as it should be"
+    else DCStatus DCSCOk ""
+computeStatusField AdminUp ActualHung =
+  DCStatus DCSCUnknown "Instance marked as running, but it appears to be hung"
+computeStatusField AdminUp actualState =
+  if actualState `notElem` [ActualRunning, ActualBlocked]
+    then DCStatus DCSCBad "The instance is not running as it should be"
+    else DCStatus DCSCOk ""
+computeStatusField AdminOffline _ =
+  -- FIXME: The "offline" status seems not to be used anywhere in the source
+  -- code, but it is defined, so we have to consider it anyway here.
+  DCStatus DCSCUnknown "The instance is marked as offline"
+
+-- Builds the status of an instance using runtime information about the Xen
+-- Domains, their uptime information and the static information provided by
+-- the ConfD server.
+buildStatus :: Map.Map String Domain -> Map.Map Int UptimeInfo -> Instance
+  -> IO InstStatus
+buildStatus domains uptimes inst = do
+  let name = instName inst
+      currDomain = Map.lookup name domains
+      idNum = fmap domId currDomain
+      currUInfo = idNum >>= (`Map.lookup` uptimes)
+      uptime = fmap uInfoUptime currUInfo
+      adminState = instAdminState inst
+      actualState =
+        if adminState == AdminDown && isNothing currDomain
+          then ActualShutdown
+          else case currDomain of
+            (Just dom@(Domain _ _ _ _ (Just isHung))) ->
+              if isHung
+                then ActualHung
+                else domState dom
+            _ -> ActualUnknown
+      status = computeStatusField adminState actualState
+  trail <- getReasonTrail name
+  return $
+    InstStatus
+      name
+      (instUuid inst)
+      adminState
+      actualState
+      uptime
+      (instMtime inst)
+      trail
+      status
+
+-- | Compute the status code and message, given the current DRBD data
+-- The final state will have the code corresponding to the worst code of
+-- all the devices, and the error message given from the concatenation of the
+-- non-empty error messages.
+computeGlobalStatus :: [InstStatus] -> DCStatus
+computeGlobalStatus instStatusList =
+  let dcstatuses = map iStatStatus instStatusList
+      statuses = map (\s -> (dcStatusCode s, dcStatusMessage s)) dcstatuses
+      (code, strList) = foldr mergeStatuses (DCSCOk, [""]) statuses
+  in DCStatus code $ intercalate "\n" strList
+
+-- | Build the report of this data collector, containing all the information
+-- about the status of the instances.
+buildInstStatusReport :: Maybe String -> Maybe Int -> IO DCReport
+buildInstStatusReport srvAddr srvPort = do
+  node <- getHostName
+  answer <- getInstances node srvAddr srvPort
+  inst <- exitIfBad "Can't get instance info from ConfD" answer
+  domains <- getInferredDomInfo
+  uptimes <- getUptimeInfo
+  let primaryInst =  fst inst
+  iStatus <- mapM (buildStatus domains uptimes) primaryInst
+  let globalStatus = computeGlobalStatus iStatus
+      jsonReport = J.showJSON $ ReportData iStatus globalStatus
+  buildReport dcName dcVersion dcFormatVersion dcCategory dcKind jsonReport
+
+-- | Main function.
+main :: Options -> [String] -> IO ()
+main opts _ = do
+  report <- buildInstStatusReport (optConfdAddr opts) (optConfdPort opts)
+  putStrLn $ J.encode report
diff --git a/src/Ganeti/DataCollectors/InstStatusTypes.hs b/src/Ganeti/DataCollectors/InstStatusTypes.hs
new file mode 100644 (file)
index 0000000..5803c3d
--- /dev/null
@@ -0,0 +1,55 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-| Type declarations specific for the instance status data collector.
+
+-}
+
+{-
+
+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.
+
+-}
+
+module Ganeti.DataCollectors.InstStatusTypes
+  ( InstStatus(..)
+  , ReportData(..)
+  ) where
+
+
+import Ganeti.DataCollectors.Types
+import Ganeti.Hypervisor.Xen.Types
+import Ganeti.Objects
+import Ganeti.THH
+import Ganeti.Types
+
+-- | Data type representing the status of an instance to be returned.
+$(buildObject "InstStatus" "iStat"
+  [ simpleField "name"         [t| String |]
+  , simpleField "uuid"         [t| String |]
+  , simpleField "adminState"   [t| AdminState |]
+  , simpleField "actualState"  [t| ActualState |]
+  , optionalNullSerField $
+    simpleField "uptime"       [t| String |]
+  , simpleField "mtime"        [t| Double |]
+  , simpleField "state_reason" [t| ReasonTrail |]
+  , simpleField "status"       [t| DCStatus |]
+  ])
+
+$(buildObject "ReportData" "rData"
+  [ simpleField "instances" [t| [InstStatus] |]
+  , simpleField "status"    [t| DCStatus |]
+  ])
index 4e5ce13..6d95dd8 100644 (file)
@@ -28,11 +28,21 @@ module Ganeti.DataCollectors.Program (personalities) where
 import Ganeti.Common (PersonalityList)
 import Ganeti.DataCollectors.CLI (Options)
 
+import qualified Ganeti.DataCollectors.Diskstats as Diskstats
 import qualified Ganeti.DataCollectors.Drbd as Drbd
+import qualified Ganeti.DataCollectors.InstStatus as InstStatus
 
 -- | Supported binaries.
 personalities :: PersonalityList Options
-personalities = [ ("drbd",   (Drbd.main, Drbd.options, Drbd.arguments,
-                             "gathers and displays DRBD statistics in JSON\
-                             \ format"))
+personalities = [ (Drbd.dcName, (Drbd.main, Drbd.options, Drbd.arguments,
+                                 "gathers and displays DRBD statistics in JSON\
+                                 \ format"))
+                , (InstStatus.dcName, (InstStatus.main, InstStatus.options,
+                                       InstStatus.arguments,
+                                       "gathers and displays the status of the\
+                                       \ instances in JSON format"))
+                , (Diskstats.dcName, (Diskstats.main, Diskstats.options,
+                                      Diskstats.arguments,
+                                      "gathers and displays the disk usage\
+                                      \ statistics in JSON format"))
                 ]
index 0626363..80df40c 100644 (file)
@@ -34,6 +34,7 @@ module Ganeti.DataCollectors.Types
   , DCStatusCode(..)
   , DCVersion(..)
   , buildReport
+  , mergeStatuses
   ) where
 
 import Data.Char
@@ -116,6 +117,17 @@ addStatus dcStatus value = makeObj
   , ("data", value)
   ]
 
+-- | Helper function for merging statuses.
+mergeStatuses :: (DCStatusCode, String) -> (DCStatusCode, [String])
+              -> (DCStatusCode, [String])
+mergeStatuses (newStat, newStr) (storedStat, storedStrs) =
+  let resStat = max newStat storedStat
+      resStrs =
+        if newStr == ""
+          then storedStrs
+          else storedStrs ++ [newStr]
+  in (resStat, resStrs)
+
 -- | Utility function for building a report automatically adding the current
 -- timestamp (rounded up to seconds).
 -- If the version is not specified, it will be set to the value indicating
index 22dad86..80a16e4 100644 (file)
@@ -85,8 +85,11 @@ parseBaseInstance n a = do
   let errorMessage = "invalid data for instance '" ++ n ++ "'"
   let extract x = tryFromObj errorMessage a x
   disk  <- extract "disk_space_total"
-  disks <- extract "disks" >>= toArray >>= asObjectList >>=
-           mapM (flip (tryFromObj errorMessage) "size" . fromJSObject)
+  jsdisks <- extract "disks" >>= toArray >>= asObjectList
+  dsizes <- mapM (flip (tryFromObj errorMessage) "size" . fromJSObject) jsdisks
+  dspindles <- mapM (annotateResult errorMessage .
+                     flip maybeFromObj "spindles" . fromJSObject) jsdisks
+  let disks = zipWith Instance.Disk dsizes dspindles
   mem   <- extract "memory"
   vcpus <- extract "vcpus"
   tags  <- extract "tags"
@@ -130,20 +133,22 @@ parseNode ktg n a = do
   vm_capable  <- annotateResult desc $ maybeFromObj a "vm_capable"
   let vm_capable' = fromMaybe True vm_capable
   gidx <- lookupGroup ktg n guuid
-  node <- if offline || drained || not vm_capable'
-            then return $ Node.create n 0 0 0 0 0 0 True 0 gidx
-            else do
-              mtotal <- extract "total_memory"
-              mnode  <- extract "reserved_memory"
-              mfree  <- extract "free_memory"
-              dtotal <- extract "total_disk"
-              dfree  <- extract "free_disk"
-              ctotal <- extract "total_cpus"
-              ndparams <- extract "ndparams" >>= asJSObject
-              spindles <- tryFromObj desc (fromJSObject ndparams)
-                          "spindle_count"
-              return $ Node.create n mtotal mnode mfree
-                     dtotal dfree ctotal False spindles gidx
+  ndparams <- extract "ndparams" >>= asJSObject
+  excl_stor <- tryFromObj desc (fromJSObject ndparams) "exclusive_storage"
+  let live = not offline && not drained && vm_capable'
+      lvextract def = eitherLive live def . extract
+  sptotal <- if excl_stor
+             then lvextract 0 "total_spindles"
+             else tryFromObj desc (fromJSObject ndparams) "spindle_count"
+  spfree <- lvextract 0 "free_spindles"
+  mtotal <- lvextract 0.0 "total_memory"
+  mnode  <- lvextract 0 "reserved_memory"
+  mfree  <- lvextract 0 "free_memory"
+  dtotal <- lvextract 0.0 "total_disk"
+  dfree  <- lvextract 0 "free_disk"
+  ctotal <- lvextract 0.0 "total_cpus"
+  let node = Node.create n mtotal mnode mfree dtotal dfree ctotal (not live)
+             sptotal spfree gidx excl_stor
   return (n, node)
 
 -- | Parses a group as found in the cluster group list.
index 71ef347..e0e75cd 100644 (file)
@@ -83,6 +83,11 @@ fromJValWithStatus (st, v) = do
   st' <- fromJVal st
   Qlang.checkRS st' v >>= fromJVal
 
+annotateConvert :: String -> String -> String -> Result a -> Result a
+annotateConvert otype oname oattr =
+  annotateResult $ otype ++ " '" ++ oname ++
+    "', error while reading attribute '" ++ oattr ++ "'"
+
 -- | Annotate errors when converting values with owner/attribute for
 -- better debugging.
 genericConvert :: (Text.JSON.JSON a) =>
@@ -92,9 +97,18 @@ genericConvert :: (Text.JSON.JSON a) =>
                -> (JSValue, JSValue) -- ^ The value we're trying to convert
                -> Result a           -- ^ The annotated result
 genericConvert otype oname oattr =
-  annotateResult (otype ++ " '" ++ oname ++
-                  "', error while reading attribute '" ++
-                  oattr ++ "'") . fromJValWithStatus
+  annotateConvert otype oname oattr . fromJValWithStatus
+
+convertArrayMaybe :: (Text.JSON.JSON a) =>
+                  String             -- ^ The object type
+               -> String             -- ^ The object name
+               -> String             -- ^ The attribute we're trying to convert
+               -> (JSValue, JSValue) -- ^ The value we're trying to convert
+               -> Result [Maybe a]   -- ^ The annotated result
+convertArrayMaybe otype oname oattr (st, v) = do
+  st' <- fromJVal st
+  Qlang.checkRS st' v >>=
+    annotateConvert otype oname oattr . arrayMaybeFromJVal
 
 -- * Data querying functionality
 
@@ -104,7 +118,8 @@ queryNodesMsg =
   L.Query (Qlang.ItemTypeOpCode Qlang.QRNode)
      ["name", "mtotal", "mnode", "mfree", "dtotal", "dfree",
       "ctotal", "offline", "drained", "vm_capable",
-      "ndp/spindle_count", "group.uuid"] Qlang.EmptyFilter
+      "ndp/spindle_count", "group.uuid", "tags",
+      "ndp/exclusive_storage", "sptotal", "spfree"] Qlang.EmptyFilter
 
 -- | The input data for instance query.
 queryInstancesMsg :: L.LuxiOp
@@ -113,7 +128,7 @@ queryInstancesMsg =
      ["name", "disk_usage", "be/memory", "be/vcpus",
       "status", "pnode", "snodes", "tags", "oper_ram",
       "be/auto_balance", "disk_template",
-      "be/spindle_use"] Qlang.EmptyFilter
+      "be/spindle_use", "disk.sizes", "disk.spindles"] Qlang.EmptyFilter
 
 -- | The input data for cluster query.
 queryClusterInfoMsg :: L.LuxiOp
@@ -154,7 +169,8 @@ parseInstance :: NameAssoc
               -> Result (String, Instance.Instance)
 parseInstance ktn [ name, disk, mem, vcpus
                   , status, pnode, snodes, tags, oram
-                  , auto_balance, disk_template, su ] = do
+                  , auto_balance, disk_template, su
+                  , dsizes, dspindles ] = do
   xname <- annotateResult "Parsing new instance" (fromJValWithStatus name)
   let convert a = genericConvert "Instance" xname a
   xdisk <- convert "disk_usage" disk
@@ -172,8 +188,11 @@ parseInstance ktn [ name, disk, mem, vcpus
   xauto_balance <- convert "auto_balance" auto_balance
   xdt <- convert "disk_template" disk_template
   xsu <- convert "be/spindle_use" su
-  let inst = Instance.create xname xmem xdisk [xdisk] xvcpus
-             xrunning xtags xauto_balance xpnode snode xdt xsu []
+  xdsizes <- convert "disk.sizes" dsizes
+  xdspindles <- convertArrayMaybe "Instance" xname "disk.spindles" dspindles
+  let disks = zipWith Instance.Disk xdsizes xdspindles
+      inst = Instance.create xname xmem xdisk disks
+             xvcpus xrunning xtags xauto_balance xpnode snode xdt xsu []
   return (xname, inst)
 
 parseInstance _ v = fail ("Invalid instance query result: " ++ show v)
@@ -185,26 +204,32 @@ getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg)
 -- | Construct a node from a JSON object.
 parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node)
 parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree
-              , ctotal, offline, drained, vm_capable, spindles, g_uuid ]
+              , ctotal, offline, drained, vm_capable, spindles, g_uuid
+              , tags, excl_stor, sptotal, spfree ]
     = do
   xname <- annotateResult "Parsing new node" (fromJValWithStatus name)
   let convert a = genericConvert "Node" xname a
   xoffline <- convert "offline" offline
   xdrained <- convert "drained" drained
   xvm_capable <- convert "vm_capable" vm_capable
-  xspindles <- convert "spindles" spindles
   xgdx   <- convert "group.uuid" g_uuid >>= lookupGroup ktg xname
-  node <- if xoffline || xdrained || not xvm_capable
-            then return $ Node.create xname 0 0 0 0 0 0 True xspindles xgdx
-            else do
-              xmtotal  <- convert "mtotal" mtotal
-              xmnode   <- convert "mnode" mnode
-              xmfree   <- convert "mfree" mfree
-              xdtotal  <- convert "dtotal" dtotal
-              xdfree   <- convert "dfree" dfree
-              xctotal  <- convert "ctotal" ctotal
-              return $ Node.create xname xmtotal xmnode xmfree
-                     xdtotal xdfree xctotal False xspindles xgdx
+  xtags <- convert "tags" tags
+  xexcl_stor <- convert "exclusive_storage" excl_stor
+  let live = not xoffline && not xdrained && xvm_capable
+      lvconvert def n d = eitherLive live def $ convert n d
+  xsptotal <- if xexcl_stor
+              then lvconvert 0 "sptotal" sptotal
+              else convert "spindles" spindles
+  xspfree <- lvconvert 0 "spfree" spfree
+  xmtotal <- lvconvert 0.0 "mtotal" mtotal
+  xmnode <- lvconvert 0 "mnode" mnode
+  xmfree <- lvconvert 0 "mfree" mfree
+  xdtotal <- lvconvert 0.0 "dtotal" dtotal
+  xdfree <- lvconvert 0 "dfree" dfree
+  xctotal <- lvconvert 0.0 "ctotal" ctotal
+  let node = flip Node.setNodeTags xtags $
+             Node.create xname xmtotal xmnode xmfree xdtotal xdfree
+             xctotal (not live) xsptotal xspfree xgdx xexcl_stor
   return (xname, node)
 
 parseNode _ v = fail ("Invalid node query result: " ++ show v)
index 0672e70..d9ea716 100644 (file)
@@ -122,7 +122,8 @@ parseInstance ktn a = do
   let owner_name = "Instance '" ++ name ++ "', error while parsing data"
   let extract s x = tryFromObj owner_name x s
   disk <- extract "disk_usage" a
-  disks <- extract "disk.sizes" a
+  dsizes <- extract "disk.sizes" a
+  dspindles <- tryArrayMaybeFromObj owner_name a "disk.spindles"
   beparams <- liftM fromJSObject (extract "beparams" a)
   omem <- extract "oper_ram" a
   mem <- case omem of
@@ -139,6 +140,7 @@ parseInstance ktn a = do
   auto_balance <- extract "auto_balance" beparams
   dt <- extract "disk_template" a
   su <- extract "spindle_use" beparams
+  let disks = zipWith Instance.Disk dsizes dspindles
   let inst = Instance.create name mem disk disks vcpus running tags
              auto_balance pnode snode dt su []
   return (name, inst)
@@ -154,20 +156,25 @@ parseNode ktg a = do
   vm_cap  <- annotateResult desc $ maybeFromObj a "vm_capable"
   let vm_cap' = fromMaybe True vm_cap
   ndparams <- extract "ndparams" >>= asJSObject
-  spindles <- tryFromObj desc (fromJSObject ndparams) "spindle_count"
+  excl_stor <- tryFromObj desc (fromJSObject ndparams) "exclusive_storage"
   guuid   <- annotateResult desc $ maybeFromObj a "group.uuid"
   guuid' <-  lookupGroup ktg name (fromMaybe defaultGroupID guuid)
-  node <- if offline || drained || not vm_cap'
-            then return $ Node.create name 0 0 0 0 0 0 True 0 guuid'
-            else do
-              mtotal  <- extract "mtotal"
-              mnode   <- extract "mnode"
-              mfree   <- extract "mfree"
-              dtotal  <- extract "dtotal"
-              dfree   <- extract "dfree"
-              ctotal  <- extract "ctotal"
-              return $ Node.create name mtotal mnode mfree
-                     dtotal dfree ctotal False spindles guuid'
+  let live = not offline && not drained && vm_cap'
+      lvextract def = eitherLive live def . extract
+  sptotal <- if excl_stor
+             then lvextract 0 "sptotal"
+             else tryFromObj desc (fromJSObject ndparams) "spindle_count"
+  spfree <- lvextract 0 "spfree"
+  mtotal <- lvextract 0.0 "mtotal"
+  mnode <- lvextract 0 "mnode"
+  mfree <- lvextract 0 "mfree"
+  dtotal <- lvextract 0.0 "dtotal"
+  dfree <- lvextract 0 "dfree"
+  ctotal <- lvextract 0.0 "ctotal"
+  tags <- extract "tags"
+  let node = flip Node.setNodeTags tags $
+             Node.create name mtotal mnode mfree dtotal dfree ctotal (not live)
+             sptotal spfree guuid' excl_stor
   return (name, node)
 
 -- | Construct a group from a JSON object.
index a9b31d4..858dfc5 100644 (file)
@@ -6,7 +6,7 @@ This module holds the code for parsing a cluster description.
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -51,18 +51,21 @@ apolAbbrev c | c == "p"  = return AllocPreferred
 
 -- | Parse the string description into nodes.
 parseDesc :: String -> [String]
-          -> Result (AllocPolicy, Int, Int, Int, Int, Int)
-parseDesc _ [a, n, d, m, c, s] = do
+          -> Result (AllocPolicy, Int, Int, Int, Int, Int, Bool)
+parseDesc _ [a, n, d, m, c, s, exstor] = do
   apol <- allocPolicyFromRaw a `mplus` apolAbbrev a
   ncount <- tryRead "node count" n
   disk <- annotateResult "disk size" (parseUnit d)
   mem <- annotateResult "memory size" (parseUnit m)
   cpu <- tryRead "cpu count" c
   spindles <- tryRead "spindles" s
-  return (apol, ncount, disk, mem, cpu, spindles)
+  excl_stor <- tryRead "exclusive storage" exstor
+  return (apol, ncount, disk, mem, cpu, spindles, excl_stor)
 
 parseDesc desc [a, n, d, m, c] = parseDesc desc [a, n, d, m, c, "1"]
 
+parseDesc desc [a, n, d, m, c, s] = parseDesc desc [a, n, d, m, c, s, "False"]
+
 parseDesc desc es =
   fail $ printf
          "Invalid cluster specification, expected 6 comma-separated\
@@ -75,14 +78,14 @@ createGroup :: Int    -- ^ The group index
             -> String -- ^ The group specification
             -> Result (Group.Group, [Node.Node])
 createGroup grpIndex spec = do
-  (apol, ncount, disk, mem, cpu, spindles) <- parseDesc spec $
-                                              sepSplit ',' spec
+  (apol, ncount, disk, mem, cpu, spindles, excl_stor) <- parseDesc spec $
+                                                         sepSplit ',' spec
   let nodes = map (\idx ->
                     flip Node.setMaster (grpIndex == 1 && idx == 1) $
                     Node.create (printf "node-%02d-%03d" grpIndex idx)
                       (fromIntegral mem) 0 mem
                       (fromIntegral disk) disk
-                      (fromIntegral cpu) False spindles grpIndex
+                      (fromIntegral cpu) False spindles 0 grpIndex excl_stor
                   ) [1..ncount]
       -- TODO: parse networks to which this group is connected
       grp = Group.create (printf "group-%02d" grpIndex)
index 8ba93cc..360b3da 100644 (file)
@@ -34,6 +34,7 @@ module Ganeti.HTools.Backend.Text
   , loadISpec
   , loadMultipleMinMaxISpecs
   , loadIPolicy
+  , serializeInstance
   , serializeInstances
   , serializeNode
   , serializeNodes
@@ -83,13 +84,16 @@ serializeNode :: Group.List -- ^ The list of groups (needed for group uuid)
               -> Node.Node  -- ^ The node to be serialised
               -> String
 serializeNode gl node =
-  printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c|%s|%d" (Node.name node)
+  printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c|%s|%d|%s|%s|%d" (Node.name node)
            (Node.tMem node) (Node.nMem node) (Node.fMem node)
            (Node.tDsk node) (Node.fDsk node) (Node.tCpu node)
            (if Node.offline node then 'Y' else
               if Node.isMaster node then 'M' else 'N')
            (Group.uuid grp)
-           (Node.spindleCount node)
+           (Node.tSpindles node)
+           (intercalate "," (Node.nTags node))
+           (if Node.exclStorage node then "Y" else "N")
+           (Node.fSpindles node)
     where grp = Container.find (Node.group node) gl
 
 -- | Generate node file data from node objects.
@@ -108,12 +112,16 @@ serializeInstance nl inst =
       snode = (if sidx == Node.noSecondary
                  then ""
                  else Container.nameOf nl sidx)
-  in printf "%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%d"
+  in printf "%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%d|%s"
        iname (Instance.mem inst) (Instance.dsk inst)
        (Instance.vcpus inst) (instanceStatusToRaw (Instance.runSt inst))
        (if Instance.autoBalance inst then "Y" else "N")
        pnode snode (diskTemplateToRaw (Instance.diskTemplate inst))
        (intercalate "," (Instance.allTags inst)) (Instance.spindleUse inst)
+       -- disk spindles are summed together, as it's done for disk size
+       (case Instance.getTotalSpindles inst of
+          Nothing -> "-"
+          Just x -> show x)
 
 -- | Generate instance file data from instance objects.
 serializeInstances :: Node.List -> Instance.List -> String
@@ -197,12 +205,14 @@ loadNode :: (Monad m) =>
          -> [String]              -- ^ Input data as a list of fields
          -> m (String, Node.Node) -- ^ The result, a tuple o node name
                                   -- and node object
-loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles] = do
+loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, tags,
+              excl_stor, free_spindles] = do
   gdx <- lookupGroup ktg name gu
   new_node <-
       if "?" `elem` [tm,nm,fm,td,fd,tc] || fo == "Y" then
-          return $ Node.create name 0 0 0 0 0 0 True 0 gdx
+          return $ Node.create name 0 0 0 0 0 0 True 0 0 gdx False
       else do
+        let vtags = commaSplit tags
         vtm <- tryRead name tm
         vnm <- tryRead name nm
         vfm <- tryRead name fm
@@ -210,13 +220,32 @@ loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles] = do
         vfd <- tryRead name fd
         vtc <- tryRead name tc
         vspindles <- tryRead name spindles
-        return . flip Node.setMaster (fo == "M") $
-          Node.create name vtm vnm vfm vtd vfd vtc False vspindles gdx
+        vfree_spindles <- tryRead name free_spindles
+        vexcl_stor <- case excl_stor of
+                        "Y" -> return True
+                        "N" -> return False
+                        _ -> fail $
+                             "Invalid exclusive_storage value for node '" ++
+                             name ++ "': " ++ excl_stor
+        return . flip Node.setMaster (fo == "M") . flip Node.setNodeTags vtags $
+          Node.create name vtm vnm vfm vtd vfd vtc False vspindles
+          vfree_spindles gdx vexcl_stor
   return (name, new_node)
 
 loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu] =
   loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, "1"]
 
+loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles] =
+  loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, ""]
+
+loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, tags] =
+  loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, tags, "N"]
+
+loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, tags,
+              excl_stor] =
+  loadNode ktg [name, tm, nm, fm, td, fd, tc, fo, gu, spindles, tags,
+                excl_stor, "0"]
+
 loadNode _ s = fail $ "Invalid/incomplete node data: '" ++ show s ++ "'"
 
 -- | Load an instance from a field list.
@@ -226,13 +255,13 @@ loadInst :: NameAssoc -- ^ Association list with the current nodes
                                                -- instance name and
                                                -- the instance object
 loadInst ktn [ name, mem, dsk, vcpus, status, auto_bal, pnode, snode
-             , dt, tags, su ] = do
+             , dt, tags, su, spindles ] = do
   pidx <- lookupNode ktn name pnode
   sidx <- if null snode
             then return Node.noSecondary
             else lookupNode ktn name snode
   vmem <- tryRead name mem
-  vdsk <- tryRead name dsk
+  dsize <- tryRead name dsk
   vvcpus <- tryRead name vcpus
   vstatus <- instanceStatusFromRaw status
   auto_balance <- case auto_bal of
@@ -243,17 +272,27 @@ loadInst ktn [ name, mem, dsk, vcpus, status, auto_bal, pnode, snode
   disk_template <- annotateResult ("Instance " ++ name)
                    (diskTemplateFromRaw dt)
   spindle_use <- tryRead name su
-  when (sidx == pidx) . fail $ "Instance " ++ name ++
-           " has same primary and secondary node - " ++ pnode
+  vspindles <- case spindles of
+                 "-" -> return Nothing
+                 _ -> liftM Just (tryRead name spindles)
+  let disk = Instance.Disk dsize vspindles
   let vtags = commaSplit tags
-      newinst = Instance.create name vmem vdsk [vdsk] vvcpus vstatus vtags
+      newinst = Instance.create name vmem dsize [disk] vvcpus vstatus vtags
                 auto_balance pidx sidx disk_template spindle_use []
+  when (Instance.hasSecondary newinst && sidx == pidx) . fail $
+    "Instance " ++ name ++ " has same primary and secondary node - " ++ pnode
   return (name, newinst)
 
 loadInst ktn [ name, mem, dsk, vcpus, status, auto_bal, pnode, snode
              , dt, tags ] = loadInst ktn [ name, mem, dsk, vcpus, status,
                                            auto_bal, pnode, snode, dt, tags,
                                            "1" ]
+
+loadInst ktn [ name, mem, dsk, vcpus, status, auto_bal, pnode, snode
+             , dt, tags, su ] =
+  loadInst ktn [ name, mem, dsk, vcpus, status, auto_bal, pnode, snode, dt
+               , tags, su, "-" ]
+
 loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ show s ++ "'"
 
 -- | Loads a spec from a field list.
@@ -365,7 +404,7 @@ parseData fdata = do
   {- group file: name uuid alloc_policy -}
   (ktg, gl) <- loadTabular glines loadGroup
   {- node file: name t_mem n_mem f_mem t_disk f_disk t_cpu offline grp_uuid
-                spindles -}
+                spindles tags -}
   (ktn, nl) <- loadTabular nlines (loadNode ktg)
   {- instance file: name mem disk vcpus status auto_bal pnode snode
                     disk_template tags spindle_use -}
index 073b878..e6c0520 100644 (file)
@@ -55,6 +55,7 @@ module Ganeti.HTools.CLI
   , oForce
   , oGroup
   , oIAllocSrc
+  , oIgnoreNonRedundant
   , oInstMoves
   , oJobDelay
   , genOLuxiSocket
@@ -69,10 +70,14 @@ module Ganeti.HTools.CLI
   , oNoHeaders
   , oNoSimulation
   , oNodeSim
+  , oNodeTags
+  , oOfflineMaintenance
   , oOfflineNode
+  , oOneStepOnly
   , oOutputDir
   , oPrintCommands
   , oPrintInsts
+  , oPrintMoves
   , oPrintNodes
   , oQuiet
   , oRapiMaster
@@ -81,6 +86,7 @@ module Ganeti.HTools.CLI
   , oShowHelp
   , oShowVer
   , oShowComp
+  , oSkipNonRedundant
   , oStdSpec
   , oTieredSpec
   , oVerbose
@@ -121,6 +127,7 @@ data Options = Options
   , optForce       :: Bool           -- ^ Force the execution
   , optGroup       :: Maybe GroupID  -- ^ The UUID of the group to process
   , optIAllocSrc   :: Maybe FilePath -- ^ The iallocation spec
+  , optIgnoreNonRedundant :: Bool    -- ^ Ignore non-redundant instances
   , optSelInst     :: [String]       -- ^ Instances to be excluded
   , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
   , optJobDelay    :: Double         -- ^ Delay before executing first job
@@ -135,8 +142,12 @@ data Options = Options
   , optNoHeaders   :: Bool           -- ^ Do not show a header line
   , optNoSimulation :: Bool          -- ^ Skip the rebalancing dry-run
   , optNodeSim     :: [String]       -- ^ Cluster simulation mode
+  , optNodeTags    :: Maybe [String] -- ^ List of node tags to restrict to 
   , optOffline     :: [String]       -- ^ Names of offline nodes
+  , optOfflineMaintenance :: Bool    -- ^ Pretend all instances are offline
+  , optOneStepOnly :: Bool           -- ^ Only do the first step
   , optOutPath     :: FilePath       -- ^ Path to the output directory
+  , optPrintMoves  :: Bool           -- ^ Whether to show the instance moves
   , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
   , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
   , optShowHelp    :: Bool           -- ^ Just show the help
@@ -144,6 +155,7 @@ data Options = Options
   , optShowInsts   :: Bool           -- ^ Whether to show the instance map
   , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
   , optShowVer     :: Bool           -- ^ Just show the program version
+  , optSkipNonRedundant :: Bool      -- ^ Skip nodes with non-redundant instance
   , optStdSpec     :: Maybe RSpec    -- ^ Requested standard specs
   , optTestCount   :: Maybe Int      -- ^ Optional test count override
   , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
@@ -168,6 +180,7 @@ defaultOptions  = Options
   , optForce       = False
   , optGroup       = Nothing
   , optIAllocSrc   = Nothing
+  , optIgnoreNonRedundant = False
   , optSelInst     = []
   , optLuxi        = Nothing
   , optJobDelay    = 10
@@ -182,8 +195,13 @@ defaultOptions  = Options
   , optNoHeaders   = False
   , optNoSimulation = False
   , optNodeSim     = []
+  , optNodeTags    = Nothing
+  , optSkipNonRedundant = False
   , optOffline     = []
+  , optOfflineMaintenance = False
+  , optOneStepOnly = False
   , optOutPath     = "."
+  , optPrintMoves  = False
   , optSaveCluster = Nothing
   , optShowCmds    = Nothing
   , optShowHelp    = False
@@ -217,14 +235,21 @@ parseISpecString descr inp = do
   let sp = sepSplit ',' inp
       err = Bad ("Invalid " ++ descr ++ " specification: '" ++ inp ++
                  "', expected disk,ram,cpu")
-  when (length sp /= 3) err
+  when (length sp < 3 || length sp > 4) err
   prs <- mapM (\(fn, val) -> fn val) $
          zip [ annotateResult (descr ++ " specs disk") . parseUnit
              , annotateResult (descr ++ " specs memory") . parseUnit
              , tryRead (descr ++ " specs cpus")
+             , tryRead (descr ++ " specs spindles")
              ] sp
   case prs of
-    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
+    {- Spindles are optional, so that they are not needed when exclusive storage
+       is disabled. When exclusive storage is disabled, spindles are ignored,
+       so the actual value doesn't matter. We use 1 as a default so that in
+       case someone forgets and exclusive storage is enabled, we don't run into
+       weird situations. -}
+    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk 1
+    [dsk, ram, cpu, spn] -> return $ RSpec cpu ram dsk spn
     _ -> err
 
 -- | Disk template choices.
@@ -344,6 +369,13 @@ oIAllocSrc =
    "Specify an iallocator spec as the cluster data source",
    OptComplFile)
 
+oIgnoreNonRedundant :: OptType
+oIgnoreNonRedundant =
+  (Option "" ["ignore-non-redundant"]
+   (NoArg (\ opts -> Ok opts { optIgnoreNonRedundant = True }))
+    "Pretend that there are no non-redundant instances in the cluster",
+    OptComplNone)
+
 oJobDelay :: OptType
 oJobDelay =
   (Option "" ["job-delay"]
@@ -452,6 +484,21 @@ oNodeSim =
    \ 'alloc_policy,num_nodes,disk,ram,cpu'",
    OptComplString)
 
+oNodeTags :: OptType
+oNodeTags =
+  (Option "" ["node-tags"]
+   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
+    "TAG,...") "Restrict to nodes with the given tags",
+   OptComplString)
+     
+oOfflineMaintenance :: OptType
+oOfflineMaintenance =
+  (Option "" ["offline-maintenance"]
+   (NoArg (\ opts -> Ok opts {optOfflineMaintenance = True}))
+   "Schedule offline maintenance, i.e., pretend that all instance are\
+   \ offline.",
+   OptComplNone)
+
 oOfflineNode :: OptType
 oOfflineNode =
   (Option "O" ["offline"]
@@ -459,6 +506,13 @@ oOfflineNode =
    "set node as offline",
    OptComplOneNode)
 
+oOneStepOnly :: OptType
+oOneStepOnly =
+  (Option "" ["one-step-only"]
+   (NoArg (\ opts -> Ok opts {optOneStepOnly = True}))
+   "Only do the first step",
+   OptComplNone)
+
 oOutputDir :: OptType
 oOutputDir =
   (Option "d" ["output-dir"]
@@ -484,6 +538,13 @@ oPrintInsts =
    "print the final instance map",
    OptComplNone)
 
+oPrintMoves :: OptType
+oPrintMoves =
+  (Option "" ["print-moves"]
+   (NoArg (\ opts -> Ok opts { optPrintMoves = True }))
+   "print the moves of the instances",
+   OptComplNone)
+
 oPrintNodes :: OptType
 oPrintNodes =
   (Option "p" ["print-nodes"]
@@ -518,6 +579,13 @@ oSaveCluster =
    "Save cluster state at the end of the processing to FILE",
    OptComplNone)
 
+oSkipNonRedundant :: OptType
+oSkipNonRedundant =
+  (Option "" ["skip-non-redundant"]
+   (NoArg (\ opts -> Ok opts { optSkipNonRedundant = True }))
+    "Skip nodes that host a non-redundant instance",
+    OptComplNone)
+
 oStdSpec :: OptType
 oStdSpec =
   (Option "" ["standard-alloc"]
index efed5fa..4de461f 100644 (file)
@@ -151,6 +151,7 @@ data Table = Table Node.List Instance.List Score [Placement]
 data CStats = CStats
   { csFmem :: Integer -- ^ Cluster free mem
   , csFdsk :: Integer -- ^ Cluster free disk
+  , csFspn :: Integer -- ^ Cluster free spindles
   , csAmem :: Integer -- ^ Cluster allocatable mem
   , csAdsk :: Integer -- ^ Cluster allocatable disk
   , csAcpu :: Integer -- ^ Cluster allocatable cpus
@@ -159,9 +160,11 @@ data CStats = CStats
   , csMcpu :: Integer -- ^ Max node allocatable cpu
   , csImem :: Integer -- ^ Instance used mem
   , csIdsk :: Integer -- ^ Instance used disk
+  , csIspn :: Integer -- ^ Instance used spindles
   , csIcpu :: Integer -- ^ Instance used cpu
   , csTmem :: Double  -- ^ Cluster total mem
   , csTdsk :: Double  -- ^ Cluster total disk
+  , csTspn :: Double  -- ^ Cluster total spindles
   , csTcpu :: Double  -- ^ Cluster total cpus
   , csVcpu :: Integer -- ^ Cluster total virtual cpus
   , csNcpu :: Double  -- ^ Equivalent to 'csIcpu' but in terms of
@@ -223,7 +226,7 @@ instanceNodes nl inst =
 
 -- | Zero-initializer for the CStats type.
 emptyCStats :: CStats
-emptyCStats = CStats 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+emptyCStats = CStats 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 
 -- | Update stats with data from a new node.
 updateCStats :: CStats -> Node.Node -> CStats
@@ -234,7 +237,8 @@ updateCStats cs node =
                csImem = x_imem, csIdsk = x_idsk, csIcpu = x_icpu,
                csTmem = x_tmem, csTdsk = x_tdsk, csTcpu = x_tcpu,
                csVcpu = x_vcpu, csNcpu = x_ncpu,
-               csXmem = x_xmem, csNmem = x_nmem, csNinst = x_ninst
+               csXmem = x_xmem, csNmem = x_nmem, csNinst = x_ninst,
+               csFspn = x_fspn, csIspn = x_ispn, csTspn = x_tspn
              }
         = cs
       inc_amem = Node.fMem node - Node.rMem node
@@ -244,12 +248,14 @@ updateCStats cs node =
                  - Node.xMem node - Node.fMem node
       inc_icpu = Node.uCpu node
       inc_idsk = truncate (Node.tDsk node) - Node.fDsk node
+      inc_ispn = Node.tSpindles node - Node.fSpindles node
       inc_vcpu = Node.hiCpu node
       inc_acpu = Node.availCpu node
       inc_ncpu = fromIntegral (Node.uCpu node) /
                  iPolicyVcpuRatio (Node.iPolicy node)
   in cs { csFmem = x_fmem + fromIntegral (Node.fMem node)
         , csFdsk = x_fdsk + fromIntegral (Node.fDsk node)
+        , csFspn = x_fspn + fromIntegral (Node.fSpindles node)
         , csAmem = x_amem + fromIntegral inc_amem'
         , csAdsk = x_adsk + fromIntegral inc_adsk
         , csAcpu = x_acpu + fromIntegral inc_acpu
@@ -258,9 +264,11 @@ updateCStats cs node =
         , csMcpu = max x_mcpu (fromIntegral inc_acpu)
         , csImem = x_imem + fromIntegral inc_imem
         , csIdsk = x_idsk + fromIntegral inc_idsk
+        , csIspn = x_ispn + fromIntegral inc_ispn
         , csIcpu = x_icpu + fromIntegral inc_icpu
         , csTmem = x_tmem + Node.tMem node
         , csTdsk = x_tdsk + Node.tDsk node
+        , csTspn = x_tspn + fromIntegral (Node.tSpindles node)
         , csTcpu = x_tcpu + Node.tCpu node
         , csVcpu = x_vcpu + fromIntegral inc_vcpu
         , csNcpu = x_ncpu + inc_ncpu
@@ -284,24 +292,28 @@ totalResources nl =
 computeAllocationDelta :: CStats -> CStats -> AllocStats
 computeAllocationDelta cini cfin =
   let CStats {csImem = i_imem, csIdsk = i_idsk, csIcpu = i_icpu,
-              csNcpu = i_ncpu } = cini
+              csNcpu = i_ncpu, csIspn = i_ispn } = cini
       CStats {csImem = f_imem, csIdsk = f_idsk, csIcpu = f_icpu,
               csTmem = t_mem, csTdsk = t_dsk, csVcpu = f_vcpu,
-              csNcpu = f_ncpu, csTcpu = f_tcpu } = cfin
+              csNcpu = f_ncpu, csTcpu = f_tcpu,
+              csIspn = f_ispn, csTspn = t_spn } = cfin
       rini = AllocInfo { allocInfoVCpus = fromIntegral i_icpu
                        , allocInfoNCpus = i_ncpu
                        , allocInfoMem   = fromIntegral i_imem
                        , allocInfoDisk  = fromIntegral i_idsk
+                       , allocInfoSpn   = fromIntegral i_ispn
                        }
       rfin = AllocInfo { allocInfoVCpus = fromIntegral (f_icpu - i_icpu)
                        , allocInfoNCpus = f_ncpu - i_ncpu
                        , allocInfoMem   = fromIntegral (f_imem - i_imem)
                        , allocInfoDisk  = fromIntegral (f_idsk - i_idsk)
+                       , allocInfoSpn   = fromIntegral (f_ispn - i_ispn)
                        }
       runa = AllocInfo { allocInfoVCpus = fromIntegral (f_vcpu - f_icpu)
                        , allocInfoNCpus = f_tcpu - f_ncpu
                        , allocInfoMem   = truncate t_mem - fromIntegral f_imem
                        , allocInfoDisk  = truncate t_dsk - fromIntegral f_idsk
+                       , allocInfoSpn   = truncate t_spn - fromIntegral f_ispn
                        }
   in (rini, rfin, runa)
 
@@ -490,7 +502,7 @@ allocateOnSingle nl inst new_pdx =
   let p = Container.find new_pdx nl
       new_inst = Instance.setBoth inst new_pdx Node.noSecondary
   in do
-    Instance.instMatchesPolicy inst (Node.iPolicy p)
+    Instance.instMatchesPolicy inst (Node.iPolicy p) (Node.exclStorage p)
     new_p <- Node.addPri p inst
     let new_nl = Container.add new_pdx new_p nl
         new_score = compCV new_nl
@@ -504,6 +516,7 @@ allocateOnPair nl inst new_pdx new_sdx =
       tgt_s = Container.find new_sdx nl
   in do
     Instance.instMatchesPolicy inst (Node.iPolicy tgt_p)
+      (Node.exclStorage tgt_p)
     new_p <- Node.addPri tgt_p inst
     new_s <- Node.addSec tgt_s inst new_pdx
     let new_inst = Instance.setBoth inst new_pdx new_sdx
@@ -1499,9 +1512,11 @@ iMoveToJob nl il idx move =
                       Ok ne -> Just ne
       opF = OpCodes.OpInstanceMigrate
               { OpCodes.opInstanceName        = iname
+              , OpCodes.opInstanceUuid        = Nothing
               , OpCodes.opMigrationMode       = Nothing -- default
               , OpCodes.opOldLiveMode         = Nothing -- default as well
               , OpCodes.opTargetNode          = Nothing -- this is drbd
+              , OpCodes.opTargetNodeUuid      = Nothing
               , OpCodes.opAllowRuntimeChanges = False
               , OpCodes.opIgnoreIpolicy       = False
               , OpCodes.opMigrationCleanup    = False
@@ -1510,11 +1525,13 @@ iMoveToJob nl il idx move =
       opFA n = opF { OpCodes.opTargetNode = lookNode n } -- not drbd
       opR n = OpCodes.OpInstanceReplaceDisks
                 { OpCodes.opInstanceName     = iname
+                , OpCodes.opInstanceUuid     = Nothing
                 , OpCodes.opEarlyRelease     = False
                 , OpCodes.opIgnoreIpolicy    = False
                 , OpCodes.opReplaceDisksMode = OpCodes.ReplaceNewSecondary
                 , OpCodes.opReplaceDisksList = []
                 , OpCodes.opRemoteNode       = lookNode n
+                , OpCodes.opRemoteNodeUuid   = Nothing
                 , OpCodes.opIallocator       = Nothing
                 }
   in case move of
index 99c7c62..5416425 100644 (file)
@@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 module Ganeti.HTools.Instance
   ( Instance(..)
+  , Disk(..)
   , AssocList
   , List
   , create
@@ -45,6 +46,7 @@ module Ganeti.HTools.Instance
   , setBoth
   , setMovable
   , specOf
+  , getTotalSpindles
   , instBelowISpec
   , instAboveISpec
   , instMatchesPolicy
@@ -57,6 +59,8 @@ module Ganeti.HTools.Instance
   , mirrorType
   ) where
 
+import Control.Monad (liftM2)
+
 import Ganeti.BasicTypes
 import qualified Ganeti.HTools.Types as T
 import qualified Ganeti.HTools.Container as Container
@@ -65,6 +69,10 @@ import Ganeti.HTools.Nic (Nic)
 import Ganeti.Utils
 
 -- * Type declarations
+data Disk = Disk
+  { dskSize     :: Int       -- ^ Size in bytes
+  , dskSpindles :: Maybe Int -- ^ Number of spindles
+  } deriving (Show, Eq)
 
 -- | The instance type.
 data Instance = Instance
@@ -72,7 +80,7 @@ data Instance = Instance
   , alias        :: String    -- ^ The shortened name
   , mem          :: Int       -- ^ Memory of the instance
   , dsk          :: Int       -- ^ Total disk usage of the instance
-  , disks        :: [Int]     -- ^ Sizes of the individual disks
+  , disks        :: [Disk]    -- ^ Sizes of the individual disks
   , vcpus        :: Int       -- ^ Number of VCPUs
   , runSt        :: T.InstanceStatus -- ^ Original run status
   , pNode        :: T.Ndx     -- ^ Original primary node
@@ -165,7 +173,7 @@ type List = Container.Container Instance
 --
 -- Some parameters are not initialized by function, and must be set
 -- later (via 'setIdx' for example).
-create :: String -> Int -> Int -> [Int] -> Int -> T.InstanceStatus
+create :: String -> Int -> Int -> [Disk] -> Int -> T.InstanceStatus
        -> [String] -> Bool -> T.Ndx -> T.Ndx -> T.DiskTemplate -> Int
        -> [Nic] -> Instance
 create name_init mem_init dsk_init disks_init vcpus_init run_init tags_init
@@ -250,60 +258,89 @@ shrinkByType inst T.FailMem = let v = mem inst - T.unitMem
                                  then Bad "out of memory"
                                  else Ok inst { mem = v }
 shrinkByType inst T.FailDisk =
-  let newdisks = map (flip (-) T.unitDsk) $ disks inst
+  let newdisks = [d {dskSize = dskSize d - T.unitDsk}| d <- disks inst]
       v = dsk inst - (length . disks $ inst) * T.unitDsk
-  in if any (< T.unitDsk) newdisks
+  in if any (< T.unitDsk) $ map dskSize newdisks
      then Bad "out of disk"
      else Ok inst { dsk = v, disks = newdisks }
 shrinkByType inst T.FailCPU = let v = vcpus inst - T.unitCpu
                               in if v < T.unitCpu
                                  then Bad "out of vcpus"
                                  else Ok inst { vcpus = v }
+shrinkByType inst T.FailSpindles =
+  case disks inst of
+    [Disk ds sp] -> case sp of
+                      Nothing -> Bad "No spindles, shouldn't have happened"
+                      Just sp' -> let v = sp' - T.unitSpindle
+                                  in if v < T.unitSpindle
+                                     then Bad "out of spindles"
+                                     else Ok inst { disks = [Disk ds (Just v)] }
+    d -> Bad $ "Expected one disk, but found " ++ show d
 shrinkByType _ f = Bad $ "Unhandled failure mode " ++ show f
 
+-- | Get the number of disk spindles
+getTotalSpindles :: Instance -> Maybe Int
+getTotalSpindles inst =
+  foldr (liftM2 (+) . dskSpindles ) (Just 0) (disks inst)
+
 -- | Return the spec of an instance.
 specOf :: Instance -> T.RSpec
-specOf Instance { mem = m, dsk = d, vcpus = c } =
-  T.RSpec { T.rspecCpu = c, T.rspecMem = m, T.rspecDsk = d }
-
--- | Checks if an instance is smaller than a given spec. Returns
+specOf Instance { mem = m, dsk = d, vcpus = c, disks = dl } =
+  let sp = case dl of
+             [Disk _ (Just sp')] -> sp'
+             _ -> 0
+  in T.RSpec { T.rspecCpu = c, T.rspecMem = m,
+               T.rspecDsk = d, T.rspecSpn = sp }
+
+-- | Checks if an instance is smaller/bigger than a given spec. Returns
 -- OpGood for a correct spec, otherwise Bad one of the possible
 -- failure modes.
-instBelowISpec :: Instance -> T.ISpec -> T.OpResult ()
-instBelowISpec inst ispec
-  | mem inst > T.iSpecMemorySize ispec = Bad T.FailMem
-  | any (> T.iSpecDiskSize ispec) (disks inst) = Bad T.FailDisk
-  | vcpus inst > T.iSpecCpuCount ispec = Bad T.FailCPU
+instCompareISpec :: Ordering -> Instance-> T.ISpec -> Bool -> T.OpResult ()
+instCompareISpec which inst ispec exclstor
+  | which == mem inst `compare` T.iSpecMemorySize ispec = Bad T.FailMem
+  | which `elem` map ((`compare` T.iSpecDiskSize ispec) . dskSize)
+    (disks inst) = Bad T.FailDisk
+  | which == vcpus inst `compare` T.iSpecCpuCount ispec = Bad T.FailCPU
+  | exclstor &&
+    case getTotalSpindles inst of
+      Nothing -> True
+      Just sp_sum -> which == sp_sum `compare` T.iSpecSpindleUse ispec
+    = Bad T.FailSpindles
+  | not exclstor && which == spindleUse inst `compare` T.iSpecSpindleUse ispec
+    = Bad T.FailSpindles
+  | diskTemplate inst /= T.DTDiskless &&
+    which == length (disks inst) `compare` T.iSpecDiskCount ispec
+    = Bad T.FailDiskCount
   | otherwise = Ok ()
 
+-- | Checks if an instance is smaller than a given spec.
+instBelowISpec :: Instance -> T.ISpec -> Bool -> T.OpResult ()
+instBelowISpec = instCompareISpec GT
+
 -- | Checks if an instance is bigger than a given spec.
-instAboveISpec :: Instance -> T.ISpec -> T.OpResult ()
-instAboveISpec inst ispec
-  | mem inst < T.iSpecMemorySize ispec = Bad T.FailMem
-  | any (< T.iSpecDiskSize ispec) (disks inst) = Bad T.FailDisk
-  | vcpus inst < T.iSpecCpuCount ispec = Bad T.FailCPU
-  | otherwise = Ok ()
+instAboveISpec :: Instance -> T.ISpec -> Bool -> T.OpResult ()
+instAboveISpec = instCompareISpec LT
 
 -- | Checks if an instance matches a min/max specs pair
-instMatchesMinMaxSpecs :: Instance -> T.MinMaxISpecs -> T.OpResult ()
-instMatchesMinMaxSpecs inst minmax = do
-  instAboveISpec inst (T.minMaxISpecsMinSpec minmax)
-  instBelowISpec inst (T.minMaxISpecsMaxSpec minmax)
+instMatchesMinMaxSpecs :: Instance -> T.MinMaxISpecs -> Bool -> T.OpResult ()
+instMatchesMinMaxSpecs inst minmax exclstor = do
+  instAboveISpec inst (T.minMaxISpecsMinSpec minmax) exclstor
+  instBelowISpec inst (T.minMaxISpecsMaxSpec minmax) exclstor
 
 -- | Checks if an instance matches any specs of a policy
-instMatchesSpecs :: Instance -> [T.MinMaxISpecs] -> T.OpResult ()
+instMatchesSpecs :: Instance -> [T.MinMaxISpecs] -> Bool -> T.OpResult ()
  -- Return Ok for no constraints, though this should never happen
-instMatchesSpecs _ [] = Ok ()
-instMatchesSpecs inst (minmax:minmaxes) =
-  foldr eithermatch (instMatchesMinMaxSpecs inst minmax) minmaxes
-  where eithermatch mm (Bad _) = instMatchesMinMaxSpecs inst mm
+instMatchesSpecs _ [] _ = Ok ()
+instMatchesSpecs inst minmaxes exclstor =
+  -- The initial "Bad" should be always replaced by a real result
+  foldr eithermatch (Bad T.FailInternal) minmaxes
+  where eithermatch mm (Bad _) = instMatchesMinMaxSpecs inst mm exclstor
         eithermatch _ y@(Ok ()) = y
---  # See 04f231771
 
 -- | Checks if an instance matches a policy.
-instMatchesPolicy :: Instance -> T.IPolicy -> T.OpResult ()
-instMatchesPolicy inst ipol = do
-  instMatchesSpecs inst $ T.iPolicyMinMaxISpecs ipol
+instMatchesPolicy :: Instance -> T.IPolicy -> Bool -> T.OpResult ()
+instMatchesPolicy inst ipol exclstor = do
+  instMatchesSpecs inst (T.iPolicyMinMaxISpecs ipol) exclstor
   if diskTemplate inst `elem` T.iPolicyDiskTemplates ipol
     then Ok ()
     else Bad T.FailDisk
index 49109ab..07c7de7 100644 (file)
@@ -34,6 +34,7 @@ module Ganeti.HTools.Loader
   , lookupNode
   , lookupInstance
   , lookupGroup
+  , eitherLive
   , commonSuffix
   , RqType(..)
   , Request(..)
@@ -332,3 +333,8 @@ nodeIdsk node il =
   let rfind = flip Container.find il
   in sum . map (Instance.dsk . rfind)
        $ Node.pList node ++ Node.sList node
+
+-- | Get live information or a default value
+eitherLive :: (Monad m) => Bool -> a -> m a -> m a
+eitherLive True _ live_data = live_data
+eitherLive False def_data _ = return def_data
index 83a3dee..ba7457d 100644 (file)
@@ -40,6 +40,7 @@ module Ganeti.HTools.Node
   , setPri
   , setSec
   , setMaster
+  , setNodeTags
   , setMdsk
   , setMcpu
   , setPolicy
@@ -72,9 +73,12 @@ module Ganeti.HTools.Node
   , noSecondary
   , computeGroups
   , mkNodeGraph
+  , mkRebootNodeGraph
+  , haveExclStorage
   ) where
 
 import Control.Monad (liftM, liftM2)
+import Control.Applicative ((<$>), (<*>))
 import qualified Data.Foldable as Foldable
 import Data.Function (on)
 import qualified Data.Graph as Graph
@@ -108,7 +112,9 @@ data Node = Node
   , fDsk     :: Int       -- ^ Free disk space (MiB)
   , tCpu     :: Double    -- ^ Total CPU count
   , uCpu     :: Int       -- ^ Used VCPU count
-  , spindleCount :: Int   -- ^ Node spindles (spindle_count node parameter)
+  , tSpindles :: Int      -- ^ Node spindles (spindle_count node parameter,
+                          -- or actual spindles, see note below)
+  , fSpindles :: Int      -- ^ Free spindles (see note below)
   , pList    :: [T.Idx]   -- ^ List of primary instance indices
   , sList    :: [T.Idx]   -- ^ List of secondary instance indices
   , idx      :: T.Ndx     -- ^ Internal index for book-keeping
@@ -125,19 +131,31 @@ data Node = Node
                           -- threshold
   , hiCpu    :: Int       -- ^ Autocomputed from mCpu high cpu
                           -- threshold
-  , hiSpindles :: Double  -- ^ Auto-computed from policy spindle_ratio
-                          -- and the node spindle count
-  , instSpindles :: Double -- ^ Spindles used by instances
+  , hiSpindles :: Double  -- ^ Limit auto-computed from policy spindle_ratio
+                          -- and the node spindle count (see note below)
+  , instSpindles :: Double -- ^ Spindles used by instances (see note below)
   , offline  :: Bool      -- ^ Whether the node should not be used for
                           -- allocations and skipped from score
                           -- computations
   , isMaster :: Bool      -- ^ Whether the node is the master node
+  , nTags    :: [String]  -- ^ The node tags for this node
   , utilPool :: T.DynUtil -- ^ Total utilisation capacity
   , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
   , pTags    :: TagMap    -- ^ Primary instance exclusion tags and their count
   , group    :: T.Gdx     -- ^ The node's group (index)
   , iPolicy  :: T.IPolicy -- ^ The instance policy (of the node's group)
+  , exclStorage :: Bool   -- ^ Effective value of exclusive_storage
   } deriving (Show, Eq)
+{- A note on how we handle spindles
+
+With exclusive storage spindles is a resource, so we track the number of
+spindles still available (fSpindles). This is the only reliable way, as some
+spindles could be used outside of Ganeti. When exclusive storage is off,
+spindles are a way to represent disk I/O pressure, and hence we track the amount
+used by the instances. We compare it against 'hiSpindles', computed from the
+instance policy, to avoid policy violations. In both cases we store the total
+spindles in 'tSpindles'.
+-}
 
 instance T.Element Node where
   nameOf = name
@@ -205,6 +223,11 @@ decIf :: (Num a) => Bool -> a -> a -> a
 decIf True  base delta = base - delta
 decIf False base _     = base
 
+-- | Is exclusive storage enabled on any node?
+haveExclStorage :: List -> Bool
+haveExclStorage nl =
+  any exclStorage $ Container.elems nl
+
 -- * Initialization functions
 
 -- | Create a new node.
@@ -212,10 +235,10 @@ decIf False base _     = base
 -- The index and the peers maps are empty, and will be need to be
 -- update later via the 'setIdx' and 'buildPeers' functions.
 create :: String -> Double -> Int -> Int -> Double
-       -> Int -> Double -> Bool -> Int -> T.Gdx -> Node
+       -> Int -> Double -> Bool -> Int -> Int -> T.Gdx -> Bool -> Node
 create name_init mem_t_init mem_n_init mem_f_init
-       dsk_t_init dsk_f_init cpu_t_init offline_init spindles_init
-       group_init =
+       dsk_t_init dsk_f_init cpu_t_init offline_init spindles_t_init
+       spindles_f_init group_init excl_stor =
   Node { name = name_init
        , alias = name_init
        , tMem = mem_t_init
@@ -224,7 +247,8 @@ create name_init mem_t_init mem_n_init mem_f_init
        , tDsk = dsk_t_init
        , fDsk = dsk_f_init
        , tCpu = cpu_t_init
-       , spindleCount = spindles_init
+       , tSpindles = spindles_t_init
+       , fSpindles = spindles_f_init
        , uCpu = 0
        , pList = []
        , sList = []
@@ -233,23 +257,27 @@ create name_init mem_t_init mem_n_init mem_f_init
        , peers = P.empty
        , rMem = 0
        , pMem = fromIntegral mem_f_init / mem_t_init
-       , pDsk = computePDsk dsk_f_init dsk_t_init
+       , pDsk = if excl_stor
+                then computePDsk spindles_f_init $ fromIntegral spindles_t_init
+                else computePDsk dsk_f_init dsk_t_init
        , pRem = 0
        , pCpu = 0
        , offline = offline_init
        , isMaster = False
+       , nTags = []
        , xMem = 0
        , mDsk = T.defReservedDiskRatio
        , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
        , hiCpu = mCpuTohiCpu (T.iPolicyVcpuRatio T.defIPolicy) cpu_t_init
        , hiSpindles = computeHiSpindles (T.iPolicySpindleRatio T.defIPolicy)
-                      spindles_init
+                      spindles_t_init
        , instSpindles = 0
        , utilPool = T.baseUtil
        , utilLoad = T.zeroUtil
        , pTags = Map.empty
        , group = group_init
        , iPolicy = T.defIPolicy
+       , exclStorage = excl_stor
        }
 
 -- | Conversion formula from mDsk\/tDsk to loDsk.
@@ -284,6 +312,10 @@ setOffline t val = t { offline = val }
 setMaster :: Node -> Bool -> Node
 setMaster t val = t { isMaster = val }
 
+-- | Sets the node tags attribute
+setNodeTags :: Node -> [String] -> Node
+setNodeTags t val = t { nTags = val }
+
 -- | Sets the unnaccounted memory.
 setXmem :: Node -> Int -> Node
 setXmem t val = t { xMem = val }
@@ -305,7 +337,7 @@ setPolicy pol node =
   node { iPolicy = pol
        , hiCpu = mCpuTohiCpu (T.iPolicyVcpuRatio pol) (tCpu node)
        , hiSpindles = computeHiSpindles (T.iPolicySpindleRatio pol)
-                      (spindleCount node)
+                      (tSpindles node)
        }
 
 -- | Computes the maximum reserved memory for peers from a peer map.
@@ -329,9 +361,28 @@ buildPeers t il =
   in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
 
 -- | Calculate the new spindle usage
-calcSpindleUse :: Node -> Instance.Instance -> Double
-calcSpindleUse n i = incIf (Instance.usesLocalStorage i) (instSpindles n)
-                       (fromIntegral $ Instance.spindleUse i)
+calcSpindleUse ::
+                  Bool -- Action: True = adding instance, False = removing it
+               -> Node -> Instance.Instance -> Double
+calcSpindleUse _ (Node {exclStorage = True}) _ = 0.0
+calcSpindleUse act n@(Node {exclStorage = False}) i =
+  f (Instance.usesLocalStorage i) (instSpindles n)
+    (fromIntegral $ Instance.spindleUse i)
+    where
+      f :: Bool -> Double -> Double -> Double -- avoid monomorphism restriction
+      f = if act then incIf else decIf
+
+-- | Calculate the new number of free spindles
+calcNewFreeSpindles ::
+                       Bool -- Action: True = adding instance, False = removing
+                    -> Node -> Instance.Instance -> Int
+calcNewFreeSpindles _ (Node {exclStorage = False}) _ = 0
+calcNewFreeSpindles act n@(Node {exclStorage = True}) i =
+  case Instance.getTotalSpindles i of
+    Nothing -> if act
+               then -1 -- Force a spindle error, so the instance don't go here
+               else fSpindles n -- No change, as we aren't sure
+    Just s -> (if act then (-) else (+)) (fSpindles n) s
 
 -- | Assigns an instance to a node as primary and update the used VCPU
 -- count, utilisation data and tags map.
@@ -341,25 +392,32 @@ setPri t inst = t { pList = Instance.idx inst:pList t
                   , pCpu = fromIntegral new_count / tCpu t
                   , utilLoad = utilLoad t `T.addUtil` Instance.util inst
                   , pTags = addTags (pTags t) (Instance.exclTags inst)
-                  , instSpindles = calcSpindleUse t inst
+                  , instSpindles = calcSpindleUse True t inst
                   }
   where new_count = Instance.applyIfOnline inst (+ Instance.vcpus inst)
                     (uCpu t )
 
--- | Assigns an instance to a node as secondary without other updates.
+-- | Assigns an instance to a node as secondary and updates disk utilisation.
 setSec :: Node -> Instance.Instance -> Node
 setSec t inst = t { sList = Instance.idx inst:sList t
                   , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
                                           T.dskWeight (Instance.util inst) }
-                  , instSpindles = calcSpindleUse t inst
+                  , instSpindles = calcSpindleUse True t inst
                   }
   where old_load = utilLoad t
 
 -- | Computes the new 'pDsk' value, handling nodes without local disk
--- storage (we consider all their disk used).
+-- storage (we consider all their disk unused).
 computePDsk :: Int -> Double -> Double
 computePDsk _    0     = 1
-computePDsk used total = fromIntegral used / total
+computePDsk free total = fromIntegral free / total
+
+-- | Computes the new 'pDsk' value, handling the exclusive storage state.
+computeNewPDsk :: Node -> Int -> Int -> Double
+computeNewPDsk node new_free_sp new_free_dsk =
+  if exclStorage node
+  then computePDsk new_free_sp . fromIntegral $ tSpindles node
+  else computePDsk new_free_dsk $ tDsk node
 
 -- * Update functions
 
@@ -379,9 +437,10 @@ removePri t inst =
       new_plist = delete iname (pList t)
       new_mem = incIf i_online (fMem t) (Instance.mem inst)
       new_dsk = incIf uses_disk (fDsk t) (Instance.dsk inst)
-      new_spindles = decIf uses_disk (instSpindles t) 1
+      new_free_sp = calcNewFreeSpindles False t inst
+      new_inst_sp = calcSpindleUse False t inst
       new_mp = fromIntegral new_mem / tMem t
-      new_dp = computePDsk new_dsk (tDsk t)
+      new_dp = computeNewPDsk t new_free_sp new_dsk
       new_failn1 = new_mem <= rMem t
       new_ucpu = decIf i_online (uCpu t) (Instance.vcpus inst)
       new_rcpu = fromIntegral new_ucpu / tCpu t
@@ -390,7 +449,7 @@ removePri t inst =
        , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
        , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
        , pTags = delTags (pTags t) (Instance.exclTags inst)
-       , instSpindles = new_spindles
+       , instSpindles = new_inst_sp, fSpindles = new_free_sp
        }
 
 -- | Removes a secondary instance.
@@ -402,7 +461,8 @@ removeSec t inst =
       pnode = Instance.pNode inst
       new_slist = delete iname (sList t)
       new_dsk = incIf uses_disk cur_dsk (Instance.dsk inst)
-      new_spindles = decIf uses_disk (instSpindles t) 1
+      new_free_sp = calcNewFreeSpindles False t inst
+      new_inst_sp = calcSpindleUse False t inst
       old_peers = peers t
       old_peem = P.find pnode old_peers
       new_peem = decIf (Instance.usesSecMem inst) old_peem (Instance.mem inst)
@@ -415,14 +475,14 @@ removeSec t inst =
                    else computeMaxRes new_peers
       new_prem = fromIntegral new_rmem / tMem t
       new_failn1 = fMem t <= new_rmem
-      new_dp = computePDsk new_dsk (tDsk t)
+      new_dp = computeNewPDsk t new_free_sp new_dsk
       old_load = utilLoad t
       new_load = old_load { T.dskWeight = T.dskWeight old_load -
                                           T.dskWeight (Instance.util inst) }
   in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
        , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
        , pRem = new_prem, utilLoad = new_load
-       , instSpindles = new_spindles
+       , instSpindles = new_inst_sp, fSpindles = new_free_sp
        }
 
 -- | Adds a primary instance (basic version).
@@ -446,11 +506,12 @@ addPriEx force t inst =
       cur_dsk = fDsk t
       new_mem = decIf i_online (fMem t) (Instance.mem inst)
       new_dsk = decIf uses_disk cur_dsk (Instance.dsk inst)
-      new_spindles = incIf uses_disk (instSpindles t) 1
+      new_free_sp = calcNewFreeSpindles True t inst
+      new_inst_sp = calcSpindleUse True t inst
       new_failn1 = new_mem <= rMem t
       new_ucpu = incIf i_online (uCpu t) (Instance.vcpus inst)
       new_pcpu = fromIntegral new_ucpu / tCpu t
-      new_dp = computePDsk new_dsk (tDsk t)
+      new_dp = computeNewPDsk t new_free_sp new_dsk
       l_cpu = T.iPolicyVcpuRatio $ iPolicy t
       new_load = utilLoad t `T.addUtil` Instance.util inst
       inst_tags = Instance.exclTags inst
@@ -459,9 +520,9 @@ addPriEx force t inst =
   in case () of
        _ | new_mem <= 0 -> Bad T.FailMem
          | uses_disk && new_dsk <= 0 -> Bad T.FailDisk
-         | uses_disk && mDsk t > new_dp && strict -> Bad T.FailDisk
-         | uses_disk && new_spindles > hiSpindles t
-             && strict -> Bad T.FailDisk
+         | uses_disk && new_dsk < loDsk t && strict -> Bad T.FailDisk
+         | uses_disk && exclStorage t && new_free_sp < 0 -> Bad T.FailSpindles
+         | uses_disk && new_inst_sp > hiSpindles t && strict -> Bad T.FailDisk
          | new_failn1 && not (failN1 t) && strict -> Bad T.FailMem
          | l_cpu >= 0 && l_cpu < new_pcpu && strict -> Bad T.FailCPU
          | rejectAddTags old_tags inst_tags -> Bad T.FailTags
@@ -473,7 +534,8 @@ addPriEx force t inst =
                      , uCpu = new_ucpu, pCpu = new_pcpu
                      , utilLoad = new_load
                      , pTags = addTags old_tags inst_tags
-                     , instSpindles = new_spindles
+                     , instSpindles = new_inst_sp
+                     , fSpindles = new_free_sp
                      }
            in Ok r
 
@@ -488,7 +550,8 @@ addSecEx force t inst pdx =
       old_peers = peers t
       old_mem = fMem t
       new_dsk = fDsk t - Instance.dsk inst
-      new_spindles = instSpindles t + 1
+      new_free_sp = calcNewFreeSpindles True t inst
+      new_inst_sp = calcSpindleUse True t inst
       secondary_needed_mem = if Instance.usesSecMem inst
                                then Instance.mem inst
                                else 0
@@ -497,7 +560,7 @@ addSecEx force t inst pdx =
       new_rmem = max (rMem t) new_peem
       new_prem = fromIntegral new_rmem / tMem t
       new_failn1 = old_mem <= new_rmem
-      new_dp = computePDsk new_dsk (tDsk t)
+      new_dp = computeNewPDsk t new_free_sp new_dsk
       old_load = utilLoad t
       new_load = old_load { T.dskWeight = T.dskWeight old_load +
                                           T.dskWeight (Instance.util inst) }
@@ -505,8 +568,9 @@ addSecEx force t inst pdx =
   in case () of
        _ | not (Instance.hasSecondary inst) -> Bad T.FailDisk
          | new_dsk <= 0 -> Bad T.FailDisk
-         | mDsk t > new_dp && strict -> Bad T.FailDisk
-         | new_spindles > hiSpindles t && strict -> Bad T.FailDisk
+         | new_dsk < loDsk t && strict -> Bad T.FailDisk
+         | exclStorage t && new_free_sp < 0 -> Bad T.FailSpindles
+         | new_inst_sp > hiSpindles t && strict -> Bad T.FailDisk
          | secondary_needed_mem >= old_mem && strict -> Bad T.FailMem
          | new_failn1 && not (failN1 t) && strict -> Bad T.FailMem
          | otherwise ->
@@ -515,7 +579,8 @@ addSecEx force t inst pdx =
                      , peers = new_peers, failN1 = new_failn1
                      , rMem = new_rmem, pDsk = new_dp
                      , pRem = new_prem, utilLoad = new_load
-                     , instSpindles = new_spindles
+                     , instSpindles = new_inst_sp
+                     , fSpindles = new_free_sp
                      }
            in Ok r
 
@@ -582,11 +647,37 @@ nodesToBounds nl = liftM2 (,) nmin nmax
     where nmin = fmap (fst . fst) (IntMap.minViewWithKey nl)
           nmax = fmap (fst . fst) (IntMap.maxViewWithKey nl)
 
+-- | The clique of the primary nodes of the instances with a given secondary.
+-- Return the full graph of those nodes that are primary node of at least one
+-- instance that has the given node as secondary.
+nodeToSharedSecondaryEdge :: Instance.List -> Node -> [Graph.Edge]
+nodeToSharedSecondaryEdge il n = (,) <$> primaries <*> primaries
+  where primaries = map (Instance.pNode . flip Container.find il) $ sList n
+
+
+-- | Predicate of an edge having both vertices in a set of nodes.
+filterValid :: List -> [Graph.Edge] -> [Graph.Edge]
+filterValid nl  =  filter $ \(x,y) -> IntMap.member x nl && IntMap.member y nl
+
 -- | Transform a Node + Instance list into a NodeGraph type.
 -- Returns Nothing if the node list is empty.
 mkNodeGraph :: List -> Instance.List -> Maybe Graph.Graph
 mkNodeGraph nl il =
-  liftM (`Graph.buildG` instancesToEdges il) (nodesToBounds nl)
+  liftM (`Graph.buildG` (filterValid nl . instancesToEdges $ il))
+  (nodesToBounds nl)
+
+-- | Transform a Nodes + Instances into a NodeGraph with all reboot exclusions.
+-- This includes edges between nodes that are the primary nodes of instances
+-- that have the same secondary node. Nodes not in the node list will not be
+-- part of the graph, but they are still considered for the edges arising from
+-- two instances having the same secondary node.
+-- Return Nothing if the node list is empty.
+mkRebootNodeGraph :: List -> List -> Instance.List -> Maybe Graph.Graph
+mkRebootNodeGraph allnodes nl il =
+  liftM (`Graph.buildG` filterValid nl edges) (nodesToBounds nl)
+  where
+    edges = instancesToEdges il `union`
+            (Container.elems allnodes >>= nodeToSharedSecondaryEdge il) 
 
 -- * Display functions
 
@@ -628,7 +719,7 @@ showField t field =
     "ptags" -> intercalate "," . map (uncurry (printf "%s=%d")) .
                Map.toList $ pTags t
     "peermap" -> show $ peers t
-    "spindle_count" -> show $ spindleCount t
+    "spindle_count" -> show $ tSpindles t
     "hi_spindles" -> show $ hiSpindles t
     "inst_spindles" -> show $ instSpindles t
     _ -> T.unknownField
index 520deeb..cb18085 100644 (file)
@@ -292,6 +292,7 @@ detectBroken nl inst =
          Just (
            ArReinstall,
            [ OpInstanceRecreateDisks { opInstanceName = iname
+                                     , opInstanceUuid = Nothing
                                      , opRecreateDisksInfo = RecreateDisksAll
                                      , opNodes = []
                                        -- FIXME: there should be a better way to
@@ -299,9 +300,11 @@ detectBroken nl inst =
                                        -- mkNonEmpty in this way (using the fact
                                        -- that Maybe is used both for optional
                                        -- fields, and to express failure).
+                                     , opNodeUuids = Nothing
                                      , opIallocator = mkNonEmpty "hail"
                                      }
            , OpInstanceReinstall { opInstanceName = iname
+                                 , opInstanceUuid = Nothing
                                  , opOsType = Nothing
                                  , opTempOsParams = Nothing
                                  , opForceVariant = False
@@ -311,11 +314,13 @@ detectBroken nl inst =
          Just (
            ArFailover,
            [ OpInstanceFailover { opInstanceName = iname
+                                , opInstanceUuid = Nothing
                                   -- FIXME: ditto, see above.
                                 , opShutdownTimeout = fromJust $ mkNonNegative
                                                       C.defaultShutdownTimeout
                                 , opIgnoreConsistency = False
                                 , opTargetNode = Nothing
+                                , opTargetNodeUuid = Nothing
                                 , opIgnoreIpolicy = False
                                 , opIallocator = Nothing
                                 }
@@ -324,10 +329,12 @@ detectBroken nl inst =
          Just (
            ArFixStorage,
            [ OpInstanceReplaceDisks { opInstanceName = iname
+                                    , opInstanceUuid = Nothing
                                     , opReplaceDisksMode = ReplaceNewSecondary
                                     , opReplaceDisksList = []
                                     , opRemoteNode = Nothing
                                       -- FIXME: ditto, see above.
+                                    , opRemoteNodeUuid = Nothing
                                     , opIallocator = mkNonEmpty "hail"
                                     , opEarlyRelease = False
                                     , opIgnoreIpolicy = False
@@ -340,12 +347,15 @@ detectBroken nl inst =
          Just (
            ArReinstall,
            [ OpInstanceRecreateDisks { opInstanceName = iname
+                                     , opInstanceUuid = Nothing
                                      , opRecreateDisksInfo = RecreateDisksAll
                                      , opNodes = []
                                        -- FIXME: ditto, see above.
+                                     , opNodeUuids = Nothing
                                      , opIallocator = mkNonEmpty "hail"
                                      }
            , OpInstanceReinstall { opInstanceName = iname
+                                 , opInstanceUuid = Nothing
                                  , opOsType = Nothing
                                  , opTempOsParams = Nothing
                                  , opForceVariant = False
@@ -410,6 +420,7 @@ doRepair client delay instData (rtype, opcodes) =
                 OpTestDelay { opDelayDuration = delay
                             , opDelayOnMaster = True
                             , opDelayOnNodes = []
+                            , opDelayOnNodeUuids = Nothing
                             , opDelayRepeat = fromJust $ mkNonNegative 0
                             } : opcodes
               else
index 72e8632..e0f56b0 100644 (file)
@@ -29,21 +29,28 @@ module Ganeti.HTools.Program.Hroller
   , arguments
   ) where
 
+import Control.Applicative
+import Control.Arrow
 import Control.Monad
+import Data.Function
 import Data.List
 import Data.Ord
+import Text.Printf
 
 import qualified Data.IntMap as IntMap
 
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Node as Node
+import qualified Ganeti.HTools.Instance as Instance
 import qualified Ganeti.HTools.Group as Group
 
+import Ganeti.BasicTypes
 import Ganeti.Common
 import Ganeti.HTools.CLI
 import Ganeti.HTools.ExtLoader
 import Ganeti.HTools.Graph
 import Ganeti.HTools.Loader
+import Ganeti.HTools.Types
 import Ganeti.Utils
 
 -- | Options list and functions.
@@ -56,18 +63,82 @@ options = do
     , oDataFile
     , oIAllocSrc
     , oOfflineNode
+    , oOfflineMaintenance
     , oVerbose
     , oQuiet
     , oNoHeaders
+    , oNodeTags
     , oSaveCluster
     , oGroup
+    , oPrintMoves
+    , oSkipNonRedundant
+    , oIgnoreNonRedundant
     , oForce
+    , oOneStepOnly
     ]
 
 -- | The list of arguments supported by the program.
 arguments :: [ArgCompletion]
 arguments = []
 
+-- | Compute the result of moving an instance to a different node.
+move :: Idx -> Ndx -> (Node.List, Instance.List)
+        -> OpResult (Node.List, Instance.List)
+move idx new_ndx (nl, il) = do
+  let new_node = Container.find new_ndx nl
+      inst = Container.find idx il
+      old_ndx = Instance.pNode inst
+      old_node = Container.find old_ndx nl
+  new_node' <- Node.addPriEx True new_node inst
+  let old_node' = Node.removePri old_node inst
+      inst' = Instance.setPri inst new_ndx
+      nl' = Container.addTwo old_ndx old_node' new_ndx new_node' nl
+      il' = Container.add idx inst' il
+  return (nl', il')
+
+-- | Move an instance to one of the candidate nodes mentioned.
+locateInstance :: Idx -> [Ndx] -> (Node.List, Instance.List)
+                  -> Result (Node.List, Instance.List)
+locateInstance idx ndxs conf =
+  msum $ map (opToResult . flip (move idx) conf) ndxs
+
+-- | Move a list of instances to some of the candidate nodes mentioned.
+locateInstances :: [Idx] -> [Ndx] -> (Node.List, Instance.List)
+                   -> Result (Node.List, Instance.List)
+locateInstances idxs ndxs conf =
+  foldM (\ cf idx -> locateInstance idx ndxs cf) conf idxs
+
+-- | Greedily move the non-redundant instances away from a list of nodes.
+-- The arguments are the list of nodes to be cleared, a list of nodes the
+-- instances can be moved to, and an initial configuration. Returned is a
+-- list of nodes that can be cleared simultaneously and the configuration
+-- after these nodes are cleared.
+clearNodes :: [Ndx] -> [Ndx] -> (Node.List, Instance.List)
+              -> Result ([Ndx], (Node.List, Instance.List))
+clearNodes [] _ conf = return ([], conf)
+clearNodes (ndx:ndxs) targets conf@(nl, _) =
+  withFirst `mplus` withoutFirst where
+  withFirst = do
+     let othernodes = delete ndx targets
+         grp = Node.group $ Container.find ndx nl
+         othernodesSameGroup =
+           filter ((==) grp . Node.group . flip Container.find nl) othernodes
+     conf' <- locateInstances (nonRedundant conf ndx) othernodesSameGroup conf
+     (ndxs', conf'') <- clearNodes ndxs othernodes conf'
+     return (ndx:ndxs', conf'')
+  withoutFirst = clearNodes ndxs targets conf
+
+-- | Parition a list of nodes into chunks according cluster capacity.
+partitionNonRedundant :: [Ndx] -> [Ndx] -> (Node.List, Instance.List)
+                         -> Result [([Ndx], (Node.List, Instance.List))]
+partitionNonRedundant [] _ _ = return []
+partitionNonRedundant ndxs targets conf = do
+  (grp, conf') <- clearNodes ndxs targets conf
+  guard . not . null $ grp
+  let remaining = ndxs \\ grp
+  part <- partitionNonRedundant remaining targets conf
+  return $ (grp, conf') : part
+
 -- | Gather statistics for the coloring algorithms.
 -- Returns a string with a summary on how each algorithm has performed,
 -- in order of non-decreasing effectiveness, and whether it tied or lost
@@ -86,26 +157,45 @@ getStats colorings = snd . foldr helper (0,"") $ algBySize colorings
             | otherwise = (elsize, str ++ " LOOSE " ++ algostat el)
               where elsize = (IntMap.size.snd) el
 
--- | Filter the output list.
--- Only online nodes are shown, optionally belonging to a particular target
--- nodegroup.  Output groups which are empty after being filtered are removed
--- as well.
-filterOutput :: Maybe Group.Group -> [[Node.Node]] -> [[Node.Node]]
-filterOutput g l =
-  let onlineOnly = filter (not . Node.offline)
-      hasGroup grp node = Node.group node == Group.idx grp
-      byGroupOnly Nothing xs = xs
-      byGroupOnly (Just grp) xs = filter (hasGroup grp) xs
-      nonNullOnly = filter (not . null)
-  in nonNullOnly (map (onlineOnly . byGroupOnly g) l)
+-- | Predicate of belonging to a given group restriction.
+hasGroup :: Maybe Group.Group -> Node.Node -> Bool
+hasGroup Nothing _ = True
+hasGroup (Just grp) node = Node.group node == Group.idx grp
+
+-- | Predicate of having at least one tag in a given set.
+hasTag :: Maybe [String] -> Node.Node -> Bool
+hasTag Nothing _ = True
+hasTag (Just tags) node = not . null $ Node.nTags node `intersect` tags
+
+-- | From a cluster configuration, get the list of non-redundant instances
+-- of a node.
+nonRedundant :: (Node.List, Instance.List) -> Ndx -> [Idx]
+nonRedundant (nl, il) ndx =
+  filter (not . Instance.hasSecondary . flip Container.find  il) $
+  Node.pList (Container.find ndx nl)
+
+-- | Within a cluster configuration, decide if the node hosts non-redundant
+-- Instances.
+noNonRedundant :: (Node.List, Instance.List) -> Node.Node -> Bool
+noNonRedundant conf = null . nonRedundant conf . Node.idx
 
 -- | Put the master node last.
--- Reorder a list of lists of nodes such that the master node (if present)
--- is the last node of the last group.
-masterLast :: [[Node.Node]] -> [[Node.Node]]
+-- Reorder a list groups of nodes (with additional information) such that the
+-- master node (if present) is the last node of the last group.
+masterLast :: [([Node.Node], a)] -> [([Node.Node], a)]
 masterLast rebootgroups =
-  map (uncurry (++)) . uncurry (++) . partition (null . snd) $
-  map (partition (not . Node.isMaster)) rebootgroups
+  map (first $ uncurry (++)) . uncurry (++) . partition (null . snd . fst) $
+  map (first $ partition (not . Node.isMaster)) rebootgroups
+
+-- | From two configurations compute the list of moved instances.
+getMoves :: (Node.List, Instance.List) -> (Node.List, Instance.List)
+            -> [(Instance.Instance, Node.Node)]
+getMoves (_, il) (nl', il') = do
+  ix <- Container.keys il
+  let inst = Container.find ix il
+      inst' = Container.find ix il'
+  guard $ Instance.pNode inst /= Instance.pNode inst'
+  return (inst', Container.find (Instance.pNode inst') nl')
 
 -- | Main function.
 main :: Options -> [String] -> IO ()
@@ -137,9 +227,21 @@ main opts args = do
       Nothing -> exitErr "Cannot find target group."
       Just grp -> return (Just grp)
 
+  let nodes = IntMap.filter (foldl (liftA2 (&&)) (const True)
+                             [ not . Node.offline
+                             , if optSkipNonRedundant opts
+                                  then noNonRedundant (nlf, ilf)
+                                  else const True
+                             , hasTag $ optNodeTags opts
+                             , hasGroup wantedGroup ])
+              nlf
+      mkGraph = if optOfflineMaintenance opts
+                   then Node.mkNodeGraph
+                   else Node.mkRebootNodeGraph nlf
+
   -- TODO: fail if instances are running (with option to warn only)
 
-  nodeGraph <- case Node.mkNodeGraph nlf ilf of
+  nodeGraph <- case mkGraph nodes ilf of
                      Nothing -> exitErr "Cannot create node graph"
                      Just g -> return g
 
@@ -150,16 +252,46 @@ main opts args = do
                         , ("Dcolor", colorDcolor)
                         ]
       colorings = map (\(v,a) -> (v,(colorVertMap.a) nodeGraph)) colorAlgorithms
-      smallestColoring =
+      smallestColoring = IntMap.elems $
         (snd . minimumBy (comparing (IntMap.size . snd))) colorings
-      idToNode = (`Container.find` nlf)
-      nodesRebootGroups = map (map idToNode) $ IntMap.elems smallestColoring
-      outputRebootGroups = masterLast $
-                           filterOutput wantedGroup nodesRebootGroups
-      outputRebootNames = map (map Node.name) outputRebootGroups
+      allNdx = map Node.idx $ Container.elems nlf
+      splitted = mapM (\ grp -> partitionNonRedundant grp allNdx (nlf,ilf))
+                 smallestColoring
+  rebootGroups <- if optIgnoreNonRedundant opts
+                     then return $ zip smallestColoring (repeat (nlf, ilf))
+                     else case splitted of
+                            Ok splitgroups -> return $ concat splitgroups
+                            Bad _ -> exitErr "Not enough capacity to move\ 
+                                             \ non-redundant instances"
+  let idToNode = (`Container.find` nodes)
+      nodesRebootGroups =
+        map (first $ map idToNode . filter (`IntMap.member` nodes)) rebootGroups
+      outputRebootGroups = masterLast .
+                           sortBy (flip compare `on` length . fst) $
+                           nodesRebootGroups
+      confToMoveNames = map (Instance.name *** Node.name) . getMoves (nlf, ilf)
+      namesAndMoves = map (map Node.name *** confToMoveNames) outputRebootGroups
 
   when (verbose > 1) . putStrLn $ getStats colorings
 
-  unless (optNoHeaders opts) $
-         putStrLn "'Node Reboot Groups'"
-  mapM_ (putStrLn . commaJoin) outputRebootNames
+  let showGroup = if optOneStepOnly opts
+                    then mapM_ putStrLn
+                    else putStrLn . commaJoin
+      showMoves :: [(String, String)] -> IO ()
+      showMoves = if optPrintMoves opts
+                    then mapM_ $ putStrLn . uncurry (printf "  %s %s")
+                    else const $ return ()
+      showBoth = liftM2 (>>) (showGroup . fst) (showMoves . snd)
+
+
+  if optOneStepOnly opts
+     then do
+       unless (optNoHeaders opts) $
+              putStrLn "'First Reboot Group'"
+       case namesAndMoves of
+         [] -> return ()
+         y : _ -> showBoth y
+     else do
+       unless (optNoHeaders opts) $
+              putStrLn "'Node Reboot Groups'"
+       mapM_ showBoth namesAndMoves
index 42c0aab..9f0561e 100644 (file)
@@ -128,6 +128,10 @@ dskEff = effFn Cluster.csIdsk Cluster.csTdsk
 cpuEff :: Cluster.CStats -> Double
 cpuEff = effFn Cluster.csIcpu (fromIntegral . Cluster.csVcpu)
 
+-- | Spindles efficiency.
+spnEff :: Cluster.CStats -> Double
+spnEff = effFn Cluster.csIspn Cluster.csTspn
+
 -- | Holds data for converting a 'Cluster.CStats' structure into
 -- detailed statistics.
 statsData :: [(String, Cluster.CStats -> String)]
@@ -147,6 +151,9 @@ statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
                \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
             , ("DSK_INST", printf "%d" . Cluster.csIdsk)
             , ("DSK_EFF", printf "%.8f" . dskEff)
+            , ("SPN_FREE", printf "%d" . Cluster.csFspn)
+            , ("SPN_INST", printf "%d" . Cluster.csIspn)
+            , ("SPN_EFF", printf "%.8f" . spnEff)
             , ("CPU_INST", printf "%d" . Cluster.csIcpu)
             , ("CPU_EFF", printf "%.8f" . cpuEff)
             , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
@@ -160,6 +167,10 @@ specData = [ ("MEM", printf "%d" . rspecMem)
            , ("CPU", printf "%d" . rspecCpu)
            ]
 
+-- | 'RSpec' formatting information including spindles.
+specDataSpn :: [(String, RSpec -> String)]
+specDataSpn = specData ++ [("SPN", printf "%d" . rspecSpn)]
+
 -- | List holding 'Cluster.CStats' formatting information.
 clusterData :: [(String, Cluster.CStats -> String)]
 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
@@ -168,6 +179,10 @@ clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
               , ("VCPU", printf "%d" . Cluster.csVcpu)
               ]
 
+-- | 'Cluster.CStats' formatting information including spindles
+clusterDataSpn :: [(String, Cluster.CStats -> String)]
+clusterDataSpn = clusterData ++ [("SPN", printf "%.0f" . Cluster.csTspn)]
+
 -- | Function to print stats for a given phase.
 printStats :: Phase -> Cluster.CStats -> [(String, String)]
 printStats ph cs =
@@ -182,7 +197,7 @@ printFRScores :: Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
 printFRScores ini_nl fin_nl sreason = do
   printf "  - most likely failure reason: %s\n" $ failureReason sreason::IO ()
   printClusterScores ini_nl fin_nl
-  printClusterEff (Cluster.totalResources fin_nl)
+  printClusterEff (Cluster.totalResources fin_nl) (Node.haveExclStorage fin_nl)
 
 -- | Print final stats and related metrics.
 printResults :: Bool -> Node.List -> Node.List -> Int -> Int
@@ -233,8 +248,8 @@ tieredSpecMap trl_ixes =
 -- | Formats a spec map to strings.
 formatSpecMap :: [(RSpec, Int)] -> [String]
 formatSpecMap =
-  map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
-                       (rspecDsk spec) (rspecCpu spec) cnt)
+  map (\(spec, cnt) -> printf "%d,%d,%d,%d=%d" (rspecMem spec)
+                       (rspecDsk spec) (rspecCpu spec) (rspecSpn spec) cnt)
 
 -- | Formats \"key-metrics\" values.
 formatRSpec :: String -> AllocInfo -> [(String, String)]
@@ -243,6 +258,7 @@ formatRSpec s r =
   , ("KM_" ++ s ++ "_NPU", show $ allocInfoNCpus r)
   , ("KM_" ++ s ++ "_MEM", show $ allocInfoMem r)
   , ("KM_" ++ s ++ "_DSK", show $ allocInfoDisk r)
+  , ("KM_" ++ s ++ "_SPN", show $ allocInfoSpn r)
   ]
 
 -- | Shows allocations stats.
@@ -269,6 +285,11 @@ printInstance nl i = [ Instance.name i
                      , show (Instance.mem i)
                      , show (Instance.dsk i)
                      , show (Instance.vcpus i)
+                     , if Node.haveExclStorage nl
+                       then case Instance.getTotalSpindles i of
+                              Nothing -> "?"
+                              Just sp -> show sp
+                       else ""
                      ]
 
 -- | Optionally print the allocation map.
@@ -282,7 +303,7 @@ printAllocationMap verbose msg nl ixes =
                         -- This is the numberic-or-not field
                         -- specification; the first three fields are
                         -- strings, whereas the rest are numeric
-                       [False, False, False, True, True, True]
+                       [False, False, False, True, True, True, True]
 
 -- | Formats nicely a list of resources.
 formatResources :: a -> [(String, a->String)] -> String
@@ -290,34 +311,37 @@ formatResources res =
     intercalate ", " . map (\(a, fn) -> a ++ " " ++ fn res)
 
 -- | Print the cluster resources.
-printCluster :: Bool -> Cluster.CStats -> Int -> IO ()
-printCluster True ini_stats node_count = do
-  printKeysHTS $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
+printCluster :: Bool -> Cluster.CStats -> Int -> Bool -> IO ()
+printCluster True ini_stats node_count _ = do
+  printKeysHTS $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats))
+    clusterDataSpn
   printKeysHTS [("CLUSTER_NODES", printf "%d" node_count)]
   printKeysHTS $ printStats PInitial ini_stats
 
-printCluster False ini_stats node_count = do
+printCluster False ini_stats node_count print_spn = do
+  let cldata = if print_spn then clusterDataSpn else clusterData
   printf "The cluster has %d nodes and the following resources:\n  %s.\n"
-         node_count (formatResources ini_stats clusterData)::IO ()
+         node_count (formatResources ini_stats cldata)::IO ()
   printf "There are %s initial instances on the cluster.\n"
              (if inst_count > 0 then show inst_count else "no" )
       where inst_count = Cluster.csNinst ini_stats
 
 -- | Prints the normal instance spec.
-printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> IO ()
-printISpec True ispec spec disk_template = do
-  printKeysHTS $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specData
+printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> Bool -> IO ()
+printISpec True ispec spec disk_template _ = do
+  printKeysHTS $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specDataSpn
   printKeysHTS [ (prefix ++ "_RQN", printf "%d" req_nodes) ]
   printKeysHTS [ (prefix ++ "_DISK_TEMPLATE",
                   diskTemplateToRaw disk_template) ]
       where req_nodes = Instance.requiredNodes disk_template
             prefix = specPrefix spec
 
-printISpec False ispec spec disk_template =
-  printf "%s instance spec is:\n  %s, using disk\
-         \ template '%s'.\n"
-         (specDescription spec)
-         (formatResources ispec specData) (diskTemplateToRaw disk_template)
+printISpec False ispec spec disk_template print_spn =
+  let spdata = if print_spn then specDataSpn else specData
+  in printf "%s instance spec is:\n  %s, using disk\
+            \ template '%s'.\n"
+            (specDescription spec)
+            (formatResources ispec spdata) (diskTemplateToRaw disk_template)
 
 -- | Prints the tiered results.
 printTiered :: Bool -> [(RSpec, Int)]
@@ -329,11 +353,12 @@ printTiered True spec_map nl trl_nl _ = do
 
 printTiered False spec_map ini_nl fin_nl sreason = do
   _ <- printf "Tiered allocation results:\n"
+  let spdata = if Node.haveExclStorage ini_nl then specDataSpn else specData
   if null spec_map
     then putStrLn "  - no instances allocated"
     else mapM_ (\(ispec, cnt) ->
                   printf "  - %3d instances of spec %s\n" cnt
-                           (formatResources ispec specData)) spec_map
+                           (formatResources ispec spdata)) spec_map
   printFRScores ini_nl fin_nl sreason
 
 -- | Displays the initial/final cluster scores.
@@ -343,13 +368,16 @@ printClusterScores ini_nl fin_nl = do
   printf "  -   final cluster score: %.8f\n" $ Cluster.compCV fin_nl
 
 -- | Displays the cluster efficiency.
-printClusterEff :: Cluster.CStats -> IO ()
-printClusterEff cs =
+printClusterEff :: Cluster.CStats -> Bool -> IO ()
+printClusterEff cs print_spn = do
+  let format = [("memory", memEff),
+                ("disk", dskEff),
+                ("vcpu", cpuEff)] ++
+               [("spindles", spnEff) | print_spn]
+      len = maximum $ map (length . fst) format
   mapM_ (\(s, fn) ->
-           printf "  - %s usage efficiency: %5.2f%%\n" s (fn cs * 100))
-          [("memory", memEff),
-           ("  disk", dskEff),
-           ("  vcpu", cpuEff)]
+          printf "  - %*s usage efficiency: %5.2f%%\n" len s (fn cs * 100))
+    format
 
 -- | Computes the most likely failure reason.
 failureReason :: [(FailMode, Int)] -> String
@@ -377,8 +405,9 @@ runAllocation cdata stop_allocation actual_result spec dt mode opts = do
   let name = specName mode
       descr = name ++ " allocation"
       ldescr = "after " ++ map toLower descr
+      excstor = Node.haveExclStorage new_nl
 
-  printISpec (optMachineReadable opts) spec mode dt
+  printISpec (optMachineReadable opts) spec mode dt excstor
 
   printAllocationMap (optVerbose opts) descr new_nl new_ixes
 
@@ -395,7 +424,8 @@ runAllocation cdata stop_allocation actual_result spec dt mode opts = do
 -- having a single disk).
 instFromSpec :: RSpec -> DiskTemplate -> Int -> Instance.Instance
 instFromSpec spx dt su =
-  Instance.create "new" (rspecMem spx) (rspecDsk spx) [rspecDsk spx]
+  Instance.create "new" (rspecMem spx) (rspecDsk spx)
+    [Instance.Disk (rspecDsk spx) (Just $ rspecSpn spx)]
     (rspecCpu spx) Running [] True (-1) (-1) dt su []
 
 combineTiered :: Maybe Int -> Cluster.AllocNodes -> Cluster.AllocResult ->
@@ -445,6 +475,7 @@ main opts args = do
                  (Cluster.compCV nl) (Cluster.printStats "  " nl)
 
   printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
+    (Node.haveExclStorage nl)
 
   let stop_allocation = case Cluster.computeBadItems nl il of
                           ([], _) -> Nothing
index 21d7ee1..764c56c 100644 (file)
@@ -53,6 +53,7 @@ module Ganeti.HTools.Types
   , unitMem
   , unitCpu
   , unitDsk
+  , unitSpindle
   , unknownField
   , Placement
   , IMove(..)
@@ -141,6 +142,7 @@ data RSpec = RSpec
   { rspecCpu  :: Int  -- ^ Requested VCPUs
   , rspecMem  :: Int  -- ^ Requested memory
   , rspecDsk  :: Int  -- ^ Requested disk
+  , rspecSpn  :: Int  -- ^ Requested spindles
   } deriving (Show, Eq)
 
 -- | Allocation stats type. This is used instead of 'RSpec' (which was
@@ -153,6 +155,7 @@ data AllocInfo = AllocInfo
   , allocInfoNCpus :: Double -- ^ Normalised CPUs
   , allocInfoMem   :: Int    -- ^ Memory
   , allocInfoDisk  :: Int    -- ^ Disk
+  , allocInfoSpn   :: Int    -- ^ Spindles
   } deriving (Show, Eq)
 
 -- | Currently used, possibly to allocate, unallocable.
@@ -231,6 +234,7 @@ rspecFromISpec :: ISpec -> RSpec
 rspecFromISpec ispec = RSpec { rspecCpu = iSpecCpuCount ispec
                              , rspecMem = iSpecMemorySize ispec
                              , rspecDsk = iSpecDiskSize ispec
+                             , rspecSpn = iSpecSpindleUse ispec
                              }
 
 -- | The default instance policy.
@@ -317,12 +321,19 @@ unitDsk = 256
 unitCpu :: Int
 unitCpu = 1
 
+-- | Base spindles unit.
+unitSpindle :: Int
+unitSpindle = 1
+
 -- | Reason for an operation's falure.
 data FailMode = FailMem  -- ^ Failed due to not enough RAM
               | FailDisk -- ^ Failed due to not enough disk
               | FailCPU  -- ^ Failed due to not enough CPU capacity
               | FailN1   -- ^ Failed due to not passing N1 checks
               | FailTags -- ^ Failed due to tag exclusion
+              | FailDiskCount -- ^ Failed due to wrong number of disks
+              | FailSpindles -- ^ Failed due to wrong/missing spindles
+              | FailInternal -- ^ Internal error
                 deriving (Eq, Enum, Bounded, Show)
 
 -- | List with failure statistics.
diff --git a/src/Ganeti/Hypervisor/Xen.hs b/src/Ganeti/Hypervisor/Xen.hs
new file mode 100644 (file)
index 0000000..06c928e
--- /dev/null
@@ -0,0 +1,89 @@
+{-| Module to access the information provided by the Xen hypervisor.
+
+-}
+{-
+
+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.
+
+-}
+module Ganeti.Hypervisor.Xen
+  ( getDomainsInfo
+  , getInferredDomInfo
+  , getUptimeInfo
+  --Data types to be re-exported from here
+  , Domain(..)
+  , UptimeInfo(..)
+  ) where
+
+import qualified Control.Exception as E
+import Data.Attoparsec.Text as A
+import qualified Data.Map as Map
+import Data.Text (pack)
+import System.Process
+
+import qualified Ganeti.BasicTypes as BT
+import qualified Ganeti.Constants as C
+import Ganeti.Hypervisor.Xen.Types
+import Ganeti.Hypervisor.Xen.XmParser
+import Ganeti.Utils
+
+
+-- | Get information about the current Xen domains as a map where the domain
+-- name is the key. This only includes the information made available by Xen
+-- itself.
+getDomainsInfo :: IO (Map.Map String Domain)
+getDomainsInfo = do
+  contents <-
+    ((E.try $ readProcess C.xenCmdXm ["list", "--long"] "")
+      :: IO (Either IOError String)) >>=
+      exitIfBad "running command" . either (BT.Bad . show) BT.Ok
+  case A.parseOnly xmListParser $ pack contents of
+    Left msg -> exitErr msg
+    Right dom -> return dom
+
+-- | Given a domain and a map containing information about multiple domains,
+-- infer additional information about that domain (specifically, whether it is
+-- hung).
+inferDomInfos :: Map.Map String Domain -> Domain -> Domain
+inferDomInfos domMap dom1 =
+  case Map.lookup (domName dom1) domMap of
+    Just dom2 ->
+      dom1 { domIsHung = Just $ domCpuTime dom1 == domCpuTime dom2 }
+    Nothing -> dom1 { domIsHung = Nothing }
+
+-- | Get information about the current Xen domains as a map where the domain
+-- name is the key. This includes information made available by Xen itself as
+-- well as further information that can be inferred by querying Xen multiple
+-- times and comparing the results.
+getInferredDomInfo :: IO (Map.Map String Domain)
+getInferredDomInfo = do
+  domMap1 <- getDomainsInfo
+  domMap2 <- getDomainsInfo
+  return $ fmap (inferDomInfos domMap2) domMap1
+
+-- | Get information about the uptime of domains, as a map where the domain ID
+-- is the key.
+getUptimeInfo :: IO (Map.Map Int UptimeInfo)
+getUptimeInfo = do
+  contents <-
+    ((E.try $ readProcess C.xenCmdXm ["uptime"] "")
+      :: IO (Either IOError String)) >>=
+      exitIfBad "running command" . either (BT.Bad . show) BT.Ok
+  case A.parseOnly xmUptimeParser $ pack contents of
+    Left msg -> exitErr msg
+    Right uInfo -> return uInfo
index 16c4b0a..9437c75 100644 (file)
@@ -5,7 +5,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -36,9 +36,12 @@ module Ganeti.JSON
   , fromJVal
   , jsonHead
   , getMaybeJsonHead
+  , getMaybeJsonElem
   , asJSObject
   , asObjectList
   , tryFromObj
+  , arrayMaybeFromJVal
+  , tryArrayMaybeFromObj
   , toArray
   , optionalJSField
   , optFieldsToObj
@@ -104,12 +107,16 @@ loadJSArray :: (Monad m)
                -> m [J.JSObject J.JSValue]
 loadJSArray s = fromJResult s . J.decodeStrict
 
+-- | Helper function for missing-key errors
+buildNoKeyError :: JSRecord -> String -> String
+buildNoKeyError o k =
+  printf "key '%s' not found, object contains only %s" k (show (map fst o))
+
 -- | Reads the value of a key in a JSON object.
 fromObj :: (J.JSON a, Monad m) => JSRecord -> String -> m a
 fromObj o k =
   case lookup k o of
-    Nothing -> fail $ printf "key '%s' not found, object contains only %s"
-               k (show (map fst o))
+    Nothing -> fail $ buildNoKeyError o k
     Just val -> fromKeyValue k val
 
 -- | Reads the value of an optional key in a JSON object. Missing
@@ -136,6 +143,31 @@ fromObjWithDefault :: (J.JSON a, Monad m) =>
                       JSRecord -> String -> a -> m a
 fromObjWithDefault o k d = liftM (fromMaybe d) $ maybeFromObj o k
 
+arrayMaybeFromJVal :: (J.JSON a, Monad m) => J.JSValue -> m [Maybe a]
+arrayMaybeFromJVal (J.JSArray xs) =
+  mapM parse xs
+    where
+      parse J.JSNull = return Nothing
+      parse x = liftM Just $ fromJVal x
+arrayMaybeFromJVal v =
+  fail $ "Expecting array, got '" ++ show (pp_value v) ++ "'"
+
+-- | Reads an array of optional items
+arrayMaybeFromObj :: (J.JSON a, Monad m) =>
+                     JSRecord -> String -> m [Maybe a]
+arrayMaybeFromObj o k =
+  case lookup k o of
+    Just a -> arrayMaybeFromJVal a
+    _ -> fail $ buildNoKeyError o k
+
+-- | Wrapper for arrayMaybeFromObj with better diagnostic
+tryArrayMaybeFromObj :: (J.JSON a)
+                     => String     -- ^ Textual "owner" in error messages
+                     -> JSRecord   -- ^ The object array
+                     -> String     -- ^ The desired key from the object
+                     -> Result [Maybe a]
+tryArrayMaybeFromObj t o = annotateResult t . arrayMaybeFromObj o
+
 -- | Reads a JValue, that originated from an object key.
 fromKeyValue :: (J.JSON a, Monad m)
               => String     -- ^ The key name
@@ -162,6 +194,14 @@ getMaybeJsonHead :: (J.JSON b) => [a] -> (a -> Maybe b) -> J.JSValue
 getMaybeJsonHead [] _ = J.JSNull
 getMaybeJsonHead (x:_) f = maybe J.JSNull J.showJSON (f x)
 
+-- | Helper for extracting Maybe values from a list that might be too short.
+getMaybeJsonElem :: (J.JSON b) => [a] -> Int -> (a -> Maybe b) -> J.JSValue
+getMaybeJsonElem [] _ _ = J.JSNull
+getMaybeJsonElem xs 0 f = getMaybeJsonHead xs f
+getMaybeJsonElem (_:xs) n f
+  | n < 0 = J.JSNull
+  | otherwise = getMaybeJsonElem xs (n - 1) f
+
 -- | Converts a JSON value into a JSON object.
 asJSObject :: (Monad m) => J.JSValue -> m (J.JSObject J.JSValue)
 asJSObject (J.JSObject a) = return a
index 80a5220..d6da57d 100644 (file)
@@ -42,7 +42,9 @@ import qualified Text.JSON as J
 
 import qualified Ganeti.BasicTypes as BT
 import Ganeti.Daemon
+import qualified Ganeti.DataCollectors.Diskstats as Diskstats
 import qualified Ganeti.DataCollectors.Drbd as Drbd
+import qualified Ganeti.DataCollectors.InstStatus as InstStatus
 import Ganeti.DataCollectors.Types
 import qualified Ganeti.Constants as C
 
@@ -71,7 +73,11 @@ data DataCollector = DataCollector
 -- | The list of available builtin data collectors.
 collectors :: [DataCollector]
 collectors =
-  [ DataCollector Drbd.dcName Drbd.dcCategory Drbd.dcKind Drbd.dcReport
+  [ DataCollector Diskstats.dcName Diskstats.dcCategory Diskstats.dcKind
+      Diskstats.dcReport
+  , DataCollector Drbd.dcName Drbd.dcCategory Drbd.dcKind Drbd.dcReport
+  , DataCollector InstStatus.dcName InstStatus.dcCategory InstStatus.dcKind
+      InstStatus.dcReport
   ]
 
 -- * Configuration handling
index cd8d024..433c456 100644 (file)
@@ -426,6 +426,7 @@ data Disk = Disk
   , diskSize       :: Int
   , diskMode       :: DiskMode
   , diskName       :: Maybe String
+  , diskSpindles   :: Maybe Int
   , diskUuid       :: String
   } deriving (Show, Eq)
 
@@ -438,6 +439,7 @@ $(buildObjectSerialisation "Disk" $
   , simpleField "size" [t| Int |]
   , defaultField [| DiskRdWr |] $ simpleField "mode" [t| DiskMode |]
   , optionalField $ simpleField "name" [t| String |]
+  , optionalField $ simpleField "spindles" [t| Int |]
   ]
   ++ uuidFields)
 
index 92f5644..cf3c135 100644 (file)
@@ -66,30 +66,37 @@ $(genOpCode "OpCode"
      [ pDelayDuration
      , pDelayOnMaster
      , pDelayOnNodes
+     , pDelayOnNodeUuids
      , pDelayRepeat
      ])
   , ("OpInstanceReplaceDisks",
      [ pInstanceName
+     , pInstanceUuid
      , pEarlyRelease
      , pIgnoreIpolicy
      , pReplaceDisksMode
      , pReplaceDisksList
      , pRemoteNode
+     , pRemoteNodeUuid
      , pIallocator
      ])
   , ("OpInstanceFailover",
      [ pInstanceName
+     , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreConsistency
      , pMigrationTargetNode
+     , pMigrationTargetNodeUuid
      , pIgnoreIpolicy
      , pIallocator
      ])
   , ("OpInstanceMigrate",
      [ pInstanceName
+     , pInstanceUuid
      , pMigrationMode
      , pMigrationLive
      , pMigrationTargetNode
+     , pMigrationTargetNodeUuid
      , pAllowRuntimeChgs
      , pIgnoreIpolicy
      , pMigrationCleanup
@@ -193,12 +200,16 @@ $(genOpCode "OpCode"
      ])
   , ("OpOobCommand",
      [ pNodeNames
+     , pNodeUuids
      , pOobCommand
      , pOobTimeout
      , pIgnoreStatus
      , pPowerDelay
      ])
-  , ("OpNodeRemove", [ pNodeName ])
+  , ("OpNodeRemove",
+     [ pNodeName
+     , pNodeUuid
+     ])
   , ("OpNodeAdd",
      [ pNodeName
      , pHvState
@@ -224,18 +235,21 @@ $(genOpCode "OpCode"
      ])
   , ("OpNodeModifyStorage",
      [ pNodeName
+     , pNodeUuid
      , pStorageType
      , pStorageName
      , pStorageChanges
      ])
   , ("OpRepairNodeStorage",
      [ pNodeName
+     , pNodeUuid
      , pStorageType
      , pStorageName
      , pIgnoreConsistency
      ])
   , ("OpNodeSetParams",
      [ pNodeName
+     , pNodeUuid
      , pForce
      , pHvState
      , pDiskState
@@ -251,13 +265,16 @@ $(genOpCode "OpCode"
      ])
   , ("OpNodePowercycle",
      [ pNodeName
+     , pNodeUuid
      , pForce
      ])
   , ("OpNodeMigrate",
      [ pNodeName
+     , pNodeUuid
      , pMigrationMode
      , pMigrationLive
      , pMigrationTargetNode
+     , pMigrationTargetNodeUuid
      , pAllowRuntimeChgs
      , pIgnoreIpolicy
      , pIallocator
@@ -265,7 +282,9 @@ $(genOpCode "OpCode"
   , ("OpNodeEvacuate",
      [ pEarlyRelease
      , pNodeName
+     , pNodeUuid
      , pRemoteNode
+     , pRemoteNodeUuid
      , pIallocator
      , pEvacMode
      ])
@@ -292,12 +311,15 @@ $(genOpCode "OpCode"
      , pInstOsParams
      , pInstOs
      , pPrimaryNode
+     , pPrimaryNodeUuid
      , pSecondaryNode
+     , pSecondaryNodeUuid
      , pSourceHandshake
      , pSourceInstance
      , pSourceShutdownTimeout
      , pSourceX509Ca
      , pSrcNode
+     , pSrcNodeUuid
      , pSrcPath
      , pStartInstance
      , pOpportunisticLocking
@@ -310,23 +332,27 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceReinstall",
      [ pInstanceName
+     , pInstanceUuid
      , pForceVariant
      , pInstOs
      , pTempOsParams
      ])
   , ("OpInstanceRemove",
      [ pInstanceName
+     , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreFailures
      ])
   , ("OpInstanceRename",
      [ pInstanceName
+     , pInstanceUuid
      , pNewName
      , pNameCheck
      , pIpCheck
      ])
   , ("OpInstanceStartup",
      [ pInstanceName
+     , pInstanceUuid
      , pForce
      , pIgnoreOfflineNodes
      , pTempHvParams
@@ -336,6 +362,7 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceShutdown",
      [ pInstanceName
+     , pInstanceUuid
      , pForce
      , pIgnoreOfflineNodes
      , pShutdownTimeout'
@@ -343,32 +370,41 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceReboot",
      [ pInstanceName
+     , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreSecondaries
      , pRebootType
      ])
   , ("OpInstanceMove",
      [ pInstanceName
+     , pInstanceUuid
      , pShutdownTimeout
      , pIgnoreIpolicy
      , pMoveTargetNode
+     , pMoveTargetNodeUuid
      , pIgnoreConsistency
      ])
   , ("OpInstanceConsole",
-     [ pInstanceName ])
+     [ pInstanceName
+     , pInstanceUuid
+     ])
   , ("OpInstanceActivateDisks",
      [ pInstanceName
+     , pInstanceUuid
      , pIgnoreDiskSize
      , pWaitForSyncFalse
      ])
   , ("OpInstanceDeactivateDisks",
      [ pInstanceName
+     , pInstanceUuid
      , pForce
      ])
   , ("OpInstanceRecreateDisks",
      [ pInstanceName
+     , pInstanceUuid
      , pRecreateDisksInfo
      , pNodes
+     , pNodeUuids
      , pIallocator
      ])
   , ("OpInstanceQuery", dOldQuery)
@@ -379,6 +415,7 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceSetParams",
      [ pInstanceName
+     , pInstanceUuid
      , pForce
      , pForceVariant
      , pIgnoreIpolicy
@@ -389,7 +426,9 @@ $(genOpCode "OpCode"
      , pInstHvParams
      , pOptDiskTemplate
      , pPrimaryNode
+     , pPrimaryNodeUuid
      , pRemoteNode
+     , pRemoteNodeUuid
      , pOsNameChange
      , pInstOsParams
      , pWaitForSync
@@ -398,6 +437,7 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceGrowDisk",
      [ pInstanceName
+     , pInstanceUuid
      , pWaitForSync
      , pDiskIndex
      , pDiskChgAmount
@@ -405,6 +445,7 @@ $(genOpCode "OpCode"
      ])
   , ("OpInstanceChangeGroup",
      [ pInstanceName
+     , pInstanceUuid
      , pEarlyRelease
      , pIallocator
      , pTargetGroups
@@ -422,6 +463,7 @@ $(genOpCode "OpCode"
      [ pGroupName
      , pForce
      , pRequiredNodes
+     , pRequiredNodeUuids
      ])
   , ("OpGroupQuery", dOldQueryNoLocking)
   , ("OpGroupSetParams",
@@ -457,12 +499,15 @@ $(genOpCode "OpCode"
      ])
   , ("OpBackupPrepare",
      [ pInstanceName
+     , pInstanceUuid
      , pExportMode
      ])
   , ("OpBackupExport",
      [ pInstanceName
+     , pInstanceUuid
      , pShutdownTimeout
      , pExportTargetNode
+     , pExportTargetNodeUuid
      , pShutdownInstance
      , pRemoveInstance
      , pIgnoreRemoveFailures
@@ -471,7 +516,9 @@ $(genOpCode "OpCode"
      , pX509DestCA
      ])
   , ("OpBackupRemove",
-     [ pInstanceName ])
+     [ pInstanceName
+     , pInstanceUuid
+     ])
   , ("OpTestAllocator",
      [ pIAllocatorDirection
      , pIAllocatorMode
@@ -542,6 +589,7 @@ $(genOpCode "OpCode"
   , ("OpRestrictedCommand",
      [ pUseLocking
      , pRequiredNodes
+     , pRequiredNodeUuids
      , pRestrictedCommand
      ])
   ])
index e445aa9..3cc01f1 100644 (file)
@@ -50,6 +50,7 @@ module Ganeti.OpParams
   , SetParamsMods(..)
   , ExportTarget(..)
   , pInstanceName
+  , pInstanceUuid
   , pInstances
   , pName
   , pTagsList
@@ -61,7 +62,9 @@ module Ganeti.OpParams
   , pForce
   , pIgnoreOfflineNodes
   , pNodeName
+  , pNodeUuid
   , pNodeNames
+  , pNodeUuids
   , pGroupName
   , pMigrationMode
   , pMigrationLive
@@ -82,7 +85,9 @@ module Ganeti.OpParams
   , pIpConflictsCheck
   , pNoRemember
   , pMigrationTargetNode
+  , pMigrationTargetNodeUuid
   , pMoveTargetNode
+  , pMoveTargetNodeUuid
   , pStartupPaused
   , pVerbose
   , pDebugSimulateErrors
@@ -144,6 +149,7 @@ module Ganeti.OpParams
   , pNames
   , pNodes
   , pRequiredNodes
+  , pRequiredNodeUuids
   , pStorageType
   , pStorageChanges
   , pMasterCandidate
@@ -153,17 +159,21 @@ module Ganeti.OpParams
   , pPowered
   , pIallocator
   , pRemoteNode
+  , pRemoteNodeUuid
   , pEvacMode
   , pInstCreateMode
   , pNoInstall
   , pInstOs
   , pPrimaryNode
+  , pPrimaryNodeUuid
   , pSecondaryNode
+  , pSecondaryNodeUuid
   , pSourceHandshake
   , pSourceInstance
   , pSourceShutdownTimeout
   , pSourceX509Ca
   , pSrcNode
+  , pSrcNodeUuid
   , pSrcPath
   , pStartInstance
   , pInstTags
@@ -188,6 +198,7 @@ module Ganeti.OpParams
   , pTargetGroups
   , pExportMode
   , pExportTargetNode
+  , pExportTargetNodeUuid
   , pRemoveInstance
   , pIgnoreRemoveFailures
   , pX509KeyName
@@ -200,6 +211,7 @@ module Ganeti.OpParams
   , pDelayDuration
   , pDelayOnMaster
   , pDelayOnNodes
+  , pDelayOnNodeUuids
   , pDelayRepeat
   , pIAllocatorDirection
   , pIAllocatorMode
@@ -511,6 +523,10 @@ instance JSON ExportTarget where
 pInstanceName :: Field
 pInstanceName = simpleField "instance_name" [t| String |]
 
+-- | An instance UUID (for single-instance LUs).
+pInstanceUuid :: Field
+pInstanceUuid = optionalField $ simpleField "instance_uuid" [t| String |]
+
 -- | A list of instances.
 pInstances :: Field
 pInstances = defaultField [| [] |] $
@@ -563,11 +579,20 @@ pIgnoreOfflineNodes = defaultFalse "ignore_offline_nodes"
 pNodeName :: Field
 pNodeName = simpleField "node_name" [t| NonEmptyString |]
 
+-- | A node UUID (for single-node LUs).
+pNodeUuid :: Field
+pNodeUuid = optionalField $ simpleField "node_uuid" [t| NonEmptyString |]
+
 -- | List of nodes.
 pNodeNames :: Field
 pNodeNames =
   defaultField [| [] |] $ simpleField "node_names" [t| [NonEmptyString] |]
 
+-- | List of node UUIDs.
+pNodeUuids :: Field
+pNodeUuids =
+  optionalField $ simpleField "node_uuids" [t| [NonEmptyString] |]
+
 -- | A required node group name (for single-group LUs).
 pGroupName :: Field
 pGroupName = simpleField "group_name" [t| NonEmptyString |]
@@ -656,12 +681,22 @@ pNoRemember = defaultFalse "no_remember"
 pMigrationTargetNode :: Field
 pMigrationTargetNode = optionalNEStringField "target_node"
 
+-- | Target node UUID for instance migration/failover.
+pMigrationTargetNodeUuid :: Field
+pMigrationTargetNodeUuid = optionalNEStringField "target_node_uuid"
+
 -- | Target node for instance move (required).
 pMoveTargetNode :: Field
 pMoveTargetNode =
   renameField "MoveTargetNode" $
   simpleField "target_node" [t| NonEmptyString |]
 
+-- | Target node UUID for instance move.
+pMoveTargetNodeUuid :: Field
+pMoveTargetNodeUuid =
+  renameField "MoveTargetNodeUuid" . optionalField $
+  simpleField "target_node_uuid" [t| NonEmptyString |]
+
 -- | Pause instance at startup.
 pStartupPaused :: Field
 pStartupPaused = defaultFalse "startup_paused"
@@ -990,6 +1025,12 @@ pRequiredNodes :: Field
 pRequiredNodes =
   renameField "ReqNodes " $ simpleField "nodes" [t| [NonEmptyString] |]
 
+-- | Required list of node names.
+pRequiredNodeUuids :: Field
+pRequiredNodeUuids =
+  renameField "ReqNodeUuids " . optionalField $
+    simpleField "node_uuids" [t| [NonEmptyString] |]
+
 -- | Storage type.
 pStorageType :: Field
 pStorageType = simpleField "storage_type" [t| StorageType |]
@@ -1027,6 +1068,10 @@ pIallocator = optionalNEStringField "iallocator"
 pRemoteNode :: Field
 pRemoteNode = optionalNEStringField "remote_node"
 
+-- | New secondary node UUID.
+pRemoteNodeUuid :: Field
+pRemoteNodeUuid = optionalNEStringField "remote_node_uuid"
+
 -- | Node evacuation mode.
 pEvacMode :: Field
 pEvacMode = renameField "EvacMode" $ simpleField "mode" [t| NodeEvacMode |]
@@ -1048,10 +1093,18 @@ pInstOs = optionalNEStringField "os_type"
 pPrimaryNode :: Field
 pPrimaryNode = optionalNEStringField "pnode"
 
+-- | Primary node UUID for an instance.
+pPrimaryNodeUuid :: Field
+pPrimaryNodeUuid = optionalNEStringField "pnode_uuid"
+
 -- | Secondary node for an instance.
 pSecondaryNode :: Field
 pSecondaryNode = optionalNEStringField "snode"
 
+-- | Secondary node UUID for an instance.
+pSecondaryNodeUuid :: Field
+pSecondaryNodeUuid = optionalNEStringField "snode_uuid"
+
 -- | Signed handshake from source (remote import only).
 pSourceHandshake :: Field
 pSourceHandshake =
@@ -1076,6 +1129,10 @@ pSourceX509Ca = optionalNEStringField "source_x509_ca"
 pSrcNode :: Field
 pSrcNode = optionalNEStringField "src_node"
 
+-- | Source node for import.
+pSrcNodeUuid :: Field
+pSrcNodeUuid = optionalNEStringField "src_node_uuid"
+
 -- | Source directory for import.
 pSrcPath :: Field
 pSrcPath = optionalNEStringField "src_path"
@@ -1181,6 +1238,12 @@ pExportTargetNode =
   renameField "ExportTarget" $
   simpleField "target_node" [t| ExportTarget |]
 
+-- | Export target node UUID field.
+pExportTargetNodeUuid :: Field
+pExportTargetNodeUuid =
+  renameField "ExportTargetNodeUuid" . optionalField $
+  simpleField "target_node_uuid" [t| NonEmptyString |]
+
 -- | Whether to remove instance after export.
 pRemoveInstance :: Field
 pRemoveInstance = defaultFalse "remove_instance"
@@ -1241,6 +1304,12 @@ pDelayOnNodes =
   defaultField [| [] |] $
   simpleField "on_nodes" [t| [NonEmptyString] |]
 
+-- | on_node_uuids field for 'OpTestDelay'.
+pDelayOnNodeUuids :: Field
+pDelayOnNodeUuids =
+  renameField "DelayOnNodeUuids" . optionalField $
+  simpleField "on_node_uuids" [t| [NonEmptyString] |]
+
 -- | Repeat parameter for OpTestDelay.
 pDelayRepeat :: Field
 pDelayRepeat =
index 9383c18..1c54966 100644 (file)
@@ -36,6 +36,8 @@ module Ganeti.Path
   , queueDir
   , jobQueueSerialFile
   , jobQueueArchiveSubDir
+  , instanceReasonDir
+  , getInstReasonFilename
   ) where
 
 import System.FilePath
@@ -111,3 +113,13 @@ jobQueueSerialFile = dataDirP "serial"
 -- | Job queue archive directory.
 jobQueueArchiveSubDir :: FilePath
 jobQueueArchiveSubDir = "archive"
+
+-- | Directory containing the reason trails for the last change of status of
+-- instances.
+instanceReasonDir :: IO FilePath
+instanceReasonDir = runDir `pjoin` "instance-reason"
+
+-- | The path of the file containing the reason trail for an instance, given the
+-- instance name.
+getInstReasonFilename :: String -> IO FilePath
+getInstReasonFilename instName = instanceReasonDir `pjoin` instName
diff --git a/src/Ganeti/Query/Cluster.hs b/src/Ganeti/Query/Cluster.hs
new file mode 100644 (file)
index 0000000..0c6d985
--- /dev/null
@@ -0,0 +1,41 @@
+{-| Implementation of the Ganeti Query2 cluster queries.
+
+ -}
+
+{-
+
+Copyright (C) 2012, 2013 Google Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Ganeti.Query.Cluster
+  ( clusterMasterNodeName
+  ) where
+
+import Control.Monad (liftM)
+
+import Ganeti.Objects
+import Ganeti.Config
+import Ganeti.Errors
+
+-- | Get master node name.
+clusterMasterNodeName :: ConfigData -> ErrorResult String
+clusterMasterNodeName cfg =
+  let cluster = configCluster cfg
+      masterNodeUuid = clusterMasterNode cluster
+  in liftM nodeName $ getNode cfg masterNodeUuid
index 293ceb7..1776844 100644 (file)
@@ -39,9 +39,11 @@ import Ganeti.Config
 import Ganeti.Objects
 import Ganeti.JSON
 import Ganeti.Rpc
+import Ganeti.Types
 import Ganeti.Query.Language
 import Ganeti.Query.Common
 import Ganeti.Query.Types
+import qualified Ganeti.Types as T
 import Ganeti.Utils (niceSort)
 
 -- | Runtime is the resulting type for NodeInfo call.
@@ -59,10 +61,14 @@ nodeLiveFieldsDefs =
      "Number of physical CPU sockets (if exported by hypervisor)")
   , ("ctotal", "CTotal", QFTNumber, "cpu_total",
      "Number of logical processors")
-  , ("dfree", "DFree", QFTUnit, "vg_free",
-     "Available disk space in volume group")
-  , ("dtotal", "DTotal", QFTUnit, "vg_size",
-     "Total disk space in volume group used for instance disk allocation")
+  , ("dfree", "DFree", QFTUnit, "storage_free",
+     "Available storage space on storage unit")
+  , ("dtotal", "DTotal", QFTUnit, "storage_size",
+     "Total storage space on storage unit for instance disk allocation")
+  , ("spfree", "SpFree", QFTNumber, "spindles_free",
+     "Available spindles in volume group (exclusive storage only)")
+  , ("sptotal", "SpTotal", QFTNumber, "spindles_total",
+     "Total spindles in volume group (exclusive storage only)")
   , ("mfree", "MFree", QFTUnit, "memory_free",
      "Memory available for instance allocations")
   , ("mnode", "MNode", QFTUnit, "memory_dom0",
@@ -83,9 +89,13 @@ nodeLiveFieldExtract "csockets" res =
 nodeLiveFieldExtract "ctotal" res =
   jsonHead (rpcResNodeInfoHvInfo res) hvInfoCpuTotal
 nodeLiveFieldExtract "dfree" res =
-  getMaybeJsonHead (rpcResNodeInfoVgInfo res) vgInfoVgFree
+  getMaybeJsonHead (rpcResNodeInfoStorageInfo res) storageInfoStorageFree
 nodeLiveFieldExtract "dtotal" res =
-  getMaybeJsonHead (rpcResNodeInfoVgInfo res) vgInfoVgSize
+  getMaybeJsonHead (rpcResNodeInfoStorageInfo res) storageInfoStorageSize
+nodeLiveFieldExtract "spfree" res =
+  getMaybeJsonElem (rpcResNodeInfoStorageInfo res) 1 storageInfoStorageFree
+nodeLiveFieldExtract "sptotal" res =
+  getMaybeJsonElem (rpcResNodeInfoStorageInfo res) 1 storageInfoStorageSize
 nodeLiveFieldExtract "mfree" res =
   jsonHead (rpcResNodeInfoHvInfo res) hvInfoMemoryFree
 nodeLiveFieldExtract "mnode" res =
@@ -174,21 +184,21 @@ nodeFields =
   , (FieldDefinition "pinst_cnt" "Pinst" QFTNumber
        "Number of instances with this node as primary",
      FieldConfig (\cfg ->
-                    rsNormal . length . fst . getNodeInstances cfg . nodeName),
+                    rsNormal . length . fst . getNodeInstances cfg . nodeUuid),
      QffNormal)
   , (FieldDefinition "sinst_cnt" "Sinst" QFTNumber
        "Number of instances with this node as secondary",
      FieldConfig (\cfg ->
-                    rsNormal . length . snd . getNodeInstances cfg . nodeName),
+                    rsNormal . length . snd . getNodeInstances cfg . nodeUuid),
      QffNormal)
   , (FieldDefinition "pinst_list" "PriInstances" QFTOther
        "List of instances with this node as primary",
      FieldConfig (\cfg -> rsNormal . niceSort . map instName . fst .
-                          getNodeInstances cfg . nodeName), QffNormal)
+                          getNodeInstances cfg . nodeUuid), QffNormal)
   , (FieldDefinition "sinst_list" "SecInstances" QFTOther
        "List of instances with this node as secondary",
      FieldConfig (\cfg -> rsNormal . niceSort . map instName . snd .
-                          getNodeInstances cfg . nodeName), QffNormal)
+                          getNodeInstances cfg . nodeUuid), QffNormal)
   , (FieldDefinition "role" "Role" QFTText nodeRoleDoc,
      FieldConfig ((rsNormal .) . getNodeRole), QffNormal)
   , (FieldDefinition "powered" "Powered" QFTBool
@@ -224,7 +234,10 @@ collectLiveData False _ nodes =
   return $ zip nodes (repeat $ Left (RpcResultError "Live data disabled"))
 collectLiveData True cfg nodes = do
   let vgs = maybeToList . clusterVolumeGroupName $ configCluster cfg
-      hvs = [getDefaultHypervisor cfg]
+      -- FIXME: This currently sets every storage unit to LVM
+      storage_units = zip (repeat T.StorageLvmVg) vgs ++
+                      zip (repeat T.StorageLvmPv) vgs
+      hvs = [getDefaultHypervisorSpec cfg]
       step n (bn, gn, em) =
         let ndp' = getNodeNdParams cfg n
         in case ndp' of
@@ -232,7 +245,20 @@ collectLiveData True cfg nodes = do
                           (nodeName n, ndpExclusiveStorage ndp) : em)
              Nothing -> (n : bn, gn, em)
       (bnodes, gnodes, emap) = foldr step ([], [], []) nodes
-  rpcres <- executeRpcCall gnodes (RpcCallNodeInfo vgs hvs (Map.fromList emap))
+  rpcres <- executeRpcCall gnodes (RpcCallNodeInfo storage_units hvs
+    (Map.fromList emap))
   -- FIXME: The order of nodes in the result could be different from the input
   return $ zip bnodes (repeat $ Left (RpcResultError "Broken configuration"))
            ++ rpcres
+
+-- | Looks up the default hypervisor and it's hvparams
+getDefaultHypervisorSpec :: ConfigData -> (Hypervisor, HvParams)
+getDefaultHypervisorSpec cfg = (hv, getHvParamsFromCluster cfg hv)
+  where hv = getDefaultHypervisor cfg
+
+-- | Looks up the cluster's hvparams of the given hypervisor
+getHvParamsFromCluster :: ConfigData -> Hypervisor -> HvParams
+getHvParamsFromCluster cfg hv =
+  fromMaybe (GenericContainer (Map.fromList []))
+    (Map.lookup (hypervisorToRaw hv)
+       (fromContainer (clusterHvparams (configCluster cfg))))
index 21454fa..c8d5443 100644 (file)
@@ -52,6 +52,7 @@ import Ganeti.Logging
 import Ganeti.Luxi
 import Ganeti.OpCodes (TagObject(..))
 import qualified Ganeti.Query.Language as Qlang
+import qualified Ganeti.Query.Cluster as QCluster
 import Ganeti.Query.Query
 import Ganeti.Query.Filter (FilterConstructor, makeSimpleFilter
                            , makeHostnameFilter)
@@ -91,6 +92,7 @@ handleCallWrapper (Ok config) op = handleCall config op
 handleCall :: ConfigData -> LuxiOp -> IO (ErrorResult JSValue)
 handleCall cdata QueryClusterInfo =
   let cluster = configCluster cdata
+      master = QCluster.clusterMasterNodeName cdata
       hypervisors = clusterEnabledHypervisors cluster
       diskTemplates = clusterEnabledDiskTemplates cluster
       def_hv = case hypervisors of
@@ -105,7 +107,9 @@ handleCall cdata QueryClusterInfo =
             , ("export_version", showJSON C.exportVersion)
             , ("architecture", showJSON arch_tuple)
             , ("name", showJSON $ clusterClusterName cluster)
-            , ("master", showJSON $ clusterMasterNode cluster)
+            , ("master", showJSON (case master of
+                                     Ok name -> name
+                                     _ -> undefined))
             , ("default_hypervisor", def_hv)
             , ("enabled_hypervisors", showJSON hypervisors)
             , ("hvparams", showJSON $ clusterHvparams cluster)
@@ -148,7 +152,9 @@ handleCall cdata QueryClusterInfo =
             , ("enabled_disk_templates", showJSON diskTemplates)
             ]
 
-  in return . Ok . J.makeObj $ obj
+  in case master of
+    Ok _ -> return . Ok . J.makeObj $ obj
+    Bad ex -> return $ Bad ex
 
 handleCall cfg (QueryTags kind) =
   let tags = case kind of
index 73d22ca..ae9fbe8 100644 (file)
@@ -52,7 +52,7 @@ module Ganeti.Rpc
   , RpcResultInstanceList(..)
 
   , HvInfo(..)
-  , VgInfo(..)
+  , StorageInfo(..)
   , RpcCallNodeInfo(..)
   , RpcResultNodeInfo(..)
 
@@ -337,15 +337,16 @@ instance Rpc RpcCallInstanceList RpcResultInstanceList where
 -- | NodeInfo
 -- Return node information.
 $(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
-  [ simpleField "volume_groups" [t| [String] |]
-  , simpleField "hypervisors" [t| [Hypervisor] |]
+  [ simpleField "storage_units" [t| [ (StorageType, String) ] |]
+  , simpleField "hypervisors" [t| [ (Hypervisor, HvParams) ] |]
   , simpleField "exclusive_storage" [t| Map.Map String Bool |]
   ])
 
-$(buildObject "VgInfo" "vgInfo"
+$(buildObject "StorageInfo" "storageInfo"
   [ simpleField "name" [t| String |]
-  , optionalField $ simpleField "vg_free" [t| Int |]
-  , optionalField $ simpleField "vg_size" [t| Int |]
+  , simpleField "type" [t| String |]
+  , optionalField $ simpleField "storage_free" [t| Int |]
+  , optionalField $ simpleField "storage_size" [t| Int |]
   ])
 
 -- | We only provide common fields as described in hv_base.py.
@@ -360,7 +361,7 @@ $(buildObject "HvInfo" "hvInfo"
 
 $(buildObject "RpcResultNodeInfo" "rpcResNodeInfo"
   [ simpleField "boot_id" [t| String |]
-  , simpleField "vg_info" [t| [VgInfo] |]
+  , simpleField "storage_info" [t| [StorageInfo] |]
   , simpleField "hv_info" [t| [HvInfo] |]
   ])
 
@@ -369,7 +370,7 @@ instance RpcCall RpcCallNodeInfo where
   rpcCallTimeout _       = rpcTimeoutToRaw Urgent
   rpcCallAcceptOffline _ = False
   rpcCallData n call     = J.encode
-    ( rpcCallNodeInfoVolumeGroups call
+    ( rpcCallNodeInfoStorageUnits call
     , rpcCallNodeInfoHypervisors call
     , fromMaybe (error $ "Programmer error: missing parameter for node named "
                          ++ nodeName n)
diff --git a/src/Ganeti/Storage/Diskstats/Parser.hs b/src/Ganeti/Storage/Diskstats/Parser.hs
new file mode 100644 (file)
index 0000000..1199f32
--- /dev/null
@@ -0,0 +1,65 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-| Diskstats proc file parser
+
+This module holds the definition of the parser that extracts status
+information about the disks of the system from the @/proc/diskstats@ file.
+
+-}
+{-
+
+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.
+
+-}
+module Ganeti.Storage.Diskstats.Parser (diskstatsParser) where
+
+import Control.Applicative ((<*>), (*>), (<*), (<$>))
+import qualified Data.Attoparsec.Text as A
+import qualified Data.Attoparsec.Combinator as AC
+import Data.Attoparsec.Text (Parser)
+import Data.Text (unpack)
+
+import Ganeti.Storage.Diskstats.Types
+
+-- * Utility functions
+
+-- | Our own space-skipping function, because A.skipSpace also skips
+-- newline characters. It skips ZERO or more spaces, so it does not
+-- fail if there are no spaces.
+skipSpaces :: Parser ()
+skipSpaces = A.skipWhile A.isHorizontalSpace
+
+-- | A parser recognizing a number preceeded by spaces.
+numberP :: Parser Int
+numberP = skipSpaces *> A.decimal
+
+-- | A parser recognizing a word preceded by spaces, and closed by a space.
+stringP :: Parser String
+stringP = skipSpaces *> fmap unpack (A.takeWhile $ not . A.isHorizontalSpace)
+
+-- * Parser implementation
+
+-- | The parser for one line of the diskstatus file.
+oneDiskstatsParser :: Parser Diskstats
+oneDiskstatsParser =
+  Diskstats <$> numberP <*> numberP <*> stringP <*> numberP <*> numberP
+    <*> numberP <*> numberP <*> numberP <*> numberP <*> numberP <*> numberP
+    <*> numberP <*> numberP <*> numberP <* A.endOfLine
+
+-- | The parser for a whole diskstatus file.
+diskstatsParser :: Parser [Diskstats]
+diskstatsParser = oneDiskstatsParser `AC.manyTill` A.endOfInput
diff --git a/src/Ganeti/Storage/Diskstats/Types.hs b/src/Ganeti/Storage/Diskstats/Types.hs
new file mode 100644 (file)
index 0000000..97bc12e
--- /dev/null
@@ -0,0 +1,51 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-| Diskstats data types
+
+This module holds the definition of the data types describing the status of the
+disks according to the information contained in @/proc/diskstats@.
+
+-}
+{-
+
+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.
+
+-}
+module Ganeti.Storage.Diskstats.Types
+  ( Diskstats(..)
+  ) where
+
+import Ganeti.THH
+
+
+-- | This is the format of the report produced by each data collector.
+$(buildObject "Diskstats" "ds"
+  [ simpleField "major"        [t| Int |]
+  , simpleField "minor"        [t| Int |]
+  , simpleField "name"         [t| String |]
+  , simpleField "readsNum"        [t| Int |]
+  , simpleField "mergedReads"  [t| Int |]
+  , simpleField "secRead"      [t| Int |]
+  , simpleField "timeRead"     [t| Int |]
+  , simpleField "writes"       [t| Int |]
+  , simpleField "mergedWrites" [t| Int |]
+  , simpleField "secWritten"   [t| Int |]
+  , simpleField "timeWrite"    [t| Int |]
+  , simpleField "ios"          [t| Int |]
+  , simpleField "timeIO"       [t| Int |]
+  , simpleField "wIOmillis"    [t| Int |]
+  ])
similarity index 99%
rename from src/Ganeti/Block/Drbd/Parser.hs
rename to src/Ganeti/Storage/Drbd/Parser.hs
index 9d3e66c..952401e 100644 (file)
@@ -25,7 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301, USA.
 
 -}
-module Ganeti.Block.Drbd.Parser (drbdStatusParser, commaIntParser) where
+module Ganeti.Storage.Drbd.Parser (drbdStatusParser, commaIntParser) where
 
 import Control.Applicative ((<*>), (*>), (<*), (<$>), (<|>), pure)
 import qualified Data.Attoparsec.Text as A
@@ -35,7 +35,7 @@ import Data.List
 import Data.Maybe
 import Data.Text (Text, unpack)
 
-import Ganeti.Block.Drbd.Types
+import Ganeti.Storage.Drbd.Types
 
 -- | Our own space-skipping function, because A.skipSpace also skips
 -- newline characters. It skips ZERO or more spaces, so it does not
similarity index 99%
rename from src/Ganeti/Block/Drbd/Types.hs
rename to src/Ganeti/Storage/Drbd/Types.hs
index d81ad82..ec9befe 100644 (file)
@@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301, USA.
 
 -}
-module Ganeti.Block.Drbd.Types
+module Ganeti.Storage.Drbd.Types
   ( DRBDStatus(..)
   , VersionInfo(..)
   , DeviceInfo(..)
index c0fa6bd..ccb2c9b 100644 (file)
@@ -60,6 +60,7 @@ module Ganeti.Types
   , CVErrorCode(..)
   , cVErrorCodeToRaw
   , Hypervisor(..)
+  , hypervisorToRaw
   , OobCommand(..)
   , StorageType(..)
   , NodeEvacMode(..)
index 89a054d..f8fc3bf 100644 (file)
@@ -56,6 +56,7 @@ module Ganeti.Utils
   , exitIfEmpty
   , splitEithers
   , recombineEithers
+  , resolveAddr
   ) where
 
 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
@@ -64,6 +65,7 @@ import Data.List
 import Control.Monad (foldM)
 
 import Debug.Trace
+import Network.Socket
 
 import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
@@ -431,3 +433,16 @@ recombineEithers lefts rights trail =
           recombiner (_,  ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
                                       show ls ++ ", r=" ++ show rs ++ ",t=" ++
                                       show t
+
+-- | Default hints for the resolver
+resolveAddrHints :: Maybe AddrInfo
+resolveAddrHints =
+  Just defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV] }
+
+-- | Resolves a numeric address.
+resolveAddr :: Int -> String -> IO (Result (Family, SockAddr))
+resolveAddr port str = do
+  resolved <- getAddrInfo resolveAddrHints (Just str) (Just (show port))
+  return $ case resolved of
+             [] -> Bad "Invalid results from lookup?"
+             best:_ -> Ok (addrFamily best, addrAddress best)
diff --git a/test/data/bdev-drbd-8.4.txt b/test/data/bdev-drbd-8.4.txt
new file mode 100644 (file)
index 0000000..973e0c8
--- /dev/null
@@ -0,0 +1,25 @@
+resource resource0 {
+    options {
+    }
+    net {
+        cram-hmac-alg           "md5";
+        shared-secret           "shared_secret_123";
+        after-sb-0pri           discard-zero-changes;
+        after-sb-1pri           consensus;
+    }
+    _remote_host {
+        address                 ipv4 192.0.2.2:11000;
+    }
+    _this_host {
+        address                 ipv4 192.0.2.1:11000;
+        volume 0 {
+            device                      minor 0;
+            disk                        "/dev/xenvg/test.data";
+            meta-disk                   "/dev/xenvg/test.meta" [ 0 ];
+            disk {
+                size                    2097152s; # bytes
+                resync-rate             61440k; # bytes/second
+            }
+        }
+    }
+}
similarity index 87%
rename from test/data/cluster_config_downgraded_2.7.json
rename to test/data/cluster_config_2.8.json
index 8f3847a..e4dee4d 100644 (file)
       "sharedfile": {}
     },
     "drbd_usermode_helper": "/bin/true",
+    "enabled_disk_templates": [
+      "drbd",
+      "plain",
+      "file",
+      "sharedfile"
+    ],
     "enabled_hypervisors": [
       "xen-pvm"
     ],
         "file",
         "rbd"
       ],
-      "max": {
-        "cpu-count": 8,
-        "disk-count": 16,
-        "disk-size": 1048576,
-        "memory-size": 32768,
-        "nic-count": 8,
-        "spindle-use": 12
-      },
-      "min": {
-        "cpu-count": 1,
-        "disk-count": 1,
-        "disk-size": 1024,
-        "memory-size": 128,
-        "nic-count": 1,
-        "spindle-use": 1
-      },
+      "minmax": [
+        {
+          "max": {
+            "cpu-count": 8,
+            "disk-count": 16,
+            "disk-size": 1048576,
+            "memory-size": 32768,
+            "nic-count": 8,
+            "spindle-use": 12
+          },
+          "min": {
+            "cpu-count": 1,
+            "disk-count": 1,
+            "disk-size": 1024,
+            "memory-size": 128,
+            "nic-count": 1,
+            "spindle-use": 1
+          }
+        }
+      ],
       "spindle-ratio": 32.0,
       "std": {
         "cpu-count": 1,
             0,
             "d3c3fd475fcbaf5fd177fb245ac43b71247ada38"
           ],
-          "size": 1024
+          "size": 1024,
+          "uuid": "77ced3a5-6756-49ae-8d1f-274e27664c05"
         }
       ],
       "hvparams": {},
       "nics": [
         {
           "mac": "aa:bb:cc:b2:6e:0b",
-          "nicparams": {}
+          "nicparams": {},
+          "uuid": "2c953d72-fac4-4aa9-a225-4131bb271791"
         }
       ],
       "os": "busybox",
             "xenvg",
             "3e559cd7-1024-4294-a923-a9fd13182b2f.disk0"
           ],
-          "size": 102400
+          "size": 102400,
+          "uuid": "79acf611-be58-4334-9fe4-4f2b73ae8abb"
         }
       ],
       "hvparams": {},
       "nics": [
         {
           "mac": "aa:bb:cc:56:83:fb",
-          "nicparams": {}
+          "nicparams": {},
+          "uuid": "1cf95562-e676-4fd0-8214-e8b84a2f7bd1"
         }
       ],
       "os": "debian-image",
             "xenvg",
             "b27a576a-13f7-4f07-885c-63fcad4fdfcc.disk0"
           ],
-          "size": 1280
+          "size": 1280,
+          "uuid": "150bd154-8e23-44d1-b762-5065ae5a507b"
         }
       ],
       "hvparams": {},
       "nics": [
         {
           "mac": "aa:bb:cc:5e:5c:75",
-          "nicparams": {}
+          "nicparams": {},
+          "uuid": "1ab090c1-e017-406c-afb4-fc285cb43e31"
         }
       ],
       "os": "debian-image",
       "uuid": "4e091bdc-e205-4ed7-8a47-0c9130a6619f"
     }
   },
-  "mtime": 1361984633.373014,
+  "mtime": 1367352404.758083,
   "networks": {
     "99f0128a-1c84-44da-90b9-9581ea00c075": {
       "ext_reservations": "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
     "5244a46d-7506-4e14-922d-02b58153dde1": {
       "alloc_policy": "preferred",
       "diskparams": {},
-      "ipolicy": {
-        "max": {},
-        "min": {},
-        "std": {}
-      },
+      "ipolicy": {},
       "mtime": 1361963775.575009,
       "name": "default",
       "ndparams": {},
       "alloc_policy": "preferred",
       "diskparams": {},
       "ipolicy": {
-       "disk-templates": [
-         "plain"
-       ],
-       "max": {},
-       "min": {},
-       "spindle-ratio": 5.2,
-       "std": {},
-       "vcpu-ratio": 3.14
+        "disk-templates": [
+          "plain"
+        ],
+        "minmax": [
+          {
+            "max": {
+              "cpu-count": 8,
+              "disk-count": 16,
+              "disk-size": 1048576,
+              "memory-size": 32768,
+              "nic-count": 18,
+              "spindle-use": 14
+            },
+            "min": {
+              "cpu-count": 2,
+              "disk-count": 2,
+              "disk-size": 1024,
+              "memory-size": 128,
+              "nic-count": 1,
+              "spindle-use": 1
+            }
+          }
+        ],
+        "spindle-ratio": 5.2,
+        "vcpu-ratio": 3.14
       },
       "mtime": 1361963775.575009,
       "name": "another",
       "vm_capable": true
     }
   },
-  "serial_no": 7624,
-  "version": 2070000
+  "serial_no": 7625,
+  "version": 2080000
 }
index 868933e..26fc73a 100644 (file)
     "instance14": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance13": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
     "instance18": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance19": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance2": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance3": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         },
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance4": {
       "disks": [
         {
+          "spindles": 2,
           "mode": "rw",
           "size": 2048
         }
     "instance8": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         }
     "instance9": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance20": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
       "free_memory": 31389,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.1",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31746,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.2",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31234,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.3",
       "i_pri_memory": 2432,
+      "free_spindles": 6,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 22914,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.4",
       "i_pri_memory": 23552,
+      "free_spindles": 0,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     }
   "request": {
     "disks": [
       {
+        "spindles": 1,
         "mode": "rw",
         "size": 1024
       }
index 72d5cbb..ebaf151 100644 (file)
       "drained": false,
       "free_disk": 7168,
       "free_memory": 4096,
+      "free_spindles": 0,
       "group": "uuid-group-1",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 4096
+      "total_memory": 4096,
+      "total_spindles": 0
     },
     "node2_1": {
       "drained": false,
       "free_disk": 7168,
       "free_memory": 4096,
+      "free_spindles": 0,
       "group": "uuid-group-2",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 4096
+      "total_memory": 4096,
+      "total_spindles": 0
     }
   },
   "request": {
index 4a15e64..49c1ea9 100644 (file)
       "drained": false,
       "free_disk": 1377280,
       "free_memory": 31389,
+      "free_spindles": 12,
       "group": "uuid-group-1",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 1377280,
-      "total_memory": 32763
+      "total_memory": 32763,
+      "total_spindles": 12
     }
   },
   "request": {
     "disk_template": "file",
     "disks": [
       {
+        "spindles": 1,
         "size": 768
       },
       {
+        "spindles": 1,
         "size": 768
       }
     ],
index 988d0fc..df3e819 100644 (file)
       "drained": false,
       "free_disk": 4096,
       "free_memory": 3840,
+      "free_spindles": 0,
       "group": "uuid-group-1",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 4096
+      "total_memory": 4096,
+      "total_spindles": 0
     },
     "node1_2": {
       "drained": false,
       "free_disk": 4096,
       "free_memory": 3968,
+      "free_spindles": 0,
       "group": "uuid-group-1",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 32763
+      "total_memory": 32763,
+      "total_spindles": 0
     },
     "node2_1": {
       "drained": false,
       "free_disk": 7168,
       "free_memory": 4096,
+      "free_spindles": 0,
       "group": "uuid-group-2",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 4096
+      "total_memory": 4096,
+      "total_spindles": 0
     },
     "node2_2": {
       "drained": false,
       "free_disk": 7168,
       "free_memory": 4096,
+      "free_spindles": 0,
       "group": "uuid-group-2",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 7168,
-      "total_memory": 4096
+      "total_memory": 4096,
+      "total_spindles": 0
     }
   },
   "request": {
diff --git a/test/data/htools/hail-alloc-spindles.json b/test/data/htools/hail-alloc-spindles.json
new file mode 100644 (file)
index 0000000..20d2c97
--- /dev/null
@@ -0,0 +1,404 @@
+{
+  "cluster_tags": [
+    "htools:iextags:test",
+    "htools:iextags:service-group"
+  ],
+  "nodegroups": {
+    "uuid-group-1": {
+      "ipolicy": {
+        "std": {
+          "nic-count": 1,
+          "disk-size": 1024,
+          "disk-count": 1,
+          "memory-size": 128,
+          "cpu-count": 1,
+          "spindle-use": 1
+        },
+        "minmax": [
+          {
+            "min": {
+              "nic-count": 1,
+              "disk-size": 128,
+              "disk-count": 1,
+              "memory-size": 128,
+              "cpu-count": 1,
+              "spindle-use": 1
+            },
+            "max": {
+              "nic-count": 8,
+              "disk-size": 1048576,
+              "disk-count": 16,
+              "memory-size": 32768,
+              "cpu-count": 8,
+              "spindle-use": 2
+            }
+          }
+        ],
+        "vcpu-ratio": 4.0,
+        "disk-templates": [
+          "sharedfile",
+          "diskless",
+          "plain",
+          "blockdev",
+          "drbd",
+          "file",
+          "rbd"
+        ],
+        "spindle-ratio": 32.0
+      },
+      "alloc_policy": "preferred",
+      "networks": [],
+      "tags": [],
+      "name": "group1"
+    },
+    "uuid-group-2": {
+      "ipolicy": {
+        "std": {
+          "nic-count": 1,
+          "disk-size": 1024,
+          "disk-count": 1,
+          "memory-size": 128,
+          "cpu-count": 1,
+          "spindle-use": 2
+        },
+        "minmax": [
+          {
+            "min": {
+              "nic-count": 1,
+              "disk-size": 128,
+              "disk-count": 1,
+              "memory-size": 128,
+              "cpu-count": 1,
+              "spindle-use": 2
+            },
+            "max": {
+              "nic-count": 8,
+              "disk-size": 1048576,
+              "disk-count": 16,
+              "memory-size": 32768,
+              "cpu-count": 8,
+              "spindle-use": 3
+            }
+          }
+        ],
+        "vcpu-ratio": 4.0,
+        "disk-templates": [
+          "sharedfile",
+          "diskless",
+          "plain",
+          "blockdev",
+          "drbd",
+          "file",
+          "rbd"
+        ],
+        "spindle-ratio": 32.0
+      },
+      "alloc_policy": "preferred",
+      "networks": [],
+      "tags": [],
+      "name": "group2"
+    }
+  },
+  "ipolicy": {
+    "std": {
+      "nic-count": 1,
+      "disk-size": 1024,
+      "memory-size": 128,
+      "cpu-count": 1,
+      "disk-count": 1,
+      "spindle-use": 1
+    },
+    "minmax": [
+      {
+        "min": {
+          "nic-count": 1,
+          "disk-size": 1024,
+          "memory-size": 128,
+          "cpu-count": 1,
+          "disk-count": 1,
+          "spindle-use": 1
+        },
+        "max": {
+          "nic-count": 8,
+          "disk-size": 1048576,
+          "memory-size": 32768,
+          "cpu-count": 8,
+          "disk-count": 16,
+          "spindle-use": 8
+        }
+      }
+    ],
+    "vcpu-ratio": 4.0,
+    "disk-templates": [
+      "sharedfile",
+      "diskless",
+      "plain",
+      "blockdev",
+      "drbd",
+      "file",
+      "rbd"
+    ],
+    "spindle-ratio": 32.0
+  },
+  "enabled_hypervisors": [
+    "xen-pvm",
+    "xen-hvm"
+  ],
+  "cluster_name": "cluster",
+  "instances": {
+    "instance1": {
+      "disks": [
+        {
+          "spindles": 1,
+          "mode": "rw",
+          "size": 650000
+        }
+      ],
+      "disk_space_total": 650000,
+      "hypervisor": "xen-pvm",
+      "tags": [],
+      "nics": [
+        {
+          "ip": null,
+          "mac": "aa:00:00:7f:8c:91",
+          "link": "xen-br1",
+          "mode": "bridged",
+          "bridge": "xen-br1"
+        }
+      ],
+      "vcpus": 1,
+      "spindle_use": 1,
+      "admin_state": "up",
+      "disk_template": "plain",
+      "memory": 1024,
+      "nodes": [
+        "node1"
+      ],
+      "os": "instance-debootstrap"
+    },
+    "instance2": {
+      "disks": [
+        {
+          "spindles": 2,
+          "mode": "rw",
+          "size": 256
+        }
+      ],
+      "disk_space_total": 256,
+      "hypervisor": "xen-pvm",
+      "tags": [],
+      "nics": [
+        {
+          "ip": null,
+          "mac": "aa:00:00:7f:8c:92",
+          "link": "xen-br1",
+          "mode": "bridged",
+          "bridge": "xen-br1"
+        }
+      ],
+      "vcpus": 1,
+      "spindle_use": 1,
+      "admin_state": "up",
+      "disk_template": "plain",
+      "memory": 1024,
+      "nodes": [
+        "node2"
+      ],
+      "os": "instance-debootstrap"
+    },
+    "instance3": {
+      "disks": [
+        {
+          "spindles": 1,
+          "mode": "rw",
+          "size": 650000
+        }
+      ],
+      "disk_space_total": 650000,
+      "hypervisor": "xen-pvm",
+      "tags": [],
+      "nics": [
+        {
+          "ip": null,
+          "mac": "aa:00:00:7f:8c:93",
+          "link": "xen-br1",
+          "mode": "bridged",
+          "bridge": "xen-br1"
+        }
+      ],
+      "vcpus": 1,
+      "spindle_use": 1,
+      "admin_state": "up",
+      "disk_template": "plain",
+      "memory": 1024,
+      "nodes": [
+        "node3"
+      ],
+      "os": "instance-debootstrap"
+    },
+    "instance4": {
+      "disks": [
+        {
+          "spindles": 2,
+          "mode": "rw",
+          "size": 256
+        }
+      ],
+      "disk_space_total": 256,
+      "hypervisor": "xen-pvm",
+      "tags": [],
+      "nics": [
+        {
+          "ip": null,
+          "mac": "aa:00:00:7f:8c:94",
+          "link": "xen-br1",
+          "mode": "bridged",
+          "bridge": "xen-br1"
+        }
+      ],
+      "vcpus": 1,
+      "spindle_use": 1,
+      "admin_state": "up",
+      "disk_template": "plain",
+      "memory": 1024,
+      "nodes": [
+        "node4"
+      ],
+      "os": "instance-debootstrap"
+    }
+  },
+  "version": 2,
+  "nodes": {
+    "node1": {
+      "total_disk": 1377280,
+      "total_cpus": 4,
+      "group": "uuid-group-1",
+      "secondary_ip": "192.168.2.1",
+      "i_pri_up_memory": 1024,
+      "tags": [],
+      "master_candidate": true,
+      "free_memory": 30722,
+      "ndparams": {
+        "spindle_count": 2,
+        "oob_program": null,
+        "exclusive_storage": false
+      },
+      "reserved_memory": 1017,
+      "master_capable": true,
+      "free_disk": 687280,
+      "drained": false,
+      "total_memory": 32763,
+      "primary_ip": "192.168.1.1",
+      "i_pri_memory": 1024,
+      "free_spindles": 1,
+      "total_spindles": 2,
+      "vm_capable": true,
+      "offline": false
+    },
+    "node2": {
+      "total_disk": 1377280,
+      "total_cpus": 4,
+      "group": "uuid-group-1",
+      "secondary_ip": "192.168.2.2",
+      "i_pri_up_memory": 1024,
+      "tags": [],
+      "master_candidate": true,
+      "free_memory": 30722,
+      "ndparams": {
+        "spindle_count": 2,
+        "oob_program": null,
+        "exclusive_storage": false
+      },
+      "reserved_memory": 1017,
+      "master_capable": true,
+      "free_disk": 1377024,
+      "drained": false,
+      "total_memory": 32763,
+      "primary_ip": "192.168.1.2",
+      "i_pri_memory": 1024,
+      "free_spindles": 0,
+      "total_spindles": 2,
+      "vm_capable": true,
+      "offline": false
+    },
+    "node3": {
+      "total_disk": 1377280,
+      "total_cpus": 4,
+      "group": "uuid-group-2",
+      "secondary_ip": "192.168.2.3",
+      "i_pri_up_memory": 1024,
+      "tags": [],
+      "master_candidate": true,
+      "free_memory": 30722,
+      "ndparams": {
+        "spindle_count": 2,
+        "oob_program": null,
+        "exclusive_storage": false
+      },
+      "reserved_memory": 1017,
+      "master_capable": true,
+      "free_disk": 687280,
+      "drained": false,
+      "total_memory": 32763,
+      "primary_ip": "192.168.1.3",
+      "i_pri_memory": 1204,
+      "free_spindles": 1,
+      "total_spindles": 2,
+      "vm_capable": true,
+      "offline": false
+    },
+    "node4": {
+      "total_disk": 1377280,
+      "total_cpus": 4,
+      "group": "uuid-group-2",
+      "secondary_ip": "192.168.2.4",
+      "i_pri_up_memory": 1024,
+      "tags": [],
+      "master_candidate": true,
+      "free_memory": 30722,
+      "ndparams": {
+        "spindle_count": 2,
+        "oob_program": null,
+        "exclusive_storage": false
+      },
+      "reserved_memory": 1017,
+      "master_capable": true,
+      "free_disk": 1377024,
+      "drained": false,
+      "total_memory": 32763,
+      "primary_ip": "192.168.1.4",
+      "i_pri_memory": 1024,
+      "free_spindles": 0,
+      "total_spindles": 2,
+      "vm_capable": true,
+      "offline": false
+    }
+  },
+  "request": {
+    "disks": [
+      {
+        "spindles": 1,
+        "mode": "rw",
+        "size": 1024
+      }
+    ],
+    "required_nodes": 1,
+    "name": "instance10",
+    "tags": [],
+    "hypervisor": "xen-pvm",
+    "disk_space_total": 1024,
+    "nics": [
+      {
+        "ip": null,
+        "mac": "00:11:22:33:44:55",
+        "bridge": null
+      }
+    ],
+    "vcpus": 1,
+    "spindle_use": 3,
+    "os": "instance-debootstrap",
+    "disk_template": "plain",
+    "memory": 1024,
+    "type": "allocate"
+  }
+}
index 5e9e196..c2046b9 100644 (file)
       "drained": false,
       "free_disk": 1377280,
       "free_memory": 31389,
+      "free_spindles": 12,
       "group": "uuid-group-1",
       "ndparams": {
-        "spindle_count": 1
+        "spindle_count": 1,
+        "exclusive_storage": false
       },
       "offline": false,
       "reserved_memory": 1017,
       "total_cpus": 4,
       "total_disk": 1377280,
-      "total_memory": 32763
+      "total_memory": 32763,
+      "total_spindles": 12
     }
   },
   "request": {
     "disk_template": "file",
     "disks": [
       {
+        "spindles": 1,
         "size": 1536
       },
       {
+        "spindles": 1,
         "size": 1536
       }
     ],
index 84aa631..e5e5a3a 100644 (file)
     "instance14": {
       "disks": [
         {
+         "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance13": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
     "instance18": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance19": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance2": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance3": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         },
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance4": {
       "disks": [
         {
+          "spindles": 2,
           "mode": "rw",
           "size": 2048
         }
     "instance8": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         }
     "instance9": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance20": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
       "free_memory": 31389,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.1",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31234,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.3",
       "i_pri_memory": 2432,
+      "free_spindles": 6,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 22914,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.4",
       "i_pri_memory": 23552,
+      "free_spindles": 0,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31746,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.10",
       "i_pri_memory": 23552,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31746,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.11",
       "i_pri_memory": 23552,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     }
index 31c7928..1d65d7c 100644 (file)
@@ -98,6 +98,7 @@
     "instance14": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance13": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
     "instance18": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance19": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance2": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance3": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         },
         {
+         "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance4": {
       "disks": [
         {
+          "spindles": 2,
           "mode": "rw",
           "size": 2048
         }
     "instance8": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         }
     "instance9": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance20": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
       "free_memory": 31389,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.1",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31746,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.2",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31234,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.3",
       "i_pri_memory": 2432,
+      "free_spindles": 6,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 22914,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.4",
       "i_pri_memory": 23552,
+      "free_spindles": 0,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     }
index b745660..eaab46e 100644 (file)
     "instance14": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance13": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
     "instance18": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance19": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance2": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance3": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         },
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance4": {
       "disks": [
         {
+          "spindles": 2,
           "mode": "rw",
           "size": 2048
         }
     "instance8": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 256
         }
     "instance9": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 128
         }
     "instance20": {
       "disks": [
         {
+          "spindles": 1,
           "mode": "rw",
           "size": 512
         }
       "free_memory": 31389,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.1",
       "i_pri_memory": 0,
+      "free_spindles": 12,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31746,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.2",
       "i_pri_memory": 0,
+      "free_spindles": 11,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 31234,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.3",
       "i_pri_memory": 2432,
+      "free_spindles": 6,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     },
       "free_memory": 22914,
       "ndparams": {
         "spindle_count": 1,
-        "oob_program": null
+        "oob_program": null,
+        "exclusive_storage": false
       },
       "reserved_memory": 1017,
       "master_capable": true,
       "total_memory": 32763,
       "primary_ip": "192.168.1.4",
       "i_pri_memory": 23552,
+      "free_spindles": 0,
+      "total_spindles": 12,
       "vm_capable": true,
       "offline": false
     }
diff --git a/test/data/htools/hroller-nodegroups.data b/test/data/htools/hroller-nodegroups.data
new file mode 100644 (file)
index 0000000..db8f9ee
--- /dev/null
@@ -0,0 +1,18 @@
+group-01|fake-uuid-01|preferred|
+group-02|fake-uuid-02|preferred|
+
+node-01-000|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-001|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-002|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-02-000|91552|0|91552|3100|3100|16|M|fake-uuid-02|1
+
+inst-00|128|1024|1|running|Y|node-01-000||plain||1
+inst-01|128|1024|1|running|Y|node-01-000||plain||1
+inst-10|128|1024|1|running|Y|node-01-001||plain||1
+inst-11|128|1024|1|running|Y|node-01-001||plain||1
+inst-20|128|1024|1|running|Y|node-01-002||plain||1
+inst-21|128|1024|1|running|Y|node-01-002||plain||1
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+
diff --git a/test/data/htools/hroller-nonredundant.data b/test/data/htools/hroller-nonredundant.data
new file mode 100644 (file)
index 0000000..73f73aa
--- /dev/null
@@ -0,0 +1,25 @@
+group-01|fake-uuid-01|preferred|
+
+node-01-000|91552|0|91424|3100|1052|16|M|fake-uuid-01|1
+node-01-001|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-002|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-003|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-004|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+node-01-005|91552|0|91424|3100|1052|16|N|fake-uuid-01|1
+
+inst-00|128|1024|1|running|Y|node-01-000||plain||1
+inst-01|128|1024|1|running|Y|node-01-000||plain||1
+inst-10|128|1024|1|running|Y|node-01-001||plain||1
+inst-11|128|1024|1|running|Y|node-01-001||plain||1
+inst-20|128|1024|1|running|Y|node-01-002||plain||1
+inst-21|128|1024|1|running|Y|node-01-002||plain||1
+inst-30|128|1024|1|running|Y|node-01-003||plain||1
+inst-31|128|1024|1|running|Y|node-01-003||plain||1
+inst-40|128|1024|1|running|Y|node-01-004||plain||1
+inst-41|128|1024|1|running|Y|node-01-004||plain||1
+inst-50|128|1024|1|running|Y|node-01-005||plain||1
+inst-51|128|1024|1|running|Y|node-01-005||plain||1
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+
diff --git a/test/data/htools/hroller-online.data b/test/data/htools/hroller-online.data
new file mode 100644 (file)
index 0000000..e6399ae
--- /dev/null
@@ -0,0 +1,14 @@
+group-01|fake-uuid-01|preferred|
+
+node-01-001|91552|0|91424|953674|953674|16|N|fake-uuid-01|1
+node-01-002|91552|0|91296|953674|953674|16|N|fake-uuid-01|1
+node-01-003|91552|0|91296|953674|953674|16|M|fake-uuid-01|1
+node-01-004|91552|0|91296|953674|953674|16|N|fake-uuid-01|1
+
+new-0|128|1152|1|running|Y|node-01-001|node-01-002|drbd||1
+new-1|128|1152|1|running|Y|node-01-003|node-01-002|drbd||1
+new-2|128|1152|1|running|Y|node-01-004|node-01-003|drbd||1
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+
diff --git a/test/data/htools/hspace-tiered-dualspec-exclusive.data b/test/data/htools/hspace-tiered-dualspec-exclusive.data
new file mode 100644 (file)
index 0000000..03d2871
--- /dev/null
@@ -0,0 +1,11 @@
+group-01|fake-uuid-01|preferred||
+
+node-01-001|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|10
+node-01-002|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|9
+node-01-003|262144|1024|261120|2097152|2097152|8|N|fake-uuid-01|8||Y|8
+node-01-004|262144|1024|261120|2097152|2097152|8|N|fake-uuid-01|8||Y|8
+
+
+
+|63488,2,522240,1,1,2|129024,4,1047552,1,1,4;131072,4,1048576,16,8,4;63488,2,522240,1,1,2;65536,2,524288,16,8,2|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|63488,2,522240,1,1,2|129024,4,1047552,1,1,4;131072,4,1048576,16,8,4;63488,2,522240,1,1,2;65536,2,524288,16,8,2|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/hspace-tiered-exclusive.data b/test/data/htools/hspace-tiered-exclusive.data
new file mode 100644 (file)
index 0000000..e393099
--- /dev/null
@@ -0,0 +1,11 @@
+group-01|fake-uuid-01|preferred||
+
+node-01-001|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|10
+node-01-002|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|9
+node-01-003|262144|1024|261120|2097152|2097152|8|N|fake-uuid-01|8||Y|8
+node-01-004|262144|1024|261120|2097152|2097152|8|N|fake-uuid-01|8||Y|8
+
+
+
+|129024,4,1047552,1,1,1|129024,4,1047552,1,1,1;131072,4,1048576,16,8,12|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|129024,4,1047552,1,1,1|129024,4,1047552,1,1,1;131072,4,1048576,16,8,12|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/hspace-tiered-mixed.data b/test/data/htools/hspace-tiered-mixed.data
new file mode 100644 (file)
index 0000000..3d79b17
--- /dev/null
@@ -0,0 +1,13 @@
+group-01|fake-uuid-01|preferred||
+group-02|fake-uuid-02|preferred||
+
+node-01-001|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|10
+node-01-002|262144|65536|196608|2097152|2097152|8|N|fake-uuid-01|10||Y|10
+node-01-003|262144|1024|261120|2097152|2097152|8|N|fake-uuid-02|8||N|8
+node-01-004|262144|1024|261120|2097152|2097152|8|N|fake-uuid-02|8||N|8
+
+
+
+|129024,4,1047552,1,1,1|129024,4,1047552,1,1,1;131072,4,1048576,16,8,12|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|129024,4,1047552,1,1,1|129024,4,1047552,1,1,1;131072,4,1048576,16,8,12|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
+group-02|129024,4,1047552,1,1,1|129024,4,1047552,1,1,1;131072,4,1048576,16,8,12|plain,diskless,file,sharedfile,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/htools/multiple-tags.data b/test/data/htools/multiple-tags.data
new file mode 100644 (file)
index 0000000..35cb17a
--- /dev/null
@@ -0,0 +1,16 @@
+group-01|fake-uuid-01|preferred|
+
+node-01-001|91552|0|91424|953674|953674|16|M|fake-uuid-01|1|red
+node-01-002|91552|0|91296|953674|953674|16|N|fake-uuid-01|1|blue
+node-01-003|91552|0|91296|953674|953674|16|N|fake-uuid-01|1|
+node-01-004|91552|0|91296|953674|953674|16|N|fake-uuid-01|1|blue,red
+node-01-005|91552|0|91296|953674|953674|16|N|fake-uuid-01|1|red
+node-01-006|91552|0|91296|953674|953674|16|N|fake-uuid-01|1|blue
+
+new-0|128|1152|1|running|Y|node-01-001|node-01-002|drbd||1
+new-1|128|1152|1|running|Y|node-01-003|node-01-004|drbd||1
+new-1|128|1152|1|running|Y|node-01-005|node-01-006|drbd||1
+
+
+|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
+group-01|128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
index 001ed97..42ccb0f 100644 (file)
@@ -49,6 +49,9 @@
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "up",
     "nic.links": [
       "xen-br0"
       256,
       128
     ],
+    "disk.spindles": [
+      null,
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       2048
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       256
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       512
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br1"
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       512
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
     "disk.sizes": [
       128
     ],
+    "disk.spindles": [
+      null
+    ],
     "admin_state": "down",
     "nic.links": [
       "xen-br0"
index c788728..84ea2c0 100644 (file)
     "sinst_cnt": 0,
     "sinst_list": [],
     "sip": "192.168.1.2",
+    "spfree": 0,
+    "sptotal": 0,
     "tags": [],
     "uuid": "7750ef3d-450f-4724-9d3d-8726d6335417",
     "vm_capable": true,
     "ndparams": {
       "spindle_count": 1,
-      "oob_program": null
+      "oob_program": null,
+      "exclusive_storage": false
     }
   },
   {
     "sinst_cnt": 0,
     "sinst_list": [],
     "sip": "192.168.2.2",
+    "spfree": 0,
+    "sptotal": 0,
     "tags": [],
     "uuid": "7750ef3d-450f-4724-9d3d-8726d6335417",
     "vm_capable": true,
     "ndparams": {
       "spindle_count": 1,
-      "oob_program": null
+      "oob_program": null,
+      "exclusive_storage": false
     }
   },
   {
     "master_candidate": true,
     "ctime": 1271425438.5,
     "mnode": 1017,
+    "spfree": 0,
+    "sptotal": 0,
     "pinst_list": [
       "instance14",
       "instance19",
     ],
     "ndparams": {
       "spindle_count": 1,
-      "oob_program": null
+      "oob_program": null,
+      "exclusive_storage": false
     }
   },
   {
     "master_candidate": true,
     "ctime": 1309185898.51,
     "mnode": 1017,
+    "spfree": 0,
+    "sptotal": 0,
     "pinst_list": [
       "instance20",
       "instance3",
     ],
     "ndparams": {
       "spindle_count": 1,
-      "oob_program": null
+      "oob_program": null,
+      "exclusive_storage": false
     }
   }
 ]
index 7007aee..ece9986 100644 (file)
@@ -1,11 +1,13 @@
 group-01|fake-uuid-01|preferred||
 
-node-01-001|91552|0|91424|953674|953674|16|M|fake-uuid-01|1
-node-01-002|91552|0|91296|953674|953674|16|N|fake-uuid-01|1
-node-01-003|91552|0|91296|953674|953674|16|N|fake-uuid-01|1
+node-01-001|91552|0|91424|3500|1196|16|M|fake-uuid-01|1
+node-01-002|91552|0|91296|3500|1196|16|N|fake-uuid-01|1
+node-01-003|91552|0|91296|3500|1196|16|N|fake-uuid-01|1
 
 new-0|128|1152|1|running|Y|node-01-001|node-01-002|drbd||1
 new-1|128|1152|1|running|Y|node-01-002|node-01-003|drbd||1
+nonred-0|128|1152|1|running|Y|node-01-001||plain||1
+nonred-1|128|1152|1|running|Y|node-01-003||plain||1
 
 
 |128,1,1024,1,1,1|128,1,1024,1,1,1;32768,8,1048576,16,8,12|diskless,file,sharedfile,plain,blockdev,drbd,rbd,ext|4.0|32.0
diff --git a/test/data/proc_diskstats.txt b/test/data/proc_diskstats.txt
new file mode 100644 (file)
index 0000000..04d7c56
--- /dev/null
@@ -0,0 +1,32 @@
+   1       0 ram0 0 0 0 0 0 0 0 0 0 0 0
+   1       1 ram1 0 0 0 0 0 0 0 0 0 0 0
+   1       2 ram2 0 0 0 0 0 0 0 0 0 0 0
+   1       3 ram3 0 0 0 0 0 0 0 0 0 0 0
+   1       4 ram4 0 0 0 0 0 0 0 0 0 0 0
+   1       5 ram5 0 0 0 0 0 0 0 0 0 0 0
+   1       6 ram6 0 0 0 0 0 0 0 0 0 0 0
+   1       7 ram7 0 0 0 0 0 0 0 0 0 0 0
+   1       8 ram8 0 0 0 0 0 0 0 0 0 0 0
+   1       9 ram9 0 0 0 0 0 0 0 0 0 0 0
+   1      10 ram10 0 0 0 0 0 0 0 0 0 0 0
+   1      11 ram11 0 0 0 0 0 0 0 0 0 0 0
+   1      12 ram12 0 0 0 0 0 0 0 0 0 0 0
+   1      13 ram13 0 0 0 0 0 0 0 0 0 0 0
+   1      14 ram14 0 0 0 0 0 0 0 0 0 0 0
+   1      15 ram15 0 0 0 0 0 0 0 0 0 0 0
+   7       0 loop0 0 0 0 0 0 0 0 0 0 0 0
+   7       1 loop1 0 0 0 0 0 0 0 0 0 0 0
+   7       2 loop2 0 0 0 0 0 0 0 0 0 0 0
+   7       3 loop3 0 0 0 0 0 0 0 0 0 0 0
+   7       4 loop4 0 0 0 0 0 0 0 0 0 0 0
+   7       5 loop5 0 0 0 0 0 0 0 0 0 0 0
+   7       6 loop6 0 0 0 0 0 0 0 0 0 0 0
+   7       7 loop7 0 0 0 0 0 0 0 0 0 0 0
+   8       0 sda 89502 4833 4433387 89244 519115 62738 16059726 465120 0 149148 554564
+   8       1 sda1 505 2431 8526 132 478 174 124358 8500 0 340 8632
+   8       2 sda2 2 0 4 4 0 0 0 0 0 4 4
+   8       5 sda5 88802 2269 4422249 89032 453703 62564 15935368 396244 0 90064 485500
+ 252       0 dm-0 90978 0 4420002 158632 582226 0 15935368 5592012 0 167688 5750652
+ 252       1 dm-1 88775 0 4402378 157204 469594 0 15136008 4910424 0 164556 5067640
+ 252       2 dm-2 1956 0 15648 1052 99920 0 799360 682492 0 4516 683552
+   8      16 sdb 0 0 0 0 0 0 0 0 0 0 0
index d7c3d58..03fafae 100644 (file)
@@ -1,3 +1,4 @@
+version: 8.0.12 (api:86/proto:86)
 GIT-hash: 5c9f89594553e32adb87d9638dce591782f947e3 build by root@node1.example.com, 2009-05-22 12:47:52
  0: cs:Connected st:Primary/Secondary ds:UpToDate/UpToDate C r---
     ns:78728316 nr:0 dw:77675644 dr:1277039 al:254 bm:270 lo:0 pe:0 ua:0 ap:0
diff --git a/test/data/proc_drbd80-emptyversion.txt b/test/data/proc_drbd80-emptyversion.txt
new file mode 100644 (file)
index 0000000..cefd15f
--- /dev/null
@@ -0,0 +1,9 @@
+GIT-hash: 5c9f89594553e32adb87d9638dce591782f947e3 build by root@node1.example.com, 2009-05-22 12:47:52
+ 0: cs:Connected st:Primary/Secondary ds:UpToDate/UpToDate C r---
+    ns:78728316 nr:0 dw:77675644 dr:1277039 al:254 bm:270 lo:0 pe:0 ua:0 ap:0
+        resync: used:0/61 hits:65657 misses:135 starving:0 dirty:0 changed:135
+        act_log: used:0/257 hits:11378843 misses:254 starving:0 dirty:0 changed:254
+ 1: cs:Unconfigured
+ 2: cs:Unconfigured
+ 5: cs:Unconfigured
+ 6: cs:Unconfigured
diff --git a/test/data/proc_drbd84.txt b/test/data/proc_drbd84.txt
new file mode 100644 (file)
index 0000000..a8b3cbf
--- /dev/null
@@ -0,0 +1,16 @@
+version: 8.4.2 (api:1/proto:86-101)
+GIT-hash: 7ad5f850d711223713d6dcadc3dd48860321070c build by root@example.com, 2013-04-10 07:45:25
+ 0: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
+    ns:1048576 nr:0 dw:0 dr:1048776 al:0 bm:64 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
+ 1: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r-----
+    ns:0 nr:1048576 dw:1048576 dr:0 al:0 bm:64 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
+ 2: cs:Unconfigured
+
+ 4: cs:WFConnection ro:Primary/Unknown ds:UpToDate/DUnknown C r-----
+    ns:0 nr:0 dw:0 dr:200 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:1048320
+
+ 6: cs:Connected ro:Secondary/Primary ds:Diskless/UpToDate C r-----
+    ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:b oos:0
+
+ 8: cs:StandAlone ro:Secondary/Unknown ds:UpToDate/DUnknown   r-----
+    ns:0 nr:0 dw:0 dr:200 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:1048320
diff --git a/test/data/proc_drbd84_sync.txt b/test/data/proc_drbd84_sync.txt
new file mode 100644 (file)
index 0000000..20971ba
--- /dev/null
@@ -0,0 +1,11 @@
+version: 8.4.2 (api:1/proto:86-101)
+GIT-hash: 7ad5f850d711223713d6dcadc3dd48860321070c build by root@example.com, 2013-04-10 07:45:25
+ 0: cs:StandAlone ro:Primary/Unknown ds:UpToDate/DUnknown   r-----
+    ns:0 nr:0 dw:33318 dr:730 al:15 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:d oos:1048320
+
+ 3: cs:Unconfigured
+
+ 5: cs:SyncSource ro:Secondary/Secondary ds:UpToDate/Inconsistent C r---n-
+    ns:716992 nr:0 dw:0 dr:719432 al:0 bm:43 lo:0 pe:33 ua:18 ap:0 ep:1 wo:f oos:335744
+        [============>.......] sync'ed: 68.5% (335744/1048576)K
+        finish: 0:00:05 speed: 64,800 (64,800) K/sec
index 088a53b..1e54a21 100644 (file)
@@ -38,8 +38,9 @@ import System.Time (ClockTime(..))
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 import Test.Ganeti.TestHTools
-import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode)
-import Test.Ganeti.HTools.Node (genNode, genOnlineNode)
+import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode,
+                                    genInstanceOnNodeList)
+import Test.Ganeti.HTools.Node (genNode, genOnlineNode, genUniqueNodeList)
 
 import Ganeti.BasicTypes
 import qualified Ganeti.HTools.Backend.Text as Text
@@ -97,13 +98,25 @@ prop_Load_Instance name mem dsk vcpus status
 
 prop_Load_InstanceFail :: [(String, Int)] -> [String] -> Property
 prop_Load_InstanceFail ktn fields =
-  length fields /= 10 && length fields /= 11 ==>
+  length fields < 10 || length fields > 12 ==>
     case Text.loadInst nl fields of
       Ok _ -> failTest "Managed to load instance from invalid data"
       Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
                  "Invalid/incomplete instance data: '" `isPrefixOf` msg
     where nl = Map.fromList ktn
 
+genInstanceNodes :: Gen (Instance.Instance, Node.List, Types.NameAssoc)
+genInstanceNodes = do
+    (nl, na) <- genUniqueNodeList genOnlineNode
+    inst <- genInstanceOnNodeList nl
+    return (inst, nl, na)
+
+prop_InstanceLSIdempotent :: Property
+prop_InstanceLSIdempotent =
+  forAll genInstanceNodes $ \(inst, nl, assoc) ->
+    (Text.loadInst assoc . Utils.sepSplit '|' . Text.serializeInstance nl)
+    inst ==? Ok (Instance.name inst, inst)
+
 prop_Load_Node :: String -> Int -> Int -> Int -> Int -> Int
                -> Int -> Bool -> Bool
 prop_Load_Node name tm nm fm td fd tc fo =
@@ -213,6 +226,7 @@ prop_CreateSerialise =
 testSuite "HTools/Backend/Text"
             [ 'prop_Load_Instance
             , 'prop_Load_InstanceFail
+            , 'prop_InstanceLSIdempotent
             , 'prop_Load_Node
             , 'prop_Load_NodeFail
             , 'prop_NodeLSIdempotent
index eb0cda4..15881ae 100644 (file)
@@ -47,15 +47,18 @@ import qualified Ganeti.HTools.Types as Types
 {-# ANN module "HLint: ignore Use camelCase" #-}
 
 -- | Test correct parsing.
-prop_parseISpec :: String -> Int -> Int -> Int -> Property
-prop_parseISpec descr dsk mem cpu =
-  let str = printf "%d,%d,%d" dsk mem cpu::String
-  in parseISpecString descr str ==? Ok (Types.RSpec cpu mem dsk)
+prop_parseISpec :: String -> Int -> Int -> Int -> Maybe Int -> Property
+prop_parseISpec descr dsk mem cpu spn =
+  let (str, spn') = case spn of
+                      Nothing -> (printf "%d,%d,%d" dsk mem cpu::String, 1)
+                      Just spn'' ->
+                        (printf "%d,%d,%d,%d" dsk mem cpu spn''::String, spn'')
+  in parseISpecString descr str ==? Ok (Types.RSpec cpu mem dsk spn')
 
 -- | Test parsing failure due to wrong section count.
 prop_parseISpecFail :: String -> Property
 prop_parseISpecFail descr =
-  forAll (choose (0,100) `suchThat` (/= 3)) $ \nelems ->
+  forAll (choose (0,100) `suchThat` (not . flip elem [3, 4])) $ \nelems ->
   forAll (replicateM nelems arbitrary) $ \values ->
   let str = intercalate "," $ map show (values::[Int])
   in case parseISpecString descr str of
index 75525b4..b51093b 100644 (file)
@@ -106,7 +106,8 @@ prop_Score_Zero :: Node.Node -> Property
 prop_Score_Zero node =
   forAll (choose (1, 1024)) $ \count ->
     (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
-     (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
+     (Node.tDsk node > 0) && (Node.tMem node > 0) &&
+     (Node.tSpindles node > 0)) ==>
   let fn = Node.buildPeers node Container.empty
       nlst = replicate count fn
       score = Cluster.compCVNodes nlst
@@ -394,8 +395,9 @@ prop_AllocPolicy =
   forAll genOnlineNode $ \node ->
   forAll (choose (5, 20)) $ \count ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
-  forAll (arbitrary `suchThat` (isBad .
-                                Instance.instMatchesPolicy inst)) $ \ipol ->
+  forAll (arbitrary `suchThat`
+          (isBad . flip (Instance.instMatchesPolicy inst)
+           (Node.exclStorage node))) $ \ipol ->
   let rqn = Instance.requiredNodes $ Instance.diskTemplate inst
       node' = Node.setPolicy ipol node
       nl = makeSmallCluster node' count
index 53445ba..9c09103 100644 (file)
@@ -7,7 +7,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -30,12 +30,12 @@ module Test.Ganeti.HTools.Instance
   ( testHTools_Instance
   , genInstanceSmallerThanNode
   , genInstanceMaybeBiggerThanNode
-  , genInstanceSmallerThan
   , genInstanceOnNodeList
   , genInstanceList
   , Instance.Instance(..)
   ) where
 
+import Control.Monad (liftM)
 import Test.QuickCheck hiding (Result)
 
 import Test.Ganeti.TestHelper
@@ -52,8 +52,9 @@ import qualified Ganeti.HTools.Types as Types
 -- * Arbitrary instances
 
 -- | Generates a random instance with maximum disk/mem/cpu values.
-genInstanceSmallerThan :: Int -> Int -> Int -> Gen Instance.Instance
-genInstanceSmallerThan lim_mem lim_dsk lim_cpu = do
+genInstanceSmallerThan :: Int -> Int -> Int -> Maybe Int ->
+                          Gen Instance.Instance
+genInstanceSmallerThan lim_mem lim_dsk lim_cpu lim_spin = do
   name <- genFQDN
   mem <- choose (0, lim_mem)
   dsk <- choose (0, lim_dsk)
@@ -62,7 +63,12 @@ genInstanceSmallerThan lim_mem lim_dsk lim_cpu = do
   sn <- arbitrary
   vcpus <- choose (0, lim_cpu)
   dt <- arbitrary
-  return $ Instance.create name mem dsk [dsk] vcpus run_st [] True pn sn dt 1 []
+  spindles <- case lim_spin of
+    Nothing -> genMaybe $ choose (0, maxSpindles)
+    Just ls -> liftM Just $ choose (0, ls)
+  let disk = Instance.Disk dsk spindles
+  return $ Instance.create
+    name mem dsk [disk] vcpus run_st [] True pn sn dt 1 []
 
 -- | Generates an instance smaller than a node.
 genInstanceSmallerThanNode :: Node.Node -> Gen Instance.Instance
@@ -70,6 +76,9 @@ genInstanceSmallerThanNode node =
   genInstanceSmallerThan (Node.availMem node `div` 2)
                          (Node.availDisk node `div` 2)
                          (Node.availCpu node `div` 2)
+                         (if Node.exclStorage node
+                          then Just $ Node.fSpindles node `div` 2
+                          else Nothing)
 
 -- | Generates an instance possibly bigger than a node.
 genInstanceMaybeBiggerThanNode :: Node.Node -> Gen Instance.Instance
@@ -77,6 +86,10 @@ genInstanceMaybeBiggerThanNode node =
   genInstanceSmallerThan (Node.availMem  node + Types.unitMem * 2)
                          (Node.availDisk node + Types.unitDsk * 3)
                          (Node.availCpu  node + Types.unitCpu * 4)
+                         (if Node.exclStorage node
+                          then Just $ Node.fSpindles node +
+                               Types.unitSpindle * 5
+                          else Nothing)
 
 -- | Generates an instance with nodes on a node list.
 -- The following rules are respected:
@@ -103,7 +116,7 @@ genInstanceList igen = fmap (snd . Loader.assignIndices) names_instances
 
 -- let's generate a random instance
 instance Arbitrary Instance.Instance where
-  arbitrary = genInstanceSmallerThan maxMem maxDsk maxCpu
+  arbitrary = genInstanceSmallerThan maxMem maxDsk maxCpu Nothing
 
 -- * Test cases
 
@@ -178,7 +191,8 @@ prop_shrinkDG inst =
 prop_shrinkDF :: Instance.Instance -> Property
 prop_shrinkDF inst =
   forAll (choose (0, 2 * Types.unitDsk - 1)) $ \dsk ->
-    let inst' = inst { Instance.dsk = dsk, Instance.disks = [dsk] }
+    let inst' = inst { Instance.dsk = dsk
+                     , Instance.disks = [Instance.Disk dsk Nothing] }
     in isBad $ Instance.shrinkByType inst' Types.FailDisk
 
 prop_setMovable :: Instance.Instance -> Bool -> Property
index 4b36db4..1cbed20 100644 (file)
@@ -7,7 +7,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@ module Test.Ganeti.HTools.Node
   , genNode
   , genOnlineNode
   , genNodeList
+  , genUniqueNodeList
   ) where
 
 import Test.QuickCheck
@@ -68,18 +69,20 @@ genNode :: Maybe Int -- ^ Minimum node size in terms of units
                      -- just by the max... constants)
         -> Gen Node.Node
 genNode min_multiplier max_multiplier = do
-  let (base_mem, base_dsk, base_cpu) =
+  let (base_mem, base_dsk, base_cpu, base_spindles) =
         case min_multiplier of
           Just mm -> (mm * Types.unitMem,
                       mm * Types.unitDsk,
-                      mm * Types.unitCpu)
-          Nothing -> (0, 0, 0)
-      (top_mem, top_dsk, top_cpu)  =
+                      mm * Types.unitCpu,
+                      mm)
+          Nothing -> (0, 0, 0, 0)
+      (top_mem, top_dsk, top_cpu, top_spindles)  =
         case max_multiplier of
           Just mm -> (mm * Types.unitMem,
                       mm * Types.unitDsk,
-                      mm * Types.unitCpu)
-          Nothing -> (maxMem, maxDsk, maxCpu)
+                      mm * Types.unitCpu,
+                      mm)
+          Nothing -> (maxMem, maxDsk, maxCpu, maxSpindles)
   name  <- genFQDN
   mem_t <- choose (base_mem, top_mem)
   mem_f <- choose (base_mem, mem_t)
@@ -88,8 +91,10 @@ genNode min_multiplier max_multiplier = do
   dsk_f <- choose (base_dsk, dsk_t)
   cpu_t <- choose (base_cpu, top_cpu)
   offl  <- arbitrary
+  spindles <- choose (base_spindles, top_spindles)
   let n = Node.create name (fromIntegral mem_t) mem_n mem_f
-          (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl 1 0
+          (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl spindles
+          0 0 False
       n' = Node.setPolicy nullIPolicy n
   return $ Node.buildPeers n' Container.empty
 
@@ -100,7 +105,23 @@ genOnlineNode =
                               not (Node.failN1 n) &&
                               Node.availDisk n > 0 &&
                               Node.availMem n > 0 &&
-                              Node.availCpu n > 0)
+                              Node.availCpu n > 0 &&
+                              Node.tSpindles n > 0)
+
+-- | Generate a node with exclusive storage enabled.
+genExclStorNode :: Gen Node.Node
+genExclStorNode = do
+  n <- genOnlineNode
+  fs <- choose (Types.unitSpindle, Node.tSpindles n)
+  let pd = fromIntegral fs / fromIntegral (Node.tSpindles n)::Double
+  return n { Node.exclStorage = True
+           , Node.fSpindles = fs
+           , Node.pDsk = pd
+           }
+
+-- | Generate a node with exclusive storage possibly enabled.
+genMaybeExclStorNode :: Gen Node.Node
+genMaybeExclStorNode = oneof [genOnlineNode, genExclStorNode]
 
 -- and a random node
 instance Arbitrary Node.Node where
@@ -114,6 +135,15 @@ genNodeList :: Gen Node.Node -> Gen Node.List
 genNodeList ngen = fmap (snd . Loader.assignIndices) names_nodes
     where names_nodes = (fmap . map) (\n -> (Node.name n, n)) $ listOf1 ngen
 
+-- | Node list generator where node names are unique
+genUniqueNodeList :: Gen Node.Node -> Gen (Node.List, Types.NameAssoc)
+genUniqueNodeList ngen = (do
+  nl <- genNodeList ngen
+  let na = (fst . Loader.assignIndices) $
+           map (\n -> (Node.name n, n)) (Container.elems nl)
+  return (nl, na)) `suchThat`
+    (\(nl, na) -> Container.size nl == Map.size na)
+
 -- | Generate a node list, an instance list, and a node graph.
 -- We choose instances with nodes contained in the node list.
 genNodeGraph :: Gen (Maybe Graph.Graph, Node.List, Instance.List)
@@ -160,7 +190,7 @@ prop_setFmemExact node =
 -- memory does not raise an N+1 error
 prop_addPri_NoN1Fail :: Property
 prop_addPri_NoN1Fail =
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
   let inst' = inst { Instance.mem = Node.fMem node - Node.rMem node }
   in (Node.addPri node inst' /=? Bad Types.FailN1)
@@ -186,12 +216,32 @@ prop_addPriFD node inst =
                      , Instance.diskTemplate = dt }
   in (Node.addPri node inst'' ==? Bad Types.FailDisk)
 
+-- | Check if an instance exceeds a spindles limit or has no spindles set.
+hasInstTooManySpindles :: Instance.Instance -> Int -> Bool
+hasInstTooManySpindles inst sp_lim =
+  case Instance.getTotalSpindles inst of
+    Just s -> s > sp_lim
+    Nothing -> True
+
+-- | Check that adding a primary instance with too many spindles fails
+-- with type FailSpindles (when exclusive storage is enabled).
+prop_addPriFS :: Instance.Instance -> Property
+prop_addPriFS inst =
+  forAll genExclStorNode $ \node ->
+  forAll (elements Instance.localStorageTemplates) $ \dt ->
+  hasInstTooManySpindles inst (Node.fSpindles node) &&
+    not (Node.failN1 node) ==>
+  let inst' = setInstanceSmallerThanNode node inst
+      inst'' = inst' { Instance.disks = Instance.disks inst
+                     , Instance.diskTemplate = dt }
+  in (Node.addPri node inst'' ==? Bad Types.FailSpindles)
+
 -- | Check that adding a primary instance with too many VCPUs fails
 -- with type FailCPU.
 prop_addPriFC :: Property
 prop_addPriFC =
   forAll (choose (1, maxCpu)) $ \extra ->
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (arbitrary `suchThat` Instance.notOffline) $ \inst ->
   let inst' = setInstanceSmallerThanNode node inst
       inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
@@ -205,7 +255,9 @@ prop_addSec :: Node.Node -> Instance.Instance -> Int -> Property
 prop_addSec node inst pdx =
   ((Instance.mem inst >= (Node.fMem node - Node.rMem node) &&
     not (Instance.isOffline inst)) ||
-   Instance.dsk inst >= Node.fDsk node) &&
+   Instance.dsk inst >= Node.fDsk node ||
+   (Node.exclStorage node &&
+    hasInstTooManySpindles inst (Node.fSpindles node))) &&
   not (Node.failN1 node) ==>
       isBad (Node.addSec node inst pdx)
 
@@ -213,7 +265,7 @@ prop_addSec node inst pdx =
 -- extra mem/cpu can always be added.
 prop_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
 prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
   let inst' = inst { Instance.runSt = Types.StatusOffline
                    , Instance.mem = Node.availMem node + extra_mem
@@ -227,7 +279,7 @@ prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
 prop_addOfflineSec :: NonNegative Int -> NonNegative Int
                    -> Types.Ndx -> Property
 prop_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
   let inst' = inst { Instance.runSt = Types.StatusOffline
                    , Instance.mem = Node.availMem node + extra_mem
@@ -241,7 +293,8 @@ prop_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
 prop_rMem :: Instance.Instance -> Property
 prop_rMem inst =
   not (Instance.isOffline inst) ==>
-  forAll (genOnlineNode `suchThat` ((> Types.unitMem) . Node.fMem)) $ \node ->
+  forAll (genMaybeExclStorNode `suchThat` ((> Types.unitMem) . Node.fMem)) $
+    \node ->
   -- ab = auto_balance, nb = non-auto_balance
   -- we use -1 as the primary node of the instance
   let inst' = inst { Instance.pNode = -1, Instance.autoBalance = True
@@ -314,7 +367,7 @@ prop_computeGroups nodes =
 -- Check idempotence of add/remove operations
 prop_addPri_idempotent :: Property
 prop_addPri_idempotent =
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
   case Node.addPri node inst of
     Ok node' -> Node.removePri node' inst ==? node
@@ -322,7 +375,7 @@ prop_addPri_idempotent =
 
 prop_addSec_idempotent :: Property
 prop_addSec_idempotent =
-  forAll genOnlineNode $ \node ->
+  forAll genMaybeExclStorNode $ \node ->
   forAll (genInstanceSmallerThanNode node) $ \inst ->
   let pdx = Node.idx node + 1
       inst' = Instance.setPri inst pdx
@@ -390,6 +443,7 @@ testSuite "HTools/Node"
             , 'prop_setXmem
             , 'prop_addPriFM
             , 'prop_addPriFD
+            , 'prop_addPriFS
             , 'prop_addPriFC
             , 'prop_addPri_NoN1Fail
             , 'prop_addSec
index a5477a5..006e195 100644 (file)
@@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 module Test.Ganeti.JSON (testJSON) where
 
+import Data.List
 import Test.QuickCheck
 
 import qualified Text.JSON as J
@@ -53,7 +54,31 @@ prop_toArrayFail i s b =
     BasicTypes.Bad _ -> passTest
     BasicTypes.Ok result -> failTest $ "Unexpected parse, got " ++ show result
 
+arrayMaybeToJson :: (J.JSON a) => [Maybe a] -> String -> JSON.JSRecord
+arrayMaybeToJson xs k = [(k, J.JSArray $ map sh xs)]
+  where
+    sh x = case x of
+      Just v -> J.showJSON v
+      Nothing -> J.JSNull
+
+prop_arrayMaybeFromObj :: String -> [Maybe Int] -> String -> Property
+prop_arrayMaybeFromObj t xs k =
+  case JSON.tryArrayMaybeFromObj t (arrayMaybeToJson xs k) k of
+    BasicTypes.Ok xs' -> xs' ==? xs
+    BasicTypes.Bad e -> failTest $ "Parsing failing, got: " ++ show e
+
+prop_arrayMaybeFromObjFail :: String -> String -> Property
+prop_arrayMaybeFromObjFail t k =
+  case JSON.tryArrayMaybeFromObj t [] k of
+    BasicTypes.Ok r -> fail $
+                       "Unexpected result, got: " ++ show (r::[Maybe Int])
+    BasicTypes.Bad e -> conjoin [ Data.List.isInfixOf t e ==? True
+                                , Data.List.isInfixOf k e ==? True
+                                ]
+
 testSuite "JSON"
           [ 'prop_toArray
           , 'prop_toArrayFail
+          , 'prop_arrayMaybeFromObj
+          , 'prop_arrayMaybeFromObjFail
           ]
index b27bba5..e550014 100644 (file)
@@ -93,7 +93,7 @@ instance Arbitrary DiskLogicalId where
 instance Arbitrary Disk where
   arbitrary = Disk <$> arbitrary <*> pure [] <*> arbitrary
                    <*> arbitrary <*> arbitrary <*> arbitrary
-                   <*> arbitrary
+                   <*> arbitrary <*> arbitrary
 
 -- FIXME: we should generate proper values, >=0, etc., but this is
 -- hard for partial ones, where all must be wrapped in a 'Maybe'
@@ -181,8 +181,9 @@ genDiskWithChildren num_children = do
   size <- arbitrary
   mode <- arbitrary
   name <- genMaybe genName
+  spindles <- arbitrary
   uuid <- genName
-  let disk = Disk logicalid children ivname size mode name uuid
+  let disk = Disk logicalid children ivname size mode name spindles uuid
   return disk
 
 genDisk :: Gen Disk
index eb539a6..a8e7eed 100644 (file)
@@ -109,18 +109,20 @@ instance Arbitrary OpCodes.OpCode where
     case op_id of
       "OP_TEST_DELAY" ->
         OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
-          genNodeNamesNE <*> arbitrary
+          genNodeNamesNE <*> return Nothing <*> arbitrary
       "OP_INSTANCE_REPLACE_DISKS" ->
-        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> genDiskIndices <*>
-          genMaybe genNodeNameNE <*> genMaybe genNameNE
+        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
+          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
       "OP_INSTANCE_FAILOVER" ->
-        OpCodes.OpInstanceFailover <$> genFQDN <*> arbitrary <*> arbitrary <*>
-          genMaybe genNodeNameNE <*> arbitrary <*> genMaybe genNameNE
+        OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
+        arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
+        return Nothing <*> arbitrary <*> genMaybe genNameNE
       "OP_INSTANCE_MIGRATE" ->
-        OpCodes.OpInstanceMigrate <$> genFQDN <*> arbitrary <*> arbitrary <*>
-          genMaybe genNodeNameNE <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> genMaybe genNameNE <*> arbitrary
+        OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
+          return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
+          genMaybe genNameNE <*> arbitrary
       "OP_TAGS_GET" ->
         OpCodes.OpTagsGet <$> arbitrary <*> arbitrary
       "OP_TAGS_SEARCH" ->
@@ -172,9 +174,11 @@ instance Arbitrary OpCodes.OpCode where
       "OP_QUERY_FIELDS" ->
         OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
       "OP_OOB_COMMAND" ->
-        OpCodes.OpOobCommand <$> genNodeNamesNE <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> (arbitrary `suchThat` (>0))
-      "OP_NODE_REMOVE" -> OpCodes.OpNodeRemove <$> genNodeNameNE
+        OpCodes.OpOobCommand <$> genNodeNamesNE <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> arbitrary <*>
+          (arbitrary `suchThat` (>0))
+      "OP_NODE_REMOVE" ->
+        OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
       "OP_NODE_ADD" ->
         OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
           genMaybe genName <*> genMaybe genNameNE <*> arbitrary <*>
@@ -187,25 +191,27 @@ instance Arbitrary OpCodes.OpCode where
         OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
           genNodeNamesNE <*> genNameNE
       "OP_NODE_MODIFY_STORAGE" ->
-        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> arbitrary <*>
-          genNameNE <*> pure emptyJSObject
+        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
+          arbitrary <*> genNameNE <*> pure emptyJSObject
       "OP_REPAIR_NODE_STORAGE" ->
-        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> arbitrary <*>
-          genNameNE <*> arbitrary
+        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
+          arbitrary <*> genNameNE <*> arbitrary
       "OP_NODE_SET_PARAMS" ->
-        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> arbitrary <*>
-          emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
-          emptyMUD <*> arbitrary
+        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
+          arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
+          genMaybe genNameNE <*> emptyMUD <*> arbitrary
       "OP_NODE_POWERCYCLE" ->
-        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> arbitrary
+        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
+          arbitrary
       "OP_NODE_MIGRATE" ->
-        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> arbitrary <*>
-          arbitrary <*> genMaybe genNodeNameNE <*> arbitrary <*>
-          arbitrary <*> genMaybe genNameNE
+        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
+          return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
       "OP_NODE_EVACUATE" ->
         OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
-          genMaybe genNodeNameNE <*> genMaybe genNameNE <*> arbitrary
+          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
+          genMaybe genNameNE <*> arbitrary
       "OP_INSTANCE_CREATE" ->
         OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
           arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
@@ -214,69 +220,77 @@ instance Arbitrary OpCodes.OpCode where
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
           arbitrary <*> arbitrary <*> pure emptyJSObject <*>
           genMaybe genNameNE <*>
-          genMaybe genNodeNameNE <*> genMaybe genNodeNameNE <*>
+          genMaybe genNodeNameNE <*> return Nothing <*>
+          genMaybe genNodeNameNE <*> return Nothing <*>
           genMaybe (pure []) <*> genMaybe genNodeNameNE <*>
-          arbitrary <*> genMaybe genNodeNameNE <*>
+          arbitrary <*> genMaybe genNodeNameNE <*> return Nothing <*>
           genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
           arbitrary <*> arbitrary <*> (genTags >>= mapM mkNonEmpty)
       "OP_INSTANCE_MULTI_ALLOC" ->
         OpCodes.OpInstanceMultiAlloc <$> genMaybe genNameNE <*> pure [] <*>
           arbitrary
       "OP_INSTANCE_REINSTALL" ->
-        OpCodes.OpInstanceReinstall <$> genFQDN <*> arbitrary <*>
-          genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
+        OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
       "OP_INSTANCE_REMOVE" ->
-        OpCodes.OpInstanceRemove <$> genFQDN <*> arbitrary <*> arbitrary
-      "OP_INSTANCE_RENAME" ->
-        OpCodes.OpInstanceRename <$> genFQDN <*> genNodeNameNE <*>
+        OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary
+      "OP_INSTANCE_RENAME" ->
+        OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
+          genNodeNameNE <*> arbitrary <*> arbitrary
       "OP_INSTANCE_STARTUP" ->
-        OpCodes.OpInstanceStartup <$> genFQDN <*> arbitrary <*> arbitrary <*>
-          pure emptyJSObject <*> pure emptyJSObject <*>
-          arbitrary <*> arbitrary
+        OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
+          pure emptyJSObject <*> arbitrary <*> arbitrary
       "OP_INSTANCE_SHUTDOWN" ->
-        OpCodes.OpInstanceShutdown <$> genFQDN <*> arbitrary <*> arbitrary <*>
-          arbitrary <*> arbitrary
+        OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
       "OP_INSTANCE_REBOOT" ->
-        OpCodes.OpInstanceReboot <$> genFQDN <*> arbitrary <*>
-          arbitrary <*> arbitrary
+        OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> arbitrary
       "OP_INSTANCE_MOVE" ->
-        OpCodes.OpInstanceMove <$> genFQDN <*> arbitrary <*> arbitrary <*>
-          genNodeNameNE <*> arbitrary
-      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN
+        OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
+          arbitrary
+      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
+          return Nothing
       "OP_INSTANCE_ACTIVATE_DISKS" ->
-        OpCodes.OpInstanceActivateDisks <$> genFQDN <*>
+        OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary
       "OP_INSTANCE_DEACTIVATE_DISKS" ->
-        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> arbitrary
+        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
+          arbitrary
       "OP_INSTANCE_RECREATE_DISKS" ->
-        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> arbitrary <*>
-          genNodeNamesNE <*> genMaybe genNameNE
+        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> genNodeNamesNE <*> return Nothing <*>
+          genMaybe genNameNE
       "OP_INSTANCE_QUERY" ->
         OpCodes.OpInstanceQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
       "OP_INSTANCE_QUERY_DATA" ->
         OpCodes.OpInstanceQueryData <$> arbitrary <*>
           genNodeNamesNE <*> arbitrary
       "OP_INSTANCE_SET_PARAMS" ->
-        OpCodes.OpInstanceSetParams <$> genFQDN <*> arbitrary <*>
+        OpCodes.OpInstanceSetParams <$> genFQDN <*> return Nothing <*>
           arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
-          pure emptyJSObject <*> arbitrary <*> pure emptyJSObject <*>
-          arbitrary <*> genMaybe genNodeNameNE <*> genMaybe genNodeNameNE <*>
+          arbitrary <*> pure emptyJSObject <*> arbitrary <*>
+          pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
+          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
           genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
           arbitrary <*> arbitrary
       "OP_INSTANCE_GROW_DISK" ->
-        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> arbitrary
+        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
       "OP_INSTANCE_CHANGE_GROUP" ->
-        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> arbitrary <*>
-          genMaybe genNameNE <*> genMaybe (resize maxNodes (listOf genNameNE))
+        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> genMaybe genNameNE <*>
+          genMaybe (resize maxNodes (listOf genNameNE))
       "OP_GROUP_ADD" ->
         OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
           emptyMUD <*> genMaybe genEmptyContainer <*>
           emptyMUD <*> emptyMUD <*> emptyMUD
       "OP_GROUP_ASSIGN_NODES" ->
         OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
-          genNodeNamesNE
+          genNodeNamesNE <*> return Nothing
       "OP_GROUP_QUERY" ->
         OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
       "OP_GROUP_SET_PARAMS" ->
@@ -297,13 +311,14 @@ instance Arbitrary OpCodes.OpCode where
       "OP_BACKUP_QUERY" ->
         OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
       "OP_BACKUP_PREPARE" ->
-        OpCodes.OpBackupPrepare <$> genFQDN <*> arbitrary
+        OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
       "OP_BACKUP_EXPORT" ->
-        OpCodes.OpBackupExport <$> genFQDN <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
-          arbitrary <*> genMaybe (pure []) <*> genMaybe genNameNE
+        OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
+          arbitrary <*> arbitrary <*> return Nothing <*> arbitrary <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe (pure []) <*>
+          genMaybe genNameNE
       "OP_BACKUP_REMOVE" ->
-        OpCodes.OpBackupRemove <$> genFQDN
+        OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
       "OP_TEST_ALLOCATOR" ->
         OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
           genNameNE <*> pure [] <*> pure [] <*>
@@ -339,7 +354,7 @@ instance Arbitrary OpCodes.OpCode where
         OpCodes.OpNetworkQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
       "OP_RESTRICTED_COMMAND" ->
         OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
-          genNameNE
+          return Nothing <*> genNameNE
       _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
 
 -- | Generates one element of a reason trail
index c6b9ddb..a940c9b 100644 (file)
@@ -40,6 +40,8 @@ import Test.Ganeti.Objects ()
 
 import qualified Ganeti.Rpc as Rpc
 import qualified Ganeti.Objects as Objects
+import qualified Ganeti.Types as Types
+import qualified Ganeti.JSON as JSON
 
 instance Arbitrary Rpc.RpcCallAllInstancesInfo where
   arbitrary = Rpc.RpcCallAllInstancesInfo <$> arbitrary
@@ -48,9 +50,23 @@ instance Arbitrary Rpc.RpcCallInstanceList where
   arbitrary = Rpc.RpcCallInstanceList <$> arbitrary
 
 instance Arbitrary Rpc.RpcCallNodeInfo where
-  arbitrary = Rpc.RpcCallNodeInfo <$> arbitrary <*> arbitrary <*>
+  arbitrary = Rpc.RpcCallNodeInfo <$> arbitrary <*> genHvSpecs <*>
                 pure Map.empty
 
+-- | Generate hypervisor specifications to be used for the NodeInfo call
+genHvSpecs :: Gen [ (Types.Hypervisor, Objects.HvParams) ]
+genHvSpecs = do
+  numhv <- choose (0, 5)
+  hvs <- vectorOf numhv arbitrary
+  hvparams <- vectorOf numhv genHvParams
+  let specs = zip hvs hvparams
+  return specs
+
+-- FIXME: Generate more interesting hvparams
+-- | Generate Hvparams
+genHvParams :: Gen Objects.HvParams
+genHvParams = return $ JSON.GenericContainer Map.empty
+
 -- | Monadic check that, for an offline node and a call that does not
 -- offline nodes, we get a OfflineNodeError response.
 -- FIXME: We need a way of generalizing this, running it for
diff --git a/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs b/test/hs/Test/Ganeti/Storage/Diskstats/Parser.hs
new file mode 100644 (file)
index 0000000..38430ec
--- /dev/null
@@ -0,0 +1,118 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-| Unittests for the @/proc/diskstats@ parser -}
+
+{-
+
+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.
+
+-}
+
+module Test.Ganeti.Storage.Diskstats.Parser (testBlock_Diskstats_Parser) where
+
+import Test.QuickCheck as QuickCheck hiding (Result)
+import Test.HUnit
+
+import Test.Ganeti.TestHelper
+import Test.Ganeti.TestCommon
+
+import Control.Applicative ((<*>), (<$>))
+import qualified Data.Attoparsec.Text as A
+import Data.Text (pack)
+import Text.Printf
+
+import Ganeti.Storage.Diskstats.Parser (diskstatsParser)
+import Ganeti.Storage.Diskstats.Types
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
+
+
+-- | Test a diskstats.
+case_diskstats :: Assertion
+case_diskstats = testParser diskstatsParser "proc_diskstats.txt"
+  [ Diskstats 1 0 "ram0" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 1 "ram1" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 2 "ram2" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 3 "ram3" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 4 "ram4" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 5 "ram5" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 6 "ram6" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 7 "ram7" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 8 "ram8" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 9 "ram9" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 10 "ram10" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 11 "ram11" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 12 "ram12" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 13 "ram13" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 14 "ram14" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 1 15 "ram15" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 0 "loop0" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 1 "loop1" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 2 "loop2" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 3 "loop3" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 4 "loop4" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 5 "loop5" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 6 "loop6" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 7 7 "loop7" 0 0 0 0 0 0 0 0 0 0 0
+  , Diskstats 8 0 "sda" 89502 4833 4433387 89244 519115 62738 16059726 465120 0
+    149148 554564
+  , Diskstats 8 1 "sda1" 505 2431 8526 132 478 174 124358 8500 0 340 8632
+  , Diskstats 8 2 "sda2" 2 0 4 4 0 0 0 0 0 4 4
+  , Diskstats 8 5 "sda5" 88802 2269 4422249 89032 453703 62564 15935368 396244 0
+    90064 485500
+  , Diskstats 252 0 "dm-0" 90978 0 4420002 158632 582226 0 15935368 5592012 0
+  167688 5750652
+  , Diskstats 252 1 "dm-1" 88775 0 4402378 157204 469594 0 15136008 4910424 0
+  164556 5067640
+  , Diskstats 252 2 "dm-2" 1956 0 15648 1052 99920 0 799360 682492 0 4516 683552
+  , Diskstats 8 16 "sdb" 0 0 0 0 0 0 0 0 0 0 0
+  ]
+
+-- | The instance for generating arbitrary Diskstats
+instance Arbitrary Diskstats where
+  arbitrary =
+    Diskstats <$> genNonNegative <*> genNonNegative <*> genName
+              <*> genNonNegative <*> genNonNegative <*> genNonNegative
+              <*> genNonNegative <*> genNonNegative <*> genNonNegative
+              <*> genNonNegative <*> genNonNegative <*> genNonNegative
+              <*> genNonNegative <*> genNonNegative
+
+-- | Serialize a list of Diskstats in a parsable way
+serializeDiskstatsList :: [Diskstats] -> String
+serializeDiskstatsList = concatMap serializeDiskstats
+
+-- | Serialize a Diskstats in a parsable way
+serializeDiskstats :: Diskstats -> String
+serializeDiskstats ds =
+  printf "\t%d\t%d %s %d %d %d %d %d %d %d %d %d %d %d\n"
+    (dsMajor ds) (dsMinor ds) (dsName ds) (dsReadsNum ds) (dsMergedReads ds)
+    (dsSecRead ds) (dsTimeRead ds) (dsWrites ds) (dsMergedWrites ds)
+    (dsSecWritten ds) (dsTimeWrite ds) (dsIos ds) (dsTimeIO ds) (dsWIOmillis ds)
+
+-- | Test whether an arbitrary Diskstats is parsed correctly
+prop_diskstats :: [Diskstats] -> Property
+prop_diskstats dsList =
+    case A.parseOnly diskstatsParser $ pack (serializeDiskstatsList dsList) of
+      Left msg -> failTest $ "Parsing failed: " ++ msg
+      Right obtained -> dsList ==? obtained
+
+testSuite "Block/Diskstats/Parser"
+          [ 'case_diskstats,
+            'prop_diskstats
+          ]
similarity index 75%
rename from test/hs/Test/Ganeti/Block/Drbd/Parser.hs
rename to test/hs/Test/Ganeti/Storage/Drbd/Parser.hs
index 099f52e..3796442 100644 (file)
@@ -1,6 +1,6 @@
 {-# LANGUAGE TemplateHaskell #-}
 
-{-| Unittests for Attoparsec support for unicode -}
+{-| Unittests for the DRBD Parser -}
 
 {-
 
@@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 -}
 
-module Test.Ganeti.Block.Drbd.Parser (testBlock_Drbd_Parser) where
+module Test.Ganeti.Storage.Drbd.Parser (testBlock_Drbd_Parser) where
 
 import Test.QuickCheck as QuickCheck hiding (Result)
 import Test.HUnit
@@ -35,23 +35,37 @@ import qualified Data.Attoparsec.Text as A
 import Data.List (intercalate)
 import Data.Text (pack)
 
-import Ganeti.Block.Drbd.Parser (drbdStatusParser, commaIntParser)
-import Ganeti.Block.Drbd.Types
+import Ganeti.Storage.Drbd.Parser (drbdStatusParser, commaIntParser)
+import Ganeti.Storage.Drbd.Types
 
 {-# ANN module "HLint: ignore Use camelCase" #-}
 
--- | Function for testing whether a file is parsed correctly.
-testFile :: String -> DRBDStatus -> Assertion
-testFile fileName expectedContent = do
-    fileContent <- readTestData fileName
-    case A.parseOnly (drbdStatusParser []) $ pack fileContent of
-        Left msg -> assertFailure $ "Parsing failed: " ++ msg
-        Right obtained -> assertEqual fileName expectedContent obtained
-
 -- | Test a DRBD 8.0 file with an empty line inside.
 case_drbd80_emptyline :: Assertion
-case_drbd80_emptyline = testFile "proc_drbd80-emptyline.txt" $
-  DRBDStatus
+case_drbd80_emptyline = testParser (drbdStatusParser [])
+  "proc_drbd80-emptyline.txt" $ DRBDStatus
+    ( VersionInfo (Just "8.0.12") (Just "86") (Just "86") Nothing
+        (Just "5c9f89594553e32adb87d9638dce591782f947e3")
+        (Just "root@node1.example.com, 2009-05-22 12:47:52")
+    )
+    [ DeviceInfo 0 Connected (LocalRemote Primary Secondary)
+        (LocalRemote UpToDate UpToDate) 'C' "r---"
+        (PerfIndicators 78728316 0 77675644 1277039 254 270 0 0 0 0
+          Nothing Nothing Nothing)
+        Nothing
+        (Just $ AdditionalInfo 0 61 65657 135 0 0 135)
+        (Just $ AdditionalInfo 0 257 11378843 254 0 0 254)
+        Nothing,
+      UnconfiguredDevice 1,
+      UnconfiguredDevice 2,
+      UnconfiguredDevice 5,
+      UnconfiguredDevice 6
+    ]
+
+-- | Test a DRBD 8.0 file with an empty version.
+case_drbd80_emptyversion :: Assertion
+case_drbd80_emptyversion = testParser (drbdStatusParser [])
+  "proc_drbd80-emptyversion.txt" $ DRBDStatus
     ( VersionInfo Nothing Nothing Nothing Nothing
         (Just "5c9f89594553e32adb87d9638dce591782f947e3")
         (Just "root@node1.example.com, 2009-05-22 12:47:52")
@@ -70,10 +84,89 @@ case_drbd80_emptyline = testFile "proc_drbd80-emptyline.txt" $
       UnconfiguredDevice 6
     ]
 
+-- | Test a DRBD 8.4 file with an ongoing synchronization.
+case_drbd84_sync :: Assertion
+case_drbd84_sync = testParser (drbdStatusParser []) "proc_drbd84_sync.txt" $
+  DRBDStatus
+    ( VersionInfo (Just "8.4.2") (Just "1") (Just "86-101") Nothing
+        (Just "7ad5f850d711223713d6dcadc3dd48860321070c")
+        (Just "root@example.com, 2013-04-10 07:45:25")
+    )
+    [ DeviceInfo 0 StandAlone (LocalRemote Primary Unknown)
+        (LocalRemote UpToDate DUnknown) ' ' "r-----"
+        (PerfIndicators 0 0 33318 730 15 0 0 0 0 0 (Just 1)
+          (Just 'd') (Just 1048320))
+        Nothing
+        Nothing
+        Nothing
+        Nothing,
+      UnconfiguredDevice 3,
+      DeviceInfo 5 SyncSource (LocalRemote Secondary Secondary)
+        (LocalRemote UpToDate Inconsistent) 'C' "r---n-"
+        (PerfIndicators 716992 0 0 719432 0 43 0 33 18 0 (Just 1)
+          (Just 'f') (Just 335744))
+        (Just $ SyncStatus 68.5 335744 1048576 KiloByte (Time 0 0 5) 64800
+          Nothing KiloByte Second)
+        Nothing
+        Nothing
+        Nothing
+    ]
+
+-- | Test a DRBD 8.4 file.
+case_drbd84 :: Assertion
+case_drbd84 = testParser (drbdStatusParser []) "proc_drbd84.txt" $
+  DRBDStatus
+    ( VersionInfo (Just "8.4.2") (Just "1") (Just "86-101") Nothing
+      (Just "7ad5f850d711223713d6dcadc3dd48860321070c")
+      (Just "root@example.com, 2013-04-10 07:45:25")
+    )
+    [ DeviceInfo 0 Connected (LocalRemote Primary Secondary)
+        (LocalRemote UpToDate UpToDate) 'C' "r-----"
+        (PerfIndicators 1048576 0 0 1048776 0 64 0 0 0 0 (Just 1)
+          (Just 'f') (Just 0))
+        Nothing
+        Nothing
+        Nothing
+        Nothing,
+      DeviceInfo 1 Connected (LocalRemote Secondary Primary)
+        (LocalRemote UpToDate UpToDate) 'C' "r-----"
+        (PerfIndicators 0 1048576 1048576 0 0 64 0 0 0 0 (Just 1)
+          (Just 'f') (Just 0))
+        Nothing
+        Nothing
+        Nothing
+        Nothing,
+      UnconfiguredDevice 2,
+      DeviceInfo 4 WFConnection (LocalRemote Primary Unknown)
+        (LocalRemote UpToDate DUnknown) 'C' "r-----"
+        (PerfIndicators 0 0 0 200 0 0 0 0 0 0 (Just 1)
+          (Just 'f') (Just 1048320))
+        Nothing
+        Nothing
+        Nothing
+        Nothing,
+      DeviceInfo 6 Connected (LocalRemote Secondary Primary)
+        (LocalRemote Diskless UpToDate) 'C' "r-----"
+        (PerfIndicators 0 0 0 0 0 0 0 0 0 0 (Just 1) (Just 'b')
+          (Just 0))
+        Nothing
+        Nothing
+        Nothing
+        Nothing,
+      DeviceInfo 8 StandAlone (LocalRemote Secondary Unknown)
+        (LocalRemote UpToDate DUnknown) ' ' "r-----"
+        (PerfIndicators 0 0 0 200 0 0 0 0 0 0 (Just 1)
+          (Just 'f') (Just 1048320))
+        Nothing
+        Nothing
+        Nothing
+        Nothing
+    ]
+
 -- | Test a DRBD 8.3 file with a NULL caracter inside.
 case_drbd83_sync_krnl2_6_39 :: Assertion
-case_drbd83_sync_krnl2_6_39 = testFile "proc_drbd83_sync_krnl2.6.39.txt" $
-  DRBDStatus
+case_drbd83_sync_krnl2_6_39 = testParser (drbdStatusParser [])
+  "proc_drbd83_sync_krnl2.6.39.txt" $ DRBDStatus
     ( VersionInfo (Just "8.3.1") (Just "88") (Just "86-89") Nothing
         (Just "fd40f4a8f9104941537d1afc8521e584a6d3003c")
         (Just "phil@fat-tyre, 2009-03-27 12:19:49")
@@ -116,7 +209,7 @@ case_drbd83_sync_krnl2_6_39 = testFile "proc_drbd83_sync_krnl2.6.39.txt" $
 
 -- | Test a DRBD 8.3 file with an ongoing synchronization.
 case_drbd83_sync :: Assertion
-case_drbd83_sync = testFile "proc_drbd83_sync.txt" $
+case_drbd83_sync = testParser (drbdStatusParser []) "proc_drbd83_sync.txt" $
   DRBDStatus
     ( VersionInfo (Just "8.3.1") (Just "88") (Just "86-89") Nothing
         (Just "fd40f4a8f9104941537d1afc8521e584a6d3003c")
@@ -161,8 +254,8 @@ case_drbd83_sync = testFile "proc_drbd83_sync.txt" $
 -- | Test a DRBD 8.3 file not from git sources, with an ongoing synchronization
 -- and the "want" field
 case_drbd83_sync_want :: Assertion
-case_drbd83_sync_want = testFile "proc_drbd83_sync_want.txt" $
-  DRBDStatus
+case_drbd83_sync_want = testParser (drbdStatusParser [])
+  "proc_drbd83_sync_want.txt" $ DRBDStatus
     ( VersionInfo (Just "8.3.11") (Just "88") (Just "86-96")
         (Just "2D876214BAAD53B31ADC1D6")
         Nothing Nothing
@@ -183,7 +276,7 @@ case_drbd83_sync_want = testFile "proc_drbd83_sync_want.txt" $
 
 -- | Test a DRBD 8.3 file.
 case_drbd83 :: Assertion
-case_drbd83 = testFile "proc_drbd83.txt" $
+case_drbd83 = testParser (drbdStatusParser []) "proc_drbd83.txt" $
   DRBDStatus
     ( VersionInfo (Just "8.3.1") (Just "88") (Just "86-89")
       Nothing
@@ -251,7 +344,7 @@ case_drbd83 = testFile "proc_drbd83.txt" $
 
 -- | Test a DRBD 8.0 file with a missing device.
 case_drbd8 :: Assertion
-case_drbd8 = testFile "proc_drbd8.txt" $
+case_drbd8 = testParser (drbdStatusParser []) "proc_drbd8.txt" $
   DRBDStatus
     ( VersionInfo (Just "8.0.12") (Just "86") (Just "86") Nothing
         (Just "5c9f89594553e32adb87d9638dce591782f947e3")
@@ -381,6 +474,9 @@ case_commaInt_non_triplet = testCommaInt "61,736,12" 61736
 
 testSuite "Block/Drbd/Parser"
           [ 'case_drbd80_emptyline,
+            'case_drbd80_emptyversion,
+            'case_drbd84_sync,
+            'case_drbd84,
             'case_drbd83_sync_krnl2_6_39,
             'case_drbd83_sync,
             'case_drbd83_sync_want,
similarity index 98%
rename from test/hs/Test/Ganeti/Block/Drbd/Types.hs
rename to test/hs/Test/Ganeti/Storage/Drbd/Types.hs
index 6130d05..42e3c50 100644 (file)
@@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 -}
 
-module Test.Ganeti.Block.Drbd.Types (testBlock_Drbd_Types) where
+module Test.Ganeti.Storage.Drbd.Types (testBlock_Drbd_Types) where
 
 import Test.QuickCheck
 
@@ -36,7 +36,7 @@ import Text.Printf
 
 import Ganeti.JSON
 
-import Ganeti.Block.Drbd.Types
+import Ganeti.Storage.Drbd.Types
 
 {-# ANN module "HLint: ignore Use camelCase" #-}
 {-# ANN module "HLint: ignore Use string literal" #-}
index 7c861a5..7e1bc71 100644 (file)
@@ -27,6 +27,7 @@ module Test.Ganeti.TestCommon
   ( maxMem
   , maxDsk
   , maxCpu
+  , maxSpindles
   , maxVcpuRatio
   , maxSpindleRatio
   , maxNodes
@@ -59,12 +60,16 @@ module Test.Ganeti.TestCommon
   , resultProp
   , readTestData
   , genSample
+  , testParser
+  , genNonNegative
   ) where
 
 import Control.Applicative
 import Control.Exception (catchJust)
 import Control.Monad
+import Data.Attoparsec.Text (Parser, parseOnly)
 import Data.List
+import Data.Text (pack)
 import Data.Word
 import qualified Data.Set as Set
 import System.Environment (getEnv)
@@ -94,6 +99,10 @@ maxDsk = 1024 * 1024 * 8
 maxCpu :: Int
 maxCpu = 1024
 
+-- | Max spindles (1024, somewhat random value).
+maxSpindles :: Int
+maxSpindles = 1024
+
 -- | Max vcpu ratio (random value).
 maxVcpuRatio :: Double
 maxVcpuRatio = 1024.0
@@ -340,3 +349,16 @@ genSample gen = do
   case values of
     [] -> error "sample' returned an empty list of values??"
     x:_ -> return x
+
+-- | Function for testing whether a file is parsed correctly.
+testParser :: (Show a, Eq a) => Parser a -> String -> a -> HUnit.Assertion
+testParser parser fileName expectedContent = do
+  fileContent <- readTestData fileName
+  case parseOnly parser $ pack fileContent of
+    Left msg -> HUnit.assertFailure $ "Parsing failed: " ++ msg
+    Right obtained -> HUnit.assertEqual fileName expectedContent obtained
+
+-- | Generate an arbitrary non negative integer number
+genNonNegative :: Gen Int
+genNonNegative =
+  fmap fromIntegral (arbitrary::Gen (Test.QuickCheck.NonNegative Int))
index eca4f57..2788aef 100644 (file)
@@ -99,8 +99,8 @@ defGroupAssoc = Map.singleton (Group.uuid defGroup) (Group.idx defGroup)
 -- | Create an instance given its spec.
 createInstance :: Int -> Int -> Int -> Instance.Instance
 createInstance mem dsk vcpus =
-  Instance.create "inst-unnamed" mem dsk [dsk] vcpus Types.Running [] True (-1)
-    (-1) Types.DTDrbd8 1 []
+  Instance.create "inst-unnamed" mem dsk [Instance.Disk dsk Nothing] vcpus
+    Types.Running [] True (-1) (-1) Types.DTDrbd8 1 []
 
 -- | Create a small cluster by repeating a node spec.
 makeSmallCluster :: Node.Node -> Int -> Node.List
@@ -119,7 +119,12 @@ makeSmallCluster node count =
 setInstanceSmallerThanNode :: Node.Node
                            -> Instance.Instance -> Instance.Instance
 setInstanceSmallerThanNode node inst =
-  inst { Instance.mem = Node.availMem node `div` 2
-       , Instance.dsk = Node.availDisk node `div` 2
-       , Instance.vcpus = Node.availCpu node `div` 2
-       }
+  let new_dsk = Node.availDisk node `div` 2
+  in inst { Instance.mem = Node.availMem node `div` 2
+          , Instance.dsk = new_dsk
+          , Instance.vcpus = Node.availCpu node `div` 2
+          , Instance.disks = [Instance.Disk new_dsk
+                              (if Node.exclStorage node
+                               then Just $ Node.fSpindles node `div` 2
+                               else Nothing)]
+          }
index b93ba81..72d536d 100644 (file)
@@ -33,8 +33,9 @@ import System.Log.Logger
 import Test.Ganeti.TestImports ()
 import Test.Ganeti.Attoparsec
 import Test.Ganeti.BasicTypes
-import Test.Ganeti.Block.Drbd.Parser
-import Test.Ganeti.Block.Drbd.Types
+import Test.Ganeti.Storage.Diskstats.Parser
+import Test.Ganeti.Storage.Drbd.Parser
+import Test.Ganeti.Storage.Drbd.Types
 import Test.Ganeti.Common
 import Test.Ganeti.Confd.Utils
 import Test.Ganeti.Confd.Types
@@ -91,6 +92,7 @@ allTests =
   , testConfd_Types
   , testConfd_Utils
   , testDaemon
+  , testBlock_Diskstats_Parser
   , testBlock_Drbd_Parser
   , testBlock_Drbd_Types
   , testErrors
index cbcba41..07f002e 100755 (executable)
@@ -67,9 +67,19 @@ export BACKEND_EXCL="-t $T/simu-onegroup.standard"
 echo -n Generating data files for IAllocator checks...
 for evac_mode in primary-only secondary-only all; do
   sed -e 's/"evac_mode": "all"/"evac_mode": "'${evac_mode}'"/' \
+    -e 's/"spindles": [0-9]\+,//' \
     < $TESTDATA_DIR/hail-node-evac.json \
     > $T/hail-node-evac.json.$evac_mode
 done
+for bf in hail-alloc-drbd hail-alloc-invalid-twodisks hail-alloc-twodisks \
+  hail-change-group hail-node-evac hail-reloc-drbd hail-alloc-spindles; do
+  f=$bf.json
+  sed -e 's/"exclusive_storage": false/"exclusive_storage": true/' \
+    < $TESTDATA_DIR/$f > $T/$f.excl-stor
+  sed -e 's/"exclusive_storage": false/"exclusive_storage": true/' \
+    -e 's/"spindles": [0-9]\+,//' \
+    < $TESTDATA_DIR/$f > $T/$f.fail-excl-stor
+done
 echo OK
 
 echo -n Checking file-based RAPI...
index 6d34b66..7bb6478 100644 (file)
@@ -62,6 +62,52 @@ cat $TESTDATA_DIR/hail-alloc-invalid-network.json | grep -v -e '"network":"uuid-
 >>> /"success":true,"info":"Request successful: Selected group: Group 2.*/
 >>>= 0
 
+# Run some of the tests above, with exclusive storage enabled
+./test/hs/hail $T/hail-alloc-drbd.json.excl-stor
+>>> /"success":true,.*,"result":\["node2","node1"\]/
+>>>= 0
+
+./test/hs/hail $T/hail-reloc-drbd.json.excl-stor
+>>> /"success":true,.*,"result":\["node1"\]/
+>>>= 0
+
+./test/hs/hail $T/hail-node-evac.json.excl-stor
+>>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
+>>>= 0
+
+./test/hs/hail $T/hail-change-group.json.excl-stor
+>>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
+>>>= 0
+
+./test/hs/hail $T/hail-alloc-twodisks.json.excl-stor
+>>> /"success":true,.*,"result":\["node1"\]/
+>>>= 0
+
+./test/hs/hail $T/hail-alloc-invalid-twodisks.json.excl-stor
+>>> /"success":false,.*FailDisk: 1"/
+>>>= 0
+
+# Same tests with exclusive storage enabled, but no spindles info in instances
+./test/hs/hail $T/hail-alloc-drbd.json.fail-excl-stor
+>>> /"success":false,.*FailSpindles: 12"/
+>>>= 0
+
+./test/hs/hail $T/hail-reloc-drbd.json.fail-excl-stor
+>>> /"success":false,.*FailSpindles/
+>>>= 0
+
+./test/hs/hail $T/hail-node-evac.json.fail-excl-stor
+>>> /"success":true,"info":"Request successful: 1 instances failed to move and 0 were moved successfully",.*FailSpindles/
+>>>= 0
+
+./test/hs/hail $T/hail-change-group.json.fail-excl-stor
+>>> /"success":true,"info":"Request successful: 1 instances failed to move and 0 were moved successfully",.*FailSpindles: 2"/
+>>>= 0
+
+./test/hs/hail $T/hail-alloc-twodisks.json.fail-excl-stor
+>>> /"success":false,.*FailSpindles: 1"/
+>>>= 0
+
 # check that hail can use the simu backend
 ./test/hs/hail --simu p,8,8T,16g,16 $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,/
@@ -99,3 +145,12 @@ cat $TESTDATA_DIR/hail-alloc-invalid-network.json | grep -v -e '"network":"uuid-
 ./test/hs/hail $T/hail-node-evac.json.all
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
+
+# Check interaction between policies and spindles
+./test/hs/hail $TESTDATA_DIR/hail-alloc-spindles.json
+>>> /"success":true,"info":"Request successful: Selected group: group2,.*FailSpindles: 2,.*"result":\["node4"\]/
+>>>= 0
+
+./test/hs/hail $T/hail-alloc-spindles.json.excl-stor
+>>> /"success":true,"info":"Request successful: Selected group: group1,.*FailSpindles: 2",.*"result":\["node1"\]/
+>>>= 0
index 8a2e133..e7b4e7f 100644 (file)
@@ -1,5 +1,68 @@
-./test/hs/hroller --no-headers -t $TESTDATA_DIR/unique-reboot-order.data
+./test/hs/hroller --no-headers --ignore-non-redundant -t $TESTDATA_DIR/unique-reboot-order.data
+>>>
+node-01-002
+node-01-003,node-01-001
+>>>= 0
+
+./test/hs/hroller --no-headers --skip-non-redundant -t $TESTDATA_DIR/unique-reboot-order.data
 >>>
 node-01-002
+>>>= 0
+
+./test/hs/hroller --no-headers -t $TESTDATA_DIR/unique-reboot-order.data
+>>>/^node-01-00.
+node-01-00.
+node-01-001$/
+>>>= 0
+
+./test/hs/hroller --ignore-non-redundant -O node-01-002 --no-headers -t $TESTDATA_DIR/unique-reboot-order.data
+>>>
 node-01-003,node-01-001
 >>>= 0
+
+./test/hs/hroller --ignore-non-redundant -O node-01-003 --no-headers -t $TESTDATA_DIR/unique-reboot-order.data
+>>>
+node-01-002
+node-01-001
+>>>= 0
+
+./test/hs/hroller --node-tags=red --no-headers -t $TESTDATA_DIR/multiple-tags.data
+>>>/^node-01-00[45],node-01-00[45],node-01-001$/
+>>>= 0
+
+./test/hs/hroller --node-tags=blue --no-headers -t $TESTDATA_DIR/multiple-tags.data
+>>>/^node-01-00[246],node-01-00[246],node-01-00[246]$/
+>>>= 0
+
+./test/hs/hroller --no-headers --offline-maintenance -t $TESTDATA_DIR/hroller-online.data
+>>>/node-01-00.,node-01-00.
+node-01-001,node-01-003/
+>>>= 0
+
+./test/hs/hroller --no-headers -t $TESTDATA_DIR/hroller-online.data
+>>>/node-01-00.,node-01-00.
+node-01-002
+node-01-003/
+>>>= 0
+
+./test/hs/hroller --no-headers -t $TESTDATA_DIR/hroller-nonredundant.data
+>>>/^node-01-00.,node-01-00.
+node-01-00.,node-01-00.
+node-01-00.,node-01-000$/
+>>>= 0
+
+./test/hs/hroller --skip-non-redundant -t $TESTDATA_DIR/hroller-nonredundant.data
+>>>2
+Error: Cannot create node graph
+>>>=1
+
+./test/hs/hroller --no-headers --ignore-non-redundant -t $TESTDATA_DIR/hroller-nonredundant.data
+>>>/^node-01-00.,node-01-00.,node-01-00.,node-01-00.,node-01-00.,node-01-000$/
+>>>= 0
+
+
+./test/hs/hroller --no-headers -t $TESTDATA_DIR/hroller-nodegroups.data
+>>>/^node-01-00.
+node-01-00.
+node-01-00.,node-02-000$/
+>>>= 0
index b2d8dd0..4405881 100644 (file)
@@ -8,14 +8,27 @@
 >>>= 0
 
 # standard & tiered allocation, using shell parsing to do multiple checks
-./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4=4 129984,1048320,4=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 6"
+./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4,12=4 129984,1048320,4,12=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 6"
 >>>=0
 
 # again, but with a policy containing two min/max specs pairs
-./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-dualspec.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4=4 129984,1048320,4=2 65472,524288,2=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 14"
+./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-dualspec.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4,12=4 129984,1048320,4,12=2 65472,524288,2,12=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 14"
 >>>2
 >>>=0
 
+# With exclusive storage
+./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-exclusive.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4,10=1 131072,1048576,4,9=1 131072,1048576,4,8=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 6 && test \"\${HTS_TRL_SPN_FREE}\" = 0 && test \"\${HTS_FIN_SPN_FREE}\" = 29"
+>>>=0
+
+# With exclusive storage and a policy containing two min/max specs pairs
+./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-dualspec-exclusive.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4,4=4 129984,1048320,4,4=2 65472,524288,2,2=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 14 && test \"\${HTS_TRL_SPN_FREE}\" = 7 && test \"\${HTS_FIN_SPN_FREE}\" = 7"
+>>>2
+>>>=0
+
+# Mixed cluster, half with exclusive storage
+./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-mixed.data > $T/capacity && sh -c ". $T/capacity && test \"\${HTS_TSPEC}\" = '131072,1048576,4,12=2 131072,1048576,4,10=2 129984,1048320,4,10=2' && test \"\${HTS_ALLOC_INSTANCES}\" = 6 && test \"\${HTS_TRL_SPN_FREE}\" = 0 && test \"\${HTS_FIN_SPN_FREE}\" = 18"
+>>>=0
+
 # Verify that instance policy for disks is adhered to
 ./test/hs/hspace --machine-readable -t $TESTDATA_DIR/hspace-tiered-ipolicy.data
 >>>/HTS_TRL_INST_CNT=4/
@@ -39,4 +52,3 @@
  node-01-003    2
  node-01-004    2/
 >>>=0
-
index f9bf1c9..3284ad4 100644 (file)
@@ -49,3 +49,32 @@ Error: "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL
 []
 Failed reading: versionInfo
 >>>= !0
+
+# Tests for diskstats
+./test/hs/hpc-mon-collector diskstats --help
+>>>= 0
+
+./test/hs/hpc-mon-collector diskstats --help-completion
+>>>= 0
+
+./test/hs/hpc-mon-collector diskstats --version
+>>>= 0
+
+# Test that the diskstats collector fails parsing a non-diskstats file
+./test/hs/hpc-mon-collector diskstats -f /dev/zero
+>>>2/Failed reading/
+>>>= !0
+
+# Test that a non-existent file is correctly reported
+./test/hs/hpc-mon-collector diskstats --file=/proc/no-such-file
+>>>2/Error: reading from file: .* does not exist/
+>>>= !0
+
+# Test that arguments are rejected
+./test/hs/hpc-mon-collector diskstats /dev/null
+>>>2/takes exactly zero arguments/
+>>>= !0
+
+# Test that a standard test file is parsed correctly
+./test/hs/hpc-mon-collector diskstats -f $PYTESTDATA_DIR/proc_diskstats.txt
+>>>=0
index 52a9299..c94920b 100755 (executable)
@@ -37,6 +37,23 @@ from ganeti import netutils
 import testutils
 
 
+def GetMinimalConfig():
+  return {
+    "version": constants.CONFIG_VERSION,
+    "cluster": {
+      "master_node": "node1-uuid"
+    },
+    "instances": {},
+    "nodegroups": {},
+    "nodes": {
+      "node1-uuid": {
+        "name": "node1",
+        "uuid": "node1-uuid"
+      }
+    },
+  }
+
+
 def _RunUpgrade(path, dry_run, no_verify, ignore_hostname=True,
                 downgrade=False):
   cmd = [sys.executable, "%s/tools/cfgupgrade" % testutils.GetSourceDir(),
@@ -95,12 +112,8 @@ class TestCfgupgrade(unittest.TestCase):
   def testWrongHostname(self):
     self._CreateValidConfigDir()
 
-    utils.WriteFile(self.config_path, data=serializer.DumpJson({
-      "version": constants.CONFIG_VERSION,
-      "cluster": {},
-      "instances": {},
-      "nodegroups": {},
-      }))
+    utils.WriteFile(self.config_path,
+                    data=serializer.DumpJson(GetMinimalConfig()))
 
     hostname = netutils.GetHostname().name
     assert hostname != utils.ReadOneLineFile(self.ss_master_node_path)
@@ -111,12 +124,8 @@ class TestCfgupgrade(unittest.TestCase):
   def testCorrectHostname(self):
     self._CreateValidConfigDir()
 
-    utils.WriteFile(self.config_path, data=serializer.DumpJson({
-      "version": constants.CONFIG_VERSION,
-      "cluster": {},
-      "instances": {},
-      "nodegroups": {},
-      }))
+    utils.WriteFile(self.config_path,
+                    data=serializer.DumpJson(GetMinimalConfig()))
 
     utils.WriteFile(self.ss_master_node_path,
                     data="%s\n" % netutils.GetHostname().name)
@@ -126,14 +135,9 @@ class TestCfgupgrade(unittest.TestCase):
   def testInconsistentConfig(self):
     self._CreateValidConfigDir()
     # There should be no "config_version"
-    cfg = {
-      "version": 0,
-      "cluster": {
-        "config_version": 0,
-        },
-      "instances": {},
-      "nodegroups": {},
-      }
+    cfg = GetMinimalConfig()
+    cfg["version"] = 0
+    cfg["cluster"]["config_version"] = 0
     utils.WriteFile(self.config_path, data=serializer.DumpJson(cfg))
     self.assertRaises(Exception, _RunUpgrade, self.tmpdir, False, True)
 
@@ -150,19 +154,15 @@ class TestCfgupgrade(unittest.TestCase):
   def _TestSimpleUpgrade(self, from_version, dry_run,
                          file_storage_dir=None,
                          shared_file_storage_dir=None):
-    cluster = {}
+    cfg = GetMinimalConfig()
+    cfg["version"] = from_version
+    cluster = cfg["cluster"]
 
     if file_storage_dir:
       cluster["file_storage_dir"] = file_storage_dir
     if shared_file_storage_dir:
       cluster["shared_file_storage_dir"] = shared_file_storage_dir
 
-    cfg = {
-      "version": from_version,
-      "cluster": cluster,
-      "instances": {},
-      "nodegroups": {},
-      }
     self._TestUpgradeFromData(cfg, dry_run)
 
   def _TestUpgradeFromData(self, cfg, dry_run):
@@ -365,6 +365,9 @@ class TestCfgupgrade(unittest.TestCase):
   def testUpgradeFullConfigFrom_2_7(self):
     self._TestUpgradeFromFile("cluster_config_2.7.json", False)
 
+  def testUpgradeFullConfigFrom_2_8(self):
+    self._TestUpgradeFromFile("cluster_config_2.8.json", False)
+
   def testUpgradeCurrent(self):
     self._TestSimpleUpgrade(constants.CONFIG_VERSION, False)
 
@@ -382,9 +385,7 @@ class TestCfgupgrade(unittest.TestCase):
   def testDowngradeFullConfig(self):
     """Test for upgrade + downgrade combination."""
     # This test can work only with the previous version of a configuration!
-    # For 2.7, downgrading returns the original file only if group policies
-    # don't override instance specs, so we need to use an ad-hoc configuration.
-    oldconfname = "cluster_config_downgraded_2.7.json"
+    oldconfname = "cluster_config_2.8.json"
     self._TestUpgradeFromFile(oldconfname, False)
     _RunUpgrade(self.tmpdir, False, True, downgrade=True)
     oldconf = self._LoadTestDataConfig(oldconfname)
index 4ad743a..2731b14 100755 (executable)
@@ -26,6 +26,7 @@ import sys
 import shutil
 import tempfile
 import unittest
+import mock
 
 from ganeti import utils
 from ganeti import constants
@@ -33,6 +34,7 @@ from ganeti import backend
 from ganeti import netutils
 from ganeti import errors
 from ganeti import serializer
+from ganeti import hypervisor
 
 import testutils
 import mocks
@@ -75,11 +77,22 @@ class TestX509Certificates(unittest.TestCase):
 
 
 class TestNodeVerify(testutils.GanetiTestCase):
+
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+    self._mock_hv = None
+
+  def _GetHypervisor(self, hv_name):
+    self._mock_hv = hypervisor.GetHypervisor(hv_name)
+    self._mock_hv.ValidateParameters = mock.Mock()
+    self._mock_hv.Verify = mock.Mock()
+    return self._mock_hv
+
   def testMasterIPLocalhost(self):
     # this a real functional test, but requires localhost to be reachable
     local_data = (netutils.Hostname.GetSysName(),
                   constants.IP4_ADDRESS_LOCALHOST)
-    result = backend.VerifyNode({constants.NV_MASTERIP: local_data}, None)
+    result = backend.VerifyNode({constants.NV_MASTERIP: local_data}, None, {})
     self.failUnless(constants.NV_MASTERIP in result,
                     "Master IP data not returned")
     self.failUnless(result[constants.NV_MASTERIP], "Cannot reach localhost")
@@ -90,12 +103,32 @@ class TestNodeVerify(testutils.GanetiTestCase):
     bad_data =  ("master.example.com", "192.0.2.1")
     # we just test that whatever TcpPing returns, VerifyNode returns too
     netutils.TcpPing = lambda a, b, source=None: False
-    result = backend.VerifyNode({constants.NV_MASTERIP: bad_data}, None)
+    result = backend.VerifyNode({constants.NV_MASTERIP: bad_data}, None, {})
     self.failUnless(constants.NV_MASTERIP in result,
                     "Master IP data not returned")
     self.failIf(result[constants.NV_MASTERIP],
                 "Result from netutils.TcpPing corrupted")
 
+  def testVerifyHvparams(self):
+    test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    test_what = {constants.NV_HVPARAMS: \
+        [("mynode", constants.HT_XEN_PVM, test_hvparams)]}
+    result = {}
+    backend._VerifyHvparams(test_what, True, result,
+                            get_hv_fn=self._GetHypervisor)
+    self._mock_hv.ValidateParameters.assert_called_with(test_hvparams)
+
+  def testVerifyHypervisors(self):
+    hvname = constants.HT_XEN_PVM
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    all_hvparams = {hvname: hvparams}
+    test_what = {constants.NV_HYPERVISOR: [hvname]}
+    result = {}
+    backend._VerifyHypervisors(
+        test_what, True, result, all_hvparams=all_hvparams,
+        get_hv_fn=self._GetHypervisor)
+    self._mock_hv.Verify.assert_called_with(hvparams=hvparams)
+
 
 def _DefRestrictedCmdOwner():
   return (os.getuid(), os.getgid())
@@ -538,5 +571,109 @@ class TestGetBlockDevSymlinkPath(unittest.TestCase):
       self._Test("inst1.example.com", idx)
 
 
+class TestGetInstanceList(unittest.TestCase):
+
+  def setUp(self):
+    self._test_hv = self._TestHypervisor()
+    self._test_hv.ListInstances = mock.Mock(
+      return_value=["instance1", "instance2", "instance3"] )
+
+  class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
+    def __init__(self):
+      hypervisor.hv_base.BaseHypervisor.__init__(self)
+
+  def _GetHypervisor(self, name):
+    return self._test_hv
+
+  def testHvparams(self):
+    fake_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    hvparams = {constants.HT_FAKE: fake_hvparams}
+    backend.GetInstanceList([constants.HT_FAKE], all_hvparams=hvparams,
+                            get_hv_fn=self._GetHypervisor)
+    self._test_hv.ListInstances.assert_called_with(hvparams=fake_hvparams)
+
+
+class TestGetHvInfo(unittest.TestCase):
+
+  def setUp(self):
+    self._test_hv = self._TestHypervisor()
+    self._test_hv.GetNodeInfo = mock.Mock()
+
+  class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
+    def __init__(self):
+      hypervisor.hv_base.BaseHypervisor.__init__(self)
+
+  def _GetHypervisor(self, name):
+    return self._test_hv
+
+  def testGetHvInfoAllNone(self):
+    result = backend._GetHvInfoAll(None)
+    self.assertTrue(result is None)
+
+  def testGetHvInfoAll(self):
+    hvname = constants.HT_XEN_PVM
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    hv_specs = [(hvname, hvparams)]
+
+    result = backend._GetHvInfoAll(hv_specs, self._GetHypervisor)
+    self._test_hv.GetNodeInfo.assert_called_with(hvparams=hvparams)
+
+
+class TestApplyStorageInfoFunction(unittest.TestCase):
+
+  _STORAGE_KEY = "some_key"
+  _SOME_ARGS = "some_args"
+
+  def setUp(self):
+    self.mock_storage_fn = mock.Mock()
+
+  def testApplyValidStorageType(self):
+    storage_type = constants.ST_LVM_VG
+    backend._STORAGE_TYPE_INFO_FN = {
+        storage_type: self.mock_storage_fn
+      }
+
+    backend._ApplyStorageInfoFunction(
+        storage_type, self._STORAGE_KEY, self._SOME_ARGS)
+
+    self.mock_storage_fn.assert_called_with(self._STORAGE_KEY, self._SOME_ARGS)
+
+  def testApplyInValidStorageType(self):
+    storage_type = "invalid_storage_type"
+    backend._STORAGE_TYPE_INFO_FN = {}
+
+    self.assertRaises(KeyError, backend._ApplyStorageInfoFunction,
+                      storage_type, self._STORAGE_KEY, self._SOME_ARGS)
+
+  def testApplyNotImplementedStorageType(self):
+    storage_type = "not_implemented_storage_type"
+    backend._STORAGE_TYPE_INFO_FN = {storage_type: None}
+
+    self.assertRaises(NotImplementedError,
+                      backend._ApplyStorageInfoFunction,
+                      storage_type, self._STORAGE_KEY, self._SOME_ARGS)
+
+
+class TestGetNodeInfo(unittest.TestCase):
+
+  _SOME_RESULT = None
+
+  def testApplyStorageInfoFunction(self):
+    excl_storage_flag = False
+    backend._ApplyStorageInfoFunction = mock.Mock(
+        return_value=self._SOME_RESULT)
+    storage_units = [(st, st + "_key") for st in
+                     constants.VALID_STORAGE_TYPES]
+
+    backend.GetNodeInfo(storage_units, None, excl_storage_flag)
+
+    call_args_list = backend._ApplyStorageInfoFunction.call_args_list
+    self.assertEqual(len(constants.VALID_STORAGE_TYPES), len(call_args_list))
+    for call in call_args_list:
+      storage_type, storage_key, excl_storage = call[0]
+      self.assertEqual(storage_type + "_key", storage_key)
+      self.assertTrue(storage_type in constants.VALID_STORAGE_TYPES)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.bdev_unittest.py b/test/py/ganeti.bdev_unittest.py
deleted file mode 100755 (executable)
index 8243375..0000000
+++ /dev/null
@@ -1,613 +0,0 @@
-#!/usr/bin/python
-#
-
-# Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
-
-
-"""Script for unittesting the bdev module"""
-
-
-import os
-import random
-import unittest
-
-from ganeti import bdev
-from ganeti import compat
-from ganeti import constants
-from ganeti import errors
-from ganeti import objects
-from ganeti import utils
-
-import testutils
-
-
-class TestBaseDRBD(testutils.GanetiTestCase):
-  def testGetVersion(self):
-    data = [
-      ["version: 8.0.12 (api:76/proto:86-91)"],
-      ["version: 8.2.7 (api:88/proto:0-100)"],
-      ["version: 8.3.7.49 (api:188/proto:13-191)"],
-    ]
-    result = [
-      {
-      "k_major": 8,
-      "k_minor": 0,
-      "k_point": 12,
-      "api": 76,
-      "proto": 86,
-      "proto2": "91",
-      },
-      {
-      "k_major": 8,
-      "k_minor": 2,
-      "k_point": 7,
-      "api": 88,
-      "proto": 0,
-      "proto2": "100",
-      },
-      {
-      "k_major": 8,
-      "k_minor": 3,
-      "k_point": 7,
-      "api": 188,
-      "proto": 13,
-      "proto2": "191",
-      }
-    ]
-    for d,r in zip(data, result):
-      self.assertEqual(bdev.BaseDRBD._GetVersion(d), r)
-
-
-class TestDRBD8Runner(testutils.GanetiTestCase):
-  """Testing case for DRBD8"""
-
-  @staticmethod
-  def _has_disk(data, dname, mname):
-    """Check local disk corectness"""
-    retval = (
-      "local_dev" in data and
-      data["local_dev"] == dname and
-      "meta_dev" in data and
-      data["meta_dev"] == mname and
-      "meta_index" in data and
-      data["meta_index"] == 0
-      )
-    return retval
-
-  @staticmethod
-  def _has_net(data, local, remote):
-    """Check network connection parameters"""
-    retval = (
-      "local_addr" in data and
-      data["local_addr"] == local and
-      "remote_addr" in data and
-      data["remote_addr"] == remote
-      )
-    return retval
-
-  def testParserCreation(self):
-    """Test drbdsetup show parser creation"""
-    bdev.DRBD8._GetShowParser()
-
-  def testParser80(self):
-    """Test drbdsetup show parser for disk and network version 8.0"""
-    data = testutils.ReadTestData("bdev-drbd-8.0.txt")
-    result = bdev.DRBD8._GetDevInfo(data)
-    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
-                                   "/dev/xenvg/test.meta"),
-                    "Wrong local disk info")
-    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
-                                  ("192.0.2.2", 11000)),
-                    "Wrong network info (8.0.x)")
-
-  def testParser83(self):
-    """Test drbdsetup show parser for disk and network version 8.3"""
-    data = testutils.ReadTestData("bdev-drbd-8.3.txt")
-    result = bdev.DRBD8._GetDevInfo(data)
-    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
-                                   "/dev/xenvg/test.meta"),
-                    "Wrong local disk info")
-    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
-                                  ("192.0.2.2", 11000)),
-                    "Wrong network info (8.0.x)")
-
-  def testParserNetIP4(self):
-    """Test drbdsetup show parser for IPv4 network"""
-    data = testutils.ReadTestData("bdev-drbd-net-ip4.txt")
-    result = bdev.DRBD8._GetDevInfo(data)
-    self.failUnless(("local_dev" not in result and
-                     "meta_dev" not in result and
-                     "meta_index" not in result),
-                    "Should not find local disk info")
-    self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
-                                  ("192.0.2.2", 11002)),
-                    "Wrong network info (IPv4)")
-
-  def testParserNetIP6(self):
-    """Test drbdsetup show parser for IPv6 network"""
-    data = testutils.ReadTestData("bdev-drbd-net-ip6.txt")
-    result = bdev.DRBD8._GetDevInfo(data)
-    self.failUnless(("local_dev" not in result and
-                     "meta_dev" not in result and
-                     "meta_index" not in result),
-                    "Should not find local disk info")
-    self.failUnless(self._has_net(result, ("2001:db8:65::1", 11048),
-                                  ("2001:db8:66::1", 11048)),
-                    "Wrong network info (IPv6)")
-
-  def testParserDisk(self):
-    """Test drbdsetup show parser for disk"""
-    data = testutils.ReadTestData("bdev-drbd-disk.txt")
-    result = bdev.DRBD8._GetDevInfo(data)
-    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
-                                   "/dev/xenvg/test.meta"),
-                    "Wrong local disk info")
-    self.failUnless(("local_addr" not in result and
-                     "remote_addr" not in result),
-                    "Should not find network info")
-
-  def testBarriersOptions(self):
-    """Test class method that generates drbdsetup options for disk barriers"""
-    # Tests that should fail because of wrong version/options combinations
-    should_fail = [
-      (8, 0, 12, "bfd", True),
-      (8, 0, 12, "fd", False),
-      (8, 0, 12, "b", True),
-      (8, 2, 7, "bfd", True),
-      (8, 2, 7, "b", True)
-    ]
-
-    for vmaj, vmin, vrel, opts, meta in should_fail:
-      self.assertRaises(errors.BlockDeviceError,
-                        bdev.DRBD8._ComputeDiskBarrierArgs,
-                        vmaj, vmin, vrel, opts, meta)
-
-    # get the valid options from the frozenset(frozenset()) in constants.
-    valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
-
-    # Versions that do not support anything
-    for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
-      for opts in valid_options:
-        self.assertRaises(errors.BlockDeviceError,
-                          bdev.DRBD8._ComputeDiskBarrierArgs,
-                          vmaj, vmin, vrel, opts, True)
-
-    # Versions with partial support (testing only options that are supported)
-    tests = [
-      (8, 0, 12, "n", False, []),
-      (8, 0, 12, "n", True, ["--no-md-flushes"]),
-      (8, 2, 7, "n", False, []),
-      (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
-      (8, 0, 12, "n", True, ["--no-md-flushes"]),
-      ]
-
-    # Versions that support everything
-    for vmaj, vmin, vrel in ((8, 3, 0), (8, 3, 12)):
-      tests.append((vmaj, vmin, vrel, "bfd", True,
-                    ["--no-disk-barrier", "--no-disk-drain",
-                     "--no-disk-flushes", "--no-md-flushes"]))
-      tests.append((vmaj, vmin, vrel, "n", False, []))
-      tests.append((vmaj, vmin, vrel, "b", True,
-                    ["--no-disk-barrier", "--no-md-flushes"]))
-      tests.append((vmaj, vmin, vrel, "fd", False,
-                    ["--no-disk-flushes", "--no-disk-drain"]))
-      tests.append((vmaj, vmin, vrel, "n", True, ["--no-md-flushes"]))
-
-    # Test execution
-    for test in tests:
-      vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
-      args = \
-        bdev.DRBD8._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
-                                           disabled_barriers,
-                                           disable_meta_flush)
-      self.failUnless(set(args) == set(expected),
-                      "For test %s, got wrong results %s" % (test, args))
-
-    # Unsupported or invalid versions
-    for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
-      self.assertRaises(errors.BlockDeviceError,
-                        bdev.DRBD8._ComputeDiskBarrierArgs,
-                        vmaj, vmin, vrel, "n", True)
-
-    # Invalid options
-    for option in ("", "c", "whatever", "nbdfc", "nf"):
-      self.assertRaises(errors.BlockDeviceError,
-                        bdev.DRBD8._ComputeDiskBarrierArgs,
-                        8, 3, 11, option, True)
-
-
-class TestDRBD8Status(testutils.GanetiTestCase):
-  """Testing case for DRBD8 /proc status"""
-
-  def setUp(self):
-    """Read in txt data"""
-    testutils.GanetiTestCase.setUp(self)
-    proc_data = testutils.TestDataFilename("proc_drbd8.txt")
-    proc80e_data = testutils.TestDataFilename("proc_drbd80-emptyline.txt")
-    proc83_data = testutils.TestDataFilename("proc_drbd83.txt")
-    proc83_sync_data = testutils.TestDataFilename("proc_drbd83_sync.txt")
-    proc83_sync_krnl_data = \
-      testutils.TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
-    self.proc_data = bdev.DRBD8._GetProcData(filename=proc_data)
-    self.proc80e_data = bdev.DRBD8._GetProcData(filename=proc80e_data)
-    self.proc83_data = bdev.DRBD8._GetProcData(filename=proc83_data)
-    self.proc83_sync_data = bdev.DRBD8._GetProcData(filename=proc83_sync_data)
-    self.proc83_sync_krnl_data = \
-      bdev.DRBD8._GetProcData(filename=proc83_sync_krnl_data)
-    self.mass_data = bdev.DRBD8._MassageProcData(self.proc_data)
-    self.mass80e_data = bdev.DRBD8._MassageProcData(self.proc80e_data)
-    self.mass83_data = bdev.DRBD8._MassageProcData(self.proc83_data)
-    self.mass83_sync_data = bdev.DRBD8._MassageProcData(self.proc83_sync_data)
-    self.mass83_sync_krnl_data = \
-      bdev.DRBD8._MassageProcData(self.proc83_sync_krnl_data)
-
-  def testIOErrors(self):
-    """Test handling of errors while reading the proc file."""
-    temp_file = self._CreateTempFile()
-    os.unlink(temp_file)
-    self.failUnlessRaises(errors.BlockDeviceError,
-                          bdev.DRBD8._GetProcData, filename=temp_file)
-
-  def testHelper(self):
-    """Test reading usermode_helper in /sys."""
-    sys_drbd_helper = testutils.TestDataFilename("sys_drbd_usermode_helper.txt")
-    drbd_helper = bdev.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper)
-    self.failUnlessEqual(drbd_helper, "/bin/true")
-
-  def testHelperIOErrors(self):
-    """Test handling of errors while reading usermode_helper in /sys."""
-    temp_file = self._CreateTempFile()
-    os.unlink(temp_file)
-    self.failUnlessRaises(errors.BlockDeviceError,
-                          bdev.DRBD8.GetUsermodeHelper, filename=temp_file)
-
-  def testMinorNotFound(self):
-    """Test not-found-minor in /proc"""
-    self.failUnless(9 not in self.mass_data)
-    self.failUnless(9 not in self.mass83_data)
-    self.failUnless(3 not in self.mass80e_data)
-
-  def testLineNotMatch(self):
-    """Test wrong line passed to DRBD8Status"""
-    self.assertRaises(errors.BlockDeviceError, bdev.DRBD8Status, "foo")
-
-  def testMinor0(self):
-    """Test connected, primary device"""
-    for data in [self.mass_data, self.mass83_data]:
-      stats = bdev.DRBD8Status(data[0])
-      self.failUnless(stats.is_in_use)
-      self.failUnless(stats.is_connected and stats.is_primary and
-                      stats.peer_secondary and stats.is_disk_uptodate)
-
-  def testMinor1(self):
-    """Test connected, secondary device"""
-    for data in [self.mass_data, self.mass83_data]:
-      stats = bdev.DRBD8Status(data[1])
-      self.failUnless(stats.is_in_use)
-      self.failUnless(stats.is_connected and stats.is_secondary and
-                      stats.peer_primary and stats.is_disk_uptodate)
-
-  def testMinor2(self):
-    """Test unconfigured device"""
-    for data in [self.mass_data, self.mass83_data, self.mass80e_data]:
-      stats = bdev.DRBD8Status(data[2])
-      self.failIf(stats.is_in_use)
-
-  def testMinor4(self):
-    """Test WFconn device"""
-    for data in [self.mass_data, self.mass83_data]:
-      stats = bdev.DRBD8Status(data[4])
-      self.failUnless(stats.is_in_use)
-      self.failUnless(stats.is_wfconn and stats.is_primary and
-                      stats.rrole == "Unknown" and
-                      stats.is_disk_uptodate)
-
-  def testMinor6(self):
-    """Test diskless device"""
-    for data in [self.mass_data, self.mass83_data]:
-      stats = bdev.DRBD8Status(data[6])
-      self.failUnless(stats.is_in_use)
-      self.failUnless(stats.is_connected and stats.is_secondary and
-                      stats.peer_primary and stats.is_diskless)
-
-  def testMinor8(self):
-    """Test standalone device"""
-    for data in [self.mass_data, self.mass83_data]:
-      stats = bdev.DRBD8Status(data[8])
-      self.failUnless(stats.is_in_use)
-      self.failUnless(stats.is_standalone and
-                      stats.rrole == "Unknown" and
-                      stats.is_disk_uptodate)
-
-  def testDRBD83SyncFine(self):
-    stats = bdev.DRBD8Status(self.mass83_sync_data[3])
-    self.failUnless(stats.is_in_resync)
-    self.failUnless(stats.sync_percent is not None)
-
-  def testDRBD83SyncBroken(self):
-    stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[3])
-    self.failUnless(stats.is_in_resync)
-    self.failUnless(stats.sync_percent is not None)
-
-
-class TestRADOSBlockDevice(testutils.GanetiTestCase):
-  def setUp(self):
-    """Set up input data"""
-    testutils.GanetiTestCase.setUp(self)
-
-    self.plain_output_old_ok = \
-      testutils.ReadTestData("bdev-rbd/plain_output_old_ok.txt")
-    self.plain_output_old_no_matches = \
-      testutils.ReadTestData("bdev-rbd/plain_output_old_no_matches.txt")
-    self.plain_output_old_extra_matches = \
-      testutils.ReadTestData("bdev-rbd/plain_output_old_extra_matches.txt")
-    self.plain_output_old_empty = \
-      testutils.ReadTestData("bdev-rbd/plain_output_old_empty.txt")
-    self.plain_output_new_ok = \
-      testutils.ReadTestData("bdev-rbd/plain_output_new_ok.txt")
-    self.plain_output_new_no_matches = \
-      testutils.ReadTestData("bdev-rbd/plain_output_new_no_matches.txt")
-    self.plain_output_new_extra_matches = \
-      testutils.ReadTestData("bdev-rbd/plain_output_new_extra_matches.txt")
-    # This file is completely empty, and as such it's not shipped.
-    self.plain_output_new_empty = ""
-    self.json_output_ok = testutils.ReadTestData("bdev-rbd/json_output_ok.txt")
-    self.json_output_no_matches = \
-      testutils.ReadTestData("bdev-rbd/json_output_no_matches.txt")
-    self.json_output_extra_matches = \
-      testutils.ReadTestData("bdev-rbd/json_output_extra_matches.txt")
-    self.json_output_empty = \
-      testutils.ReadTestData("bdev-rbd/json_output_empty.txt")
-    self.output_invalid = testutils.ReadTestData("bdev-rbd/output_invalid.txt")
-
-    self.volume_name = "d7ab910a-4933-4ffe-88d0-faf2ce31390a.rbd.disk0"
-
-  def test_ParseRbdShowmappedJson(self):
-    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedJson
-
-    self.assertEqual(parse_function(self.json_output_ok, self.volume_name),
-                     "/dev/rbd3")
-    self.assertEqual(parse_function(self.json_output_empty, self.volume_name),
-                     None)
-    self.assertEqual(parse_function(self.json_output_no_matches,
-                     self.volume_name), None)
-    self.assertRaises(errors.BlockDeviceError, parse_function,
-                      self.json_output_extra_matches, self.volume_name)
-    self.assertRaises(errors.BlockDeviceError, parse_function,
-                      self.output_invalid, self.volume_name)
-
-  def test_ParseRbdShowmappedPlain(self):
-    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedPlain
-
-    self.assertEqual(parse_function(self.plain_output_new_ok,
-                     self.volume_name), "/dev/rbd3")
-    self.assertEqual(parse_function(self.plain_output_old_ok,
-                     self.volume_name), "/dev/rbd3")
-    self.assertEqual(parse_function(self.plain_output_new_empty,
-                     self.volume_name), None)
-    self.assertEqual(parse_function(self.plain_output_old_empty,
-                     self.volume_name), None)
-    self.assertEqual(parse_function(self.plain_output_new_no_matches,
-                     self.volume_name), None)
-    self.assertEqual(parse_function(self.plain_output_old_no_matches,
-                     self.volume_name), None)
-    self.assertRaises(errors.BlockDeviceError, parse_function,
-                      self.plain_output_new_extra_matches, self.volume_name)
-    self.assertRaises(errors.BlockDeviceError, parse_function,
-                      self.plain_output_old_extra_matches, self.volume_name)
-    self.assertRaises(errors.BlockDeviceError, parse_function,
-                      self.output_invalid, self.volume_name)
-
-class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
-  def testPaths(self):
-    paths = bdev._GetForbiddenFileStoragePaths()
-
-    for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
-      self.assertTrue(path in paths)
-
-    self.assertEqual(set(map(os.path.normpath, paths)), paths)
-
-  def test(self):
-    vfsp = bdev._ComputeWrongFileStoragePaths
-    self.assertEqual(vfsp([]), [])
-    self.assertEqual(vfsp(["/tmp"]), [])
-    self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
-    self.assertEqual(vfsp(["/bin"]), ["/bin"])
-    self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
-                     ["/usr/sbin/vim"])
-
-
-class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
-  def test(self):
-    tmpfile = self._CreateTempFile()
-
-    utils.WriteFile(tmpfile, data="""
-      /tmp
-      x/y///z/relative
-      # This is a test file
-      /srv/storage
-      /bin
-      /usr/local/lib32/
-      relative/path
-      """)
-
-    self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
-      "/bin",
-      "/usr/local/lib32",
-      "relative/path",
-      "x/y/z/relative",
-      ])
-
-
-class TestCheckFileStoragePathInternal(unittest.TestCase):
-  def testNonAbsolute(self):
-    for i in ["", "tmp", "foo/bar/baz"]:
-      self.assertRaises(errors.FileStoragePathError,
-                        bdev._CheckFileStoragePath, i, ["/tmp"])
-
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
-
-  def testNoAllowed(self):
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev._CheckFileStoragePath, "/tmp", [])
-
-  def testNoAdditionalPathComponent(self):
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
-
-  def testAllowed(self):
-    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
-    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
-
-
-class TestCheckFileStoragePath(testutils.GanetiTestCase):
-  def testNonExistantFile(self):
-    filename = "/tmp/this/file/does/not/exist"
-    assert not os.path.exists(filename)
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev.CheckFileStoragePath, "/bin/", _filename=filename)
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev.CheckFileStoragePath, "/srv/file-storage",
-                      _filename=filename)
-
-  def testAllowedPath(self):
-    tmpfile = self._CreateTempFile()
-
-    utils.WriteFile(tmpfile, data="""
-      /srv/storage
-      """)
-
-    bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
-
-    # No additional path component
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev.CheckFileStoragePath, "/srv/storage",
-                      _filename=tmpfile)
-
-    # Forbidden path
-    self.assertRaises(errors.FileStoragePathError,
-                      bdev.CheckFileStoragePath, "/usr/lib64/xyz",
-                      _filename=tmpfile)
-
-
-class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
-  def testDevNull(self):
-    self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
-
-  def testNonExistantFile(self):
-    filename = "/tmp/this/file/does/not/exist"
-    assert not os.path.exists(filename)
-    self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
-
-  def test(self):
-    tmpfile = self._CreateTempFile()
-
-    utils.WriteFile(tmpfile, data="""
-      # This is a test file
-      /tmp
-      /srv/storage
-      relative/path
-      """)
-
-    self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
-      "/tmp",
-      "/srv/storage",
-      "relative/path",
-      ])
-
-
-class TestExclusiveStoragePvs(unittest.TestCase):
-  """Test cases for functions dealing with LVM PV and exclusive storage"""
-  # Allowance for rounding
-  _EPS = 1e-4
-  _MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS
-
-  @staticmethod
-  def _GenerateRandomPvInfo(rnd, name, vg):
-    # Granularity is .01 MiB
-    size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100)
-    if rnd.choice([False, True]):
-      free = float(rnd.randint(0, size)) / 100.0
-    else:
-      free = float(size) / 100.0
-    size = float(size) / 100.0
-    attr = "a-"
-    return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
-                             attributes=attr)
-
-  def testGetStdPvSize(self):
-    """Test cases for bdev.LogicalVolume._GetStdPvSize()"""
-    rnd = random.Random(9517)
-    for _ in range(0, 50):
-      # Identical volumes
-      pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg")
-      onesize = bdev.LogicalVolume._GetStdPvSize([pvi])
-      self.assertTrue(onesize <= pvi.size)
-      self.assertTrue(onesize > pvi.size * (1 - self._MARGIN))
-      for length in range(2, 10):
-        n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length)
-        self.assertEqual(onesize, n_size)
-
-      # Mixed volumes
-      for length in range(1, 10):
-        pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")
-                  for _ in range(0, length)]
-        std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
-        self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist))
-        self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN)
-                                   for pvi in pvlist))
-        pvlist.append(pvlist[0])
-        p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
-        self.assertEqual(std_size, p1_size)
-
-  def testComputeNumPvs(self):
-    """Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
-    rnd = random.Random(8067)
-    for _ in range(0, 1000):
-      pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")]
-      lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0
-      num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist)
-      std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
-      self.assertTrue(num_pv >= 1)
-      self.assertTrue(num_pv * std_size >= lv_size)
-      self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS))
-
-  def testGetEmptyPvNames(self):
-    """Test cases for bdev.LogicalVolume._GetEmptyPvNames()"""
-    rnd = random.Random(21126)
-    for _ in range(0, 100):
-      num_pvs = rnd.randint(1, 20)
-      pvlist = [self._GenerateRandomPvInfo(rnd, "disk%d" % n, "myvg")
-                for n in range(0, num_pvs)]
-      for num_req in range(1, num_pvs + 2):
-        epvs = bdev.LogicalVolume._GetEmptyPvNames(pvlist, num_req)
-        epvs_set = compat.UniqueFrozenset(epvs)
-        if len(epvs) > 1:
-          self.assertEqual(len(epvs), len(epvs_set))
-        for pvi in pvlist:
-          if pvi.name in epvs_set:
-            self.assertEqual(pvi.size, pvi.free)
-          else:
-            # There should be no remaining empty PV when less than the
-            # requeste number of PVs has been returned
-            self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size)
-
-
-if __name__ == "__main__":
-  testutils.GanetiTestProgram()
index 21b2c26..3488c0c 100755 (executable)
@@ -192,7 +192,7 @@ class TestLUGroupAssignNodes(unittest.TestCase):
                                     ("n3c", "g3"),
                                     ])
 
-    def Instance(name, pnode, snode):
+    def Instance(uuid, pnode, snode):
       if snode is None:
         disks = []
         disk_template = constants.DT_DISKLESS
@@ -201,11 +201,12 @@ class TestLUGroupAssignNodes(unittest.TestCase):
                               logical_id=[pnode, snode, 1, 17, 17])]
         disk_template = constants.DT_DRBD8
 
-      return objects.Instance(name=name, primary_node=pnode, disks=disks,
+      return objects.Instance(name="%s-name" % uuid, uuid="%s" % uuid,
+                              primary_node=pnode, disks=disks,
                               disk_template=disk_template)
 
-    instance_data = dict((name, Instance(name, pnode, snode))
-                         for name, pnode, snode in [("inst1a", "n1a", "n1b"),
+    instance_data = dict((uuid, Instance(uuid, pnode, snode))
+                         for uuid, pnode, snode in [("inst1a", "n1a", "n1b"),
                                                     ("inst1b", "n1b", "n1a"),
                                                     ("inst2a", "n2a", "n2b"),
                                                     ("inst3a", "n3a", None),
@@ -306,22 +307,33 @@ class TestClusterVerifyFiles(unittest.TestCase):
     if cond:
       errors.append((item, msg))
 
-  _VerifyFiles = cluster.LUClusterVerifyGroup._VerifyFiles
-
   def test(self):
     errors = []
-    master_name = "master.example.com"
     nodeinfo = [
-      objects.Node(name=master_name, offline=False, vm_capable=True),
-      objects.Node(name="node2.example.com", offline=False, vm_capable=True),
-      objects.Node(name="node3.example.com", master_candidate=True,
+      objects.Node(name="master.example.com",
+                   uuid="master-uuid",
+                   offline=False,
+                   vm_capable=True),
+      objects.Node(name="node2.example.com",
+                   uuid="node2-uuid",
+                   offline=False,
+                   vm_capable=True),
+      objects.Node(name="node3.example.com",
+                   uuid="node3-uuid",
+                   master_candidate=True,
                    vm_capable=False),
-      objects.Node(name="node4.example.com", offline=False, vm_capable=True),
-      objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
-      objects.Node(name="offline.example.com", offline=True),
+      objects.Node(name="node4.example.com",
+                   uuid="node4-uuid",
+                   offline=False,
+                   vm_capable=True),
+      objects.Node(name="nodata.example.com",
+                   uuid="nodata-uuid",
+                   offline=False,
+                   vm_capable=True),
+      objects.Node(name="offline.example.com",
+                   uuid="offline-uuid",
+                   offline=True),
       ]
-    cluster = objects.Cluster(modify_etc_hosts=True,
-                              enabled_hypervisors=[constants.HT_XEN_HVM])
     files_all = set([
       pathutils.CLUSTER_DOMAIN_SECRET_FILE,
       pathutils.RAPI_CERT_FILE,
@@ -341,7 +353,7 @@ class TestClusterVerifyFiles(unittest.TestCase):
       pathutils.VNC_PASSWORD_FILE,
       ])
     nvinfo = {
-      master_name: rpc.RpcResult(data=(True, {
+      "master-uuid": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
           pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
           pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
@@ -349,19 +361,19 @@ class TestClusterVerifyFiles(unittest.TestCase):
           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
         }})),
-      "node2.example.com": rpc.RpcResult(data=(True, {
+      "node2-uuid": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
           }
         })),
-      "node3.example.com": rpc.RpcResult(data=(True, {
+      "node3-uuid": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
           pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
           }
         })),
-      "node4.example.com": rpc.RpcResult(data=(True, {
+      "node4-uuid": rpc.RpcResult(data=(True, {
         constants.NV_FILELIST: {
           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
           pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
@@ -370,14 +382,30 @@ class TestClusterVerifyFiles(unittest.TestCase):
           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
           }
         })),
-      "nodata.example.com": rpc.RpcResult(data=(True, {})),
-      "offline.example.com": rpc.RpcResult(offline=True),
+      "nodata-uuid": rpc.RpcResult(data=(True, {})),
+      "offline-uuid": rpc.RpcResult(offline=True),
       }
-    assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
+    assert set(nvinfo.keys()) == set(map(operator.attrgetter("uuid"), nodeinfo))
+
+    verify_lu = cluster.LUClusterVerifyGroup(mocks.FakeProc(),
+                                             opcodes.OpClusterVerify(),
+                                             mocks.FakeContext(),
+                                             None)
+
+    verify_lu._ErrorIf = compat.partial(self._FakeErrorIf, errors)
+
+    # TODO: That's a bit hackish to mock only this single method. We should
+    # build a better FakeConfig which provides such a feature already.
+    def GetNodeName(node_uuid):
+      for node in nodeinfo:
+        if node.uuid == node_uuid:
+          return node.name
+      return None
+
+    verify_lu.cfg.GetNodeName = GetNodeName
 
-    self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
-                      master_name, nvinfo,
-                      (files_all, files_opt, files_mc, files_vm))
+    verify_lu._VerifyFiles(nodeinfo, "master-uuid", nvinfo,
+                           (files_all, files_opt, files_mc, files_vm))
     self.assertEqual(sorted(errors), sorted([
       (None, ("File %s found with 2 different checksums (variant 1 on"
               " node2.example.com, node3.example.com, node4.example.com;"
@@ -827,12 +855,21 @@ class _StubComputeIPolicySpecViolation:
 
 
 class _FakeConfigForComputeIPolicyInstanceViolation:
-  def __init__(self, be):
+  def __init__(self, be, excl_stor):
     self.cluster = objects.Cluster(beparams={"default": be})
+    self.excl_stor = excl_stor
 
   def GetClusterInfo(self):
     return self.cluster
 
+  def GetNodeInfo(self, _):
+    return {}
+
+  def GetNdParams(self, _):
+    return {
+      constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
+      }
+
 
 class TestComputeIPolicyInstanceViolation(unittest.TestCase):
   def test(self):
@@ -841,8 +878,8 @@ class TestComputeIPolicyInstanceViolation(unittest.TestCase):
       constants.BE_VCPUS: 2,
       constants.BE_SPINDLE_USE: 4,
       }
-    disks = [objects.Disk(size=512)]
-    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams)
+    disks = [objects.Disk(size=512, spindles=13)]
+    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
     instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
                                 disk_template=constants.DT_PLAIN)
     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
@@ -855,6 +892,15 @@ class TestComputeIPolicyInstanceViolation(unittest.TestCase):
     ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
                                                  cfg, _compute_fn=stub)
     self.assertEqual(ret, [])
+    cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
+    stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
+                                               constants.DT_PLAIN)
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
+                                                 cfg_es, _compute_fn=stub_es)
+    self.assertEqual(ret, [])
+    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
+                                                 cfg_es, _compute_fn=stub_es)
+    self.assertEqual(ret, [])
 
 
 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
@@ -1399,12 +1445,16 @@ class TestGenerateDiskTemplate(unittest.TestCase):
 
 
 class _ConfigForDiskWipe:
-  def __init__(self, exp_node):
-    self._exp_node = exp_node
+  def __init__(self, exp_node_uuid):
+    self._exp_node_uuid = exp_node_uuid
 
-  def SetDiskID(self, device, node):
+  def SetDiskID(self, device, node_uuid):
     assert isinstance(device, objects.Disk)
-    assert node == self._exp_node
+    assert node_uuid == self._exp_node_uuid
+
+  def GetNodeName(self, node_uuid):
+    assert node_uuid == self._exp_node_uuid
+    return "name.of.expected.node"
 
 
 class _RpcForDiskWipe:
@@ -1499,11 +1549,11 @@ class TestWipeDisks(unittest.TestCase):
     return (False, None)
 
   def testFailingWipe(self):
-    node_name = "node13445.example.com"
+    node_uuid = "node13445-uuid"
     pt = _DiskPauseTracker()
 
-    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
-                 cfg=_ConfigForDiskWipe(node_name))
+    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
+                 cfg=_ConfigForDiskWipe(node_uuid))
 
     disks = [
       objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
@@ -1514,7 +1564,7 @@ class TestWipeDisks(unittest.TestCase):
       ]
 
     inst = objects.Instance(name="inst562",
-                            primary_node=node_name,
+                            primary_node=node_uuid,
                             disk_template=constants.DT_PLAIN,
                             disks=disks)
 
index e2e75db..f3377f6 100755 (executable)
@@ -41,6 +41,7 @@ from ganeti.config import TemporaryReservationManager
 
 import testutils
 import mocks
+import mock
 
 
 def _StubGetEntResolver():
@@ -103,7 +104,9 @@ class TestConfigRunner(unittest.TestCase):
 
   def _create_instance(self):
     """Create and return an instance object"""
-    inst = objects.Instance(name="test.example.com", disks=[], nics=[],
+    inst = objects.Instance(name="test.example.com",
+                            uuid="test-uuid",
+                            disks=[], nics=[],
                             disk_template=constants.DT_DISKLESS,
                             primary_node=self._get_object().GetMasterNode())
     return inst
@@ -327,14 +330,18 @@ class TestConfigRunner(unittest.TestCase):
     cluster_serial += 1
 
     # Create two nodes
-    node1 = objects.Node(name="node1", group=grp1.uuid, ndparams={})
+    node1 = objects.Node(name="node1", group=grp1.uuid, ndparams={},
+                         uuid="node1-uuid")
     node1_serial = 1
-    node2 = objects.Node(name="node2", group=grp2.uuid, ndparams={})
+    node2 = objects.Node(name="node2", group=grp2.uuid, ndparams={},
+                         uuid="node2-uuid")
     node2_serial = 1
     cfg.AddNode(node1, "job")
     cfg.AddNode(node2, "job")
     cluster_serial += 2
-    self.assertEqual(set(cfg.GetNodeList()), set(["node1", "node2", me.name]))
+    self.assertEqual(set(cfg.GetNodeList()),
+                     set(["node1-uuid", "node2-uuid",
+                          cfg.GetNodeInfoByName(me.name).uuid]))
 
     def _VerifySerials():
       self.assertEqual(cfg.GetClusterInfo().serial_no, cluster_serial)
@@ -345,8 +352,8 @@ class TestConfigRunner(unittest.TestCase):
 
     _VerifySerials()
 
-    self.assertEqual(set(grp1.members), set(["node1"]))
-    self.assertEqual(set(grp2.members), set(["node2"]))
+    self.assertEqual(set(grp1.members), set(["node1-uuid"]))
+    self.assertEqual(set(grp2.members), set(["node2-uuid"]))
 
     # Check invalid nodes and groups
     self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
@@ -358,8 +365,8 @@ class TestConfigRunner(unittest.TestCase):
 
     self.assertEqual(node1.group, grp1.uuid)
     self.assertEqual(node2.group, grp2.uuid)
-    self.assertEqual(set(grp1.members), set(["node1"]))
-    self.assertEqual(set(grp2.members), set(["node2"]))
+    self.assertEqual(set(grp1.members), set(["node1-uuid"]))
+    self.assertEqual(set(grp2.members), set(["node2-uuid"]))
 
     # Another no-op
     cfg.AssignGroupNodes([])
@@ -369,18 +376,18 @@ class TestConfigRunner(unittest.TestCase):
     # Assign to the same group (should be a no-op)
     self.assertEqual(node2.group, grp2.uuid)
     cfg.AssignGroupNodes([
-      (node2.name, grp2.uuid),
+      (node2.uuid, grp2.uuid),
       ])
     cluster_serial += 1
     self.assertEqual(node2.group, grp2.uuid)
     _VerifySerials()
-    self.assertEqual(set(grp1.members), set(["node1"]))
-    self.assertEqual(set(grp2.members), set(["node2"]))
+    self.assertEqual(set(grp1.members), set(["node1-uuid"]))
+    self.assertEqual(set(grp2.members), set(["node2-uuid"]))
 
     # Assign node 2 to group 1
     self.assertEqual(node2.group, grp2.uuid)
     cfg.AssignGroupNodes([
-      (node2.name, grp1.uuid),
+      (node2.uuid, grp1.uuid),
       ])
     cluster_serial += 1
     node2_serial += 1
@@ -388,7 +395,7 @@ class TestConfigRunner(unittest.TestCase):
     grp2_serial += 1
     self.assertEqual(node2.group, grp1.uuid)
     _VerifySerials()
-    self.assertEqual(set(grp1.members), set(["node1", "node2"]))
+    self.assertEqual(set(grp1.members), set(["node1-uuid", "node2-uuid"]))
     self.assertFalse(grp2.members)
 
     # And assign both nodes to group 2
@@ -396,8 +403,8 @@ class TestConfigRunner(unittest.TestCase):
     self.assertEqual(node2.group, grp1.uuid)
     self.assertNotEqual(grp1.uuid, grp2.uuid)
     cfg.AssignGroupNodes([
-      (node1.name, grp2.uuid),
-      (node2.name, grp2.uuid),
+      (node1.uuid, grp2.uuid),
+      (node2.uuid, grp2.uuid),
       ])
     cluster_serial += 1
     node1_serial += 1
@@ -408,7 +415,7 @@ class TestConfigRunner(unittest.TestCase):
     self.assertEqual(node2.group, grp2.uuid)
     _VerifySerials()
     self.assertFalse(grp1.members)
-    self.assertEqual(set(grp2.members), set(["node1", "node2"]))
+    self.assertEqual(set(grp2.members), set(["node1-uuid", "node2-uuid"]))
 
     # Destructive tests
     orig_group = node2.group
@@ -418,7 +425,7 @@ class TestConfigRunner(unittest.TestCase):
                         for node in cfg.GetAllNodesInfo().values())
       node2.group = "68b3d087-6ea5-491c-b81f-0a47d90228c5"
       self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
-        ("node2", grp2.uuid),
+        (node2.uuid, grp2.uuid),
         ])
       _VerifySerials()
     finally:
@@ -579,6 +586,31 @@ class TestConfigRunner(unittest.TestCase):
     nodegroup.ipolicy = cluster.SimpleFillIPolicy(nodegroup.ipolicy)
     self._TestVerifyConfigIPolicy(nodegroup.ipolicy, nodegroup.name, cfg, True)
 
+  # Tests for Ssconf helper functions
+  def testUnlockedGetHvparamsString(self):
+    hvparams = {"a": "A", "b": "B", "c": "C"}
+    hvname = "myhv"
+    cfg_writer = self._get_object()
+    cfg_writer._config_data = mock.Mock()
+    cfg_writer._config_data.cluster = mock.Mock()
+    cfg_writer._config_data.cluster.hvparams = {hvname: hvparams}
+
+    result = cfg_writer._UnlockedGetHvparamsString(hvname)
+
+    self.assertTrue("a=A" in result)
+    lines = [line for line in result.split('\n') if line != '']
+    self.assertEqual(len(hvparams.keys()), len(lines))
+
+  def testExtendByAllHvparamsStrings(self):
+    all_hvparams = {constants.HT_XEN_PVM: "foo"}
+    ssconf_values = {}
+    cfg_writer = self._get_object()
+
+    cfg_writer._ExtendByAllHvparamsStrings(ssconf_values, all_hvparams)
+
+    expected_key = constants.SS_HVPARAMS_PREF + constants.HT_XEN_PVM
+    self.assertTrue(expected_key in ssconf_values)
+
 
 def _IsErrorInList(err_str, err_list):
   return any(map(lambda e: err_str in e, err_list))
index 30e00d8..d7cf32f 100755 (executable)
@@ -51,7 +51,7 @@ class FakeLU(cmdlib.LogicalUnit):
     return {}
 
   def BuildHooksNodes(self):
-    return ["localhost"], ["localhost"]
+    return ["a"], ["a"]
 
 
 class TestHooksRunner(unittest.TestCase):
@@ -290,7 +290,7 @@ class FakeEnvLU(cmdlib.LogicalUnit):
     return self.hook_env
 
   def BuildHooksNodes(self):
-    return (["localhost"], ["localhost"])
+    return (["a"], ["a"])
 
 
 class FakeNoHooksLU(cmdlib.NoHooksLU):
@@ -327,7 +327,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunPhase(constants.HOOKS_PHASE_PRE)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(node_list, set(["node_a.example.com"]))
     self.assertEqual(hpath, self.lu.HPATH)
     self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
     self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
@@ -337,7 +337,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(node_list, set(["node_a.example.com"]))
     self.assertEqual(hpath, self.lu.HPATH)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
@@ -353,7 +353,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunPhase(constants.HOOKS_PHASE_PRE)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(node_list, set(["node_a.example.com"]))
     self.assertEqual(hpath, self.lu.HPATH)
     self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
     self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
@@ -368,7 +368,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(node_list, set(["node_a.example.com"]))
     self.assertEqual(hpath, self.lu.HPATH)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
@@ -382,7 +382,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     # Check configuration update hook
     hm.RunConfigUpdate()
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNodeName()]))
     self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self._CheckEnv(env, constants.HOOKS_PHASE_POST,
@@ -405,7 +405,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
   def testNoNodes(self):
     self.lu.hook_env = {}
     hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
-    hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
+    hm.RunPhase(constants.HOOKS_PHASE_PRE, node_names=[])
     self.assertRaises(IndexError, self._rpcs.pop)
 
   def testSpecificNodes(self):
@@ -419,7 +419,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
 
     for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
-      hm.RunPhase(phase, nodes=nodes)
+      hm.RunPhase(phase, node_names=nodes)
 
       (node_list, hpath, rpc_phase, env) = self._rpcs.pop(0)
       self.assertEqual(set(node_list), set(nodes))
@@ -438,7 +438,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunConfigUpdate()
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNodeName()]))
     self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self.assertEqual(env["GANETI_FOO"], "value")
@@ -457,7 +457,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunPhase(constants.HOOKS_PHASE_POST)
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(node_list, set(["localhost"]))
+    self.assertEqual(node_list, set(["node_a.example.com"]))
     self.assertEqual(hpath, self.lu.HPATH)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self.assertEqual(env["GANETI_FOO"], "value")
@@ -484,7 +484,7 @@ class TestHooksRunnerEnv(unittest.TestCase):
     hm.RunConfigUpdate()
 
     (node_list, hpath, phase, env) = self._rpcs.pop(0)
-    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
+    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNodeName()]))
     self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
     self.assertEqual(phase, constants.HOOKS_PHASE_POST)
     self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
@@ -495,5 +495,49 @@ class TestHooksRunnerEnv(unittest.TestCase):
     assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
 
 
+class FakeEnvWithNodeNameLU(cmdlib.LogicalUnit):
+  HPATH = "env_test_lu"
+  HTYPE = constants.HTYPE_GROUP
+
+  def __init__(self, *args):
+    cmdlib.LogicalUnit.__init__(self, *args)
+
+  def BuildHooksEnv(self):
+    return {}
+
+  def BuildHooksNodes(self):
+    return (["a"], ["a"], ["explicit.node1.com", "explicit.node2.com"])
+
+
+class TestHooksRunnerEnv(unittest.TestCase):
+  def setUp(self):
+    self._rpcs = []
+
+    self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
+    self.lu = FakeEnvWithNodeNameLU(FakeProc(), self.op, FakeContext(), None)
+
+  def _HooksRpc(self, *args):
+    self._rpcs.append(args)
+    return FakeHooksRpcSuccess(*args)
+
+  def testEmptyEnv(self):
+    # Check pre-phase hook
+    hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
+    hm.RunPhase(constants.HOOKS_PHASE_PRE)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["node_a.example.com"]))
+
+    # Check post-phase hook
+    hm.RunPhase(constants.HOOKS_PHASE_POST)
+
+    (node_list, hpath, phase, env) = self._rpcs.pop(0)
+    self.assertEqual(node_list, set(["node_a.example.com",
+                                     "explicit.node1.com",
+                                     "explicit.node2.com"]))
+
+    self.assertRaises(IndexError, self._rpcs.pop)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
index 233808c..548d8d1 100755 (executable)
@@ -42,12 +42,14 @@ class TestConsole(unittest.TestCase):
     shutil.rmtree(self.tmpdir)
 
   def test(self):
-    instance = objects.Instance(name="fake.example.com", primary_node="node837")
-    cons = hv_chroot.ChrootManager.GetInstanceConsole(instance, {}, {},
+    instance = objects.Instance(name="fake.example.com",
+                                primary_node="node837-uuid")
+    node = objects.Node(name="node837", uuid="node837-uuid")
+    cons = hv_chroot.ChrootManager.GetInstanceConsole(instance, node, {}, {},
                                                       root_dir=self.tmpdir)
     self.assertTrue(cons.Validate())
     self.assertEqual(cons.kind, constants.CONS_SSH)
-    self.assertEqual(cons.host, instance.primary_node)
+    self.assertEqual(cons.host, node.name)
 
 
 if __name__ == "__main__":
index e0c4240..700e0e9 100755 (executable)
@@ -35,7 +35,8 @@ import testutils
 class TestConsole(unittest.TestCase):
   def test(self):
     instance = objects.Instance(name="fake.example.com")
-    cons = hv_fake.FakeHypervisor.GetInstanceConsole(instance, {}, {})
+    node = objects.Node(name="fakenode.example.com")
+    cons = hv_fake.FakeHypervisor.GetInstanceConsole(instance, node, {}, {})
     self.assertTrue(cons.Validate())
     self.assertEqual(cons.kind, constants.CONS_MESSAGE)
 
index c21c749..b3d3f79 100755 (executable)
@@ -185,35 +185,37 @@ class TestQmp(testutils.GanetiTestCase):
 
 
 class TestConsole(unittest.TestCase):
-  def _Test(self, instance, hvparams):
-    cons = hv_kvm.KVMHypervisor.GetInstanceConsole(instance, hvparams, {})
+  def _Test(self, instance, node, hvparams):
+    cons = hv_kvm.KVMHypervisor.GetInstanceConsole(instance, node, hvparams, {})
     self.assertTrue(cons.Validate())
     return cons
 
   def testSerial(self):
     instance = objects.Instance(name="kvm.example.com",
-                                primary_node="node6017")
+                                primary_node="node6017-uuid")
+    node = objects.Node(name="node6017", uuid="node6017-uuid")
     hvparams = {
       constants.HV_SERIAL_CONSOLE: True,
       constants.HV_VNC_BIND_ADDRESS: None,
       constants.HV_KVM_SPICE_BIND: None,
       }
-    cons = self._Test(instance, hvparams)
+    cons = self._Test(instance, node, hvparams)
     self.assertEqual(cons.kind, constants.CONS_SSH)
-    self.assertEqual(cons.host, instance.primary_node)
+    self.assertEqual(cons.host, node.name)
     self.assertEqual(cons.command[0], pathutils.KVM_CONSOLE_WRAPPER)
     self.assertEqual(cons.command[1], constants.SOCAT_PATH)
 
   def testVnc(self):
     instance = objects.Instance(name="kvm.example.com",
-                                primary_node="node7235",
+                                primary_node="node7235-uuid",
                                 network_port=constants.VNC_BASE_PORT + 10)
+    node = objects.Node(name="node7235", uuid="node7235-uuid")
     hvparams = {
       constants.HV_SERIAL_CONSOLE: False,
       constants.HV_VNC_BIND_ADDRESS: "192.0.2.1",
       constants.HV_KVM_SPICE_BIND: None,
       }
-    cons = self._Test(instance, hvparams)
+    cons = self._Test(instance, node, hvparams)
     self.assertEqual(cons.kind, constants.CONS_VNC)
     self.assertEqual(cons.host, "192.0.2.1")
     self.assertEqual(cons.port, constants.VNC_BASE_PORT + 10)
@@ -223,12 +225,13 @@ class TestConsole(unittest.TestCase):
     instance = objects.Instance(name="kvm.example.com",
                                 primary_node="node7235",
                                 network_port=11000)
+    node = objects.Node(name="node7235", uuid="node7235-uuid")
     hvparams = {
       constants.HV_SERIAL_CONSOLE: False,
       constants.HV_VNC_BIND_ADDRESS: None,
       constants.HV_KVM_SPICE_BIND: "192.0.2.1",
       }
-    cons = self._Test(instance, hvparams)
+    cons = self._Test(instance, node, hvparams)
     self.assertEqual(cons.kind, constants.CONS_SPICE)
     self.assertEqual(cons.host, "192.0.2.1")
     self.assertEqual(cons.port, 11000)
@@ -237,12 +240,13 @@ class TestConsole(unittest.TestCase):
     instance = objects.Instance(name="kvm.example.com",
                                 primary_node="node24325",
                                 network_port=0)
+    node = objects.Node(name="node24325", uuid="node24325-uuid")
     hvparams = {
       constants.HV_SERIAL_CONSOLE: False,
       constants.HV_VNC_BIND_ADDRESS: None,
       constants.HV_KVM_SPICE_BIND: None,
       }
-    cons = self._Test(instance, hvparams)
+    cons = self._Test(instance, node, hvparams)
     self.assertEqual(cons.kind, constants.CONS_MESSAGE)
 
 
index 9d9e441..62ba58e 100755 (executable)
@@ -34,11 +34,13 @@ import testutils
 
 class TestConsole(unittest.TestCase):
   def test(self):
-    instance = objects.Instance(name="lxc.example.com", primary_node="node199")
-    cons = hv_lxc.LXCHypervisor.GetInstanceConsole(instance, {}, {})
+    instance = objects.Instance(name="lxc.example.com",
+                                primary_node="node199-uuid")
+    node = objects.Node(name="node199", uuid="node199-uuid")
+    cons = hv_lxc.LXCHypervisor.GetInstanceConsole(instance, node, {}, {})
     self.assertTrue(cons.Validate())
     self.assertEqual(cons.kind, constants.CONS_SSH)
-    self.assertEqual(cons.host, instance.primary_node)
+    self.assertEqual(cons.host, node.name)
     self.assertEqual(cons.command[-1], instance.name)
 
 
index e87e62f..9c73f75 100755 (executable)
@@ -19,7 +19,7 @@
 # 02110-1301, USA.
 
 
-"""Script for testing ganeti.hypervisor.hv_lxc"""
+"""Script for testing ganeti.hypervisor.hv_xen"""
 
 import string # pylint: disable=W0402
 import unittest
@@ -27,6 +27,7 @@ import tempfile
 import shutil
 import random
 import os
+import mock
 
 from ganeti import constants
 from ganeti import objects
@@ -47,13 +48,15 @@ HVCLASS_TO_HVNAME = utils.InvertDict(hypervisor._HYPERVISOR_MAP)
 
 class TestConsole(unittest.TestCase):
   def test(self):
-    for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    for cls in [hv_xen.XenPvmHypervisor(), hv_xen.XenHvmHypervisor()]:
       instance = objects.Instance(name="xen.example.com",
-                                  primary_node="node24828")
-      cons = cls.GetInstanceConsole(instance, {}, {})
+                                  primary_node="node24828-uuid")
+      node = objects.Node(name="node24828", uuid="node24828-uuid")
+      cons = cls.GetInstanceConsole(instance, node, hvparams, {})
       self.assertTrue(cons.Validate())
       self.assertEqual(cons.kind, constants.CONS_SSH)
-      self.assertEqual(cons.host, instance.primary_node)
+      self.assertEqual(cons.host, node.name)
       self.assertEqual(cons.command[-1], instance.name)
 
 
@@ -76,15 +79,48 @@ class TestCreateConfigCpus(unittest.TestCase):
                       constants.CPU_PINNING_ALL_XEN))
 
 
-class TestParseXmList(testutils.GanetiTestCase):
+class TestGetCommand(testutils.GanetiTestCase):
+  def testCommandExplicit(self):
+    """Test the case when the command is given as class parameter explicitly.
+
+    """
+    expected_cmd = "xl"
+    hv = hv_xen.XenHypervisor(_cmd=constants.XEN_CMD_XL)
+    self.assertEqual(hv._GetCommand(None), expected_cmd)
+
+  def testCommandInvalid(self):
+    """Test the case an invalid command is given as class parameter explicitly.
+
+    """
+    hv = hv_xen.XenHypervisor(_cmd="invalidcommand")
+    self.assertRaises(errors.ProgrammerError, hv._GetCommand, None)
+
+  def testCommandHvparams(self):
+    expected_cmd = "xl"
+    test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    hv = hv_xen.XenHypervisor()
+    self.assertEqual(hv._GetCommand(test_hvparams), expected_cmd)
+
+  def testCommandHvparamsInvalid(self):
+    test_hvparams = {}
+    hv = hv_xen.XenHypervisor()
+    self.assertRaises(errors.HypervisorError, hv._GetCommand, test_hvparams)
+
+  def testCommandHvparamsCmdInvalid(self):
+    test_hvparams = {constants.HV_XEN_CMD: "invalidcommand"}
+    hv = hv_xen.XenHypervisor()
+    self.assertRaises(errors.ProgrammerError, hv._GetCommand, test_hvparams)
+
+
+class TestParseInstanceList(testutils.GanetiTestCase):
   def test(self):
     data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
 
     # Exclude node
-    self.assertEqual(hv_xen._ParseXmList(data.splitlines(), False), [])
+    self.assertEqual(hv_xen._ParseInstanceList(data.splitlines(), False), [])
 
     # Include node
-    result = hv_xen._ParseXmList(data.splitlines(), True)
+    result = hv_xen._ParseInstanceList(data.splitlines(), True)
     self.assertEqual(len(result), 1)
     self.assertEqual(len(result[0]), 6)
 
@@ -114,14 +150,14 @@ class TestParseXmList(testutils.GanetiTestCase):
 
     for lines in tests:
       try:
-        hv_xen._ParseXmList(["Header would be here"] + lines, False)
+        hv_xen._ParseInstanceList(["Header would be here"] + lines, False)
       except errors.HypervisorError, err:
-        self.assertTrue("Can't parse output of xm list" in str(err))
+        self.assertTrue("Can't parse instance list" in str(err))
       else:
         self.fail("Exception was not raised")
 
 
-class TestGetXmList(testutils.GanetiTestCase):
+class TestGetInstanceList(testutils.GanetiTestCase):
   def _Fail(self):
     return utils.RunResult(constants.EXIT_FAILURE, None,
                            "stdout", "stderr", None,
@@ -130,7 +166,7 @@ class TestGetXmList(testutils.GanetiTestCase):
   def testTimeout(self):
     fn = testutils.CallCounter(self._Fail)
     try:
-      hv_xen._GetXmList(fn, False, _timeout=0.1)
+      hv_xen._GetInstanceList(fn, False, _timeout=0.1)
     except errors.HypervisorError, err:
       self.assertTrue("timeout exceeded" in str(err))
     else:
@@ -148,7 +184,7 @@ class TestGetXmList(testutils.GanetiTestCase):
 
     fn = testutils.CallCounter(compat.partial(self._Success, data))
 
-    result = hv_xen._GetXmList(fn, True, _timeout=0.1)
+    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
 
     self.assertEqual(len(result), 4)
 
@@ -189,10 +225,9 @@ class TestParseNodeInfo(testutils.GanetiTestCase):
 
 class TestMergeInstanceInfo(testutils.GanetiTestCase):
   def testEmpty(self):
-    self.assertEqual(hv_xen._MergeInstanceInfo({}, lambda _: []), {})
+    self.assertEqual(hv_xen._MergeInstanceInfo({}, []), {})
 
   def _FakeXmList(self, include_node):
-    self.assertTrue(include_node)
     return [
       (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
        NotImplemented),
@@ -201,7 +236,8 @@ class TestMergeInstanceInfo(testutils.GanetiTestCase):
       ]
 
   def testMissingNodeInfo(self):
-    result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
+    instance_list = self._FakeXmList(True)
+    result = hv_xen._MergeInstanceInfo({}, instance_list)
     self.assertEqual(result, {
       "memory_dom0": 4096,
       "dom0_cpus": 7,
@@ -209,7 +245,8 @@ class TestMergeInstanceInfo(testutils.GanetiTestCase):
 
   def testWithNodeInfo(self):
     info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
-    result = hv_xen._GetNodeInfo(info, self._FakeXmList)
+    instance_list = self._FakeXmList(True)
+    result = hv_xen._GetNodeInfo(info, instance_list)
     self.assertEqual(result, {
       "cpu_nodes": 1,
       "cpu_sockets": 2,
@@ -297,14 +334,126 @@ class TestGetConfigFileDiskData(unittest.TestCase):
     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
 
 
-class TestXenHypervisorUnknownCommand(unittest.TestCase):
-  def test(self):
+class TestXenHypervisorRunXen(unittest.TestCase):
+
+  XEN_SUB_CMD = "help"
+
+  def testCommandUnknown(self):
     cmd = "#unknown command#"
     self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
                               _run_cmd_fn=NotImplemented,
                               _cmd=cmd)
-    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
+    self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
+
+  def testCommandNoHvparams(self):
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=NotImplemented)
+    hvparams = None
+    self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
+                      hvparams)
+
+  def testCommandFromHvparams(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock()
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
+
+
+class TestXenHypervisorGetInstanceList(unittest.TestCase):
+
+  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+  XEN_LIST = "list"
+
+  def testNoHvparams(self):
+    expected_xen_cmd = "xm"
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
+
+  def testFromHvparams(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._GetInstanceList(True, hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
+
+
+class TestXenHypervisorListInstances(unittest.TestCase):
+
+  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+  XEN_LIST = "list"
+
+  def testNoHvparams(self):
+    expected_xen_cmd = "xm"
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv.ListInstances)
+
+  def testHvparamsXl(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv.ListInstances(hvparams=hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
+
+
+class TestXenHypervisorCheckToolstack(unittest.TestCase):
+
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+    self.cfg_name = "xen_config"
+    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
+    self.hv = hv_xen.XenHypervisor()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testBinaryNotFound(self):
+    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackBinary("xl")
+    self.assertFalse(result)
+
+  def testCheckToolstackXlConfigured(self):
+    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackXlConfigured()
+    self.assertTrue(result)
+
+  def testCheckToolstackXlNotConfigured(self):
+    RESULT_FAILED = utils.RunResult(
+        1, None, "",
+        "ERROR:  A different toolstack (xm) have been selected!",
+        "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackXlConfigured()
+    self.assertFalse(result)
+
+  def testCheckToolstackXlFails(self):
+    RESULT_FAILED = utils.RunResult(
+        1, None, "",
+        "ERROR: The pink bunny hid the binary.",
+        "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
 
 
 class TestXenHypervisorWriteConfigFile(unittest.TestCase):
@@ -331,10 +480,46 @@ class TestXenHypervisorWriteConfigFile(unittest.TestCase):
       self.fail("Exception was not raised")
 
 
+class TestXenHypervisorVerify(unittest.TestCase):
+
+  def setUp(self):
+    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
+    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
+
+  def testVerify(self):
+    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock(return_value=self._result_ok)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock(return_value=True)
+    result = hv.Verify(hvparams)
+    self.assertTrue(result is None)
+
+  def testVerifyToolstackNotOk(self):
+    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock(return_value=self._result_ok)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock()
+    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
+    result = hv.Verify(hvparams)
+    self.assertTrue(result is not None)
+
+  def testVerifyFailing(self):
+    result_failed = utils.RunResult(1, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=result_failed)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock(return_value=True)
+    result = hv.Verify()
+    self.assertTrue(result is not None)
+
+
 class _TestXenHypervisor(object):
   TARGET = NotImplemented
   CMD = NotImplemented
   HVNAME = NotImplemented
+  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
 
   def setUp(self):
     super(_TestXenHypervisor, self).setUp()
@@ -460,16 +645,6 @@ class _TestXenHypervisor(object):
       "testinstance.example.com",
       ])
 
-  def testVerify(self):
-    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
-    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
-                                            output))
-    self.assertTrue(hv.Verify() is None)
-
-  def testVerifyFailing(self):
-    hv = self._GetHv(run_cmd=self._FailingCommand)
-    self.assertTrue("failed:" in hv.Verify())
-
   def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
     if cmd == [self.CMD, "info"]:
       output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
@@ -582,7 +757,7 @@ class _TestXenHypervisor(object):
 
         if fail:
           try:
-            hv._StopInstance(name, force)
+            hv._StopInstance(name, force, None)
           except errors.HypervisorError, err:
             self.assertTrue(str(err).startswith("Failed to stop instance"))
           else:
@@ -591,7 +766,7 @@ class _TestXenHypervisor(object):
                            msg=("Configuration was removed when stopping"
                                 " instance failed"))
         else:
-          hv._StopInstance(name, force)
+          hv._StopInstance(name, force, None)
           self.assertFalse(os.path.exists(cfgfile))
 
   def _MigrateNonRunningInstCmd(self, cmd):
@@ -612,7 +787,7 @@ class _TestXenHypervisor(object):
     for live in [False, True]:
       try:
         hv._MigrateInstance(NotImplemented, name, target, port, live,
-                            _ping_fn=NotImplemented)
+                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
       except errors.HypervisorError, err:
         self.assertEqual(str(err), "Instance not running, cannot migrate")
       else:
@@ -632,6 +807,7 @@ class _TestXenHypervisor(object):
     port = 28349
 
     hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
+    hvparams = {constants.HV_XEN_CMD: self.CMD}
 
     for live in [False, True]:
       if self.CMD == constants.XEN_CMD_XL:
@@ -640,6 +816,7 @@ class _TestXenHypervisor(object):
       else:
         try:
           hv._MigrateInstance(NotImplemented, name, target, port, live,
+                              hvparams,
                               _ping_fn=compat.partial(self._FakeTcpPing,
                                                       (target, port), False))
         except errors.HypervisorError, err:
@@ -686,6 +863,8 @@ class _TestXenHypervisor(object):
     target = constants.IP4_ADDRESS_LOCALHOST
     port = 22364
 
+    hvparams = {constants.HV_XEN_CMD: self.CMD}
+
     for live in [False, True]:
       for fail in [False, True]:
         ping_fn = \
@@ -702,14 +881,14 @@ class _TestXenHypervisor(object):
         if fail:
           try:
             hv._MigrateInstance(clustername, instname, target, port, live,
-                                _ping_fn=ping_fn)
+                                hvparams, _ping_fn=ping_fn)
           except errors.HypervisorError, err:
             self.assertTrue(str(err).startswith("Failed to migrate instance"))
           else:
             self.fail("Exception was not raised")
         else:
           hv._MigrateInstance(clustername, instname, target, port, live,
-                              _ping_fn=ping_fn)
+                              hvparams, _ping_fn=ping_fn)
 
         if self.CMD == constants.XEN_CMD_XM:
           expected_pings = 1
index e111e33..5ba7901 100755 (executable)
@@ -97,19 +97,19 @@ class TestClusterObject(unittest.TestCase):
                                           os_name="lenny-image"),
                          cl.os_hvp["lenny-image"][constants.HT_XEN_PVM])
 
-
   def testFillHvFullMerge(self):
     inst_hvparams = {
       "blah": "blubb",
       }
 
-    fake_dict = {
+    fake_dict = constants.HVC_DEFAULTS[constants.HT_FAKE].copy()
+    fake_dict.update({
       "foo": "baz",
       "bar": "foo",
       "foobar": "foobar",
       "blah": "blubb",
       "blubb": "blah",
-      }
+      })
     fake_inst = objects.Instance(name="foobar",
                                  os="lenny-image",
                                  hypervisor=constants.HT_FAKE,
index 0bff4c3..0b81c41 100755 (executable)
@@ -346,7 +346,7 @@ class TestNodeQuery(unittest.TestCase):
                    ndparams={constants.ND_SPINDLE_COUNT: 4}),
       ]
     for live_data in [None, dict.fromkeys([node.name for node in nodes], {})]:
-      nqd = query.NodeQueryData(nodes, live_data, None, None, None,
+      nqd = query.NodeQueryData(nodes, live_data, None, None, None, None,
                                 groups, None, cluster)
 
       q = self._Create(["name", "drained"])
@@ -423,8 +423,8 @@ class TestNodeQuery(unittest.TestCase):
     master_node.mtime = None
     assert master_node.name == master_name
 
-    live_data_name = node_names[4]
-    assert live_data_name != master_name
+    live_data_node = nodes[4]
+    assert live_data_node.name != master_name
 
     fake_live_data = {
       "bootid": "a2504766-498e-4b25-b21e-d23098dc3af4",
@@ -436,38 +436,50 @@ class TestNodeQuery(unittest.TestCase):
       "mtotal": 4096,
       "dfree": 5 * 1024 * 1024,
       "dtotal": 100 * 1024 * 1024,
+      "spfree": 0,
+      "sptotal": 0,
       }
 
     assert (sorted(query._NODE_LIVE_FIELDS.keys()) ==
             sorted(fake_live_data.keys()))
 
-    live_data = dict.fromkeys(node_names, {})
-    live_data[live_data_name] = \
+    live_data = dict.fromkeys([node.uuid for node in nodes], {})
+    live_data[live_data_node.uuid] = \
       dict((query._NODE_LIVE_FIELDS[name][2], value)
            for name, value in fake_live_data.items())
 
-    node_to_primary = dict((name, set()) for name in node_names)
-    node_to_primary[master_name].update(["inst1", "inst2"])
+    node_to_primary_uuid = dict((node.uuid, set()) for node in nodes)
+    node_to_primary_uuid[master_node.uuid].update(["inst1", "inst2"])
 
-    node_to_secondary = dict((name, set()) for name in node_names)
-    node_to_secondary[live_data_name].update(["instX", "instY", "instZ"])
+    node_to_secondary_uuid = dict((node.uuid, set()) for node in nodes)
+    node_to_secondary_uuid[live_data_node.uuid].update(["instX", "instY",
+                                                        "instZ"])
+
+    inst_uuid_to_inst_name = {
+      "inst1": "inst1-name",
+      "inst2": "inst2-name",
+      "instX": "instX-name",
+      "instY": "instY-name",
+      "instZ": "instZ-name"
+    }
 
     ng_uuid = "492b4b74-8670-478a-b98d-4c53a76238e6"
     groups = {
       ng_uuid: objects.NodeGroup(name="ng1", uuid=ng_uuid, ndparams={}),
       }
 
-    oob_not_powered_node = node_names[0]
-    nodes[0].powered = False
-    oob_support = dict((name, False) for name in node_names)
-    oob_support[master_name] = True
-    oob_support[oob_not_powered_node] = True
+    oob_not_powered_node = nodes[0]
+    oob_not_powered_node.powered = False
+    oob_support = dict((node.uuid, False) for node in nodes)
+    oob_support[master_node.uuid] = True
+    oob_support[oob_not_powered_node.uuid] = True
 
     master_node.group = ng_uuid
 
-    nqd = query.NodeQueryData(nodes, live_data, master_name,
-                              node_to_primary, node_to_secondary, groups,
-                              oob_support, cluster)
+    nqd = query.NodeQueryData(nodes, live_data, master_node.uuid,
+                              node_to_primary_uuid, node_to_secondary_uuid,
+                              inst_uuid_to_inst_name, groups, oob_support,
+                              cluster)
     result = q.Query(nqd)
     self.assert_(compat.all(len(row) == len(selected) for row in result))
     self.assertEqual([row[field_index["name"]] for row in result],
@@ -509,7 +521,7 @@ class TestNodeQuery(unittest.TestCase):
                  row[field_index["powered"]] == (constants.RS_UNAVAIL, None)
                  for row, node in zip(result, nodes))
 
-    live_data_row = result[node_to_row[live_data_name]]
+    live_data_row = result[node_to_row[live_data_node.name]]
 
     for (field, value) in fake_live_data.items():
       self.assertEqual(live_data_row[field_index[field]],
@@ -521,10 +533,13 @@ class TestNodeQuery(unittest.TestCase):
                      (constants.RS_NORMAL, 3))
     self.assertEqual(master_row[field_index["pinst_list"]],
                      (constants.RS_NORMAL,
-                      list(node_to_primary[master_name])))
+                      [inst_uuid_to_inst_name[uuid] for uuid in
+                       node_to_primary_uuid[master_node.uuid]]))
     self.assertEqual(live_data_row[field_index["sinst_list"]],
                      (constants.RS_NORMAL,
-                      utils.NiceSort(list(node_to_secondary[live_data_name]))))
+                      utils.NiceSort(
+                        [inst_uuid_to_inst_name[uuid] for uuid in
+                        node_to_secondary_uuid[live_data_node.uuid]])))
 
   def testGetLiveNodeField(self):
     nodes = [
@@ -542,7 +557,8 @@ class TestNodeQuery(unittest.TestCase):
     live_data = dict.fromkeys([node.name for node in nodes], {})
 
     # No data
-    nqd = query.NodeQueryData(None, None, None, None, None, None, None, None)
+    nqd = query.NodeQueryData(None, None, None, None, None, None, None, None,
+                              None)
     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
                                              nqd, nodes[0]),
                      query._FS_NODATA)
@@ -664,27 +680,28 @@ class TestInstanceQuery(unittest.TestCase):
           },
         })
 
-    offline_nodes = ["nodeoff1", "nodeoff2"]
-    bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
-    nodes = ["node%s" % i for i in range(10)] + bad_nodes
+    offline_nodes = ["nodeoff1-uuid", "nodeoff2-uuid"]
+    bad_nodes = ["nodebad1-uuid", "nodebad2-uuid", "nodebad3-uuid"] +\
+                offline_nodes
+    node_uuids = ["node%s-uuid" % i for i in range(10)] + bad_nodes
 
     instances = [
       objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
-        uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
+        uuid="inst1-uuid",
         ctime=1291244000, mtime=1291244400, serial_no=30,
         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_PVM,
         os="linux1",
-        primary_node="node1",
+        primary_node="node1-uuid",
         disk_template=constants.DT_PLAIN,
         disks=[],
         disks_active=True,
         osparams={}),
       objects.Instance(name="inst2", hvparams={}, nics=[],
-        uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
+        uuid="inst2-uuid",
         ctime=1291211000, mtime=1291211077, serial_no=1,
         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
         os="deb99",
-        primary_node="node5",
+        primary_node="node5-uuid",
         disk_template=constants.DT_DISKLESS,
         disks=[],
         disks_active=True,
@@ -694,11 +711,11 @@ class TestInstanceQuery(unittest.TestCase):
         },
         osparams={}),
       objects.Instance(name="inst3", hvparams={}, beparams={},
-        uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
+        uuid="inst3-uuid",
         ctime=1291011000, mtime=1291013000, serial_no=1923,
         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_KVM,
         os="busybox",
-        primary_node="node6",
+        primary_node="node6-uuid",
         disk_template=constants.DT_DRBD8,
         disks=[],
         disks_active=False,
@@ -711,11 +728,11 @@ class TestInstanceQuery(unittest.TestCase):
           ],
         osparams={}),
       objects.Instance(name="inst4", hvparams={}, beparams={},
-        uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
+        uuid="inst4-uuid",
         ctime=1291244390, mtime=1291244395, serial_no=25,
         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_PVM,
         os="linux1",
-        primary_node="nodeoff2",
+        primary_node="nodeoff2-uuid",
         disk_template=constants.DT_DRBD8,
         disks=[],
         disks_active=True,
@@ -737,11 +754,11 @@ class TestInstanceQuery(unittest.TestCase):
           ],
         osparams={}),
       objects.Instance(name="inst5", hvparams={}, nics=[],
-        uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
+        uuid="inst5-uuid",
         ctime=1231211000, mtime=1261200000, serial_no=3,
         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
         os="deb99",
-        primary_node="nodebad2",
+        primary_node="nodebad2-uuid",
         disk_template=constants.DT_DISKLESS,
         disks=[],
         disks_active=True,
@@ -751,11 +768,11 @@ class TestInstanceQuery(unittest.TestCase):
         },
         osparams={}),
       objects.Instance(name="inst6", hvparams={}, nics=[],
-        uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
+        uuid="inst6-uuid",
         ctime=7513, mtime=11501, serial_no=13390,
         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
         os="deb99",
-        primary_node="node7",
+        primary_node="node7-uuid",
         disk_template=constants.DT_DISKLESS,
         disks=[],
         disks_active=False,
@@ -767,22 +784,22 @@ class TestInstanceQuery(unittest.TestCase):
           "clean_install": "no",
           }),
       objects.Instance(name="inst7", hvparams={}, nics=[],
-        uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
+        uuid="inst7-uuid",
         ctime=None, mtime=None, serial_no=1947,
         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
         os="deb99",
-        primary_node="node6",
+        primary_node="node6-uuid",
         disk_template=constants.DT_DISKLESS,
         disks=[],
         disks_active=False,
         beparams={},
         osparams={}),
       objects.Instance(name="inst8", hvparams={}, nics=[],
-        uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920f",
+        uuid="inst8-uuid",
         ctime=None, mtime=None, serial_no=19478,
         admin_state=constants.ADMINST_OFFLINE, hypervisor=constants.HT_XEN_HVM,
         os="deb99",
-        primary_node="node6",
+        primary_node="node6-uuid",
         disk_template=constants.DT_DISKLESS,
         disks=[],
         disks_active=False,
@@ -790,48 +807,55 @@ class TestInstanceQuery(unittest.TestCase):
         osparams={}),
       ]
 
+    assert not utils.FindDuplicates(inst.uuid for inst in instances)
     assert not utils.FindDuplicates(inst.name for inst in instances)
 
     instbyname = dict((inst.name, inst) for inst in instances)
 
-    disk_usage = dict((inst.name,
+    disk_usage = dict((inst.uuid,
                        gmi.ComputeDiskSize(inst.disk_template,
                                            [{"size": disk.size}
                                            for disk in inst.disks]))
                       for inst in instances)
 
     inst_bridges = {
-      "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
-      "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
-                None, "eth123"],
+      "inst3-uuid": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
+      "inst4-uuid": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
+                     None, "eth123"],
       }
 
     live_data = {
-      "inst2": {
+      "inst2-uuid": {
         "vcpus": 3,
         },
-      "inst4": {
+      "inst4-uuid": {
         "memory": 123,
         },
-      "inst6": {
+      "inst6-uuid": {
         "memory": 768,
         },
-      "inst7": {
+      "inst7-uuid": {
         "vcpus": 3,
         },
       }
-    wrongnode_inst = set(["inst7"])
+    wrongnode_inst = set(["inst7-uuid"])
 
-    consinfo = dict((inst.name, None) for inst in instances)
-    consinfo["inst7"] = \
+    consinfo = dict((inst.uuid, None) for inst in instances)
+    consinfo["inst7-uuid"] = \
       objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
                               host=instbyname["inst7"].primary_node,
                               user="root",
                               command=["hostname"]).ToDict()
 
+    nodes = dict([(uuid, objects.Node(
+                           name="%s.example.com" % uuid,
+                           uuid=uuid,
+                           group="default-uuid"))
+                  for uuid in node_uuids])
+
     iqd = query.InstanceQueryData(instances, cluster, disk_usage,
                                   offline_nodes, bad_nodes, live_data,
-                                  wrongnode_inst, consinfo, {}, {}, {})
+                                  wrongnode_inst, consinfo, nodes, {}, {})
     result = q.Query(iqd)
     self.assertEqual(len(result), len(instances))
     self.assert_(compat.all(len(row) == len(selected)
@@ -843,7 +867,7 @@ class TestInstanceQuery(unittest.TestCase):
     tested_status = set()
 
     for (inst, row) in zip(instances, result):
-      assert inst.primary_node in nodes
+      assert inst.primary_node in node_uuids
 
       self.assertEqual(row[fieldidx["name"]],
                        (constants.RS_NORMAL, inst.name))
@@ -852,8 +876,8 @@ class TestInstanceQuery(unittest.TestCase):
         exp_status = constants.INSTST_NODEOFFLINE
       elif inst.primary_node in bad_nodes:
         exp_status = constants.INSTST_NODEDOWN
-      elif inst.name in live_data:
-        if inst.name in wrongnode_inst:
+      elif inst.uuid in live_data:
+        if inst.uuid in wrongnode_inst:
           exp_status = constants.INSTST_WRONGNODE
         elif inst.admin_state == constants.ADMINST_UP:
           exp_status = constants.INSTST_RUNNING
@@ -876,8 +900,8 @@ class TestInstanceQuery(unittest.TestCase):
       for (field, livefield) in [("oper_vcpus", "vcpus")]:
         if inst.primary_node in bad_nodes:
           exp = (constants.RS_NODATA, None)
-        elif inst.name in live_data:
-          value = live_data[inst.name].get(livefield, None)
+        elif inst.uuid in live_data:
+          value = live_data[inst.uuid].get(livefield, None)
           if value is None:
             exp = (constants.RS_UNAVAIL, None)
           else:
@@ -887,7 +911,7 @@ class TestInstanceQuery(unittest.TestCase):
 
         self.assertEqual(row[fieldidx[field]], exp)
 
-      bridges = inst_bridges.get(inst.name, [])
+      bridges = inst_bridges.get(inst.uuid, [])
       self.assertEqual(row[fieldidx["nic.bridges"]],
                        (constants.RS_NORMAL, bridges))
       if bridges:
@@ -907,12 +931,12 @@ class TestInstanceQuery(unittest.TestCase):
       if inst.primary_node in bad_nodes:
         exp = (constants.RS_NODATA, None)
       else:
-        exp = (constants.RS_NORMAL, inst.name in live_data)
+        exp = (constants.RS_NORMAL, inst.uuid in live_data)
       self.assertEqual(row[fieldidx["oper_state"]], exp)
 
       cust_exp = (constants.RS_NORMAL, {})
       if inst.os == "deb99":
-        if inst.name == "inst6":
+        if inst.uuid == "inst6-uuid":
           exp = (constants.RS_NORMAL, {"clean_install": "no"})
           cust_exp = exp
         else:
@@ -922,7 +946,7 @@ class TestInstanceQuery(unittest.TestCase):
       self.assertEqual(row[fieldidx["osparams"]], exp)
       self.assertEqual(row[fieldidx["custom_osparams"]], cust_exp)
 
-      usage = disk_usage[inst.name]
+      usage = disk_usage[inst.uuid]
       if usage is None:
         usage = 0
       self.assertEqual(row[fieldidx["disk_usage"]],
index 7967173..0674b4b 100755 (executable)
@@ -341,7 +341,7 @@ class TestSsconfResolver(unittest.TestCase):
     ssc = GetFakeSimpleStoreClass(lambda _: node_addr_list)
     result = rpc._SsconfResolver(True, node_list, NotImplemented,
                                  ssc=ssc, nslookup_fn=NotImplemented)
-    self.assertEqual(result, zip(node_list, addr_list))
+    self.assertEqual(result, zip(node_list, addr_list, node_list))
 
   def testNsLookup(self):
     addr_list = ["192.0.2.%d" % n for n in range(0, 255, 13)]
@@ -351,7 +351,7 @@ class TestSsconfResolver(unittest.TestCase):
     nslookup_fn = lambda name, family=None: node_addr_map.get(name)
     result = rpc._SsconfResolver(True, node_list, NotImplemented,
                                  ssc=ssc, nslookup_fn=nslookup_fn)
-    self.assertEqual(result, zip(node_list, addr_list))
+    self.assertEqual(result, zip(node_list, addr_list, node_list))
 
   def testDisabledSsconfIp(self):
     addr_list = ["192.0.2.%d" % n for n in range(0, 255, 13)]
@@ -361,7 +361,7 @@ class TestSsconfResolver(unittest.TestCase):
     nslookup_fn = lambda name, family=None: node_addr_map.get(name)
     result = rpc._SsconfResolver(False, node_list, NotImplemented,
                                  ssc=ssc, nslookup_fn=nslookup_fn)
-    self.assertEqual(result, zip(node_list, addr_list))
+    self.assertEqual(result, zip(node_list, addr_list, node_list))
 
   def testBothLookups(self):
     addr_list = ["192.0.2.%d" % n for n in range(0, 255, 13)]
@@ -373,7 +373,7 @@ class TestSsconfResolver(unittest.TestCase):
     nslookup_fn = lambda name, family=None: node_addr_map.get(name)
     result = rpc._SsconfResolver(True, node_list, NotImplemented,
                                  ssc=ssc, nslookup_fn=nslookup_fn)
-    self.assertEqual(result, zip(node_list, addr_list))
+    self.assertEqual(result, zip(node_list, addr_list, node_list))
 
   def testAddressLookupIPv6(self):
     addr_list = ["2001:db8::%d" % n for n in range(0, 255, 11)]
@@ -382,7 +382,7 @@ class TestSsconfResolver(unittest.TestCase):
     ssc = GetFakeSimpleStoreClass(lambda _: node_addr_list)
     result = rpc._SsconfResolver(True, node_list, NotImplemented,
                                  ssc=ssc, nslookup_fn=NotImplemented)
-    self.assertEqual(result, zip(node_list, addr_list))
+    self.assertEqual(result, zip(node_list, addr_list, node_list))
 
 
 class TestStaticResolver(unittest.TestCase):
@@ -390,7 +390,7 @@ class TestStaticResolver(unittest.TestCase):
     addresses = ["192.0.2.%d" % n for n in range(0, 123, 7)]
     nodes = ["node%s.example.com" % n for n in range(0, 123, 7)]
     res = rpc._StaticResolver(addresses)
-    self.assertEqual(res(nodes, NotImplemented), zip(nodes, addresses))
+    self.assertEqual(res(nodes, NotImplemented), zip(nodes, addresses, nodes))
 
   def testWrongLength(self):
     res = rpc._StaticResolver([])
@@ -399,34 +399,40 @@ class TestStaticResolver(unittest.TestCase):
 
 class TestNodeConfigResolver(unittest.TestCase):
   @staticmethod
-  def _GetSingleOnlineNode(name):
-    assert name == "node90.example.com"
-    return objects.Node(name=name, offline=False, primary_ip="192.0.2.90")
+  def _GetSingleOnlineNode(uuid):
+    assert uuid == "node90-uuid"
+    return objects.Node(name="node90.example.com",
+                        uuid=uuid,
+                        offline=False,
+                        primary_ip="192.0.2.90")
 
   @staticmethod
-  def _GetSingleOfflineNode(name):
-    assert name == "node100.example.com"
-    return objects.Node(name=name, offline=True, primary_ip="192.0.2.100")
+  def _GetSingleOfflineNode(uuid):
+    assert uuid == "node100-uuid"
+    return objects.Node(name="node100.example.com",
+                        uuid=uuid,
+                        offline=True,
+                        primary_ip="192.0.2.100")
 
   def testSingleOnline(self):
     self.assertEqual(rpc._NodeConfigResolver(self._GetSingleOnlineNode,
                                              NotImplemented,
-                                             ["node90.example.com"], None),
-                     [("node90.example.com", "192.0.2.90")])
+                                             ["node90-uuid"], None),
+                     [("node90.example.com", "192.0.2.90", "node90-uuid")])
 
   def testSingleOffline(self):
     self.assertEqual(rpc._NodeConfigResolver(self._GetSingleOfflineNode,
                                              NotImplemented,
-                                             ["node100.example.com"], None),
-                     [("node100.example.com", rpc._OFFLINE)])
+                                             ["node100-uuid"], None),
+                     [("node100.example.com", rpc._OFFLINE, "node100-uuid")])
 
   def testSingleOfflineWithAcceptOffline(self):
     fn = self._GetSingleOfflineNode
-    assert fn("node100.example.com").offline
+    assert fn("node100-uuid").offline
     self.assertEqual(rpc._NodeConfigResolver(fn, NotImplemented,
-                                             ["node100.example.com"],
+                                             ["node100-uuid"],
                                              rpc_defs.ACCEPT_OFFLINE_NODE),
-                     [("node100.example.com", "192.0.2.100")])
+                     [("node100.example.com", "192.0.2.100", "node100-uuid")])
     for i in [False, True, "", "Hello", 0, 1]:
       self.assertRaises(AssertionError, rpc._NodeConfigResolver,
                         fn, NotImplemented, ["node100.example.com"], i)
@@ -434,7 +440,8 @@ class TestNodeConfigResolver(unittest.TestCase):
   def testUnknownSingleNode(self):
     self.assertEqual(rpc._NodeConfigResolver(lambda _: None, NotImplemented,
                                              ["node110.example.com"], None),
-                     [("node110.example.com", "node110.example.com")])
+                     [("node110.example.com", "node110.example.com",
+                       "node110.example.com")])
 
   def testMultiEmpty(self):
     self.assertEqual(rpc._NodeConfigResolver(NotImplemented,
@@ -443,10 +450,11 @@ class TestNodeConfigResolver(unittest.TestCase):
                      [])
 
   def testMultiSomeOffline(self):
-    nodes = dict(("node%s.example.com" % i,
+    nodes = dict(("node%s-uuid" % i,
                   objects.Node(name="node%s.example.com" % i,
                                offline=((i % 3) == 0),
-                               primary_ip="192.0.2.%s" % i))
+                               primary_ip="192.0.2.%s" % i,
+                               uuid="node%s-uuid" % i))
                   for i in range(1, 255))
 
     # Resolve no names
@@ -458,15 +466,15 @@ class TestNodeConfigResolver(unittest.TestCase):
     # Offline, online and unknown hosts
     self.assertEqual(rpc._NodeConfigResolver(NotImplemented,
                                              lambda: nodes,
-                                             ["node3.example.com",
-                                              "node92.example.com",
-                                              "node54.example.com",
+                                             ["node3-uuid",
+                                              "node92-uuid",
+                                              "node54-uuid",
                                               "unknown.example.com",],
                                              None), [
-      ("node3.example.com", rpc._OFFLINE),
-      ("node92.example.com", "192.0.2.92"),
-      ("node54.example.com", rpc._OFFLINE),
-      ("unknown.example.com", "unknown.example.com"),
+      ("node3.example.com", rpc._OFFLINE, "node3-uuid"),
+      ("node92.example.com", "192.0.2.92", "node92-uuid"),
+      ("node54.example.com", rpc._OFFLINE, "node54-uuid"),
+      ("unknown.example.com", "unknown.example.com", "unknown.example.com"),
       ])
 
 
@@ -672,7 +680,7 @@ class TestRpcClientBase(unittest.TestCase):
     def _Resolver(expected, hosts, options):
       self.assertEqual(hosts, nodes)
       self.assertEqual(options, expected)
-      return zip(hosts, nodes)
+      return zip(hosts, nodes, hosts)
 
     def _DynamicResolverOptions((arg0, )):
       return sum(arg0)
@@ -891,18 +899,45 @@ class TestRpcRunner(unittest.TestCase):
 
 class TestLegacyNodeInfo(unittest.TestCase):
   KEY_BOOT = "bootid"
-  KEY_VG = "disk_free"
+  KEY_VG0 = "name"
+  KEY_VG1 = "storage_free"
+  KEY_VG2 = "storage_size"
   KEY_HV = "cpu_count"
+  KEY_SP1 = "spindles_free"
+  KEY_SP2 = "spindles_total"
+  KEY_ST = "type" # key for storage type
   VAL_BOOT = 0
-  VAL_VG = 1
+  VAL_VG0 = "xy"
+  VAL_VG1 = 11
+  VAL_VG2 = 12
+  VAL_VG3 = "lvm-vg"
   VAL_HV = 2
-  DICT_VG = {KEY_VG: VAL_VG}
+  VAL_SP0 = "ab"
+  VAL_SP1 = 31
+  VAL_SP2 = 32
+  VAL_SP3 = "lvm-pv"
+  DICT_VG = {
+    KEY_VG0: VAL_VG0,
+    KEY_VG1: VAL_VG1,
+    KEY_VG2: VAL_VG2,
+    KEY_ST: VAL_VG3,
+    }
   DICT_HV = {KEY_HV: VAL_HV}
-  STD_LST = [VAL_BOOT, [DICT_VG], [DICT_HV]]
+  DICT_SP = {
+    KEY_ST: VAL_SP3,
+    KEY_VG0: VAL_SP0,
+    KEY_VG1: VAL_SP1,
+    KEY_VG2: VAL_SP2,
+    }
+  STD_LST = [VAL_BOOT, [DICT_VG, DICT_SP], [DICT_HV]]
   STD_DICT = {
     KEY_BOOT: VAL_BOOT,
-    KEY_VG: VAL_VG,
-    KEY_HV: VAL_HV
+    KEY_VG0: VAL_VG0,
+    KEY_VG1: VAL_VG1,
+    KEY_VG2: VAL_VG2,
+    KEY_HV: VAL_HV,
+    KEY_SP1: VAL_SP1,
+    KEY_SP2: VAL_SP2,
     }
 
   def testStandard(self):
@@ -911,7 +946,7 @@ class TestLegacyNodeInfo(unittest.TestCase):
 
   def testReqVg(self):
     my_lst = [self.VAL_BOOT, [], [self.DICT_HV]]
-    self.assertRaises(ValueError, rpc.MakeLegacyNodeInfo, my_lst)
+    self.assertRaises(errors.OpExecError, rpc.MakeLegacyNodeInfo, my_lst)
 
   def testNoReqVg(self):
     my_lst = [self.VAL_BOOT, [], [self.DICT_HV]]
index 201973f..c0d4154 100755 (executable)
@@ -33,6 +33,7 @@ from ganeti import errors
 from ganeti import ssconf
 
 import testutils
+import mock
 
 
 class TestReadSsconfFile(unittest.TestCase):
@@ -220,6 +221,16 @@ class TestSimpleStore(unittest.TestCase):
 
       self.assertEqual(os.listdir(self.ssdir), [])
 
+  def testGetHvparamsForHypervisor(self):
+    hvparams = [("a", "A"), ("b", "B"), ("c", "C")]
+    ssconf_file_content = '\n'.join("%s=%s" % (key, value) for (key, value)
+                                    in hvparams)
+    self.sstore._ReadFile = mock.Mock(return_value=ssconf_file_content)
+    result = self.sstore.GetHvparamsForHypervisor("foo")
+    for (key, value) in hvparams:
+      self.assertTrue(key in result)
+      self.assertEqual(value, result[key])
+
 
 class TestVerifyClusterName(unittest.TestCase):
   def setUp(self):
diff --git a/test/py/ganeti.storage.bdev_unittest.py b/test/py/ganeti.storage.bdev_unittest.py
new file mode 100755 (executable)
index 0000000..f623cef
--- /dev/null
@@ -0,0 +1,391 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the bdev module"""
+
+
+import os
+import random
+import unittest
+
+from ganeti import compat
+from ganeti import constants
+from ganeti import errors
+from ganeti import objects
+from ganeti import utils
+from ganeti.storage import bdev
+
+import testutils
+
+
+class TestRADOSBlockDevice(testutils.GanetiTestCase):
+  def setUp(self):
+    """Set up input data"""
+    testutils.GanetiTestCase.setUp(self)
+
+    self.plain_output_old_ok = \
+      testutils.ReadTestData("bdev-rbd/plain_output_old_ok.txt")
+    self.plain_output_old_no_matches = \
+      testutils.ReadTestData("bdev-rbd/plain_output_old_no_matches.txt")
+    self.plain_output_old_extra_matches = \
+      testutils.ReadTestData("bdev-rbd/plain_output_old_extra_matches.txt")
+    self.plain_output_old_empty = \
+      testutils.ReadTestData("bdev-rbd/plain_output_old_empty.txt")
+    self.plain_output_new_ok = \
+      testutils.ReadTestData("bdev-rbd/plain_output_new_ok.txt")
+    self.plain_output_new_no_matches = \
+      testutils.ReadTestData("bdev-rbd/plain_output_new_no_matches.txt")
+    self.plain_output_new_extra_matches = \
+      testutils.ReadTestData("bdev-rbd/plain_output_new_extra_matches.txt")
+    # This file is completely empty, and as such it's not shipped.
+    self.plain_output_new_empty = ""
+    self.json_output_ok = testutils.ReadTestData("bdev-rbd/json_output_ok.txt")
+    self.json_output_no_matches = \
+      testutils.ReadTestData("bdev-rbd/json_output_no_matches.txt")
+    self.json_output_extra_matches = \
+      testutils.ReadTestData("bdev-rbd/json_output_extra_matches.txt")
+    self.json_output_empty = \
+      testutils.ReadTestData("bdev-rbd/json_output_empty.txt")
+    self.output_invalid = testutils.ReadTestData("bdev-rbd/output_invalid.txt")
+
+    self.volume_name = "d7ab910a-4933-4ffe-88d0-faf2ce31390a.rbd.disk0"
+
+  def test_ParseRbdShowmappedJson(self):
+    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedJson
+
+    self.assertEqual(parse_function(self.json_output_ok, self.volume_name),
+                     "/dev/rbd3")
+    self.assertEqual(parse_function(self.json_output_empty, self.volume_name),
+                     None)
+    self.assertEqual(parse_function(self.json_output_no_matches,
+                     self.volume_name), None)
+    self.assertRaises(errors.BlockDeviceError, parse_function,
+                      self.json_output_extra_matches, self.volume_name)
+    self.assertRaises(errors.BlockDeviceError, parse_function,
+                      self.output_invalid, self.volume_name)
+
+  def test_ParseRbdShowmappedPlain(self):
+    parse_function = bdev.RADOSBlockDevice._ParseRbdShowmappedPlain
+
+    self.assertEqual(parse_function(self.plain_output_new_ok,
+                     self.volume_name), "/dev/rbd3")
+    self.assertEqual(parse_function(self.plain_output_old_ok,
+                     self.volume_name), "/dev/rbd3")
+    self.assertEqual(parse_function(self.plain_output_new_empty,
+                     self.volume_name), None)
+    self.assertEqual(parse_function(self.plain_output_old_empty,
+                     self.volume_name), None)
+    self.assertEqual(parse_function(self.plain_output_new_no_matches,
+                     self.volume_name), None)
+    self.assertEqual(parse_function(self.plain_output_old_no_matches,
+                     self.volume_name), None)
+    self.assertRaises(errors.BlockDeviceError, parse_function,
+                      self.plain_output_new_extra_matches, self.volume_name)
+    self.assertRaises(errors.BlockDeviceError, parse_function,
+                      self.plain_output_old_extra_matches, self.volume_name)
+    self.assertRaises(errors.BlockDeviceError, parse_function,
+                      self.output_invalid, self.volume_name)
+
+class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
+  def testPaths(self):
+    paths = bdev._GetForbiddenFileStoragePaths()
+
+    for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
+      self.assertTrue(path in paths)
+
+    self.assertEqual(set(map(os.path.normpath, paths)), paths)
+
+  def test(self):
+    vfsp = bdev._ComputeWrongFileStoragePaths
+    self.assertEqual(vfsp([]), [])
+    self.assertEqual(vfsp(["/tmp"]), [])
+    self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
+    self.assertEqual(vfsp(["/bin"]), ["/bin"])
+    self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
+                     ["/usr/sbin/vim"])
+
+
+class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
+  def test(self):
+    tmpfile = self._CreateTempFile()
+
+    utils.WriteFile(tmpfile, data="""
+      /tmp
+      x/y///z/relative
+      # This is a test file
+      /srv/storage
+      /bin
+      /usr/local/lib32/
+      relative/path
+      """)
+
+    self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
+      "/bin",
+      "/usr/local/lib32",
+      "relative/path",
+      "x/y/z/relative",
+      ])
+
+
+class TestCheckFileStoragePathInternal(unittest.TestCase):
+  def testNonAbsolute(self):
+    for i in ["", "tmp", "foo/bar/baz"]:
+      self.assertRaises(errors.FileStoragePathError,
+                        bdev._CheckFileStoragePath, i, ["/tmp"])
+
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev._CheckFileStoragePath, "/tmp", ["tmp", "xyz"])
+
+  def testNoAllowed(self):
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev._CheckFileStoragePath, "/tmp", [])
+
+  def testNoAdditionalPathComponent(self):
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev._CheckFileStoragePath, "/tmp/foo", ["/tmp/foo"])
+
+  def testAllowed(self):
+    bdev._CheckFileStoragePath("/tmp/foo/a", ["/tmp/foo"])
+    bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
+
+
+class TestCheckFileStoragePath(testutils.GanetiTestCase):
+  def testNonExistantFile(self):
+    filename = "/tmp/this/file/does/not/exist"
+    assert not os.path.exists(filename)
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev.CheckFileStoragePath, "/bin/", _filename=filename)
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev.CheckFileStoragePath, "/srv/file-storage",
+                      _filename=filename)
+
+  def testAllowedPath(self):
+    tmpfile = self._CreateTempFile()
+
+    utils.WriteFile(tmpfile, data="""
+      /srv/storage
+      """)
+
+    bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
+
+    # No additional path component
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev.CheckFileStoragePath, "/srv/storage",
+                      _filename=tmpfile)
+
+    # Forbidden path
+    self.assertRaises(errors.FileStoragePathError,
+                      bdev.CheckFileStoragePath, "/usr/lib64/xyz",
+                      _filename=tmpfile)
+
+
+class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
+  def testDevNull(self):
+    self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
+
+  def testNonExistantFile(self):
+    filename = "/tmp/this/file/does/not/exist"
+    assert not os.path.exists(filename)
+    self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
+
+  def test(self):
+    tmpfile = self._CreateTempFile()
+
+    utils.WriteFile(tmpfile, data="""
+      # This is a test file
+      /tmp
+      /srv/storage
+      relative/path
+      """)
+
+    self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
+      "/tmp",
+      "/srv/storage",
+      "relative/path",
+      ])
+
+
+class TestExclusiveStoragePvs(unittest.TestCase):
+  """Test cases for functions dealing with LVM PV and exclusive storage"""
+  # Allowance for rounding
+  _EPS = 1e-4
+  _MARGIN = constants.PART_MARGIN + constants.PART_RESERVED + _EPS
+
+  @staticmethod
+  def _GenerateRandomPvInfo(rnd, name, vg):
+    # Granularity is .01 MiB
+    size = rnd.randint(1024 * 100, 10 * 1024 * 1024 * 100)
+    if rnd.choice([False, True]):
+      free = float(rnd.randint(0, size)) / 100.0
+    else:
+      free = float(size) / 100.0
+    size = float(size) / 100.0
+    attr = "a-"
+    return objects.LvmPvInfo(name=name, vg_name=vg, size=size, free=free,
+                             attributes=attr)
+
+  def testGetStdPvSize(self):
+    """Test cases for bdev.LogicalVolume._GetStdPvSize()"""
+    rnd = random.Random(9517)
+    for _ in range(0, 50):
+      # Identical volumes
+      pvi = self._GenerateRandomPvInfo(rnd, "disk", "myvg")
+      onesize = bdev.LogicalVolume._GetStdPvSize([pvi])
+      self.assertTrue(onesize <= pvi.size)
+      self.assertTrue(onesize > pvi.size * (1 - self._MARGIN))
+      for length in range(2, 10):
+        n_size = bdev.LogicalVolume._GetStdPvSize([pvi] * length)
+        self.assertEqual(onesize, n_size)
+
+      # Mixed volumes
+      for length in range(1, 10):
+        pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")
+                  for _ in range(0, length)]
+        std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
+        self.assertTrue(compat.all(std_size <= pvi.size for pvi in pvlist))
+        self.assertTrue(compat.any(std_size > pvi.size * (1 - self._MARGIN)
+                                   for pvi in pvlist))
+        pvlist.append(pvlist[0])
+        p1_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
+        self.assertEqual(std_size, p1_size)
+
+  def testComputeNumPvs(self):
+    """Test cases for bdev.LogicalVolume._ComputeNumPvs()"""
+    rnd = random.Random(8067)
+    for _ in range(0, 1000):
+      pvlist = [self._GenerateRandomPvInfo(rnd, "disk", "myvg")]
+      lv_size = float(rnd.randint(10 * 100, 1024 * 1024 * 100)) / 100.0
+      num_pv = bdev.LogicalVolume._ComputeNumPvs(lv_size, pvlist)
+      std_size = bdev.LogicalVolume._GetStdPvSize(pvlist)
+      self.assertTrue(num_pv >= 1)
+      self.assertTrue(num_pv * std_size >= lv_size)
+      self.assertTrue((num_pv - 1) * std_size < lv_size * (1 + self._EPS))
+
+  def testGetEmptyPvNames(self):
+    """Test cases for bdev.LogicalVolume._GetEmptyPvNames()"""
+    rnd = random.Random(21126)
+    for _ in range(0, 100):
+      num_pvs = rnd.randint(1, 20)
+      pvlist = [self._GenerateRandomPvInfo(rnd, "disk%d" % n, "myvg")
+                for n in range(0, num_pvs)]
+      for num_req in range(1, num_pvs + 2):
+        epvs = bdev.LogicalVolume._GetEmptyPvNames(pvlist, num_req)
+        epvs_set = compat.UniqueFrozenset(epvs)
+        if len(epvs) > 1:
+          self.assertEqual(len(epvs), len(epvs_set))
+        for pvi in pvlist:
+          if pvi.name in epvs_set:
+            self.assertEqual(pvi.size, pvi.free)
+          else:
+            # There should be no remaining empty PV when less than the
+            # requeste number of PVs has been returned
+            self.assertTrue(len(epvs) == num_req or pvi.free != pvi.size)
+
+
+class TestLogicalVolume(unittest.TestCase):
+  """Tests for bdev.LogicalVolume."""
+  def testParseLvInfoLine(self):
+    """Tests for LogicalVolume._ParseLvInfoLine."""
+    broken_lines = [
+      "  toomuch#-wi-ao#253#3#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#253#3#4096.00#/dev/abc(20)",
+      "  -wi-a#253#3#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#25.3#3#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#twenty#3#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#253#3.1#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#253#three#4096.00#2#/dev/abc(20)",
+      "  -wi-ao#253#3#four#2#/dev/abc(20)",
+      "  -wi-ao#253#3#4096..00#2#/dev/abc(20)",
+      "  -wi-ao#253#3#4096.00#2.0#/dev/abc(20)",
+      "  -wi-ao#253#3#4096.00#two#/dev/abc(20)",
+      ]
+    for broken in broken_lines:
+      self.assertRaises(errors.BlockDeviceError,
+                        bdev.LogicalVolume._ParseLvInfoLine, broken, "#")
+
+    # Examples of good lines from "lvs":
+    #  -wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0)
+    #  -wi-a-|253|4|4096.00|1|/dev/sdb(208)
+    true_out = [
+      ("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"]),
+      ("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"]),
+      ("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"]),
+      ("-wc-ao", 15, 18, 4096.00, 32, ["/dev/abc", "/dev/def", "/dev/ghi0"]),
+      ]
+    for exp in true_out:
+      for sep in "#;|":
+        pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(exp[-1]))
+        lvs_line = (sep.join(("  %s", "%d", "%d", "%.2f", "%d", "%s")) %
+                    (exp[0:-1] + (pvs,)))
+        parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep)
+        self.assertEqual(parsed, exp)
+
+  @staticmethod
+  def _FakeRunCmd(success, stdout):
+    if success:
+      exit_code = 0
+    else:
+      exit_code = 1
+    return lambda cmd: utils.RunResult(exit_code, None, stdout, "", cmd,
+                                       utils.process._TIMEOUT_NONE, 5)
+
+  def testGetLvInfo(self):
+    """Tests for LogicalVolume._GetLvInfo."""
+    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
+                      "fake_path",
+                      _run_cmd=self._FakeRunCmd(False, "Fake error msg"))
+    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
+                      "fake_path", _run_cmd=self._FakeRunCmd(True, ""))
+    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
+                      "fake_path", _run_cmd=self._FakeRunCmd(True, "BadStdOut"))
+    good_line = "  -wi-ao|253|3|4096.00|2|/dev/abc(20)"
+    fake_cmd = self._FakeRunCmd(True, good_line)
+    good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
+    # If the same line is repeated, the result should be the same
+    for lines in [
+      [good_line] * 2,
+      [good_line] * 3,
+      ]:
+      fake_cmd = self._FakeRunCmd(True, "\n".join(lines))
+      same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd)
+      self.assertEqual(same_res, good_res)
+
+    # Complex multi-line examples
+    one_line = "  -wi-ao|253|3|4096.00|2|/dev/sda(20),/dev/sdb(50),/dev/sdc(0)"
+    fake_cmd = self._FakeRunCmd(True, one_line)
+    one_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
+    # These should give the same results
+    for multi_lines in [
+      ("  -wi-ao|253|3|4096.00|2|/dev/sda(30),/dev/sdb(50)\n"
+       "  -wi-ao|253|3|4096.00|2|/dev/sdb(200),/dev/sdc(300)"),
+      ("  -wi-ao|253|3|4096.00|2|/dev/sda(0)\n"
+       "  -wi-ao|253|3|4096.00|2|/dev/sdb(20)\n"
+       "  -wi-ao|253|3|4096.00|2|/dev/sdc(30)"),
+      ("  -wi-ao|253|3|4096.00|2|/dev/sda(20)\n"
+       "  -wi-ao|253|3|4096.00|2|/dev/sdb(50),/dev/sdc(0)"),
+      ]:
+      fake_cmd = self._FakeRunCmd(True, multi_lines)
+      multi_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
+      self.assertEqual(multi_res, one_res)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
similarity index 94%
rename from test/py/ganeti.storage_unittest.py
rename to test/py/ganeti.storage.container_unittest.py
index 0c62abc..e8811cc 100755 (executable)
@@ -19,7 +19,7 @@
 # 02110-1301, USA.
 
 
-"""Script for testing ganeti.storage"""
+"""Script for testing ganeti.storage.container"""
 
 import re
 import unittest
@@ -29,15 +29,15 @@ from ganeti import constants
 from ganeti import utils
 from ganeti import compat
 from ganeti import errors
-from ganeti import storage
+from ganeti.storage import container
 
 import testutils
 
 
 class TestVGReduce(testutils.GanetiTestCase):
   VGNAME = "xenvg"
-  LIST_CMD = storage.LvmVgStorage.LIST_COMMAND
-  VGREDUCE_CMD = storage.LvmVgStorage.VGREDUCE_COMMAND
+  LIST_CMD = container.LvmVgStorage.LIST_COMMAND
+  VGREDUCE_CMD = container.LvmVgStorage.VGREDUCE_COMMAND
 
   def _runCmd(self, cmd, **kwargs):
     if not self.run_history:
@@ -47,7 +47,7 @@ class TestVGReduce(testutils.GanetiTestCase):
     return result
 
   def testOldVersion(self):
-    lvmvg = storage.LvmVgStorage()
+    lvmvg = container.LvmVgStorage()
     stdout = testutils.ReadTestData("vgreduce-removemissing-2.02.02.txt")
     vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.02.txt")
     self.run_history = [
@@ -70,7 +70,7 @@ class TestVGReduce(testutils.GanetiTestCase):
       self.assertEqual(self.run_history, [])
 
   def testNewVersion(self):
-    lvmvg = storage.LvmVgStorage()
+    lvmvg = container.LvmVgStorage()
     stdout1 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-fail.txt")
     stdout2 = testutils.ReadTestData("vgreduce-removemissing-2.02.66-ok.txt")
     vgs_fail = testutils.ReadTestData("vgs-missing-pvs-2.02.66.txt")
diff --git a/test/py/ganeti.storage.drbd_unittest.py b/test/py/ganeti.storage.drbd_unittest.py
new file mode 100755 (executable)
index 0000000..f77bbdc
--- /dev/null
@@ -0,0 +1,430 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2006, 2007, 2010, 2012, 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the drbd module"""
+
+
+import os
+
+from ganeti import constants
+from ganeti import errors
+from ganeti.storage import drbd
+from ganeti.storage import drbd_info
+from ganeti.storage import drbd_cmdgen
+
+import testutils
+
+
+class TestDRBD8(testutils.GanetiTestCase):
+  def testGetVersion(self):
+    data = [
+      "version: 8.0.0 (api:76/proto:80)",
+      "version: 8.0.12 (api:76/proto:86-91)",
+      "version: 8.2.7 (api:88/proto:0-100)",
+      "version: 8.3.7.49 (api:188/proto:13-191)",
+    ]
+    result = [
+      {
+        "k_major": 8,
+        "k_minor": 0,
+        "k_point": 0,
+        "api": 76,
+        "proto": 80,
+      },
+      {
+        "k_major": 8,
+        "k_minor": 0,
+        "k_point": 12,
+        "api": 76,
+        "proto": 86,
+        "proto2": "91",
+      },
+      {
+        "k_major": 8,
+        "k_minor": 2,
+        "k_point": 7,
+        "api": 88,
+        "proto": 0,
+        "proto2": "100",
+      },
+      {
+        "k_major": 8,
+        "k_minor": 3,
+        "k_point": 7,
+        "k_fix": "49",
+        "api": 188,
+        "proto": 13,
+        "proto2": "191",
+      }
+    ]
+    for d, r in zip(data, result):
+      info = drbd.DRBD8Info.CreateFromLines([d])
+      self.assertEqual(info.GetVersion(), r)
+      self.assertEqual(info.GetVersionString(), d.replace("version: ", ""))
+
+
+class TestDRBD8Runner(testutils.GanetiTestCase):
+  """Testing case for drbd.DRBD8Dev"""
+
+  @staticmethod
+  def _has_disk(data, dname, mname):
+    """Check local disk corectness"""
+    retval = (
+      "local_dev" in data and
+      data["local_dev"] == dname and
+      "meta_dev" in data and
+      data["meta_dev"] == mname and
+      "meta_index" in data and
+      data["meta_index"] == 0
+      )
+    return retval
+
+  @staticmethod
+  def _has_net(data, local, remote):
+    """Check network connection parameters"""
+    retval = (
+      "local_addr" in data and
+      data["local_addr"] == local and
+      "remote_addr" in data and
+      data["remote_addr"] == remote
+      )
+    return retval
+
+  def testParser83Creation(self):
+    """Test drbdsetup show parser creation"""
+    drbd_info.DRBD83ShowInfo._GetShowParser()
+
+  def testParser84Creation(self):
+    """Test drbdsetup show parser creation"""
+    drbd_info.DRBD84ShowInfo._GetShowParser()
+
+  def testParser80(self):
+    """Test drbdsetup show parser for disk and network version 8.0"""
+    data = testutils.ReadTestData("bdev-drbd-8.0.txt")
+    result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
+                                  ("192.0.2.2", 11000)),
+                    "Wrong network info (8.0.x)")
+
+  def testParser83(self):
+    """Test drbdsetup show parser for disk and network version 8.3"""
+    data = testutils.ReadTestData("bdev-drbd-8.3.txt")
+    result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
+                                  ("192.0.2.2", 11000)),
+                    "Wrong network info (8.3.x)")
+
+  def testParser84(self):
+    """Test drbdsetup show parser for disk and network version 8.4"""
+    data = testutils.ReadTestData("bdev-drbd-8.4.txt")
+    result = drbd_info.DRBD84ShowInfo.GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(self._has_net(result, ("192.0.2.1", 11000),
+                                  ("192.0.2.2", 11000)),
+                    "Wrong network info (8.4.x)")
+
+  def testParserNetIP4(self):
+    """Test drbdsetup show parser for IPv4 network"""
+    data = testutils.ReadTestData("bdev-drbd-net-ip4.txt")
+    result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
+    self.failUnless(("local_dev" not in result and
+                     "meta_dev" not in result and
+                     "meta_index" not in result),
+                    "Should not find local disk info")
+    self.failUnless(self._has_net(result, ("192.0.2.1", 11002),
+                                  ("192.0.2.2", 11002)),
+                    "Wrong network info (IPv4)")
+
+  def testParserNetIP6(self):
+    """Test drbdsetup show parser for IPv6 network"""
+    data = testutils.ReadTestData("bdev-drbd-net-ip6.txt")
+    result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
+    self.failUnless(("local_dev" not in result and
+                     "meta_dev" not in result and
+                     "meta_index" not in result),
+                    "Should not find local disk info")
+    self.failUnless(self._has_net(result, ("2001:db8:65::1", 11048),
+                                  ("2001:db8:66::1", 11048)),
+                    "Wrong network info (IPv6)")
+
+  def testParserDisk(self):
+    """Test drbdsetup show parser for disk"""
+    data = testutils.ReadTestData("bdev-drbd-disk.txt")
+    result = drbd_info.DRBD83ShowInfo.GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(("local_addr" not in result and
+                     "remote_addr" not in result),
+                    "Should not find network info")
+
+  def testBarriersOptions(self):
+    """Test class method that generates drbdsetup options for disk barriers"""
+    # Tests that should fail because of wrong version/options combinations
+    should_fail = [
+      (8, 0, 12, "bfd", True),
+      (8, 0, 12, "fd", False),
+      (8, 0, 12, "b", True),
+      (8, 2, 7, "bfd", True),
+      (8, 2, 7, "b", True)
+    ]
+
+    for vmaj, vmin, vrel, opts, meta in should_fail:
+      self.assertRaises(errors.BlockDeviceError,
+                        drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
+                        vmaj, vmin, vrel, opts, meta)
+
+    # get the valid options from the frozenset(frozenset()) in constants.
+    valid_options = [list(x)[0] for x in constants.DRBD_VALID_BARRIER_OPT]
+
+    # Versions that do not support anything
+    for vmaj, vmin, vrel in ((8, 0, 0), (8, 0, 11), (8, 2, 6)):
+      for opts in valid_options:
+        self.assertRaises(
+          errors.BlockDeviceError,
+          drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
+          vmaj, vmin, vrel, opts, True)
+
+    # Versions with partial support (testing only options that are supported)
+    tests = [
+      (8, 0, 12, "n", False, []),
+      (8, 0, 12, "n", True, ["--no-md-flushes"]),
+      (8, 2, 7, "n", False, []),
+      (8, 2, 7, "fd", False, ["--no-disk-flushes", "--no-disk-drain"]),
+      (8, 0, 12, "n", True, ["--no-md-flushes"]),
+      ]
+
+    # Versions that support everything
+    for vmaj, vmin, vrel in ((8, 3, 0), (8, 3, 12)):
+      tests.append((vmaj, vmin, vrel, "bfd", True,
+                    ["--no-disk-barrier", "--no-disk-drain",
+                     "--no-disk-flushes", "--no-md-flushes"]))
+      tests.append((vmaj, vmin, vrel, "n", False, []))
+      tests.append((vmaj, vmin, vrel, "b", True,
+                    ["--no-disk-barrier", "--no-md-flushes"]))
+      tests.append((vmaj, vmin, vrel, "fd", False,
+                    ["--no-disk-flushes", "--no-disk-drain"]))
+      tests.append((vmaj, vmin, vrel, "n", True, ["--no-md-flushes"]))
+
+    # Test execution
+    for test in tests:
+      vmaj, vmin, vrel, disabled_barriers, disable_meta_flush, expected = test
+      args = \
+        drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs(
+          vmaj, vmin, vrel,
+          disabled_barriers,
+          disable_meta_flush)
+      self.failUnless(set(args) == set(expected),
+                      "For test %s, got wrong results %s" % (test, args))
+
+    # Unsupported or invalid versions
+    for vmaj, vmin, vrel in ((0, 7, 25), (9, 0, 0), (7, 0, 0), (8, 4, 0)):
+      self.assertRaises(errors.BlockDeviceError,
+                        drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
+                        vmaj, vmin, vrel, "n", True)
+
+    # Invalid options
+    for option in ("", "c", "whatever", "nbdfc", "nf"):
+      self.assertRaises(errors.BlockDeviceError,
+                        drbd_cmdgen.DRBD83CmdGenerator._ComputeDiskBarrierArgs,
+                        8, 3, 11, option, True)
+
+
+class TestDRBD8Status(testutils.GanetiTestCase):
+  """Testing case for DRBD8Dev /proc status"""
+
+  def setUp(self):
+    """Read in txt data"""
+    testutils.GanetiTestCase.setUp(self)
+    proc_data = testutils.TestDataFilename("proc_drbd8.txt")
+    proc80e_data = testutils.TestDataFilename("proc_drbd80-emptyline.txt")
+    proc83_data = testutils.TestDataFilename("proc_drbd83.txt")
+    proc83_sync_data = testutils.TestDataFilename("proc_drbd83_sync.txt")
+    proc83_sync_krnl_data = \
+      testutils.TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
+    proc84_data = testutils.TestDataFilename("proc_drbd84.txt")
+    proc84_sync_data = testutils.TestDataFilename("proc_drbd84_sync.txt")
+
+    self.proc80ev_data = \
+      testutils.TestDataFilename("proc_drbd80-emptyversion.txt")
+
+    self.drbd_info = drbd.DRBD8Info.CreateFromFile(filename=proc_data)
+    self.drbd_info80e = drbd.DRBD8Info.CreateFromFile(filename=proc80e_data)
+    self.drbd_info83 = drbd.DRBD8Info.CreateFromFile(filename=proc83_data)
+    self.drbd_info83_sync = \
+      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_data)
+    self.drbd_info83_sync_krnl = \
+      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_krnl_data)
+    self.drbd_info84 = drbd.DRBD8Info.CreateFromFile(filename=proc84_data)
+    self.drbd_info84_sync = \
+      drbd.DRBD8Info.CreateFromFile(filename=proc84_sync_data)
+
+  def testIOErrors(self):
+    """Test handling of errors while reading the proc file."""
+    temp_file = self._CreateTempFile()
+    os.unlink(temp_file)
+    self.failUnlessRaises(errors.BlockDeviceError,
+                          drbd.DRBD8Info.CreateFromFile, filename=temp_file)
+
+  def testHelper(self):
+    """Test reading usermode_helper in /sys."""
+    sys_drbd_helper = testutils.TestDataFilename("sys_drbd_usermode_helper.txt")
+    drbd_helper = drbd.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper)
+    self.failUnlessEqual(drbd_helper, "/bin/true")
+
+  def testHelperIOErrors(self):
+    """Test handling of errors while reading usermode_helper in /sys."""
+    temp_file = self._CreateTempFile()
+    os.unlink(temp_file)
+    self.failUnlessRaises(errors.BlockDeviceError,
+                          drbd.DRBD8.GetUsermodeHelper, filename=temp_file)
+
+  def testMinorNotFound(self):
+    """Test not-found-minor in /proc"""
+    self.failUnless(not self.drbd_info.HasMinorStatus(9))
+    self.failUnless(not self.drbd_info83.HasMinorStatus(9))
+    self.failUnless(not self.drbd_info80e.HasMinorStatus(3))
+
+  def testLineNotMatch(self):
+    """Test wrong line passed to drbd_info.DRBD8Status"""
+    self.assertRaises(errors.BlockDeviceError, drbd_info.DRBD8Status, "foo")
+
+  def testMinor0(self):
+    """Test connected, primary device"""
+    for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
+      stats = info.GetMinorStatus(0)
+      self.failUnless(stats.is_in_use)
+      self.failUnless(stats.is_connected and stats.is_primary and
+                      stats.peer_secondary and stats.is_disk_uptodate)
+
+  def testMinor1(self):
+    """Test connected, secondary device"""
+    for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
+      stats = info.GetMinorStatus(1)
+      self.failUnless(stats.is_in_use)
+      self.failUnless(stats.is_connected and stats.is_secondary and
+                      stats.peer_primary and stats.is_disk_uptodate)
+
+  def testMinor2(self):
+    """Test unconfigured device"""
+    for info in [self.drbd_info, self.drbd_info83,
+                 self.drbd_info80e, self.drbd_info84]:
+      stats = info.GetMinorStatus(2)
+      self.failIf(stats.is_in_use)
+
+  def testMinor4(self):
+    """Test WFconn device"""
+    for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
+      stats = info.GetMinorStatus(4)
+      self.failUnless(stats.is_in_use)
+      self.failUnless(stats.is_wfconn and stats.is_primary and
+                      stats.rrole == "Unknown" and
+                      stats.is_disk_uptodate)
+
+  def testMinor6(self):
+    """Test diskless device"""
+    for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
+      stats = info.GetMinorStatus(6)
+      self.failUnless(stats.is_in_use)
+      self.failUnless(stats.is_connected and stats.is_secondary and
+                      stats.peer_primary and stats.is_diskless)
+
+  def testMinor8(self):
+    """Test standalone device"""
+    for info in [self.drbd_info, self.drbd_info83, self.drbd_info84]:
+      stats = info.GetMinorStatus(8)
+      self.failUnless(stats.is_in_use)
+      self.failUnless(stats.is_standalone and
+                      stats.rrole == "Unknown" and
+                      stats.is_disk_uptodate)
+
+  def testDRBD83SyncFine(self):
+    stats = self.drbd_info83_sync.GetMinorStatus(3)
+    self.failUnless(stats.is_in_resync)
+    self.assertAlmostEqual(stats.sync_percent, 34.9)
+
+  def testDRBD83SyncBroken(self):
+    stats = self.drbd_info83_sync_krnl.GetMinorStatus(3)
+    self.failUnless(stats.is_in_resync)
+    self.assertAlmostEqual(stats.sync_percent, 2.4)
+
+  def testDRBD84Sync(self):
+    stats = self.drbd_info84_sync.GetMinorStatus(5)
+    self.failUnless(stats.is_in_resync)
+    self.assertAlmostEqual(stats.sync_percent, 68.5)
+
+  def testDRBDEmptyVersion(self):
+    self.assertRaises(errors.BlockDeviceError,
+                      drbd.DRBD8Info.CreateFromFile,
+                      filename=self.proc80ev_data)
+
+
+class TestDRBD8Construction(testutils.GanetiTestCase):
+  def setUp(self):
+    """Read in txt data"""
+    testutils.GanetiTestCase.setUp(self)
+    self.proc80_info = \
+      drbd_info.DRBD8Info.CreateFromFile(
+        filename=testutils.TestDataFilename("proc_drbd8.txt"))
+    self.proc83_info = \
+      drbd_info.DRBD8Info.CreateFromFile(
+        filename=testutils.TestDataFilename("proc_drbd83.txt"))
+    self.proc84_info = \
+      drbd_info.DRBD8Info.CreateFromFile(
+        filename=testutils.TestDataFilename("proc_drbd84.txt"))
+
+    self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0, "secret")
+
+  @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
+  def testConstructionWith80Data(self, mock_create_from_file):
+    mock_create_from_file.return_value = self.proc80_info
+
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
+    self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
+
+  @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
+  def testConstructionWith83Data(self, mock_create_from_file):
+    mock_create_from_file.return_value = self.proc83_info
+
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo)
+    self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator))
+
+  @testutils.patch_object(drbd.DRBD8, "GetProcInfo")
+  def testConstructionWith84Data(self, mock_create_from_file):
+    mock_create_from_file.return_value = self.proc84_info
+
+    inst = drbd.DRBD8Dev(self.test_unique_id, [], 123, {})
+    self.assertEqual(inst._show_info_cls, drbd_info.DRBD84ShowInfo)
+    self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD84CmdGenerator))
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.storage.filestorage_unittest.py b/test/py/ganeti.storage.filestorage_unittest.py
new file mode 100755 (executable)
index 0000000..f229b56
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the ganeti.storage.file module"""
+
+
+import unittest
+
+from ganeti import errors
+from ganeti.storage import filestorage
+
+import testutils
+
+
+class TestFileStorageSpaceInfo(unittest.TestCase):
+
+  def testSpaceInfoPathInvalid(self):
+    """Tests that an error is raised when the given path is not existing.
+
+    """
+    self.assertRaises(errors.CommandError, filestorage.GetFileStorageSpaceInfo,
+                      "/path/does/not/exist/")
+
+  def testSpaceInfoPathValid(self):
+    """Smoke test run on a directory that exists for sure.
+
+    """
+    info = filestorage.GetFileStorageSpaceInfo("/")
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/py/ganeti.utils.storage_unittest.py b/test/py/ganeti.utils.storage_unittest.py
new file mode 100755 (executable)
index 0000000..b8f031d
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2013 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the ganeti.utils.storage module"""
+
+import mock
+
+import unittest
+
+from ganeti import constants
+from ganeti import errors
+from ganeti import objects
+from ganeti import pathutils
+from ganeti.utils import storage
+
+import testutils
+
+
+class TestGetStorageUnitForDiskTemplate(unittest.TestCase):
+
+  def setUp(self):
+    self._default_vg_name = "some_vg_name"
+    self._cfg = mock.Mock()
+    self._cfg.GetVGName = mock.Mock(return_value=self._default_vg_name)
+
+  def testGetDefaultStorageUnitForDiskTemplateLvm(self):
+    for disk_template in [constants.DT_DRBD8, constants.DT_PLAIN]:
+      (storage_type, storage_key) = \
+          storage._GetDefaultStorageUnitForDiskTemplate(self._cfg,
+                                                        disk_template)
+      self.assertEqual(storage_type, constants.ST_LVM_VG)
+      self.assertEqual(storage_key, self._default_vg_name)
+
+  def testGetDefaultStorageUnitForDiskTemplateFile(self):
+    (storage_type, storage_key) = \
+        storage._GetDefaultStorageUnitForDiskTemplate(self._cfg,
+                                                      constants.DT_FILE)
+    self.assertEqual(storage_type, constants.ST_FILE)
+    self.assertEqual(storage_key, pathutils.DEFAULT_FILE_STORAGE_DIR)
+
+  def testGetDefaultStorageUnitForDiskTemplateSharedFile(self):
+    (storage_type, storage_key) = \
+        storage._GetDefaultStorageUnitForDiskTemplate(self._cfg,
+                                                      constants.DT_SHARED_FILE)
+    self.assertEqual(storage_type, constants.ST_FILE)
+    self.assertEqual(storage_key, pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR)
+
+  def testGetDefaultStorageUnitForDiskTemplateDiskless(self):
+    (storage_type, storage_key) = \
+        storage._GetDefaultStorageUnitForDiskTemplate(self._cfg,
+                                                      constants.DT_DISKLESS)
+    self.assertEqual(storage_type, constants.ST_DISKLESS)
+    self.assertEqual(storage_key, None)
+
+  def testGetDefaultStorageUnitForSpindles(self):
+    (storage_type, storage_key) = \
+        storage._GetDefaultStorageUnitForSpindles(self._cfg)
+    self.assertEqual(storage_type, constants.ST_LVM_PV)
+    self.assertEqual(storage_key, self._default_vg_name)
+
+
+class TestGetStorageUnitsOfCluster(unittest.TestCase):
+
+  def setUp(self):
+    storage._GetDefaultStorageUnitForDiskTemplate = \
+        mock.Mock(return_value=("foo", "bar"))
+
+    self._cluster_cfg = objects.Cluster()
+    self._enabled_disk_templates = \
+        [constants.DT_DRBD8, constants.DT_PLAIN, constants.DT_FILE,
+         constants.DT_SHARED_FILE]
+    self._cluster_cfg.enabled_disk_templates = \
+        self._enabled_disk_templates
+    self._cfg = mock.Mock()
+    self._cfg.GetClusterInfo = mock.Mock(return_value=self._cluster_cfg)
+    self._cfg.GetVGName = mock.Mock(return_value="some_vg_name")
+
+  def testGetStorageUnitsOfCluster(self):
+    storage_units = storage.GetStorageUnitsOfCluster(self._cfg)
+    self.assertEqual(len(storage_units), len(self._enabled_disk_templates))
+
+  def testGetStorageUnitsOfClusterWithSpindles(self):
+    storage_units = storage.GetStorageUnitsOfCluster(
+        self._cfg, include_spindles=True)
+    self.assertEqual(len(storage_units), len(self._enabled_disk_templates) + 1)
+    self.assertTrue(constants.ST_LVM_PV in [st for (st, sk) in storage_units])
+
+
+class TestLookupSpaceInfoByStorageType(unittest.TestCase):
+
+  def setUp(self):
+    self._space_info = [
+        {"type": st, "name": st + "_key", "storage_size": 0, "storage_free": 0}
+        for st in constants.VALID_STORAGE_TYPES]
+
+  def testValidLookup(self):
+    query_type = constants.ST_LVM_PV
+    result = storage.LookupSpaceInfoByStorageType(self._space_info, query_type)
+    self.assertEqual(query_type, result["type"])
+
+  def testNotInList(self):
+    result = storage.LookupSpaceInfoByStorageType(self._space_info,
+                                                  "non_existing_type")
+    self.assertEqual(None, result)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
index 82e286d..6d2312f 100644 (file)
@@ -53,11 +53,23 @@ class FakeConfig:
     return "test.cluster"
 
   def GetMasterNode(self):
+    return "a"
+
+  def GetMasterNodeName(self):
     return netutils.Hostname.GetSysName()
 
   def GetDefaultIAllocator(Self):
     return "testallocator"
 
+  def GetNodeName(self, node_uuid):
+    if node_uuid in self.GetNodeList():
+      return "node_%s.example.com" % (node_uuid,)
+    else:
+      return None
+
+  def GetNodeNames(self, node_uuids):
+    return map(self.GetNodeName, node_uuids)
+
 
 class FakeProc:
   """Fake processor object"""
index 5459393..8249847 100644 (file)
@@ -209,6 +209,21 @@ class GanetiTestCase(unittest.TestCase):
     return fname
 
 
+def patch_object(*args, **kwargs):
+  """Unified patch_object for various versions of Python Mock.
+
+  Different Python Mock versions provide incompatible versions of patching an
+  object. More recent versions use _patch_object, older ones used patch_object.
+  This function unifies the different variations.
+
+  """
+  import mock
+  try:
+    return mock._patch_object(*args, **kwargs)
+  except AttributeError:
+    return mock.patch_object(*args, **kwargs)
+
+
 def UnifyValueType(data):
   """Converts all tuples into lists.
 
index fe73899..554877f 100755 (executable)
@@ -52,11 +52,11 @@ args = None
 #: Target major version we will upgrade to
 TARGET_MAJOR = 2
 #: Target minor version we will upgrade to
-TARGET_MINOR = 8
+TARGET_MINOR = 9
 #: Target major version for downgrade
 DOWNGRADE_MAJOR = 2
 #: Target minor version for downgrade
-DOWNGRADE_MINOR = 7
+DOWNGRADE_MINOR = 8
 
 
 class Error(Exception):
@@ -155,12 +155,31 @@ def UpgradeGroups(config_data):
       UpgradeIPolicy(ipolicy, cl_ipolicy, True)
 
 
+def GetExclusiveStorageValue(config_data):
+  """Return a conservative value of the exclusive_storage flag.
+
+  Return C{True} if the cluster or at least a nodegroup have the flag set.
+
+  """
+  ret = False
+  cluster = config_data["cluster"]
+  ndparams = cluster.get("ndparams")
+  if ndparams is not None and ndparams.get("exclusive_storage"):
+    ret = True
+  for group in config_data["nodegroups"].values():
+    ndparams = group.get("ndparams")
+    if ndparams is not None and ndparams.get("exclusive_storage"):
+      ret = True
+  return ret
+
+
 def UpgradeInstances(config_data):
   network2uuid = dict((n["name"], n["uuid"])
                       for n in config_data["networks"].values())
   if "instances" not in config_data:
     raise Error("Can't find the 'instances' key in the configuration!")
 
+  missing_spindles = False
   for instance, iobj in config_data["instances"].items():
     for nic in iobj["nics"]:
       name = nic.get("network", None)
@@ -182,6 +201,17 @@ def UpgradeInstances(config_data):
                         " from '%s' to '%s'",
                         instance, idx, current, expected)
         dobj["iv_name"] = expected
+      if not "spindles" in dobj:
+        missing_spindles = True
+
+  if GetExclusiveStorageValue(config_data) and missing_spindles:
+    # We cannot be sure that the instances that are missing spindles have
+    # exclusive storage enabled (the check would be more complicated), so we
+    # give a noncommittal message
+    logging.warning("Some instance disks could be needing to update the"
+                    " spindles parameter; you can check by running"
+                    " 'gnt-cluster verify', and fix any problem with"
+                    " 'gnt-cluster repair-disk-sizes'")
 
 
 def UpgradeRapiUsers():
@@ -248,6 +278,64 @@ def UpgradeFileStoragePaths(config_data):
                     backup=True)
 
 
+def GetNewNodeIndex(nodes_by_old_key, old_key, new_key_field):
+  if old_key not in nodes_by_old_key:
+    logging.warning("Can't find node '%s' in configuration, assuming that it's"
+                    " already up-to-date", old_key)
+    return old_key
+  return nodes_by_old_key[old_key][new_key_field]
+
+
+def ChangeNodeIndices(config_data, old_key_field, new_key_field):
+  def ChangeDiskNodeIndices(disk):
+    if disk["dev_type"] in constants.LDS_DRBD:
+      for i in range(0, 2):
+        disk["logical_id"][i] = GetNewNodeIndex(nodes_by_old_key,
+                                                disk["logical_id"][i],
+                                                new_key_field)
+    if "children" in disk:
+      for child in disk["children"]:
+        ChangeDiskNodeIndices(child)
+
+  nodes_by_old_key = {}
+  nodes_by_new_key = {}
+  for (_, node) in config_data["nodes"].items():
+    nodes_by_old_key[node[old_key_field]] = node
+    nodes_by_new_key[node[new_key_field]] = node
+
+  config_data["nodes"] = nodes_by_new_key
+
+  cluster = config_data["cluster"]
+  cluster["master_node"] = GetNewNodeIndex(nodes_by_old_key,
+                                           cluster["master_node"],
+                                           new_key_field)
+
+  for inst in config_data["instances"].values():
+    inst["primary_node"] = GetNewNodeIndex(nodes_by_old_key,
+                                           inst["primary_node"],
+                                           new_key_field)
+    for disk in inst["disks"]:
+      ChangeDiskNodeIndices(disk)
+
+
+def ChangeInstanceIndices(config_data, old_key_field, new_key_field):
+  insts_by_old_key = {}
+  insts_by_new_key = {}
+  for (_, inst) in config_data["instances"].items():
+    insts_by_old_key[inst[old_key_field]] = inst
+    insts_by_new_key[inst[new_key_field]] = inst
+
+  config_data["instances"] = insts_by_new_key
+
+
+def UpgradeNodeIndices(config_data):
+  ChangeNodeIndices(config_data, "name", "uuid")
+
+
+def UpgradeInstanceIndices(config_data):
+  ChangeInstanceIndices(config_data, "name", "uuid")
+
+
 def UpgradeAll(config_data):
   config_data["version"] = constants.BuildVersion(TARGET_MAJOR,
                                                   TARGET_MINOR, 0)
@@ -258,64 +346,35 @@ def UpgradeAll(config_data):
   UpgradeCluster(config_data)
   UpgradeGroups(config_data)
   UpgradeInstances(config_data)
+  UpgradeNodeIndices(config_data)
+  UpgradeInstanceIndices(config_data)
 
 
-def DowngradeIPolicy(ipolicy, owner):
-  # Downgrade IPolicy to 2.7 (stable)
-  minmax_keys = ["min", "max"]
-  specs_is_split = any((k in ipolicy) for k in minmax_keys)
-  if not specs_is_split:
-    if "minmax" in ipolicy:
-      if type(ipolicy["minmax"]) is not list:
-        raise Error("Invalid minmax type in %s ipolicy: %s" %
-                    (owner, type(ipolicy["minmax"])))
-      if len(ipolicy["minmax"]) > 1:
-        logging.warning("Discarding some limit specs values from %s policy",
-                        owner)
-      minmax = ipolicy["minmax"][0]
-      del ipolicy["minmax"]
-    else:
-      minmax = {}
-    for key in minmax_keys:
-      spec = minmax.get(key, {})
-      ipolicy[key] = spec
-    if "std" not in ipolicy:
-      ipolicy["std"] = {}
-
-
-def DowngradeGroups(config_data):
-  for group in config_data["nodegroups"].values():
-    ipolicy = group.get("ipolicy", None)
-    if ipolicy is not None:
-      DowngradeIPolicy(ipolicy, "group \"%s\"" % group.get("name"))
-
+def DowngradeDisks(disks, owner):
+  for disk in disks:
+    # Remove spindles to downgrade to 2.8
+    if "spindles" in disk:
+      logging.warning("Removing spindles (value=%s) from disk %s (%s) of"
+                      " instance %s",
+                      disk["spindles"], disk["iv_name"], disk["uuid"], owner)
+      del disk["spindles"]
 
-def DowngradeEnabledTemplates(cluster):
-  # Remove enabled disk templates to downgrade to 2.7
-  edt_key = "enabled_disk_templates"
-  if edt_key in cluster:
-    logging.warning("Removing cluster's enabled disk templates; value = %s",
-                    utils.CommaJoin(cluster[edt_key]))
-    del cluster[edt_key]
 
+def DowngradeInstances(config_data):
+  if "instances" not in config_data:
+    raise Error("Cannot find the 'instances' key in the configuration!")
+  for (iname, iobj) in config_data["instances"].items():
+    if "disks" not in iobj:
+      raise Error("Cannot find 'disks' key for instance %s" % iname)
+    DowngradeDisks(iobj["disks"], iname)
 
-def DowngradeCluster(config_data):
-  cluster = config_data.get("cluster", None)
-  if cluster is None:
-    raise Error("Cannot find cluster")
-  DowngradeEnabledTemplates(cluster)
-  ipolicy = cluster.get("ipolicy", None)
-  if ipolicy:
-    DowngradeIPolicy(ipolicy, "cluster")
 
+def DowngradeNodeIndices(config_data):
+  ChangeNodeIndices(config_data, "uuid", "name")
 
-def DowngradeInstances(config_data):
-  if "instances" not in config_data:
-    raise Error("Can't find the 'instances' key in the configuration!")
 
-  for _, iobj in config_data["instances"].items():
-    if "disks_active" in iobj:
-      del iobj["disks_active"]
+def DowngradeInstanceIndices(config_data):
+  ChangeInstanceIndices(config_data, "uuid", "name")
 
 
 def DowngradeAll(config_data):
@@ -323,9 +382,9 @@ def DowngradeAll(config_data):
   # it can be removed when updating to the next version.
   config_data["version"] = constants.BuildVersion(DOWNGRADE_MAJOR,
                                                   DOWNGRADE_MINOR, 0)
-  DowngradeCluster(config_data)
-  DowngradeGroups(config_data)
   DowngradeInstances(config_data)
+  DowngradeNodeIndices(config_data)
+  DowngradeInstanceIndices(config_data)
 
 
 def main():
@@ -446,8 +505,8 @@ def main():
                    config_minor, config_revision))
     DowngradeAll(config_data)
 
-  # Upgrade from 2.{0..7} to 2.8
-  elif config_major == 2 and config_minor in range(0, 9):
+  # Upgrade from 2.{0..7} to 2.9
+  elif config_major == 2 and config_minor in range(0, 10):
     if config_revision != 0:
       logging.warning("Config revision is %s, not 0", config_revision)
     UpgradeAll(config_data)
index b75e412..3ea7e8a 100755 (executable)
@@ -486,11 +486,17 @@ class MoveDestExecutor(object):
     """
     disk_template = instance["disk_template"]
 
-    disks = [{
-      constants.IDISK_SIZE: i["size"],
-      constants.IDISK_MODE: i["mode"],
-      constants.IDISK_NAME: str(i.get("name")),
-      } for i in instance["disks"]]
+    disks = []
+    for idisk in instance["disks"]:
+      odisk = {
+        constants.IDISK_SIZE: idisk["size"],
+        constants.IDISK_MODE: idisk["mode"],
+        constants.IDISK_NAME: str(idisk.get("name")),
+        }
+      spindles = idisk.get("spindles")
+      if spindles is not None:
+        odisk[constants.IDISK_SPINDLES] = spindles
+      disks.append(odisk)
 
     nics = [{
       constants.INIC_IP: ip,
index ad918bd..d9cf403 100644 (file)
@@ -290,12 +290,10 @@ EOF
 
 Initialize cluster:
   cd $rootdir && node1/cmd gnt-cluster init --no-etc-hosts \\
-    --no-ssh-init --no-lvm-storage --no-drbd-storage $cluster_name
-
-Change cluster settings:
-  cd $rootdir && node1/cmd gnt-cluster modify \\
-    --enabled-hypervisors=fake --specs-nic-count=min=0 \\
-    --specs-disk-size=min=0 --specs-disk-count=min=0
+    --no-ssh-init --master-netdev=lo \\
+    --enabled-disk-templates=diskless --enabled-hypervisors=fake \\
+    --ipolicy-bounds-specs=min:disk-size=0,cpu-count=1,disk-count=0,memory-size=1,nic-count=0,spindle-use=0/max:disk-size=1048576,cpu-count=8,disk-count=16,memory-size=32768,nic-count=8,spindle-use=12 \\
+    $cluster_name
 
 Add node:
   cd $rootdir && node1/cmd gnt-node add --no-ssh-key-check node2