- `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::
clientdir = $(pkgpythondir)/client
cmdlibdir = $(pkgpythondir)/cmdlib
hypervisordir = $(pkgpythondir)/hypervisor
+storagedir = $(pkgpythondir)/storage
httpdir = $(pkgpythondir)/http
masterddir = $(pkgpythondir)/masterd
confddir = $(pkgpythondir)/confd
HS_DIRS = \
src \
src/Ganeti \
- src/Ganeti/Block \
- src/Ganeti/Block/Drbd \
src/Ganeti/Confd \
src/Ganeti/Curl \
src/Ganeti/DataCollectors \
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 \
lib/masterd \
lib/rapi \
lib/server \
+ lib/storage \
lib/tools \
lib/utils \
lib/watcher \
lib/__init__.py \
lib/asyncnotifier.py \
lib/backend.py \
- lib/bdev.py \
lib/bootstrap.py \
lib/cli.py \
lib/compat.py \
lib/serializer.py \
lib/ssconf.py \
lib/ssh.py \
- lib/storage.py \
lib/uidpool.py \
lib/vcluster.py \
lib/network.py \
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 \
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
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 \
$(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 \
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 \
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 \
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 \
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
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
$(client_PYTHON) \
$(cmdlib_PYTHON) \
$(hypervisor_PYTHON) \
+ $(storage_PYTHON) \
$(rapi_PYTHON) \
$(server_PYTHON) \
$(pytools_PYTHON) \
====
+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
-------------------
-Ganeti 2.8
+Ganeti 2.9
==========
For installation instructions, read the INSTALL and the doc/install.rst
# 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,
[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],
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";
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";
[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"
# 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]))
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)
$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
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 -- \
Design document drafts
======================
-.. Last updated for Ganeti 2.8
+.. Last updated for Ganeti 2.9
.. toctree::
:maxdepth: 2
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:
--- /dev/null
+========================
+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:
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.
--- /dev/null
+============================================================
+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:
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.
``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
``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
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
***********
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
--------------
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
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
-----------
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
``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
- `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
$ 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 \
Ganeti customisation using hooks
================================
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
.. contents::
Ganeti automatic instance allocation
====================================
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
.. contents::
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.
Virtual cluster support
=======================
-Documents Ganeti version 2.8
+Documents Ganeti version 2.9
.. contents::
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
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
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:
- 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):
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)
"""
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):
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
- 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
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,
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()
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)
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
_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
"""
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:
"""
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]
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.
"""
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)):
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
@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:
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 = {
@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)
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
self.tried_once = False
def __call__(self):
- if iname not in hyper.ListInstances():
+ if iname not in hyper.ListInstances(instance.hvparams):
return
try:
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
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:
@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)
"""
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
_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
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)
"""
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:
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.
@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 = []
if rbd is None:
result.append(None)
else:
- result.append(rbd.GetActualSize())
+ result.append(rbd.GetActualDimensions())
return result
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 = []
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:
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):
_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.
"""
raise utils.RetryAgain()
return stats
- bdevs = _FindDisks(nodes_ip, disks)
+ bdevs = _FindDisks(target_node_uuid, nodes_ip, disks)
min_resync = 100
alldone = True
"""
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
except Exception: # pylint: disable=W0703
pass
time.sleep(5)
- hyper.PowercycleNode()
+ hyper.PowercycleNode(hvparams=hvparams)
def _VerifyRestrictedCmdName(cmd):
+++ /dev/null
-#
-#
-
-# 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
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
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"
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,
_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 = {
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
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"
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:
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]
# 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)
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
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
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)
"""
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:
"PREALLOC_WIPE_DISKS_OPT",
"PRIMARY_IP_VERSION_OPT",
"PRIMARY_ONLY_OPT",
+ "PRINT_JOBID_OPT",
"PRIORITY_OPT",
"RAPI_CERT_OPT",
"READD_OPT",
"SRC_DIR_OPT",
"SRC_NODE_OPT",
"SUBMIT_OPT",
+ "SUBMIT_OPTS",
"STARTUP_PAUSED_OPT",
"STATIC_OPT",
"SYNC_OPT",
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"
#: 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 = [
OSPARAMS_OPT,
OS_SIZE_OPT,
SUBMIT_OPT,
+ PRINT_JOBID_OPT,
TAG_ADD_OPT,
DRY_RUN_OPT,
PRIORITY_OPT,
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)
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)
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)
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": (
"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."),
}
_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.
@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.")
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,
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
@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
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:
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():
"<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": (
"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], "",
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": (
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)],
"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,
"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": (
"<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>...]",
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
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:
"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": (
"[--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"
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": (
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],
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"),
}
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",
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"),
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,
"[<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,
"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": (
"<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"),
}
"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": (
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)"),
"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": (
[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": (
"<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,
"[<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)"),
}
"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"),
}
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, \
# 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
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
"""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)
"""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
# 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
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)
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)
" 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
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)
"""
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.
"""
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:
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 = []
# 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:
"""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"
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:
}
# 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
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.
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,
# 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")
"""
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")
"""
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")
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):
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)
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):
"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])
# 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")
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
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 = {
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.
"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)
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
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:
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)
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)
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()
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,
" 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
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:
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")
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)
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):
# 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,
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
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)
@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 = []
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: [],
# 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
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.
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))
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
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
# 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)
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
@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):
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.
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.
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
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.
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
@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)))
@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
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()
# 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.
@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
"""
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.
# 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
"""
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),
]
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)
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
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
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):
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.
@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
@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?"
("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}.
@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)):
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")
@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.
@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
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
@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
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)
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)]
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)
"""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.
# 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
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
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():
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,
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
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])
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)
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())
# 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"
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)
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)
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")
# 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)
"""
# 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:
]))
-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():
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):
"""
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) = \
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):
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)
return None
-def CheckOSParams(lu, required, nodenames, osname, osparams):
+def CheckOSParams(lu, required, node_uuids, osname, osparams):
"""OS parameters validation.
@type lu: L{LogicalUnit}
@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
@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
@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
@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):
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)" %
@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):
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):
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)
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
"""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)
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" %
" 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):
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)
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):
# 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)
"""
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,"
(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"
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)
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
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))
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()
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.
# 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):
"""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()
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,
"""
# 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]
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)
# 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]
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
# 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
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)
# 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]
# 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
# 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.
[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)
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, \
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}
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):
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." %
# 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
"""
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
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
" 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)
"""
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)
(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))
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.
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],
"""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):
"""
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):
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)
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)
#### 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)
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"
# 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:
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],
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
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
(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
# 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"
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,
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,
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:
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())
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)
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"
# 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:
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
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,
# 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
% 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")
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,
(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)
"""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
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):
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
"""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):
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
nl = [
self.cfg.GetMasterNode(),
self.instance.primary_node,
- self.op.target_node,
+ self.op.target_node_uuid,
]
return (nl, nl)
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.
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",
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):
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
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
(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])
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
@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)
raise errors.ProgrammerError("Unhandled operation '%s'" % op)
@staticmethod
- def _VerifyDiskModification(op, params):
+ def _VerifyDiskModification(op, params, excl_stor):
"""Verifies a disk modification.
"""
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"
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()
# 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] = \
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
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" %
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:
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:
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"
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:
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"
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
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 = {}
#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)
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
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, __):
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:
" (%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
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
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)
"""
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"
"""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):
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)
"""
(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:
"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)
# 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))
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)), \
# 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
# 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:
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, \
"""
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
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.
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
_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]
"""
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"] = ""
_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)
"""
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
"""
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)
@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
_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
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:
" 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")
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")
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"])
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)
" 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))
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:
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.
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.
- 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,"
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:
"""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()
"""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.
- 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)):
(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)
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)
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:
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"
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):
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")
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"
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"
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)
# 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")
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)
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
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)
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, \
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")
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)
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
"""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):
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"
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
"""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):
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()
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)
"""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))
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
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
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.
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]))
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())))
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):
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
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()
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.
"""
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
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 = []
"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,
}
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]
" 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"
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,
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
}
-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.
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}
@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.
@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}
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:
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}.
"""
(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)
@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.
@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
"""
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)
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
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.
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],
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):
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)
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],
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:
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)
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.
_MODIFYABLE = compat.UniqueFrozenset([
constants.IDISK_SIZE,
constants.IDISK_MODE,
+ constants.IDISK_SPINDLES,
])
# New or changed disk parameters may have different semantics
# 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()),
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)
" %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]):
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:
# 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
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:
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")
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",
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:
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:
# 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
@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}
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
@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
"""
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):
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:
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)
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))
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:
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
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
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
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
"""
device_info = []
disks_ok = True
- iname = instance.name
disks = ExpandCheckDisks(instance, disks)
# With the two passes mechanism we try to reduce the window of
# 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
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
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
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
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:
# 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)
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):
"""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"
"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] = []
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)
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]
# 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)
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
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):
# 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)
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)
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
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
"""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.
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:
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):
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)
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.
@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
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)
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"
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 = []
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)" %
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)
# 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):
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
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
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
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)
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:
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:
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")
# 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)
# 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,
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:
# 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.
# 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)
# 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
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)
(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"
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,))
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,
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"))
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:
# 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)
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
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,
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],
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)
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"
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
@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
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
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
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
@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)
"""
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
"""
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,
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)
# 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
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, "", [])]
}
"""
# 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:
# 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):
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))
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,
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))
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:
"""
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)]
# 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 = {
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))
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,
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
# 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 = {
# 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
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, \
@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),"
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)
"""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.
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"
# 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"
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)
errors.ECODE_ENVIRON)
if self.op.readd:
- exceptions = [node]
+ exceptions = [existing_node_info.uuid]
else:
exceptions = []
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,
# 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" %
"""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:
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):
_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,
"""
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:
}
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
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.
"""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):
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
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)
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,
# 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"
# 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)
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" %
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")
" 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")
" 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
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,
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")
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:
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)))
# 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
# 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
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)
"""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
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):
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)
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.
" 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:
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)
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",
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" %
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):
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
# 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)
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
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):
"""
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" %
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
"""
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
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):
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 = {
"""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,
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":
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))
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 = {
"""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:
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])
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:
"""
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)
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
"""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
"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"
# 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)
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
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:
"""
# 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)
@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, "", [], [])]}
}
"""
# 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,
# 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
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 = {}
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
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
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:
"""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():
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
# _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.
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.
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)
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)
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)
_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:
# 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:
(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" %
"""
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.
"""
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:
" 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)
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.
node.
"""
- return self._UnlockedSetDiskID(disk, node_name)
+ return self._UnlockedSetDiskID(disk, node_uuid)
@locking.ssynchronized(_config_lock)
def AddTcpUdpPort(self, port):
"""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)
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).
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
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()
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
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):
@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.
"""
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)
"""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):
" 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()
"""
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
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
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):
"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.
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()
"""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:
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):
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)
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.
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
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)
@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:
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.
"""
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):
"""
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):
"""
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):
"""
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.
"""
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
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
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.
"""
# 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.
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):
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
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))
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()
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
# 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:
# 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)
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():
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.
"""
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)
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")
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,
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:
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
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
@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)
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,
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"
])
# 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 = {
HV_KVM_MACHINE_VERSION = "machine_version"
HV_KVM_PATH = "kvm_path"
HV_VIF_TYPE = "vif_type"
+HV_XEN_CMD = "xen_cmd"
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())
# 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"
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,
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 = \
# 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"
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
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",
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,
HV_KVM_EXTRA: "",
HV_KVM_MACHINE_VERSION: "",
},
- HT_FAKE: {},
+ HT_FAKE: {
+ HV_MIGRATION_MODE: HT_MIGRATION_LIVE,
+ },
HT_CHROOT: {
HV_INIT_SCRIPT: "/ganeti-chroot",
},
HV_MIGRATION_PORT,
HV_MIGRATION_BANDWIDTH,
HV_MIGRATION_MODE,
+ HV_XEN_CMD,
])
BEC_DEFAULTS = {
OPCODE_REASON_SRC_USER,
])
+DISKSTATS_FILE = "/proc/diskstats"
+
# Do not re-export imported modules
del re, _vcsversion, _autoconf, socket, pathutils, compat
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
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",
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,
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:
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,
"""
# Length limits
- START_LINE_LENGTH_MAX = 4096
+ START_LINE_LENGTH_MAX = 8192
HEADER_LENGTH_MAX = 4096
def ParseStartLine(self, start_line):
"""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
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.
"""
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
"""
"""
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
"""
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
@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
(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
"""
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)
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),...]
"""
# 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
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.
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
"""
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
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"
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)
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)
"""
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
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.
"""
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
"""
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()
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
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,
# 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,
# 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
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)
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)
"""
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
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(" "))
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
"""
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
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.
"""
"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)
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
"""
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:
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()
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)
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),...]
"""
# 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
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
"""
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
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")
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
# 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])
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)
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):
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
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,
@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
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
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)
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.
"""Start an instance.
"""
- startup_memory = self._InstanceStartupMemory(instance)
+ startup_memory = self._InstanceStartupMemory(instance,
+ hvparams=instance.hvparams)
self._MakeConfigFile(instance, startup_memory, block_devices)
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.
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))
"""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
@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,
(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
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
"""
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)):
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))
"""
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
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):
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):
(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):
"""
_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, \
# 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),
}
ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
_INST_NAME = ("name", ht.TNonEmptyString)
+_INST_UUID = ("inst_uuid", ht.TNonEmptyString)
class _AutoReqParam(outils.AutoSlots):
# 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
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",
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):
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)))
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
#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")
# 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)
# 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 = {
"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.
"""
"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,
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}
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
"""
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
# 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)
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)
"""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.
"""
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
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}
@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
"""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))
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)
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
@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
"""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))
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.
"""
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
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)
"""
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.
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}
# 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 = []
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,
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)
"""
instance = self._instance
- src_node = instance.primary_node
+ src_node_uuid = instance.primary_node
assert len(self._snap_disks) == len(instance.disks)
# 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
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
"""
__slots__ = [
- "name",
+ "uuid",
"ip",
"netmask",
"netdev",
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."""
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
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
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:
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.
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.
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
"""
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)
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:
'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()
_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")
#: 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")
_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")
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.
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])))
"""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,
_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)"),
]
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid
]
OP_RESULT = ht.TNone
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid,
_PStorageType,
_PStorageName,
("changes", ht.NoDefault, ht.TDict, "Requested changes"),
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid,
_PStorageType,
_PStorageName,
_PIgnoreConsistency,
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid,
_PForce,
_PHvState,
_PDiskState,
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid,
_PForce,
]
OP_RESULT = ht.TMaybeString
OP_DSC_FIELD = "node_name"
OP_PARAMS = [
_PNodeName,
+ _PNodeUuid,
_PMigrationMode,
_PMigrationLive,
_PMigrationTargetNode,
+ _PMigrationTargetNodeUuid,
_PAllowRuntimeChgs,
_PIgnoreIpolicy,
_PIAllocFromDesc("Iallocator for deciding the target node"
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"),
("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,
("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"),
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"),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PShutdownTimeout,
("ignore_failures", False, ht.TBool,
"Whether to ignore failures during removal"),
"""Rename an instance."""
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PNameCheck,
("new_name", ht.NoDefault, ht.TNonEmptyString, "New instance name"),
("ip_check", False, ht.TBool, _PIpCheckDoc),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PForce,
_PIgnoreOfflineNodes,
("hvparams", ht.EmptyDict, ht.TDict,
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PForce,
_PIgnoreOfflineNodes,
("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TNonNegativeInt,
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"),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PEarlyRelease,
_PIgnoreIpolicy,
("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
("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
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"),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PMigrationMode,
_PMigrationLive,
_PMigrationTargetNode,
+ _PMigrationTargetNodeUuid,
_PAllowRuntimeChgs,
_PIgnoreIpolicy,
("cleanup", False, ht.TBool,
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
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
]
OP_RESULT = ht.TDict
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
("ignore_size", False, ht.TBool, "Whether to ignore recorded size"),
_PWaitForSyncFalse,
]
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PForce,
]
OP_RESULT = ht.TNone
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
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PForce,
_PForceVariant,
_PIgnoreIpolicy,
("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"),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PWaitForSync,
("disk", ht.NoDefault, ht.TInt, "Disk index"),
("amount", ht.NoDefault, ht.TNonNegativeInt,
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
_PEarlyRelease,
_PIAllocFromDesc("Iallocator for computing solution"),
_PTargetGroups,
_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
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
"Export mode"),
]
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"),
OP_DSC_FIELD = "instance_name"
OP_PARAMS = [
_PInstanceName,
+ _PInstanceUuid,
]
OP_RESULT = ht.TNone
("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),
]
"""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
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
"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",
@param node: Node object
"""
- if ctx.oob_support[node.name]:
+ if ctx.oob_support[node.uuid]:
return node.powered
return _FS_UNAVAIL
(_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"),
" \"%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 \
"""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
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):
# 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]
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
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.
@param inst: Instance object
"""
- usage = ctx.disk_usage[inst.name]
+ usage = ctx.disk_usage[inst.uuid]
if usage is None:
usage = 0
@param inst: Instance object
"""
- consinfo = ctx.console[inst.name]
+ consinfo = ctx.console[inst.uuid]
if consinfo is None:
return _FS_UNAVAIL
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"),
(_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)),
}
-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
"""
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,
# 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,
_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
"""
self._cluster = cluster
+ self.nodes = nodes
self.drain_flag = drain_flag
self.watcher_pause = watcher_pause
(_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
"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",
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,
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
"""
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
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)
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
_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)
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}.
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
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
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}.
("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"),
("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"),
("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),
("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)"),
"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"),
]
("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([
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)
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
# 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):
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
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:
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
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):
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):
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):
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):
"""
(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):
"""
(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):
"""
(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 --------------------------
"""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):
"""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):
"""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 --------------------------
"""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):
"""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):
"""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 --------------------------
"""
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",
"""
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",
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
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.
--- /dev/null
+#
+#
+
+# 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
+
+"""
--- /dev/null
+#
+#
+
+# 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
--- /dev/null
+#
+#
+
+# 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
--- /dev/null
+#
+#
+
+# 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
--- /dev/null
+#
+#
+
+# 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
--- /dev/null
+#
+#
+
+# 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
--- /dev/null
+#
+#
+
+# 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))
"""
- 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
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
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 *
--- /dev/null
+#
+#
+
+# 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
import logging
-from ganeti import bdev
from ganeti import constants
from ganeti import errors
from ganeti import hypervisor
from ganeti import ssconf
from ganeti import utils
from ganeti import confd
+from ganeti.storage import drbd
import ganeti.confd.client # pylint: disable=W0611
"""
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",
"""Get list of used DRBD minors.
"""
- return bdev.DRBD8.GetUsedDevs().keys()
+ return drbd.DRBD8.GetUsedDevs()
@classmethod
def DoMaintenance(cls, role):
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.
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
-----------
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
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
-----------
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.
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.
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
~~~~~~~~
| **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
| [-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
| [\--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*]
| [\--ipolicy-vcpu-ratio *ratio*]
| [\--disk-state *diskstate*]
| [\--hypervisor-state *hvstate*]
+| [\--drbd-usermode-helper *helper*]
| [\--enabled-disk-templates *template* [,*template*...]]
| {*clustername*}
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
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.
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).
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*...]]
| [\--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).
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
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.
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*...]]
~~~~~~~~~~~~
| **assign-nodes**
-| [\--force] [\--submit]
+| [\--force] [\--submit] [\--print-job-id]
| {*group*} {*node*...}
Assigns one or more nodes to the specified group, moving them from their
MODIFY
~~~~~~
-| **modify** [\--submit]
+| **modify** [\--submit] [\--print-job-id]
| [\--node-parameters=*NDPARAMS*]
| [\--alloc-policy=*POLICY*]
| [\--hypervisor-state *hvstate*]
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.
RENAME
~~~~~~
-| **rename** [\--submit] {*oldname*} {*newname*}
+| **rename** [\--submit] [\--print-job-id] {*oldname*} {*newname*}
Renames a given group from *oldname* to *newname*.
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
^^^
| **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]
| [\--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*}
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
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
| \--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*}
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
| **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
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
| \--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
| [\--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
| [\--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
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.
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::
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
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
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.
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
| **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,
| **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
^^^^
| **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
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
Lists available fields for jobs.
+WAIT
+~~~~~
+
+**wait** {id}
+
+Wait for the job by the given *id* to finish; do not produce
+any output.
+
WATCH
~~~~~
| [\--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
| [\--network6=*NETWORK6*]
| [\--gateway6=*GATEWAY6*]
| [\--mac-prefix=*MACPREFIX*]
-| [\--submit]
+| [\--submit] [\--print-job-id]
| {*network*}
Modifies parameters from the network.
REMOVE
~~~~~~
-| **remove** [\--submit] {*network*}
+| **remove** [\--submit] [\--print-job-id] {*network*}
Deletes the indicated network, which must be not connected to any node group.
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*}
~~~~~~~
| **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
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]
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
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
MODIFY
~~~~~~
-| **modify** [\--submit] [-H *HYPERVISOR*:option=*value*[,...]]
+| **modify** [\--submit] [\--print-job-id]
+| [-H *HYPERVISOR*:option=*value*[,...]]
| [\--hidden=*yes|no*] [\--blacklisted=*yes|no*]
| {*OS*}
{ **-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
-----------
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
----
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
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:
- 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).
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.
-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.
import qa_error
import qa_group
import qa_instance
+import qa_monitoring
import qa_network
import qa_node
import qa_os
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():
qa_cluster.AssertClusterVerify()
+def RunMonitoringTests():
+ if qa_config.TestEnabled("mon-collector"):
+ RunTest(qa_monitoring.TestInstStatusCollector)
+
+
def RunQa():
"""Main QA body.
snode.Release()
qa_cluster.AssertClusterVerify()
+ RunMonitoringTests()
+
RunTestIf("create-cluster", qa_node.TestNodeRemoveAll)
RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy)
"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": {
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:
_TestClusterModifyDiskTemplatesArguments(default_disk_template,
enabled_disk_templates)
+ _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
_RestoreEnabledDiskTemplates()
nodes = qa_config.AcquireManyNodes(2)
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)
of instances.
"""
- AssertCommand(
- ["gnt-cluster", "modify",
- "--enabled-disk-template=%s" %
- ",".join(enabled_disk_templates)],
- fail=False)
+ _RestoreEnabledDiskTemplates()
# bogus templates
AssertCommand(["gnt-cluster", "modify",
(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):
"""
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.
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.
return GetConfig().IsTemplateSupported(templ)
+def AreSpindlesSupported():
+ """Wrapper for L{_QaConfig.AreSpindlesSupported}.
+
+ """
+ return GetConfig().AreSpindlesSupported()
+
+
def _NodeSortKey(node):
"""Returns sort key for a node.
"""
-import operator
import os
import re
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.
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)
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)
"""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)
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
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])
# 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)
# 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
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"))
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'
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
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
_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])
"--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)
# 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
--- /dev/null
+#
+#
+
+# 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())
--- /dev/null
+#
+#
+
+# 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()
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])
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,
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")
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
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
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
-- | 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.
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
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)
-- | 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)
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
, oNode
, oConfdAddr
, oConfdPort
+ , oInputFile
, genericOptions
) where
, 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.
, optNode = Nothing
, optConfdAddr = Nothing
, optConfdPort = Nothing
+ , optInputFile = Nothing
}
-- | Abbreviation for the option type.
"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
--- /dev/null
+{-| @/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
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
(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, "")
--- /dev/null
+{-| 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
--- /dev/null
+{-# 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 |]
+ ])
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"))
]
, DCStatusCode(..)
, DCVersion(..)
, buildReport
+ , mergeStatuses
) where
import Data.Char
, ("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
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"
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.
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) =>
-> (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
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
["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
-> 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
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)
-- | 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)
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
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)
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.
{-
-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
-- | 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\
-> 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)
, loadISpec
, loadMultipleMinMaxISpecs
, loadIPolicy
+ , serializeInstance
, serializeInstances
, serializeNode
, serializeNodes
-> 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.
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
-> [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
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.
-- 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
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.
{- 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 -}
, oForce
, oGroup
, oIAllocSrc
+ , oIgnoreNonRedundant
, oInstMoves
, oJobDelay
, genOLuxiSocket
, oNoHeaders
, oNoSimulation
, oNodeSim
+ , oNodeTags
+ , oOfflineMaintenance
, oOfflineNode
+ , oOneStepOnly
, oOutputDir
, oPrintCommands
, oPrintInsts
+ , oPrintMoves
, oPrintNodes
, oQuiet
, oRapiMaster
, oShowHelp
, oShowVer
, oShowComp
+ , oSkipNonRedundant
, oStdSpec
, oTieredSpec
, oVerbose
, 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
, 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
, 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
, optForce = False
, optGroup = Nothing
, optIAllocSrc = Nothing
+ , optIgnoreNonRedundant = False
, optSelInst = []
, optLuxi = Nothing
, optJobDelay = 10
, optNoHeaders = False
, optNoSimulation = False
, optNodeSim = []
+ , optNodeTags = Nothing
+ , optSkipNonRedundant = False
, optOffline = []
+ , optOfflineMaintenance = False
+ , optOneStepOnly = False
, optOutPath = "."
+ , optPrintMoves = False
, optSaveCluster = Nothing
, optShowCmds = Nothing
, optShowHelp = False
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.
"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"]
\ '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"]
"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"]
"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"]
"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"]
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
, 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
-- | 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
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
- 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
, 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
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)
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
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
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
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
module Ganeti.HTools.Instance
( Instance(..)
+ , Disk(..)
, AssocList
, List
, create
, setBoth
, setMovable
, specOf
+ , getTotalSpindles
, instBelowISpec
, instAboveISpec
, instMatchesPolicy
, mirrorType
) where
+import Control.Monad (liftM2)
+
import Ganeti.BasicTypes
import qualified Ganeti.HTools.Types as T
import qualified Ganeti.HTools.Container as Container
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
, 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
--
-- 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
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
, lookupNode
, lookupInstance
, lookupGroup
+ , eitherLive
, commonSuffix
, RqType(..)
, Request(..)
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
, setPri
, setSec
, setMaster
+ , setNodeTags
, setMdsk
, setMcpu
, setPolicy
, 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
, 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
-- 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
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.
-- 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
, 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 = []
, 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.
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 }
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.
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.
, 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
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
, 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.
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)
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).
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
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
, 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
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
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) }
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 ->
, 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
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
"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
Just (
ArReinstall,
[ OpInstanceRecreateDisks { opInstanceName = iname
+ , opInstanceUuid = Nothing
, opRecreateDisksInfo = RecreateDisksAll
, opNodes = []
-- FIXME: there should be a better way to
-- 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
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
}
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
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
OpTestDelay { opDelayDuration = delay
, opDelayOnMaster = True
, opDelayOnNodes = []
+ , opDelayOnNodeUuids = Nothing
, opDelayRepeat = fromJust $ mkNonNegative 0
} : opcodes
else
, 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.
, 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
| 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 ()
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
, ("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
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)]
\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)
, ("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)
, ("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 =
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
-- | 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)]
, ("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.
, 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.
-- 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
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)]
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.
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
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
-- 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 ->
(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
, unitMem
, unitCpu
, unitDsk
+ , unitSpindle
, unknownField
, Placement
, IMove(..)
{ 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
, allocInfoNCpus :: Double -- ^ Normalised CPUs
, allocInfoMem :: Int -- ^ Memory
, allocInfoDisk :: Int -- ^ Disk
+ , allocInfoSpn :: Int -- ^ Spindles
} deriving (Show, Eq)
-- | Currently used, possibly to allocate, unallocable.
rspecFromISpec ispec = RSpec { rspecCpu = iSpecCpuCount ispec
, rspecMem = iSpecMemorySize ispec
, rspecDsk = iSpecDiskSize ispec
+ , rspecSpn = iSpecSpindleUse ispec
}
-- | The default instance policy.
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.
--- /dev/null
+{-| 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
{-
-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
, fromJVal
, jsonHead
, getMaybeJsonHead
+ , getMaybeJsonElem
, asJSObject
, asObjectList
, tryFromObj
+ , arrayMaybeFromJVal
+ , tryArrayMaybeFromObj
, toArray
, optionalJSField
, optFieldsToObj
-> 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
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
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
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
-- | 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
, diskSize :: Int
, diskMode :: DiskMode
, diskName :: Maybe String
+ , diskSpindles :: Maybe Int
, diskUuid :: String
} deriving (Show, Eq)
, simpleField "size" [t| Int |]
, defaultField [| DiskRdWr |] $ simpleField "mode" [t| DiskMode |]
, optionalField $ simpleField "name" [t| String |]
+ , optionalField $ simpleField "spindles" [t| Int |]
]
++ uuidFields)
[ 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
])
, ("OpOobCommand",
[ pNodeNames
+ , pNodeUuids
, pOobCommand
, pOobTimeout
, pIgnoreStatus
, pPowerDelay
])
- , ("OpNodeRemove", [ pNodeName ])
+ , ("OpNodeRemove",
+ [ pNodeName
+ , pNodeUuid
+ ])
, ("OpNodeAdd",
[ pNodeName
, pHvState
])
, ("OpNodeModifyStorage",
[ pNodeName
+ , pNodeUuid
, pStorageType
, pStorageName
, pStorageChanges
])
, ("OpRepairNodeStorage",
[ pNodeName
+ , pNodeUuid
, pStorageType
, pStorageName
, pIgnoreConsistency
])
, ("OpNodeSetParams",
[ pNodeName
+ , pNodeUuid
, pForce
, pHvState
, pDiskState
])
, ("OpNodePowercycle",
[ pNodeName
+ , pNodeUuid
, pForce
])
, ("OpNodeMigrate",
[ pNodeName
+ , pNodeUuid
, pMigrationMode
, pMigrationLive
, pMigrationTargetNode
+ , pMigrationTargetNodeUuid
, pAllowRuntimeChgs
, pIgnoreIpolicy
, pIallocator
, ("OpNodeEvacuate",
[ pEarlyRelease
, pNodeName
+ , pNodeUuid
, pRemoteNode
+ , pRemoteNodeUuid
, pIallocator
, pEvacMode
])
, pInstOsParams
, pInstOs
, pPrimaryNode
+ , pPrimaryNodeUuid
, pSecondaryNode
+ , pSecondaryNodeUuid
, pSourceHandshake
, pSourceInstance
, pSourceShutdownTimeout
, pSourceX509Ca
, pSrcNode
+ , pSrcNodeUuid
, pSrcPath
, pStartInstance
, pOpportunisticLocking
])
, ("OpInstanceReinstall",
[ pInstanceName
+ , pInstanceUuid
, pForceVariant
, pInstOs
, pTempOsParams
])
, ("OpInstanceRemove",
[ pInstanceName
+ , pInstanceUuid
, pShutdownTimeout
, pIgnoreFailures
])
, ("OpInstanceRename",
[ pInstanceName
+ , pInstanceUuid
, pNewName
, pNameCheck
, pIpCheck
])
, ("OpInstanceStartup",
[ pInstanceName
+ , pInstanceUuid
, pForce
, pIgnoreOfflineNodes
, pTempHvParams
])
, ("OpInstanceShutdown",
[ pInstanceName
+ , pInstanceUuid
, pForce
, pIgnoreOfflineNodes
, pShutdownTimeout'
])
, ("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)
])
, ("OpInstanceSetParams",
[ pInstanceName
+ , pInstanceUuid
, pForce
, pForceVariant
, pIgnoreIpolicy
, pInstHvParams
, pOptDiskTemplate
, pPrimaryNode
+ , pPrimaryNodeUuid
, pRemoteNode
+ , pRemoteNodeUuid
, pOsNameChange
, pInstOsParams
, pWaitForSync
])
, ("OpInstanceGrowDisk",
[ pInstanceName
+ , pInstanceUuid
, pWaitForSync
, pDiskIndex
, pDiskChgAmount
])
, ("OpInstanceChangeGroup",
[ pInstanceName
+ , pInstanceUuid
, pEarlyRelease
, pIallocator
, pTargetGroups
[ pGroupName
, pForce
, pRequiredNodes
+ , pRequiredNodeUuids
])
, ("OpGroupQuery", dOldQueryNoLocking)
, ("OpGroupSetParams",
])
, ("OpBackupPrepare",
[ pInstanceName
+ , pInstanceUuid
, pExportMode
])
, ("OpBackupExport",
[ pInstanceName
+ , pInstanceUuid
, pShutdownTimeout
, pExportTargetNode
+ , pExportTargetNodeUuid
, pShutdownInstance
, pRemoveInstance
, pIgnoreRemoveFailures
, pX509DestCA
])
, ("OpBackupRemove",
- [ pInstanceName ])
+ [ pInstanceName
+ , pInstanceUuid
+ ])
, ("OpTestAllocator",
[ pIAllocatorDirection
, pIAllocatorMode
, ("OpRestrictedCommand",
[ pUseLocking
, pRequiredNodes
+ , pRequiredNodeUuids
, pRestrictedCommand
])
])
, SetParamsMods(..)
, ExportTarget(..)
, pInstanceName
+ , pInstanceUuid
, pInstances
, pName
, pTagsList
, pForce
, pIgnoreOfflineNodes
, pNodeName
+ , pNodeUuid
, pNodeNames
+ , pNodeUuids
, pGroupName
, pMigrationMode
, pMigrationLive
, pIpConflictsCheck
, pNoRemember
, pMigrationTargetNode
+ , pMigrationTargetNodeUuid
, pMoveTargetNode
+ , pMoveTargetNodeUuid
, pStartupPaused
, pVerbose
, pDebugSimulateErrors
, pNames
, pNodes
, pRequiredNodes
+ , pRequiredNodeUuids
, pStorageType
, pStorageChanges
, pMasterCandidate
, pPowered
, pIallocator
, pRemoteNode
+ , pRemoteNodeUuid
, pEvacMode
, pInstCreateMode
, pNoInstall
, pInstOs
, pPrimaryNode
+ , pPrimaryNodeUuid
, pSecondaryNode
+ , pSecondaryNodeUuid
, pSourceHandshake
, pSourceInstance
, pSourceShutdownTimeout
, pSourceX509Ca
, pSrcNode
+ , pSrcNodeUuid
, pSrcPath
, pStartInstance
, pInstTags
, pTargetGroups
, pExportMode
, pExportTargetNode
+ , pExportTargetNodeUuid
, pRemoveInstance
, pIgnoreRemoveFailures
, pX509KeyName
, pDelayDuration
, pDelayOnMaster
, pDelayOnNodes
+ , pDelayOnNodeUuids
, pDelayRepeat
, pIAllocatorDirection
, pIAllocatorMode
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 [| [] |] $
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 |]
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"
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 |]
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 |]
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 =
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"
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"
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 =
, queueDir
, jobQueueSerialFile
, jobQueueArchiveSubDir
+ , instanceReasonDir
+ , getInstReasonFilename
) where
import System.FilePath
-- | 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
--- /dev/null
+{-| 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
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.
"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",
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 =
, (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
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
(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))))
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)
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
, ("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)
, ("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
, RpcResultInstanceList(..)
, HvInfo(..)
- , VgInfo(..)
+ , StorageInfo(..)
, RpcCallNodeInfo(..)
, RpcResultNodeInfo(..)
-- | 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.
$(buildObject "RpcResultNodeInfo" "rpcResNodeInfo"
[ simpleField "boot_id" [t| String |]
- , simpleField "vg_info" [t| [VgInfo] |]
+ , simpleField "storage_info" [t| [StorageInfo] |]
, simpleField "hv_info" [t| [HvInfo] |]
])
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)
--- /dev/null
+{-# 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
--- /dev/null
+{-# 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 |]
+ ])
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
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
02110-1301, USA.
-}
-module Ganeti.Block.Drbd.Types
+module Ganeti.Storage.Drbd.Types
( DRBDStatus(..)
, VersionInfo(..)
, DeviceInfo(..)
, CVErrorCode(..)
, cVErrorCodeToRaw
, Hypervisor(..)
+ , hypervisorToRaw
, OobCommand(..)
, StorageType(..)
, NodeEvacMode(..)
, exitIfEmpty
, splitEithers
, recombineEithers
+ , resolveAddr
) where
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
import Control.Monad (foldM)
import Debug.Trace
+import Network.Socket
import Ganeti.BasicTypes
import qualified Ganeti.Constants as C
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)
--- /dev/null
+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
+ }
+ }
+ }
+}
"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
}
"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
}
"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": {
"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
}
],
"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": {
--- /dev/null
+{
+ "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"
+ }
+}
"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
}
],
"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
}
"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
}
"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
}
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
"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"
"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
}
}
]
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
--- /dev/null
+ 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
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
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
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 =
testSuite "HTools/Backend/Text"
[ 'prop_Load_Instance
, 'prop_Load_InstanceFail
+ , 'prop_InstanceLSIdempotent
, 'prop_Load_Node
, 'prop_Load_NodeFail
, 'prop_NodeLSIdempotent
{-# 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
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
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
{-
-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
( testHTools_Instance
, genInstanceSmallerThanNode
, genInstanceMaybeBiggerThanNode
- , genInstanceSmallerThan
, genInstanceOnNodeList
, genInstanceList
, Instance.Instance(..)
) where
+import Control.Monad (liftM)
import Test.QuickCheck hiding (Result)
import Test.Ganeti.TestHelper
-- * 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)
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
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
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:
-- let's generate a random instance
instance Arbitrary Instance.Instance where
- arbitrary = genInstanceSmallerThan maxMem maxDsk maxCpu
+ arbitrary = genInstanceSmallerThan maxMem maxDsk maxCpu Nothing
-- * Test cases
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
{-
-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
, genNode
, genOnlineNode
, genNodeList
+ , genUniqueNodeList
) where
import Test.QuickCheck
-- 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)
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
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
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)
-- 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)
, 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 }
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)
-- 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
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
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
-- 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
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
, 'prop_setXmem
, 'prop_addPriFM
, 'prop_addPriFD
+ , 'prop_addPriFS
, 'prop_addPriFC
, 'prop_addPri_NoN1Fail
, 'prop_addSec
module Test.Ganeti.JSON (testJSON) where
+import Data.List
import Test.QuickCheck
import qualified Text.JSON as J
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
]
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'
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
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" ->
"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 <*>
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 <*>
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" ->
"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 [] <*>
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
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
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
--- /dev/null
+{-# 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
+ ]
{-# LANGUAGE TemplateHaskell #-}
-{-| Unittests for Attoparsec support for unicode -}
+{-| Unittests for the DRBD Parser -}
{-
-}
-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
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")
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")
-- | 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")
-- | 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
-- | 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
-- | 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")
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,
-}
-module Test.Ganeti.Block.Drbd.Types (testBlock_Drbd_Types) where
+module Test.Ganeti.Storage.Drbd.Types (testBlock_Drbd_Types) where
import Test.QuickCheck
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" #-}
( maxMem
, maxDsk
, maxCpu
+ , maxSpindles
, maxVcpuRatio
, maxSpindleRatio
, maxNodes
, 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)
maxCpu :: Int
maxCpu = 1024
+-- | Max spindles (1024, somewhat random value).
+maxSpindles :: Int
+maxSpindles = 1024
+
-- | Max vcpu ratio (random value).
maxVcpuRatio :: Double
maxVcpuRatio = 1024.0
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))
-- | 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
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)]
+ }
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
, testConfd_Types
, testConfd_Utils
, testDaemon
+ , testBlock_Diskstats_Parser
, testBlock_Drbd_Parser
, testBlock_Drbd_Types
, testErrors
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...
>>> /"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,/
./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
-./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
>>>= 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/
node-01-003 2
node-01-004 2/
>>>=0
-
[]
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
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(),
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)
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)
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)
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):
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)
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)
import shutil
import tempfile
import unittest
+import mock
from ganeti import utils
from ganeti import constants
from ganeti import netutils
from ganeti import errors
from ganeti import serializer
+from ganeti import hypervisor
import testutils
import mocks
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")
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())
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()
+++ /dev/null
-#!/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()
("n3c", "g3"),
])
- def Instance(name, pnode, snode):
+ def Instance(uuid, pnode, snode):
if snode is None:
disks = []
disk_template = constants.DT_DISKLESS
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),
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,
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",
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",
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;"
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):
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,
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):
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:
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",
]
inst = objects.Instance(name="inst562",
- primary_node=node_name,
+ primary_node=node_uuid,
disk_template=constants.DT_PLAIN,
disks=disks)
import testutils
import mocks
+import mock
def _StubGetEntResolver():
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
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)
_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, [
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([])
# 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
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
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
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
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:
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))
return {}
def BuildHooksNodes(self):
- return ["localhost"], ["localhost"]
+ return ["a"], ["a"]
class TestHooksRunner(unittest.TestCase):
return self.hook_env
def BuildHooksNodes(self):
- return (["localhost"], ["localhost"])
+ return (["a"], ["a"])
class FakeNoHooksLU(cmdlib.NoHooksLU):
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)
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)
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")
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")
# 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,
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):
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))
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")
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")
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))
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()
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__":
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)
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)
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)
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)
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)
# 02110-1301, USA.
-"""Script for testing ganeti.hypervisor.hv_lxc"""
+"""Script for testing ganeti.hypervisor.hv_xen"""
import string # pylint: disable=W0402
import unittest
import shutil
import random
import os
+import mock
from ganeti import constants
from ganeti import objects
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)
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)
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,
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:
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)
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),
]
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,
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,
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):
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()
"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")
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:
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):
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:
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:
else:
try:
hv._MigrateInstance(NotImplemented, name, target, port, live,
+ hvparams,
_ping_fn=compat.partial(self._FakeTcpPing,
(target, port), False))
except errors.HypervisorError, err:
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 = \
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
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,
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"])
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",
"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],
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]],
(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 = [
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)
},
})
- 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,
},
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,
],
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,
],
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,
},
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,
"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,
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)
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))
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
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:
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:
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:
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"]],
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)]
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)]
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)]
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)]
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):
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([])
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)
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,
[])
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
# 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"),
])
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)
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):
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]]
from ganeti import ssconf
import testutils
+import mock
class TestReadSsconfFile(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):
--- /dev/null
+#!/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()
# 02110-1301, USA.
-"""Script for testing ganeti.storage"""
+"""Script for testing ganeti.storage.container"""
import re
import unittest
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:
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 = [
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")
--- /dev/null
+#!/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()
--- /dev/null
+#!/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()
--- /dev/null
+#!/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()
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"""
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.
#: 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):
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)
" 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():
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)
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):
# 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():
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)
"""
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,
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