Merge branch 'stable-2.9' into stable-2.10
authorThomas Thrainer <thomasth@google.com>
Mon, 16 Dec 2013 09:49:55 +0000 (10:49 +0100)
committerThomas Thrainer <thomasth@google.com>
Mon, 16 Dec 2013 10:37:06 +0000 (11:37 +0100)
* stable-2.9
  Bump revision for 2.9.2
  Update NEWS for 2.9.2 release
  Pass hvparams to GetInstanceInfo
  Adapt parameters that moved to instance variables
  Avoid lines longer than 80 chars
  SingleNotifyPipeCondition: don't share pollers
  KVM: use custom KVM path if set for version checking
* stable-2.8
  Version bump for 2.8.3
  Update NEWS for 2.8.3 release
  Support reseting arbitrary params of ext disks
  Allow modification of arbitrary params for ext
  Do not clear disk.params in UpgradeConfig()
  SetDiskID() before accepting an instance
  Lock group(s) when creating instances
  Fix job error message after unclean master shutdown
  Add default file_driver if missing
  Update tests
  Xen handle domain shutdown
  Fix evacuation out of drained node
  Refactor reading live data in htools
  master-up-setup: Ping multiple times with a shorter interval
  Add a packet number limit to "fping" in master-ip-setup
  Fix a bug in InstanceSetParams concerning names
  build_chroot: hard-code the version of blaze-builder
  Fix error printing
  Allow link local IPv6 gateways
  Fix NODE/NODE_RES locking in LUInstanceCreate
  eta-reduce isIpV6
  Ganeti.Rpc: use brackets for ipv6 addresses
  Update NEWS file with socket permission fix info
  Fix socket permissions after master-failover

Conflicts:
NEWS
configure.ac
devel/build_chroot
lib/constants.py
src/Ganeti/Rpc.hs

Resolution:
    NEWS: take both additions
    configure.ac: ignore version bump
    constants.py: move constants to Constants.hs
    instance_migration.py: Remove call to SetDiskID(...), it has been removed in 2.10
    instance_unittest.py: Adapt test to new logic in LU
    Rest: trivial

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

16 files changed:
1  2 
NEWS
devel/build_chroot
lib/cmdlib/instance.py
lib/hypervisor/hv_kvm.py
lib/hypervisor/hv_xen.py
lib/jqueue.py
lib/locking.py
lib/masterd/iallocator.py
lib/network.py
lib/objects.py
src/Ganeti/Constants.hs
src/Ganeti/HTools/Backend/IAlloc.hs
src/Ganeti/Luxi.hs
src/Ganeti/Rpc.hs
test/py/cmdlib/instance_unittest.py
test/py/ganeti.hypervisor.hv_xen_unittest.py

diff --cc NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -2,85 -2,39 +2,118 @@@ New
  ====
  
  
 +Version 2.10.0 beta1
 +--------------------
 +
 +*(Released Wed, 27 Nov 2013)*
 +
 +Incompatible/important changes
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +- Adding disks with 'gnt-instance modify' now waits for the disks to sync per
 +  default. Specify --no-wait-for-sync to override this behavior.
 +- The Ganeti python code now adheres to a private-module layout. In particular,
 +  the module 'ganeti' is no longer in the python search path.
 +- On instance allocation, the iallocator now considers non-LVM storage
 +  properly. In particular, actual file storage space information is used
 +  when allocating space for a file/sharedfile instance.
 +- When disabling disk templates cluster-wide, the cluster now first
 +  checks whether there are instances still using those templates.
 +- 'gnt-node list-storage' now also reports storage information about
 +  file-based storage types.
 +
 +New features
 +~~~~~~~~~~~~
 +
 +- KVM hypervisors can now access RBD storage directly without having to
 +  go through a block device.
 +- A new command 'gnt-cluster upgrade' was added that automates the upgrade
 +  procedure between two Ganeti versions that are both 2.10 or higher.
 +- The move-instance command can now change disk templates when moving
 +  instances, and does not require any node placement options to be
 +  specified if the destination cluster has a default iallocator.
 +- Users can now change the soundhw and cpuid settings for XEN hypervisors.
 +- Hail and hbal now have the (optional) capability of accessing average CPU
 +  load information through the monitoring deamon, and to use it to dynamically
 +  adapt the allocation of instances.
 +- Hotplug support. Introduce new option '--hotplug' to ``gnt-instance modify``
 +  so that disk and NIC modifications take effect without the need of actual
 +  reboot. There are a couple of constrains currently for this feature:
 +
 +   - only KVM hypervisor (versions >= 1.0) supports it,
 +   - one can not (yet) hotplug a disk using userspace access mode for RBD
 +   - in case of a downgrade instances should suffer a reboot in order to
 +     be migratable (due to core change of runtime files)
 +
 +Misc changes
 +~~~~~~~~~~~~
 +
 +- A new test framework for logical units was introduced and the test
 +  coverage for logical units was improved significantly.
 +- Opcodes are entirely generated from Haskell using the tool 'hs2py' and
 +  the module 'src/Ganeti/OpCodes.hs'.
 +- Constants are also generated from Haskell using the tool
 +  'hs2py-constants' and the module 'src/Ganeti/Constants.hs', with the
 +  exception of socket related constants, which require changing the
 +  cluster configuration file, and HVS related constants, because they
 +  are part of a port of instance queries to Haskell.  As a result, these
 +  changes will be part of the next release of Ganeti.
 +
 +New dependencies
 +~~~~~~~~~~~~~~~~
 +
 +The following new dependencies have been added/updated.
 +
 +Python
 +
 +- The version requirements for ``python-mock`` have increased to at least
 +  version 1.0.1. It is still used for testing only.
 +
 +Known issues
 +~~~~~~~~~~~~
 +
 +The following issues are known to be present in the beta and will be fixed
 +before rc1.
 +
 +- Issue 477: Wrong permissions for confd LUXI socket
 +- Issue 621: Instance related opcodes do not aquire network/group locks
 +- Issue 622: Assertion Error: Node locks differ from node resource locks
 +- Issue 623: IPv6 Masterd <-> Luxid communication error
 +
 +
+ Version 2.9.2
+ -------------
+ *(Released Fri, 13 Dec 2013)*
+ - use custom KVM path if set for version checking
+ - SingleNotifyPipeCondition: don't share pollers
+ Inherited from the 2.8 branch:
+ - Fixed Luxi daemon socket permissions after master-failover
+ - Improve IP version detection code directly checking for colons rather than
+   passing the family from the cluster object
+ - Fix NODE/NODE_RES locking in LUInstanceCreate by not acquiring NODE_RES locks
+   opportunistically anymore (Issue 622)
+ - Allow link local IPv6 gateways (Issue 624)
+ - Fix error printing (Issue 616)
+ - Fix a bug in InstanceSetParams concerning names: in case no name is passed in
+   disk modifications, keep the old one. If name=none then set disk name to
+   None.
+ - Update build_chroot script to work with the latest hackage packages
+ - Add a packet number limit to "fping" in master-ip-setup (Issue 630)
+ - Fix evacuation out of drained node (Issue 615)
+ - Add default file_driver if missing (Issue 571)
+ - Fix job error message after unclean master shutdown (Issue 618)
+ - Lock group(s) when creating instances (Issue 621)
+ - SetDiskID() before accepting an instance (Issue 633)
+ - Allow the ext template disks to receive arbitrary parameters, both at creation
+   time and while being modified
+ - Xen handle domain shutdown (future proofing cherry-pick)
+ - Refactor reading live data in htools (future proofing cherry-pick)
  Version 2.9.1
  -------------
  
  in_chroot -- \
    apt-get update
  
 -
 -# do not install libghc6-network-dev, since it's too old, and just
 -# confuses the dependencies
 -in_chroot -- \
 -  $APT_INSTALL \
 -    autoconf automake \
 -    ghc cabal-install \
 -    libghc6-curl-dev \
 -    libghc6-parallel-dev \
 -    libghc6-text-dev \
 -    libghc6-vector-dev \
 -    libpcre3-dev \
 -    hlint hscolour pandoc \
 -    graphviz socat qemu-utils \
 -    python-docutils \
 -    python-simplejson \
 -    python-pyparsing \
 -    python-pyinotify \
 -    python-pycurl \
 -    python-ipaddr \
 -    python-yaml \
 -    python-paramiko
 -
 -in_chroot -- \
 -  $APT_INSTALL python-setuptools python-dev build-essential
 -
 -in_chroot -- \
 -  easy_install \
 -    logilab-astng==0.24.1 \
 -    logilab-common==0.58.3 \
 -    mock==1.0.1 \
 -    pylint==0.26.0
 -
 -in_chroot -- \
 -  easy_install \
 -    sphinx==1.1.3 \
 -    pep8==1.3.3 \
 -    coverage==3.4 \
 -    bitarray==0.8.0
 -
 -in_chroot -- \
 -  cabal update
 -
 -in_chroot -- \
 -  cabal install --global \
 +case $DIST_RELEASE in
 +
 +  squeeze)
 +
 +    # do not install libghc6-network-dev, since it's too old, and just
 +    # confuses the dependencies
 +    in_chroot -- \
 +      $APT_INSTALL \
 +        autoconf automake \
 +        ghc cabal-install \
 +        libghc6-curl-dev \
 +        libghc6-parallel-dev \
 +        libghc6-text-dev \
 +        libghc6-vector-dev \
 +        libpcre3-dev \
 +        hlint hscolour pandoc \
 +        graphviz qemu-utils \
 +        python-docutils \
 +        python-simplejson \
 +        python-pyparsing \
 +        python-pyinotify \
 +        python-pycurl \
 +        python-ipaddr \
 +        python-yaml \
 +        python-paramiko
 +
 +    in_chroot -- \
 +      $APT_INSTALL python-setuptools python-dev build-essential
 +
 +    in_chroot -- \
 +      easy_install \
 +        logilab-astng==0.24.1 \
 +        logilab-common==0.58.3 \
 +        mock==1.0.1 \
 +        pylint==0.26.0
 +
 +    in_chroot -- \
 +      easy_install \
 +        sphinx==1.1.3 \
 +        pep8==1.3.3 \
 +        coverage==3.4 \
 +        bitarray==0.8.0
 +
 +    in_chroot -- \
 +      cabal update
 +
 +    in_chroot -- \
 +      cabal install --global \
+     blaze-builder==0.3.1.1 \
 -    network==2.3 \
 -    regex-pcre==0.94.2 \
 -    hinotify==0.3.2 \
 -    hslogger==1.1.4 \
 -    quickcheck==2.5.1.1 \
 -    attoparsec==0.10.1.1 \
 -    crypto==4.2.4 \
 -    MonadCatchIO-transformers==0.2.2.0 \
 -    mtl==2.0.1.0 \
 -    hashable==1.1.2.0 \
 -    case-insensitive==0.3 \
 -    parsec==3.0.1 \
 -    network==2.3 \
 -    snap-server==0.8.1 \
 -    json==0.4.4
 +        network==2.3 \
 +        regex-pcre==0.94.4 \
 +        hinotify==0.3.2 \
 +        hslogger==1.1.4 \
 +        quickcheck==2.5.1.1 \
 +        attoparsec==0.10.1.1 \
 +        crypto==4.2.4 \
 +        MonadCatchIO-transformers==0.2.2.0 \
 +        mtl==2.0.1.0 \
 +        hashable==1.1.2.0 \
 +        case-insensitive==0.3 \
 +        parsec==3.0.1 \
 +        snap-server==0.8.1 \
 +        json==0.4.4
 +
 +    in_chroot -- \
 +      cabal install --global \
 +        hunit==1.2.5.2 \
 +        happy==1.18.10 \
 +        hlint==1.8.43 \
 +        hscolour==1.20.3 \
 +        temporary==1.1.2.3 \
 +        test-framework==0.6.1 \
 +        test-framework-hunit==0.2.7 \
 +        test-framework-quickcheck2==0.2.12.3
 +
 +    in_chroot -- \
 +      cabal install --global cabal-file-th
 +
 +    in_chroot -- \
 +      cabal install --global shelltestrunner
 +
 +    #Install selected packages from backports
 +    in_chroot -- \
 +      $APT_INSTALL -t squeeze-backports \
 +        git \
 +        git-email \
 +        vim
 +
 +;;
 +
 +  wheezy)
 +
 +    in_chroot -- \
 +      $APT_INSTALL \
 +      autoconf automake ghc ghc-haddock libghc-network-dev \
 +      libghc-test-framework{,-hunit,-quickcheck2}-dev \
 +      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
 +      libghc-parallel-dev libghc-utf8-string-dev \
 +      libghc-hslogger-dev libghc-crypto-dev \
 +      libghc-regex-pcre-dev libghc-attoparsec-dev \
 +      libghc-vector-dev libghc-temporary-dev \
 +      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
 +      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
 +      python-simplejson python-pycurl python-paramiko \
 +      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
 +      shelltestrunner python-dev pylint openssh-client vim git git-email
 +
 +    # We need version 0.9.4 of pyinotify because the packaged version, 0.9.3, is
 +    # incompatibile with the packaged version of python-epydoc 3.0.1.
 +    # Reason: a logger class in pyinotify calculates its superclasses at
 +    # runtime, which clashes with python-epydoc's static analysis phase.
 +    #
 +    # Problem introduced in:
 +    #   https://github.com/seb-m/pyinotify/commit/2c7e8f8959d2f8528e0d90847df360
 +    # and "fixed" in:
 +    #   https://github.com/seb-m/pyinotify/commit/98c5f41a6e2e90827a63ff1b878596
 +
 +    in_chroot -- \
 +      easy_install pyinotify==0.9.4
 +
 +;;
 +
 +  *)
 +
 +    in_chroot -- \
 +      $APT_INSTALL \
 +      autoconf automake ghc ghc-haddock libghc-network-dev \
 +      libghc-test-framework{,-hunit,-quickcheck2}-dev \
 +      libghc-json-dev libghc-curl-dev libghc-hinotify-dev \
 +      libghc-parallel-dev libghc-utf8-string-dev \
 +      libghc-hslogger-dev libghc-crypto-dev \
 +      libghc-regex-pcre-dev libghc-attoparsec-dev \
 +      libghc-vector-dev libghc-temporary-dev \
 +      libghc-snap-server-dev libpcre3 libpcre3-dev hscolour hlint pandoc \
 +      python-setuptools python-sphinx python-epydoc graphviz python-pyparsing \
 +      python-simplejson python-pyinotify python-pycurl python-paramiko \
 +      python-bitarray python-ipaddr python-yaml qemu-utils python-coverage pep8 \
 +      shelltestrunner python-dev pylint openssh-client vim git git-email
 +
 +;;
 +esac
  
 -in_chroot -- \
 -  cabal install --global \
 -    hunit==1.2.5.2 \
 -    happy==1.18.10 \
 -    hlint==1.8.43 \
 -    hscolour==1.20.3 \
 -    temporary==1.1.2.3 \
 -    test-framework==0.6.1 \
 -    test-framework-hunit==0.2.7 \
 -    test-framework-quickcheck2==0.2.12.3
 -
 -in_chroot -- \
 -  cabal install --global cabal-file-th
 -
 -in_chroot -- \
 -  cabal install --global shelltestrunner
 +echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
  
 -#Install selected packages from backports
  in_chroot -- \
 -  $APT_INSTALL -t squeeze-backports \
 -    git \
 -    git-email \
 -    vim
 -
 -in_chroot -- \
 -  $APT_INSTALL sudo fakeroot rsync locales less
 -
 -echo "en_US.UTF-8 UTF-8" >> $CHDIR/etc/locale.gen
 +  $APT_INSTALL sudo fakeroot rsync locales less socat
  
  in_chroot -- \
    locale-gen
@@@ -3290,40 -3255,11 +3339,39 @@@ class LUInstanceSetParams(LogicalUnit)
                           disks=[(idx, disk, 0)],
                           cleanup=new_disks)
  
 -    return (disk, [
 -      ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
 -      ])
 +    changes = [
 +      ("disk/%d" % idx,
 +       "add:size=%s,mode=%s" % (disk.size, disk.mode)),
 +      ]
 +    if self.op.hotplug:
 +      result = self.rpc.call_blockdev_assemble(self.instance.primary_node,
 +                                               (disk, self.instance),
 +                                               self.instance.name, True, idx)
 +      if result.fail_msg:
 +        changes.append(("disk/%d" % idx, "assemble:failed"))
 +        self.LogWarning("Can't assemble newly created disk %d: %s",
 +                        idx, result.fail_msg)
 +      else:
 +        _, link_name = result.payload
 +        msg = self._HotplugDevice(constants.HOTPLUG_ACTION_ADD,
 +                                  constants.HOTPLUG_TARGET_DISK,
 +                                  disk, link_name, idx)
 +        changes.append(("disk/%d" % idx, msg))
 +
 +    return (disk, changes)
 +
 +  def _PostAddDisk(self, _, disk):
 +    if not WaitForSync(self, self.instance, disks=[disk],
 +                       oneshot=not self.op.wait_for_sync):
 +      raise errors.OpExecError("Failed to sync disks of %s" %
 +                               self.instance.name)
 +
 +    # the disk is active at this point, so deactivate it if the instance disks
 +    # are supposed to be inactive
 +    if not self.instance.disks_active:
 +      ShutdownInstanceDisks(self, self.instance, disks=[disk])
  
-   @staticmethod
-   def _ModifyDisk(idx, disk, params, _):
+   def _ModifyDisk(self, idx, disk, params, _):
      """Modifies a disk.
  
      """
Simple merge
Simple merge
diff --cc lib/jqueue.py
Simple merge
diff --cc lib/locking.py
Simple merge
Simple merge
diff --cc lib/network.py
Simple merge
diff --cc lib/objects.py
Simple merge
index a66c30d,0000000..4b4db3a
mode 100644,000000..100644
--- /dev/null
@@@ -1,4551 -1,0 +1,4566 @@@
 +{-# OPTIONS -fno-warn-type-defaults #-}
 +{-| Constants contains the Haskell constants
 +
 +The constants in this module are used in Haskell and are also
 +converted to Python.
 +
 +Do not write any definitions in this file other than constants.  Do
 +not even write helper functions.  The definitions in this module are
 +automatically stripped to build the Makefile.am target
 +'ListConstants.hs'.  If there are helper functions in this module,
 +they will also be dragged and it will cause compilation to fail.
 +Therefore, all helper functions should go to a separate module and
 +imported.
 +
 +-}
 +
 +{-
 +
 +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.Constants where
 +
 +import Control.Arrow ((***))
 +import Data.List ((\\))
 +import Data.Map (Map)
 +import qualified Data.Map as Map (empty, fromList, keys, insert)
 +
 +import qualified AutoConf
 +import Ganeti.ConstantUtils (PythonChar(..), FrozenSet, Protocol(..),
 +                             buildVersion)
 +import qualified Ganeti.ConstantUtils as ConstantUtils
 +import Ganeti.HTools.Types (AutoRepairResult(..), AutoRepairType(..))
 +import qualified Ganeti.HTools.Types as Types
 +import Ganeti.Logging (SyslogUsage(..))
 +import qualified Ganeti.Logging as Logging (syslogUsageToRaw)
 +import qualified Ganeti.Runtime as Runtime
 +import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..),
 +                       ExtraLogReason(..))
 +import Ganeti.THH (PyValueEx(..))
 +import Ganeti.Types
 +import qualified Ganeti.Types as Types
 +import Ganeti.Confd.Types (ConfdRequestType(..), ConfdReqField(..),
 +                           ConfdReplyStatus(..), ConfdNodeRole(..),
 +                           ConfdErrorType(..))
 +import qualified Ganeti.Confd.Types as Types
 +
 +{-# ANN module "HLint: ignore Use camelCase" #-}
 +
 +-- * 'autoconf' constants for Python only ('autotools/build-bash-completion')
 +
 +htoolsProgs :: [String]
 +htoolsProgs = AutoConf.htoolsProgs
 +
 +-- * 'autoconf' constants for Python only ('lib/constants.py')
 +
 +drbdBarriers :: String
 +drbdBarriers = AutoConf.drbdBarriers
 +
 +drbdNoMetaFlush :: Bool
 +drbdNoMetaFlush = AutoConf.drbdNoMetaFlush
 +
 +lvmStripecount :: Int
 +lvmStripecount = AutoConf.lvmStripecount
 +
 +hasGnuLn :: Bool
 +hasGnuLn = AutoConf.hasGnuLn
 +
 +-- * 'autoconf' constants for Python only ('lib/pathutils.py')
 +
 +-- ** Build-time constants
 +
 +exportDir :: String
 +exportDir = AutoConf.exportDir
 +
 +osSearchPath :: [String]
 +osSearchPath = AutoConf.osSearchPath
 +
 +esSearchPath :: [String]
 +esSearchPath = AutoConf.esSearchPath
 +
 +sshConfigDir :: String
 +sshConfigDir = AutoConf.sshConfigDir
 +
 +xenConfigDir :: String
 +xenConfigDir = AutoConf.xenConfigDir
 +
 +sysconfdir :: String
 +sysconfdir = AutoConf.sysconfdir
 +
 +toolsdir :: String
 +toolsdir = AutoConf.toolsdir
 +
 +localstatedir :: String
 +localstatedir = AutoConf.localstatedir
 +
 +-- ** Paths which don't change for a virtual cluster
 +
 +pkglibdir :: String
 +pkglibdir = AutoConf.pkglibdir
 +
 +sharedir :: String
 +sharedir = AutoConf.sharedir
 +
 +-- * 'autoconf' constants for Python only ('lib/build/sphinx_ext.py')
 +
 +manPages :: Map String Int
 +manPages = Map.fromList AutoConf.manPages
 +
 +-- * 'autoconf' constants for QA cluster only ('qa/qa_cluster.py')
 +
 +versionedsharedir :: String
 +versionedsharedir = AutoConf.versionedsharedir
 +
 +-- * 'autoconf' constants for Python only ('tests/py/docs_unittest.py')
 +
 +gntScripts :: [String]
 +gntScripts = AutoConf.gntScripts
 +
 +-- * Various versions
 +
 +releaseVersion :: String
 +releaseVersion = AutoConf.packageVersion
 +
 +versionMajor :: Int
 +versionMajor = AutoConf.versionMajor
 +
 +versionMinor :: Int
 +versionMinor = AutoConf.versionMinor
 +
 +versionRevision :: Int
 +versionRevision = AutoConf.versionRevision
 +
 +dirVersion :: String
 +dirVersion = AutoConf.dirVersion
 +
 +osApiV10 :: Int
 +osApiV10 = 10
 +
 +osApiV15 :: Int
 +osApiV15 = 15
 +
 +osApiV20 :: Int
 +osApiV20 = 20
 +
 +osApiVersions :: FrozenSet Int
 +osApiVersions = ConstantUtils.mkSet [osApiV10, osApiV15, osApiV20]
 +
 +exportVersion :: Int
 +exportVersion = 0
 +
 +rapiVersion :: Int
 +rapiVersion = 2
 +
 +configMajor :: Int
 +configMajor = AutoConf.versionMajor
 +
 +configMinor :: Int
 +configMinor = AutoConf.versionMinor
 +
 +-- | The configuration is supposed to remain stable across
 +-- revisions. Therefore, the revision number is cleared to '0'.
 +configRevision :: Int
 +configRevision = 0
 +
 +configVersion :: Int
 +configVersion = buildVersion configMajor configMinor configRevision
 +
 +-- | Similarly to the configuration (see 'configRevision'), the
 +-- protocols are supposed to remain stable across revisions.
 +protocolVersion :: Int
 +protocolVersion = buildVersion configMajor configMinor configRevision
 +
 +-- * User separation
 +
 +daemonsGroup :: String
 +daemonsGroup = Runtime.daemonGroup (ExtraGroup DaemonsGroup)
 +
 +adminGroup :: String
 +adminGroup = Runtime.daemonGroup (ExtraGroup AdminGroup)
 +
 +masterdUser :: String
 +masterdUser = Runtime.daemonUser GanetiMasterd
 +
 +masterdGroup :: String
 +masterdGroup = Runtime.daemonGroup (DaemonGroup GanetiMasterd)
 +
 +rapiUser :: String
 +rapiUser = Runtime.daemonUser GanetiRapi
 +
 +rapiGroup :: String
 +rapiGroup = Runtime.daemonGroup (DaemonGroup GanetiRapi)
 +
 +confdUser :: String
 +confdUser = Runtime.daemonUser GanetiConfd
 +
 +confdGroup :: String
 +confdGroup = Runtime.daemonGroup (DaemonGroup GanetiConfd)
 +
 +luxidUser :: String
 +luxidUser = Runtime.daemonUser GanetiLuxid
 +
 +luxidGroup :: String
 +luxidGroup = Runtime.daemonGroup (DaemonGroup GanetiLuxid)
 +
 +nodedUser :: String
 +nodedUser = Runtime.daemonUser GanetiNoded
 +
 +nodedGroup :: String
 +nodedGroup = Runtime.daemonGroup (DaemonGroup GanetiNoded)
 +
 +mondUser :: String
 +mondUser = Runtime.daemonUser GanetiMond
 +
 +mondGroup :: String
 +mondGroup = Runtime.daemonGroup (DaemonGroup GanetiMond)
 +
 +sshLoginUser :: String
 +sshLoginUser = AutoConf.sshLoginUser
 +
 +sshConsoleUser :: String
 +sshConsoleUser = AutoConf.sshConsoleUser
 +
 +-- * Cpu pinning separators and constants
 +
 +cpuPinningSep :: String
 +cpuPinningSep = ":"
 +
 +cpuPinningAll :: String
 +cpuPinningAll = "all"
 +
 +-- | Internal representation of "all"
 +cpuPinningAllVal :: Int
 +cpuPinningAllVal = -1
 +
 +-- | One "all" entry in a CPU list means CPU pinning is off
 +cpuPinningOff :: [Int]
 +cpuPinningOff = [cpuPinningAllVal]
 +
 +-- | A Xen-specific implementation detail is that there is no way to
 +-- actually say "use any cpu for pinning" in a Xen configuration file,
 +-- as opposed to the command line, where you can say
 +-- @
 +-- xm vcpu-pin <domain> <vcpu> all
 +-- @
 +--
 +-- The workaround used in Xen is "0-63" (see source code function
 +-- "xm_vcpu_pin" in @<xen-source>/tools/python/xen/xm/main.py@).
 +--
 +-- To support future changes, the following constant is treated as a
 +-- blackbox string that simply means "use any cpu for pinning under
 +-- xen".
 +cpuPinningAllXen :: String
 +cpuPinningAllXen = "0-63"
 +
 +-- | A KVM-specific implementation detail - the following value is
 +-- used to set CPU affinity to all processors (--0 through --31), per
 +-- taskset man page.
 +--
 +-- FIXME: This only works for machines with up to 32 CPU cores
 +cpuPinningAllKvm :: Int
 +cpuPinningAllKvm = 0xFFFFFFFF
 +
 +-- * Wipe
 +
 +ddCmd :: String
 +ddCmd = "dd"
 +
 +-- | 1GB
 +maxWipeChunk :: Int
 +maxWipeChunk = 1024
 +
 +minWipeChunkPercent :: Int
 +minWipeChunkPercent = 10
 +
 +-- * Directories
 +
 +runDirsMode :: Int
 +runDirsMode = 0o775
 +
 +secureDirMode :: Int
 +secureDirMode = 0o700
 +
 +secureFileMode :: Int
 +secureFileMode = 0o600
 +
 +adoptableBlockdevRoot :: String
 +adoptableBlockdevRoot = "/dev/disk/"
 +
 +-- * 'autoconf' enable/disable
 +
 +enableConfd :: Bool
 +enableConfd = AutoConf.enableConfd
 +
 +enableMond :: Bool
 +enableMond = AutoConf.enableMond
 +
 +enableRestrictedCommands :: Bool
 +enableRestrictedCommands = AutoConf.enableRestrictedCommands
 +
 +enableSplitQuery :: Bool
 +enableSplitQuery = AutoConf.enableSplitQuery
 +
 +-- * SSH constants
 +
 +ssh :: String
 +ssh = "ssh"
 +
 +scp :: String
 +scp = "scp"
 +
 +-- * Daemons
 +
 +confd :: String
 +confd = Runtime.daemonName GanetiConfd
 +
 +masterd :: String
 +masterd = Runtime.daemonName GanetiMasterd
 +
 +mond :: String
 +mond = Runtime.daemonName GanetiMond
 +
 +noded :: String
 +noded = Runtime.daemonName GanetiNoded
 +
 +luxid :: String
 +luxid = Runtime.daemonName GanetiLuxid
 +
 +rapi :: String
 +rapi = Runtime.daemonName GanetiRapi
 +
 +daemons :: FrozenSet String
 +daemons =
 +  ConstantUtils.mkSet [confd,
 +                       luxid,
 +                       masterd,
 +                       mond,
 +                       noded,
 +                       rapi]
 +
 +defaultConfdPort :: Int
 +defaultConfdPort = 1814
 +
 +defaultMondPort :: Int
 +defaultMondPort = 1815
 +
 +defaultNodedPort :: Int
 +defaultNodedPort = 1811
 +
 +defaultRapiPort :: Int
 +defaultRapiPort = 5080
 +
 +daemonsPorts :: Map String (Protocol, Int)
 +daemonsPorts =
 +  Map.fromList [(confd, (Udp, defaultConfdPort)),
 +                (mond, (Tcp, defaultMondPort)),
 +                (noded, (Tcp, defaultNodedPort)),
 +                (rapi, (Tcp, defaultRapiPort)),
 +                (ssh, (Tcp, 22))]
 +
 +firstDrbdPort :: Int
 +firstDrbdPort = 11000
 +
 +lastDrbdPort :: Int
 +lastDrbdPort = 14999
 +
 +daemonsLogbase :: Map String String
 +daemonsLogbase =
 +  Map.fromList
 +  [ (Runtime.daemonName d, Runtime.daemonLogBase d) | d <- [minBound..] ]
 +
 +daemonsExtraLogbase :: Map String (Map String String)
 +daemonsExtraLogbase =
 +  Map.fromList $
 +  map (Runtime.daemonName *** id)
 +  [ (GanetiMond, Map.fromList
 +                 [ ("access", Runtime.daemonsExtraLogbase GanetiMond AccessLog)
 +                 , ("error", Runtime.daemonsExtraLogbase GanetiMond ErrorLog)
 +                 ])
 +  ]
 +
 +extraLogreasonAccess :: String
 +extraLogreasonAccess = Runtime.daemonsExtraLogbase GanetiMond AccessLog
 +
 +extraLogreasonError :: String
 +extraLogreasonError = Runtime.daemonsExtraLogbase GanetiMond ErrorLog
 +
 +devConsole :: String
 +devConsole = ConstantUtils.devConsole
 +
 +procMounts :: String
 +procMounts = "/proc/mounts"
 +
 +-- * Luxi (Local UniX Interface) related constants
 +
 +luxiEom :: PythonChar
 +luxiEom = PythonChar '\x03'
 +
 +-- | Environment variable for the luxi override socket
 +luxiOverride :: String
 +luxiOverride = "FORCE_LUXI_SOCKET"
 +
 +luxiOverrideMaster :: String
 +luxiOverrideMaster = "master"
 +
 +luxiOverrideQuery :: String
 +luxiOverrideQuery = "query"
 +
 +luxiVersion :: Int
 +luxiVersion = configVersion
 +
 +-- * Syslog
 +
 +syslogUsage :: String
 +syslogUsage = AutoConf.syslogUsage
 +
 +syslogNo :: String
 +syslogNo = Logging.syslogUsageToRaw SyslogNo
 +
 +syslogYes :: String
 +syslogYes = Logging.syslogUsageToRaw SyslogYes
 +
 +syslogOnly :: String
 +syslogOnly = Logging.syslogUsageToRaw SyslogOnly
 +
 +syslogSocket :: String
 +syslogSocket = "/dev/log"
 +
 +exportConfFile :: String
 +exportConfFile = "config.ini"
 +
 +-- * Xen
 +
 +xenBootloader :: String
 +xenBootloader = AutoConf.xenBootloader
 +
 +xenCmdXl :: String
 +xenCmdXl = "xl"
 +
 +xenCmdXm :: String
 +xenCmdXm = "xm"
 +
 +xenInitrd :: String
 +xenInitrd = AutoConf.xenInitrd
 +
 +xenKernel :: String
 +xenKernel = AutoConf.xenKernel
 +
 +-- FIXME: perhaps rename to 'validXenCommands' for consistency with
 +-- other constants
 +knownXenCommands :: FrozenSet String
 +knownXenCommands = ConstantUtils.mkSet [xenCmdXl, xenCmdXm]
 +
 +-- * KVM and socat
 +
 +kvmPath :: String
 +kvmPath = AutoConf.kvmPath
 +
 +kvmKernel :: String
 +kvmKernel = AutoConf.kvmKernel
 +
 +socatEscapeCode :: String
 +socatEscapeCode = "0x1d"
 +
 +socatPath :: String
 +socatPath = AutoConf.socatPath
 +
 +socatUseCompress :: Bool
 +socatUseCompress = AutoConf.socatUseCompress
 +
 +socatUseEscape :: Bool
 +socatUseEscape = AutoConf.socatUseEscape
 +
 +-- * Console types
 +
 +-- | Display a message for console access
 +consMessage :: String
 +consMessage = "msg"
 +
 +-- | Console as SPICE server
 +consSpice :: String
 +consSpice = "spice"
 +
 +-- | Console as SSH command
 +consSsh :: String
 +consSsh = "ssh"
 +
 +-- | Console as VNC server
 +consVnc :: String
 +consVnc = "vnc"
 +
 +consAll :: FrozenSet String
 +consAll = ConstantUtils.mkSet [consMessage, consSpice, consSsh, consVnc]
 +
 +-- | RSA key bit length
 +--
 +-- For RSA keys more bits are better, but they also make operations
 +-- more expensive. NIST SP 800-131 recommends a minimum of 2048 bits
 +-- from the year 2010 on.
 +rsaKeyBits :: Int
 +rsaKeyBits = 2048
 +
 +-- | Ciphers allowed for SSL connections.
 +--
 +-- For the format, see ciphers(1). A better way to disable ciphers
 +-- would be to use the exclamation mark (!), but socat versions below
 +-- 1.5 can't parse exclamation marks in options properly. When
 +-- modifying the ciphers, ensure not to accidentially add something
 +-- after it's been removed. Use the "openssl" utility to check the
 +-- allowed ciphers, e.g.  "openssl ciphers -v HIGH:-DES".
 +opensslCiphers :: String
 +opensslCiphers = "HIGH:-DES:-3DES:-EXPORT:-ADH"
 +
 +-- * X509
 +
 +-- | commonName (CN) used in certificates
 +x509CertCn :: String
 +x509CertCn = "ganeti.example.com"
 +
 +-- | Default validity of certificates in days
 +x509CertDefaultValidity :: Int
 +x509CertDefaultValidity = 365 * 5
 +
 +x509CertSignatureHeader :: String
 +x509CertSignatureHeader = "X-Ganeti-Signature"
 +
 +-- | Digest used to sign certificates ("openssl x509" uses SHA1 by default)
 +x509CertSignDigest :: String
 +x509CertSignDigest = "SHA1"
 +
 +-- * Import/export daemon mode
 +
 +iemExport :: String
 +iemExport = "export"
 +
 +iemImport :: String
 +iemImport = "import"
 +
 +-- * Import/export transport compression
 +
 +iecGzip :: String
 +iecGzip = "gzip"
 +
 +iecNone :: String
 +iecNone = "none"
 +
 +iecAll :: [String]
 +iecAll = [iecGzip, iecNone]
 +
 +ieCustomSize :: String
 +ieCustomSize = "fd"
 +
 +-- * Import/export I/O
 +
 +-- | Direct file I/O, equivalent to a shell's I/O redirection using
 +-- '<' or '>'
 +ieioFile :: String
 +ieioFile = "file"
 +
 +-- | Raw block device I/O using "dd"
 +ieioRawDisk :: String
 +ieioRawDisk = "raw"
 +
 +-- | OS definition import/export script
 +ieioScript :: String
 +ieioScript = "script"
 +
 +-- * Values
 +
 +valueDefault :: String
 +valueDefault = "default"
 +
 +valueAuto :: String
 +valueAuto = "auto"
 +
 +valueGenerate :: String
 +valueGenerate = "generate"
 +
 +valueNone :: String
 +valueNone = "none"
 +
 +valueTrue :: String
 +valueTrue = "true"
 +
 +valueFalse :: String
 +valueFalse = "false"
 +
 +-- * Hooks
 +
 +hooksNameCfgupdate :: String
 +hooksNameCfgupdate = "config-update"
 +
 +hooksNameWatcher :: String
 +hooksNameWatcher = "watcher"
 +
 +hooksPath :: String
 +hooksPath = "/sbin:/bin:/usr/sbin:/usr/bin"
 +
 +hooksPhasePost :: String
 +hooksPhasePost = "post"
 +
 +hooksPhasePre :: String
 +hooksPhasePre = "pre"
 +
 +hooksVersion :: Int
 +hooksVersion = 2
 +
 +-- * Hooks subject type (what object type does the LU deal with)
 +
 +htypeCluster :: String
 +htypeCluster = "CLUSTER"
 +
 +htypeGroup :: String
 +htypeGroup = "GROUP"
 +
 +htypeInstance :: String
 +htypeInstance = "INSTANCE"
 +
 +htypeNetwork :: String
 +htypeNetwork = "NETWORK"
 +
 +htypeNode :: String
 +htypeNode = "NODE"
 +
 +-- * Hkr
 +
 +hkrSkip :: Int
 +hkrSkip = 0
 +
 +hkrFail :: Int
 +hkrFail = 1
 +
 +hkrSuccess :: Int
 +hkrSuccess = 2
 +
 +-- * Storage types
 +
 +stBlock :: String
 +stBlock = Types.storageTypeToRaw StorageBlock
 +
 +stDiskless :: String
 +stDiskless = Types.storageTypeToRaw StorageDiskless
 +
 +stExt :: String
 +stExt = Types.storageTypeToRaw StorageExt
 +
 +stFile :: String
 +stFile = Types.storageTypeToRaw StorageFile
 +
 +stLvmPv :: String
 +stLvmPv = Types.storageTypeToRaw StorageLvmPv
 +
 +stLvmVg :: String
 +stLvmVg = Types.storageTypeToRaw StorageLvmVg
 +
 +stRados :: String
 +stRados = Types.storageTypeToRaw StorageRados
 +
 +storageTypes :: FrozenSet String
 +storageTypes = ConstantUtils.mkSet $ map Types.storageTypeToRaw [minBound..]
 +
 +-- | The set of storage types for which storage reporting is available
 +--
 +-- FIXME: Remove this, once storage reporting is available for all
 +-- types.
 +stsReport :: FrozenSet String
 +stsReport = ConstantUtils.mkSet [stFile, stLvmPv, stLvmVg]
 +
 +-- * Storage fields
 +-- ** First two are valid in LU context only, not passed to backend
 +
 +sfNode :: String
 +sfNode = "node"
 +
 +sfType :: String
 +sfType = "type"
 +
 +-- ** and the rest are valid in backend
 +
 +sfAllocatable :: String
 +sfAllocatable = Types.storageFieldToRaw SFAllocatable
 +
 +sfFree :: String
 +sfFree = Types.storageFieldToRaw SFFree
 +
 +sfName :: String
 +sfName = Types.storageFieldToRaw SFName
 +
 +sfSize :: String
 +sfSize = Types.storageFieldToRaw SFSize
 +
 +sfUsed :: String
 +sfUsed = Types.storageFieldToRaw SFUsed
 +
 +validStorageFields :: FrozenSet String
 +validStorageFields =
 +  ConstantUtils.mkSet $ map Types.storageFieldToRaw [minBound..] ++
 +                        [sfNode, sfType]
 +
 +modifiableStorageFields :: Map String (FrozenSet String)
 +modifiableStorageFields =
 +  Map.fromList [(Types.storageTypeToRaw StorageLvmPv,
 +                 ConstantUtils.mkSet [sfAllocatable])]
 +
 +-- * Storage operations
 +
 +soFixConsistency :: String
 +soFixConsistency = "fix-consistency"
 +
 +validStorageOperations :: Map String (FrozenSet String)
 +validStorageOperations =
 +  Map.fromList [(Types.storageTypeToRaw StorageLvmVg,
 +                 ConstantUtils.mkSet [soFixConsistency])]
 +
 +-- * Volume fields
 +
 +vfDev :: String
 +vfDev = "dev"
 +
 +vfInstance :: String
 +vfInstance = "instance"
 +
 +vfName :: String
 +vfName = "name"
 +
 +vfNode :: String
 +vfNode = "node"
 +
 +vfPhys :: String
 +vfPhys = "phys"
 +
 +vfSize :: String
 +vfSize = "size"
 +
 +vfVg :: String
 +vfVg = "vg"
 +
 +-- * Local disk status
 +
 +ldsFaulty :: Int
 +ldsFaulty = Types.localDiskStatusToRaw DiskStatusFaulty
 +
 +ldsOkay :: Int
 +ldsOkay = Types.localDiskStatusToRaw DiskStatusOk
 +
 +ldsUnknown :: Int
 +ldsUnknown = Types.localDiskStatusToRaw DiskStatusUnknown
 +
 +ldsNames :: Map Int String
 +ldsNames =
 +  Map.fromList [ (Types.localDiskStatusToRaw ds,
 +                  localDiskStatusName ds) | ds <- [minBound..] ]
 +
 +-- * Disk template types
 +
 +dtDiskless :: String
 +dtDiskless = Types.diskTemplateToRaw DTDiskless
 +
 +dtFile :: String
 +dtFile = Types.diskTemplateToRaw DTFile
 +
 +dtSharedFile :: String
 +dtSharedFile = Types.diskTemplateToRaw DTSharedFile
 +
 +dtPlain :: String
 +dtPlain = Types.diskTemplateToRaw DTPlain
 +
 +dtBlock :: String
 +dtBlock = Types.diskTemplateToRaw DTBlock
 +
 +dtDrbd8 :: String
 +dtDrbd8 = Types.diskTemplateToRaw DTDrbd8
 +
 +dtRbd :: String
 +dtRbd = Types.diskTemplateToRaw DTRbd
 +
 +dtExt :: String
 +dtExt = Types.diskTemplateToRaw DTExt
 +
 +-- | This is used to order determine the default disk template when
 +-- the list of enabled disk templates is inferred from the current
 +-- state of the cluster.  This only happens on an upgrade from a
 +-- version of Ganeti that did not support the 'enabled_disk_templates'
 +-- so far.
 +diskTemplatePreference :: [String]
 +diskTemplatePreference =
 +  map Types.diskTemplateToRaw
 +  [DTBlock, DTDiskless, DTDrbd8, DTExt, DTFile, DTPlain, DTRbd, DTSharedFile]
 +
 +diskTemplates :: FrozenSet String
 +diskTemplates = ConstantUtils.mkSet $ map Types.diskTemplateToRaw [minBound..]
 +
 +-- | Disk templates that are enabled by default
 +defaultEnabledDiskTemplates :: [String]
 +defaultEnabledDiskTemplates = map Types.diskTemplateToRaw [DTDrbd8, DTPlain]
 +
 +-- | Mapping of disk templates to storage types
 +mapDiskTemplateStorageType :: Map String String
 +mapDiskTemplateStorageType =
 +  Map.fromList $
 +  map (Types.diskTemplateToRaw *** Types.storageTypeToRaw)
 +  [(DTBlock, StorageBlock),
 +   (DTDrbd8, StorageLvmVg),
 +   (DTExt, StorageExt),
 +   (DTSharedFile, StorageFile),
 +   (DTFile, StorageFile),
 +   (DTDiskless, StorageDiskless),
 +   (DTPlain, StorageLvmVg),
 +   (DTRbd, StorageRados)]
 +
 +-- | The set of network-mirrored disk templates
 +dtsIntMirror :: FrozenSet String
 +dtsIntMirror = ConstantUtils.mkSet [dtDrbd8]
 +
 +-- | 'DTDiskless' is 'trivially' externally mirrored
 +dtsExtMirror :: FrozenSet String
 +dtsExtMirror =
 +  ConstantUtils.mkSet $
 +  map Types.diskTemplateToRaw [DTDiskless, DTBlock, DTExt, DTSharedFile, DTRbd]
 +
 +-- | The set of non-lvm-based disk templates
 +dtsNotLvm :: FrozenSet String
 +dtsNotLvm =
 +  ConstantUtils.mkSet $
 +  map Types.diskTemplateToRaw
 +  [DTSharedFile, DTDiskless, DTBlock, DTExt, DTFile, DTRbd]
 +
 +-- | The set of disk templates which can be grown
 +dtsGrowable :: FrozenSet String
 +dtsGrowable =
 +  ConstantUtils.mkSet $
 +  map Types.diskTemplateToRaw
 +  [DTSharedFile, DTDrbd8, DTPlain, DTExt, DTFile, DTRbd]
 +
 +-- | The set of disk templates that allow adoption
 +dtsMayAdopt :: FrozenSet String
 +dtsMayAdopt =
 +  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTBlock, DTPlain]
 +
 +-- | The set of disk templates that *must* use adoption
 +dtsMustAdopt :: FrozenSet String
 +dtsMustAdopt = ConstantUtils.mkSet [Types.diskTemplateToRaw DTBlock]
 +
 +-- | The set of disk templates that allow migrations
 +dtsMirrored :: FrozenSet String
 +dtsMirrored = dtsIntMirror `ConstantUtils.union` dtsExtMirror
 +
 +-- | The set of file based disk templates
 +dtsFilebased :: FrozenSet String
 +dtsFilebased =
 +  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTSharedFile, DTFile]
 +
 +-- | The set of disk templates that can be moved by copying
 +--
 +-- Note: a requirement is that they're not accessed externally or
 +-- shared between nodes; in particular, sharedfile is not suitable.
 +dtsCopyable :: FrozenSet String
 +dtsCopyable =
 +  ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTPlain, DTFile]
 +
 +-- | The set of disk templates that are supported by exclusive_storage
 +dtsExclStorage :: FrozenSet String
 +dtsExclStorage = ConstantUtils.mkSet $ map Types.diskTemplateToRaw [DTPlain]
 +
 +-- | Templates for which we don't perform checks on free space
 +dtsNoFreeSpaceCheck :: FrozenSet String
 +dtsNoFreeSpaceCheck =
 +  ConstantUtils.mkSet $
 +  map Types.diskTemplateToRaw [DTExt, DTSharedFile, DTFile, DTRbd]
 +
 +dtsBlock :: FrozenSet String
 +dtsBlock =
 +  ConstantUtils.mkSet $
 +  map Types.diskTemplateToRaw [DTPlain, DTDrbd8, DTBlock, DTRbd, DTExt]
 +
 +-- | The set of lvm-based disk templates
 +dtsLvm :: FrozenSet String
 +dtsLvm = diskTemplates `ConstantUtils.difference` dtsNotLvm
 +
 +-- * Drbd
 +
 +drbdHmacAlg :: String
 +drbdHmacAlg = "md5"
 +
 +drbdDefaultNetProtocol :: String
 +drbdDefaultNetProtocol = "C"
 +
 +drbdMigrationNetProtocol :: String
 +drbdMigrationNetProtocol = "C"
 +
 +drbdStatusFile :: String
 +drbdStatusFile = "/proc/drbd"
 +
 +-- | Size of DRBD meta block device
 +drbdMetaSize :: Int
 +drbdMetaSize = 128
 +
 +-- * Drbd barrier types
 +
 +drbdBDiskBarriers :: String
 +drbdBDiskBarriers = "b"
 +
 +drbdBDiskDrain :: String
 +drbdBDiskDrain = "d"
 +
 +drbdBDiskFlush :: String
 +drbdBDiskFlush = "f"
 +
 +drbdBNone :: String
 +drbdBNone = "n"
 +
 +-- | Valid barrier combinations: "n" or any non-null subset of "bfd"
 +drbdValidBarrierOpt :: FrozenSet (FrozenSet String)
 +drbdValidBarrierOpt =
 +  ConstantUtils.mkSet
 +  [ ConstantUtils.mkSet [drbdBNone]
 +  , ConstantUtils.mkSet [drbdBDiskBarriers]
 +  , ConstantUtils.mkSet [drbdBDiskDrain]
 +  , ConstantUtils.mkSet [drbdBDiskFlush]
 +  , ConstantUtils.mkSet [drbdBDiskDrain, drbdBDiskFlush]
 +  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskDrain]
 +  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskFlush]
 +  , ConstantUtils.mkSet [drbdBDiskBarriers, drbdBDiskFlush, drbdBDiskDrain]
 +  ]
 +
 +-- | Rbd tool command
 +rbdCmd :: String
 +rbdCmd = "rbd"
 +
 +-- * File backend driver
 +
 +fdBlktap :: String
 +fdBlktap = Types.fileDriverToRaw FileBlktap
 +
 +fdLoop :: String
 +fdLoop = Types.fileDriverToRaw FileLoop
 +
++fdDefault :: String
++fdDefault = fdLoop
++
 +fileDriver :: FrozenSet String
 +fileDriver =
 +  ConstantUtils.mkSet $
 +  map Types.fileDriverToRaw [minBound..]
 +
 +-- | The set of drbd-like disk types
 +dtsDrbd :: FrozenSet String
 +dtsDrbd = ConstantUtils.mkSet [Types.diskTemplateToRaw DTDrbd8]
 +
 +-- * Disk access mode
 +
 +diskRdonly :: String
 +diskRdonly = Types.diskModeToRaw DiskRdOnly
 +
 +diskRdwr :: String
 +diskRdwr = Types.diskModeToRaw DiskRdWr
 +
 +diskAccessSet :: FrozenSet String
 +diskAccessSet = ConstantUtils.mkSet $ map Types.diskModeToRaw [minBound..]
 +
 +-- * Disk replacement mode
 +
 +replaceDiskAuto :: String
 +replaceDiskAuto = Types.replaceDisksModeToRaw ReplaceAuto
 +
 +replaceDiskChg :: String
 +replaceDiskChg = Types.replaceDisksModeToRaw ReplaceNewSecondary
 +
 +replaceDiskPri :: String
 +replaceDiskPri = Types.replaceDisksModeToRaw ReplaceOnPrimary
 +
 +replaceDiskSec :: String
 +replaceDiskSec = Types.replaceDisksModeToRaw ReplaceOnSecondary
 +
 +replaceModes :: FrozenSet String
 +replaceModes =
 +  ConstantUtils.mkSet $ map Types.replaceDisksModeToRaw [minBound..]
 +
 +-- * Instance export mode
 +
 +exportModeLocal :: String
 +exportModeLocal = Types.exportModeToRaw ExportModeLocal
 +
 +exportModeRemote :: String
 +exportModeRemote = Types.exportModeToRaw ExportModeRemote
 +
 +exportModes :: FrozenSet String
 +exportModes = ConstantUtils.mkSet $ map Types.exportModeToRaw [minBound..]
 +
 +-- * Instance creation modes
 +
 +instanceCreate :: String
 +instanceCreate = Types.instCreateModeToRaw InstCreate
 +
 +instanceImport :: String
 +instanceImport = Types.instCreateModeToRaw InstImport
 +
 +instanceRemoteImport :: String
 +instanceRemoteImport = Types.instCreateModeToRaw InstRemoteImport
 +
 +instanceCreateModes :: FrozenSet String
 +instanceCreateModes =
 +  ConstantUtils.mkSet $ map Types.instCreateModeToRaw [minBound..]
 +
 +-- * Remote import/export handshake message and version
 +
 +rieHandshake :: String
 +rieHandshake = "Hi, I'm Ganeti"
 +
 +rieVersion :: Int
 +rieVersion = 0
 +
 +-- | Remote import/export certificate validity (seconds)
 +rieCertValidity :: Int
 +rieCertValidity = 24 * 60 * 60
 +
 +-- | Export only: how long to wait per connection attempt (seconds)
 +rieConnectAttemptTimeout :: Int
 +rieConnectAttemptTimeout = 20
 +
 +-- | Export only: number of attempts to connect
 +rieConnectRetries :: Int
 +rieConnectRetries = 10
 +
 +-- | Overall timeout for establishing connection
 +rieConnectTimeout :: Int
 +rieConnectTimeout = 180
 +
 +-- | Give child process up to 5 seconds to exit after sending a signal
 +childLingerTimeout :: Double
 +childLingerTimeout = 5.0
 +
 +-- * Import/export config options
 +
 +inisectBep :: String
 +inisectBep = "backend"
 +
 +inisectExp :: String
 +inisectExp = "export"
 +
 +inisectHyp :: String
 +inisectHyp = "hypervisor"
 +
 +inisectIns :: String
 +inisectIns = "instance"
 +
 +inisectOsp :: String
 +inisectOsp = "os"
 +
 +-- * Dynamic device modification
 +
 +ddmAdd :: String
 +ddmAdd = Types.ddmFullToRaw DdmFullAdd
 +
 +ddmModify :: String
 +ddmModify = Types.ddmFullToRaw DdmFullModify
 +
 +ddmRemove :: String
 +ddmRemove = Types.ddmFullToRaw DdmFullRemove
 +
 +ddmsValues :: FrozenSet String
 +ddmsValues = ConstantUtils.mkSet [ddmAdd, ddmRemove]
 +
 +ddmsValuesWithModify :: FrozenSet String
 +ddmsValuesWithModify = ConstantUtils.mkSet $ map Types.ddmFullToRaw [minBound..]
 +
 +-- * Common exit codes
 +
 +exitSuccess :: Int
 +exitSuccess = 0
 +
 +exitFailure :: Int
 +exitFailure = ConstantUtils.exitFailure
 +
 +exitNotcluster :: Int
 +exitNotcluster = 5
 +
 +exitNotmaster :: Int
 +exitNotmaster = 11
 +
 +exitNodesetupError :: Int
 +exitNodesetupError = 12
 +
 +-- | Need user confirmation
 +exitConfirmation :: Int
 +exitConfirmation = 13
 +
 +-- | Exit code for query operations with unknown fields
 +exitUnknownField :: Int
 +exitUnknownField = 14
 +
 +-- * Tags
 +
 +tagCluster :: String
 +tagCluster = Types.tagKindToRaw TagKindCluster
 +
 +tagInstance :: String
 +tagInstance = Types.tagKindToRaw TagKindInstance
 +
 +tagNetwork :: String
 +tagNetwork = Types.tagKindToRaw TagKindNetwork
 +
 +tagNode :: String
 +tagNode = Types.tagKindToRaw TagKindNode
 +
 +tagNodegroup :: String
 +tagNodegroup = Types.tagKindToRaw TagKindGroup
 +
 +validTagTypes :: FrozenSet String
 +validTagTypes = ConstantUtils.mkSet $ map Types.tagKindToRaw [minBound..]
 +
 +maxTagLen :: Int
 +maxTagLen = 128
 +
 +maxTagsPerObj :: Int
 +maxTagsPerObj = 4096
 +
 +-- * Others
 +
 +defaultBridge :: String
 +defaultBridge = "xen-br0"
 +
 +defaultOvs :: String
 +defaultOvs = "switch1"
 +
 +-- | 60 MiB/s, expressed in KiB/s
 +classicDrbdSyncSpeed :: Int
 +classicDrbdSyncSpeed = 60 * 1024
 +
 +ip4AddressAny :: String
 +ip4AddressAny = "0.0.0.0"
 +
 +ip4AddressLocalhost :: String
 +ip4AddressLocalhost = "127.0.0.1"
 +
 +ip6AddressAny :: String
 +ip6AddressAny = "::"
 +
 +ip6AddressLocalhost :: String
 +ip6AddressLocalhost = "::1"
 +
 +ip4Version :: Int
 +ip4Version = 4
 +
 +ip6Version :: Int
 +ip6Version = 6
 +
 +validIpVersions :: FrozenSet Int
 +validIpVersions = ConstantUtils.mkSet [ip4Version, ip6Version]
 +
 +tcpPingTimeout :: Int
 +tcpPingTimeout = 10
 +
 +defaultVg :: String
 +defaultVg = "xenvg"
 +
 +defaultDrbdHelper :: String
 +defaultDrbdHelper = "/bin/true"
 +
 +minVgSize :: Int
 +minVgSize = 20480
 +
 +defaultMacPrefix :: String
 +defaultMacPrefix = "aa:00:00"
 +
 +-- | Default maximum instance wait time (seconds)
 +defaultShutdownTimeout :: Int
 +defaultShutdownTimeout = 120
 +
 +-- | Node clock skew (seconds)
 +nodeMaxClockSkew :: Int
 +nodeMaxClockSkew = 150
 +
 +-- | Time for an intra-cluster disk transfer to wait for a connection
 +diskTransferConnectTimeout :: Int
 +diskTransferConnectTimeout = 60
 +
 +-- | Disk index separator
 +diskSeparator :: String
 +diskSeparator = AutoConf.diskSeparator
 +
 +ipCommandPath :: String
 +ipCommandPath = AutoConf.ipPath
 +
 +-- | Key for job IDs in opcode result
 +jobIdsKey :: String
 +jobIdsKey = "jobs"
 +
 +-- * Runparts results
 +
 +runpartsErr :: Int
 +runpartsErr = 2
 +
 +runpartsRun :: Int
 +runpartsRun = 1
 +
 +runpartsSkip :: Int
 +runpartsSkip = 0
 +
 +runpartsStatus :: [Int]
 +runpartsStatus = [runpartsErr, runpartsRun, runpartsSkip]
 +
 +-- * RPC
 +
 +rpcEncodingNone :: Int
 +rpcEncodingNone = 0
 +
 +rpcEncodingZlibBase64 :: Int
 +rpcEncodingZlibBase64 = 1
 +
 +-- * Timeout table
 +--
 +-- Various time constants for the timeout table
 +
 +rpcTmoUrgent :: Int
 +rpcTmoUrgent = Types.rpcTimeoutToRaw Urgent
 +
 +rpcTmoFast :: Int
 +rpcTmoFast = Types.rpcTimeoutToRaw Fast
 +
 +rpcTmoNormal :: Int
 +rpcTmoNormal = Types.rpcTimeoutToRaw Normal
 +
 +rpcTmoSlow :: Int
 +rpcTmoSlow = Types.rpcTimeoutToRaw Slow
 +
 +-- | 'rpcTmo_4hrs' contains an underscore to circumvent a limitation
 +-- in the 'Ganeti.THH.deCamelCase' function and generate the correct
 +-- Python name.
 +rpcTmo_4hrs :: Int
 +rpcTmo_4hrs = Types.rpcTimeoutToRaw FourHours
 +
 +-- | 'rpcTmo_1day' contains an underscore to circumvent a limitation
 +-- in the 'Ganeti.THH.deCamelCase' function and generate the correct
 +-- Python name.
 +rpcTmo_1day :: Int
 +rpcTmo_1day = Types.rpcTimeoutToRaw OneDay
 +
 +-- | Timeout for connecting to nodes (seconds)
 +rpcConnectTimeout :: Int
 +rpcConnectTimeout = 5
 +
 +-- OS
 +
 +osScriptCreate :: String
 +osScriptCreate = "create"
 +
 +osScriptExport :: String
 +osScriptExport = "export"
 +
 +osScriptImport :: String
 +osScriptImport = "import"
 +
 +osScriptRename :: String
 +osScriptRename = "rename"
 +
 +osScriptVerify :: String
 +osScriptVerify = "verify"
 +
 +osScripts :: [String]
 +osScripts = [osScriptCreate, osScriptExport, osScriptImport, osScriptRename,
 +             osScriptVerify]
 +
 +osApiFile :: String
 +osApiFile = "ganeti_api_version"
 +
 +osVariantsFile :: String
 +osVariantsFile = "variants.list"
 +
 +osParametersFile :: String
 +osParametersFile = "parameters.list"
 +
 +osValidateParameters :: String
 +osValidateParameters = "parameters"
 +
 +osValidateCalls :: FrozenSet String
 +osValidateCalls = ConstantUtils.mkSet [osValidateParameters]
 +
 +-- | External Storage (ES) related constants
 +
 +esActionAttach :: String
 +esActionAttach = "attach"
 +
 +esActionCreate :: String
 +esActionCreate = "create"
 +
 +esActionDetach :: String
 +esActionDetach = "detach"
 +
 +esActionGrow :: String
 +esActionGrow = "grow"
 +
 +esActionRemove :: String
 +esActionRemove = "remove"
 +
 +esActionSetinfo :: String
 +esActionSetinfo = "setinfo"
 +
 +esActionVerify :: String
 +esActionVerify = "verify"
 +
 +esScriptCreate :: String
 +esScriptCreate = esActionCreate
 +
 +esScriptRemove :: String
 +esScriptRemove = esActionRemove
 +
 +esScriptGrow :: String
 +esScriptGrow = esActionGrow
 +
 +esScriptAttach :: String
 +esScriptAttach = esActionAttach
 +
 +esScriptDetach :: String
 +esScriptDetach = esActionDetach
 +
 +esScriptSetinfo :: String
 +esScriptSetinfo = esActionSetinfo
 +
 +esScriptVerify :: String
 +esScriptVerify = esActionVerify
 +
 +esScripts :: FrozenSet String
 +esScripts =
 +  ConstantUtils.mkSet [esScriptAttach,
 +                       esScriptCreate,
 +                       esScriptDetach,
 +                       esScriptGrow,
 +                       esScriptRemove,
 +                       esScriptSetinfo,
 +                       esScriptVerify]
 +
 +esParametersFile :: String
 +esParametersFile = "parameters.list"
 +
 +-- * Reboot types
 +
 +instanceRebootSoft :: String
 +instanceRebootSoft = Types.rebootTypeToRaw RebootSoft
 +
 +instanceRebootHard :: String
 +instanceRebootHard = Types.rebootTypeToRaw RebootHard
 +
 +instanceRebootFull :: String
 +instanceRebootFull = Types.rebootTypeToRaw RebootFull
 +
 +rebootTypes :: FrozenSet String
 +rebootTypes = ConstantUtils.mkSet $ map Types.rebootTypeToRaw [minBound..]
 +
 +-- * Instance reboot behaviors
 +
 +instanceRebootAllowed :: String
 +instanceRebootAllowed = "reboot"
 +
 +instanceRebootExit :: String
 +instanceRebootExit = "exit"
 +
 +rebootBehaviors :: [String]
 +rebootBehaviors = [instanceRebootAllowed, instanceRebootExit]
 +
 +-- * VTypes
 +
 +vtypeBool :: VType
 +vtypeBool = VTypeBool
 +
 +vtypeInt :: VType
 +vtypeInt = VTypeInt
 +
 +vtypeMaybeString :: VType
 +vtypeMaybeString = VTypeMaybeString
 +
 +-- | Size in MiBs
 +vtypeSize :: VType
 +vtypeSize = VTypeSize
 +
 +vtypeString :: VType
 +vtypeString = VTypeString
 +
 +enforceableTypes :: FrozenSet VType
 +enforceableTypes = ConstantUtils.mkSet [minBound..]
 +
 +-- | Constant representing that the user does not specify any IP version
 +ifaceNoIpVersionSpecified :: Int
 +ifaceNoIpVersionSpecified = 0
 +
 +validSerialSpeeds :: [Int]
 +validSerialSpeeds =
 +  [75,
 +   110,
 +   300,
 +   600,
 +   1200,
 +   1800,
 +   2400,
 +   4800,
 +   9600,
 +   14400,
 +   19200,
 +   28800,
 +   38400,
 +   57600,
 +   115200,
 +   230400,
 +   345600,
 +   460800]
 +
 +-- * HV parameter names (global namespace)
 +
 +hvAcpi :: String
 +hvAcpi = "acpi"
 +
 +hvBlockdevPrefix :: String
 +hvBlockdevPrefix = "blockdev_prefix"
 +
 +hvBootloaderArgs :: String
 +hvBootloaderArgs = "bootloader_args"
 +
 +hvBootloaderPath :: String
 +hvBootloaderPath = "bootloader_path"
 +
 +hvBootOrder :: String
 +hvBootOrder = "boot_order"
 +
 +hvCdromImagePath :: String
 +hvCdromImagePath = "cdrom_image_path"
 +
 +hvCpuCap :: String
 +hvCpuCap = "cpu_cap"
 +
 +hvCpuCores :: String
 +hvCpuCores = "cpu_cores"
 +
 +hvCpuMask :: String
 +hvCpuMask = "cpu_mask"
 +
 +hvCpuSockets :: String
 +hvCpuSockets = "cpu_sockets"
 +
 +hvCpuThreads :: String
 +hvCpuThreads = "cpu_threads"
 +
 +hvCpuType :: String
 +hvCpuType = "cpu_type"
 +
 +hvCpuWeight :: String
 +hvCpuWeight = "cpu_weight"
 +
 +hvDeviceModel :: String
 +hvDeviceModel = "device_model"
 +
 +hvDiskCache :: String
 +hvDiskCache = "disk_cache"
 +
 +hvDiskType :: String
 +hvDiskType = "disk_type"
 +
 +hvInitrdPath :: String
 +hvInitrdPath = "initrd_path"
 +
 +hvInitScript :: String
 +hvInitScript = "init_script"
 +
 +hvKernelArgs :: String
 +hvKernelArgs = "kernel_args"
 +
 +hvKernelPath :: String
 +hvKernelPath = "kernel_path"
 +
 +hvKeymap :: String
 +hvKeymap = "keymap"
 +
 +hvKvmCdrom2ImagePath :: String
 +hvKvmCdrom2ImagePath = "cdrom2_image_path"
 +
 +hvKvmCdromDiskType :: String
 +hvKvmCdromDiskType = "cdrom_disk_type"
 +
 +hvKvmExtra :: String
 +hvKvmExtra = "kvm_extra"
 +
 +hvKvmFlag :: String
 +hvKvmFlag = "kvm_flag"
 +
 +hvKvmFloppyImagePath :: String
 +hvKvmFloppyImagePath = "floppy_image_path"
 +
 +hvKvmMachineVersion :: String
 +hvKvmMachineVersion = "machine_version"
 +
 +hvKvmPath :: String
 +hvKvmPath = "kvm_path"
 +
 +hvKvmSpiceAudioCompr :: String
 +hvKvmSpiceAudioCompr = "spice_playback_compression"
 +
 +hvKvmSpiceBind :: String
 +hvKvmSpiceBind = "spice_bind"
 +
 +hvKvmSpiceIpVersion :: String
 +hvKvmSpiceIpVersion = "spice_ip_version"
 +
 +hvKvmSpiceJpegImgCompr :: String
 +hvKvmSpiceJpegImgCompr = "spice_jpeg_wan_compression"
 +
 +hvKvmSpiceLosslessImgCompr :: String
 +hvKvmSpiceLosslessImgCompr = "spice_image_compression"
 +
 +hvKvmSpicePasswordFile :: String
 +hvKvmSpicePasswordFile = "spice_password_file"
 +
 +hvKvmSpiceStreamingVideoDetection :: String
 +hvKvmSpiceStreamingVideoDetection = "spice_streaming_video"
 +
 +hvKvmSpiceTlsCiphers :: String
 +hvKvmSpiceTlsCiphers = "spice_tls_ciphers"
 +
 +hvKvmSpiceUseTls :: String
 +hvKvmSpiceUseTls = "spice_use_tls"
 +
 +hvKvmSpiceUseVdagent :: String
 +hvKvmSpiceUseVdagent = "spice_use_vdagent"
 +
 +hvKvmSpiceZlibGlzImgCompr :: String
 +hvKvmSpiceZlibGlzImgCompr = "spice_zlib_glz_wan_compression"
 +
 +hvKvmUseChroot :: String
 +hvKvmUseChroot = "use_chroot"
 +
 +hvMemPath :: String
 +hvMemPath = "mem_path"
 +
 +hvMigrationBandwidth :: String
 +hvMigrationBandwidth = "migration_bandwidth"
 +
 +hvMigrationDowntime :: String
 +hvMigrationDowntime = "migration_downtime"
 +
 +hvMigrationMode :: String
 +hvMigrationMode = "migration_mode"
 +
 +hvMigrationPort :: String
 +hvMigrationPort = "migration_port"
 +
 +hvNicType :: String
 +hvNicType = "nic_type"
 +
 +hvPae :: String
 +hvPae = "pae"
 +
 +hvPassthrough :: String
 +hvPassthrough = "pci_pass"
 +
 +hvRebootBehavior :: String
 +hvRebootBehavior = "reboot_behavior"
 +
 +hvRootPath :: String
 +hvRootPath = "root_path"
 +
 +hvSecurityDomain :: String
 +hvSecurityDomain = "security_domain"
 +
 +hvSecurityModel :: String
 +hvSecurityModel = "security_model"
 +
 +hvSerialConsole :: String
 +hvSerialConsole = "serial_console"
 +
 +hvSerialSpeed :: String
 +hvSerialSpeed = "serial_speed"
 +
 +hvSoundhw :: String
 +hvSoundhw = "soundhw"
 +
 +hvUsbDevices :: String
 +hvUsbDevices = "usb_devices"
 +
 +hvUsbMouse :: String
 +hvUsbMouse = "usb_mouse"
 +
 +hvUseBootloader :: String
 +hvUseBootloader = "use_bootloader"
 +
 +hvUseLocaltime :: String
 +hvUseLocaltime = "use_localtime"
 +
 +hvVga :: String
 +hvVga = "vga"
 +
 +hvVhostNet :: String
 +hvVhostNet = "vhost_net"
 +
 +hvVifScript :: String
 +hvVifScript = "vif_script"
 +
 +hvVifType :: String
 +hvVifType = "vif_type"
 +
 +hvViridian :: String
 +hvViridian = "viridian"
 +
 +hvVncBindAddress :: String
 +hvVncBindAddress = "vnc_bind_address"
 +
 +hvVncPasswordFile :: String
 +hvVncPasswordFile = "vnc_password_file"
 +
 +hvVncTls :: String
 +hvVncTls = "vnc_tls"
 +
 +hvVncX509 :: String
 +hvVncX509 = "vnc_x509_path"
 +
 +hvVncX509Verify :: String
 +hvVncX509Verify = "vnc_x509_verify"
 +
 +hvVnetHdr :: String
 +hvVnetHdr = "vnet_hdr"
 +
 +hvXenCmd :: String
 +hvXenCmd = "xen_cmd"
 +
 +hvXenCpuid :: String
 +hvXenCpuid = "cpuid"
 +
 +hvsParameterTitles :: Map String String
 +hvsParameterTitles =
 +  Map.fromList
 +  [(hvAcpi, "ACPI"),
 +   (hvBootOrder, "Boot_order"),
 +   (hvCdromImagePath, "CDROM_image_path"),
 +   (hvCpuType, "cpu_type"),
 +   (hvDiskType, "Disk_type"),
 +   (hvInitrdPath, "Initrd_path"),
 +   (hvKernelPath, "Kernel_path"),
 +   (hvNicType, "NIC_type"),
 +   (hvPae, "PAE"),
 +   (hvPassthrough, "pci_pass"),
 +   (hvVncBindAddress, "VNC_bind_address")]
 +
 +hvsParameters :: FrozenSet String
 +hvsParameters = ConstantUtils.mkSet $ Map.keys hvsParameterTypes
 +
 +hvsParameterTypes :: Map String VType
 +hvsParameterTypes = Map.fromList
 +  [ (hvAcpi,                            VTypeBool)
 +  , (hvBlockdevPrefix,                  VTypeString)
 +  , (hvBootloaderArgs,                  VTypeString)
 +  , (hvBootloaderPath,                  VTypeString)
 +  , (hvBootOrder,                       VTypeString)
 +  , (hvCdromImagePath,                  VTypeString)
 +  , (hvCpuCap,                          VTypeInt)
 +  , (hvCpuCores,                        VTypeInt)
 +  , (hvCpuMask,                         VTypeString)
 +  , (hvCpuSockets,                      VTypeInt)
 +  , (hvCpuThreads,                      VTypeInt)
 +  , (hvCpuType,                         VTypeString)
 +  , (hvCpuWeight,                       VTypeInt)
 +  , (hvDeviceModel,                     VTypeString)
 +  , (hvDiskCache,                       VTypeString)
 +  , (hvDiskType,                        VTypeString)
 +  , (hvInitrdPath,                      VTypeString)
 +  , (hvInitScript,                      VTypeString)
 +  , (hvKernelArgs,                      VTypeString)
 +  , (hvKernelPath,                      VTypeString)
 +  , (hvKeymap,                          VTypeString)
 +  , (hvKvmCdrom2ImagePath,              VTypeString)
 +  , (hvKvmCdromDiskType,                VTypeString)
 +  , (hvKvmExtra,                        VTypeString)
 +  , (hvKvmFlag,                         VTypeString)
 +  , (hvKvmFloppyImagePath,              VTypeString)
 +  , (hvKvmMachineVersion,               VTypeString)
 +  , (hvKvmPath,                         VTypeString)
 +  , (hvKvmSpiceAudioCompr,              VTypeBool)
 +  , (hvKvmSpiceBind,                    VTypeString)
 +  , (hvKvmSpiceIpVersion,               VTypeInt)
 +  , (hvKvmSpiceJpegImgCompr,            VTypeString)
 +  , (hvKvmSpiceLosslessImgCompr,        VTypeString)
 +  , (hvKvmSpicePasswordFile,            VTypeString)
 +  , (hvKvmSpiceStreamingVideoDetection, VTypeString)
 +  , (hvKvmSpiceTlsCiphers,              VTypeString)
 +  , (hvKvmSpiceUseTls,                  VTypeBool)
 +  , (hvKvmSpiceUseVdagent,              VTypeBool)
 +  , (hvKvmSpiceZlibGlzImgCompr,         VTypeString)
 +  , (hvKvmUseChroot,                    VTypeBool)
 +  , (hvMemPath,                         VTypeString)
 +  , (hvMigrationBandwidth,              VTypeInt)
 +  , (hvMigrationDowntime,               VTypeInt)
 +  , (hvMigrationMode,                   VTypeString)
 +  , (hvMigrationPort,                   VTypeInt)
 +  , (hvNicType,                         VTypeString)
 +  , (hvPae,                             VTypeBool)
 +  , (hvPassthrough,                     VTypeString)
 +  , (hvRebootBehavior,                  VTypeString)
 +  , (hvRootPath,                        VTypeMaybeString)
 +  , (hvSecurityDomain,                  VTypeString)
 +  , (hvSecurityModel,                   VTypeString)
 +  , (hvSerialConsole,                   VTypeBool)
 +  , (hvSerialSpeed,                     VTypeInt)
 +  , (hvSoundhw,                         VTypeString)
 +  , (hvUsbDevices,                      VTypeString)
 +  , (hvUsbMouse,                        VTypeString)
 +  , (hvUseBootloader,                   VTypeBool)
 +  , (hvUseLocaltime,                    VTypeBool)
 +  , (hvVga,                             VTypeString)
 +  , (hvVhostNet,                        VTypeBool)
 +  , (hvVifScript,                       VTypeString)
 +  , (hvVifType,                         VTypeString)
 +  , (hvViridian,                        VTypeBool)
 +  , (hvVncBindAddress,                  VTypeString)
 +  , (hvVncPasswordFile,                 VTypeString)
 +  , (hvVncTls,                          VTypeBool)
 +  , (hvVncX509,                         VTypeString)
 +  , (hvVncX509Verify,                   VTypeBool)
 +  , (hvVnetHdr,                         VTypeBool)
 +  , (hvXenCmd,                          VTypeString)
 +  , (hvXenCpuid,                        VTypeString)
 +  ]
 +
 +-- * Migration statuses
 +
 +hvMigrationActive :: String
 +hvMigrationActive = "active"
 +
 +hvMigrationCancelled :: String
 +hvMigrationCancelled = "cancelled"
 +
 +hvMigrationCompleted :: String
 +hvMigrationCompleted = "completed"
 +
 +hvMigrationFailed :: String
 +hvMigrationFailed = "failed"
 +
 +hvMigrationValidStatuses :: FrozenSet String
 +hvMigrationValidStatuses =
 +  ConstantUtils.mkSet [hvMigrationActive,
 +                       hvMigrationCancelled,
 +                       hvMigrationCompleted,
 +                       hvMigrationFailed]
 +
 +hvMigrationFailedStatuses :: FrozenSet String
 +hvMigrationFailedStatuses =
 +  ConstantUtils.mkSet [hvMigrationFailed, hvMigrationCancelled]
 +
 +-- | KVM-specific statuses
 +--
 +-- FIXME: this constant seems unnecessary
 +hvKvmMigrationValidStatuses :: FrozenSet String
 +hvKvmMigrationValidStatuses = hvMigrationValidStatuses
 +
 +-- | Node info keys
 +hvNodeinfoKeyVersion :: String
 +hvNodeinfoKeyVersion = "hv_version"
 +
 +-- * Hypervisor state
 +
 +hvstCpuNode :: String
 +hvstCpuNode = "cpu_node"
 +
 +hvstCpuTotal :: String
 +hvstCpuTotal = "cpu_total"
 +
 +hvstMemoryHv :: String
 +hvstMemoryHv = "mem_hv"
 +
 +hvstMemoryNode :: String
 +hvstMemoryNode = "mem_node"
 +
 +hvstMemoryTotal :: String
 +hvstMemoryTotal = "mem_total"
 +
 +hvstsParameters :: FrozenSet String
 +hvstsParameters =
 +  ConstantUtils.mkSet [hvstCpuNode,
 +                       hvstCpuTotal,
 +                       hvstMemoryHv,
 +                       hvstMemoryNode,
 +                       hvstMemoryTotal]
 +
 +hvstDefaults :: Map String Int
 +hvstDefaults =
 +  Map.fromList
 +  [(hvstCpuNode, 1),
 +   (hvstCpuTotal, 1),
 +   (hvstMemoryHv, 0),
 +   (hvstMemoryTotal, 0),
 +   (hvstMemoryNode, 0)]
 +
 +hvstsParameterTypes :: Map String VType
 +hvstsParameterTypes =
 +  Map.fromList [(hvstMemoryTotal, VTypeInt),
 +                (hvstMemoryNode, VTypeInt),
 +                (hvstMemoryHv, VTypeInt),
 +                (hvstCpuTotal, VTypeInt),
 +                (hvstCpuNode, VTypeInt)]
 +
 +-- * Disk state
 +
 +dsDiskOverhead :: String
 +dsDiskOverhead = "disk_overhead"
 +
 +dsDiskReserved :: String
 +dsDiskReserved = "disk_reserved"
 +
 +dsDiskTotal :: String
 +dsDiskTotal = "disk_total"
 +
 +dsDefaults :: Map String Int
 +dsDefaults =
 +  Map.fromList
 +  [(dsDiskTotal, 0),
 +   (dsDiskReserved, 0),
 +   (dsDiskOverhead, 0)]
 +
 +dssParameterTypes :: Map String VType
 +dssParameterTypes =
 +  Map.fromList [(dsDiskTotal, VTypeInt),
 +                (dsDiskReserved, VTypeInt),
 +                (dsDiskOverhead, VTypeInt)]
 +
 +dssParameters :: FrozenSet String
 +dssParameters =
 +  ConstantUtils.mkSet [dsDiskTotal, dsDiskReserved, dsDiskOverhead]
 +
 +dsValidTypes :: FrozenSet String
 +dsValidTypes = ConstantUtils.mkSet [Types.diskTemplateToRaw DTPlain]
 +
 +-- Backend parameter names
 +
 +beAlwaysFailover :: String
 +beAlwaysFailover = "always_failover"
 +
 +beAutoBalance :: String
 +beAutoBalance = "auto_balance"
 +
 +beMaxmem :: String
 +beMaxmem = "maxmem"
 +
 +-- | Deprecated and replaced by max and min mem
 +beMemory :: String
 +beMemory = "memory"
 +
 +beMinmem :: String
 +beMinmem = "minmem"
 +
 +beSpindleUse :: String
 +beSpindleUse = "spindle_use"
 +
 +beVcpus :: String
 +beVcpus = "vcpus"
 +
 +besParameterTypes :: Map String VType
 +besParameterTypes =
 +  Map.fromList [(beAlwaysFailover, VTypeBool),
 +                (beAutoBalance, VTypeBool),
 +                (beMaxmem, VTypeSize),
 +                (beMinmem, VTypeSize),
 +                (beSpindleUse, VTypeInt),
 +                (beVcpus, VTypeInt)]
 +
 +besParameterTitles :: Map String String
 +besParameterTitles =
 +  Map.fromList [(beAutoBalance, "Auto_balance"),
 +                (beMinmem, "ConfigMinMem"),
 +                (beVcpus, "ConfigVCPUs"),
 +                (beMaxmem, "ConfigMaxMem")]
 +
 +besParameterCompat :: Map String VType
 +besParameterCompat = Map.insert beMemory VTypeSize besParameterTypes
 +
 +besParameters :: FrozenSet String
 +besParameters =
 +  ConstantUtils.mkSet [beAlwaysFailover,
 +                       beAutoBalance,
 +                       beMaxmem,
 +                       beMinmem,
 +                       beSpindleUse,
 +                       beVcpus]
 +
 +-- | Instance specs
 +--
 +-- FIXME: these should be associated with 'Ganeti.HTools.Types.ISpec'
 +
 +ispecMemSize :: String
 +ispecMemSize = ConstantUtils.ispecMemSize
 +
 +ispecCpuCount :: String
 +ispecCpuCount = ConstantUtils.ispecCpuCount
 +
 +ispecDiskCount :: String
 +ispecDiskCount = ConstantUtils.ispecDiskCount
 +
 +ispecDiskSize :: String
 +ispecDiskSize = ConstantUtils.ispecDiskSize
 +
 +ispecNicCount :: String
 +ispecNicCount = ConstantUtils.ispecNicCount
 +
 +ispecSpindleUse :: String
 +ispecSpindleUse = ConstantUtils.ispecSpindleUse
 +
 +ispecsParameterTypes :: Map String VType
 +ispecsParameterTypes =
 +  Map.fromList
 +  [(ConstantUtils.ispecDiskSize, VTypeInt),
 +   (ConstantUtils.ispecCpuCount, VTypeInt),
 +   (ConstantUtils.ispecSpindleUse, VTypeInt),
 +   (ConstantUtils.ispecMemSize, VTypeInt),
 +   (ConstantUtils.ispecNicCount, VTypeInt),
 +   (ConstantUtils.ispecDiskCount, VTypeInt)]
 +
 +ispecsParameters :: FrozenSet String
 +ispecsParameters =
 +  ConstantUtils.mkSet [ConstantUtils.ispecCpuCount,
 +                       ConstantUtils.ispecDiskCount,
 +                       ConstantUtils.ispecDiskSize,
 +                       ConstantUtils.ispecMemSize,
 +                       ConstantUtils.ispecNicCount,
 +                       ConstantUtils.ispecSpindleUse]
 +
 +ispecsMinmax :: String
 +ispecsMinmax = ConstantUtils.ispecsMinmax
 +
 +ispecsMax :: String
 +ispecsMax = "max"
 +
 +ispecsMin :: String
 +ispecsMin = "min"
 +
 +ispecsStd :: String
 +ispecsStd = ConstantUtils.ispecsStd
 +
 +ipolicyDts :: String
 +ipolicyDts = ConstantUtils.ipolicyDts
 +
 +ipolicyVcpuRatio :: String
 +ipolicyVcpuRatio = ConstantUtils.ipolicyVcpuRatio
 +
 +ipolicySpindleRatio :: String
 +ipolicySpindleRatio = ConstantUtils.ipolicySpindleRatio
 +
 +ispecsMinmaxKeys :: FrozenSet String
 +ispecsMinmaxKeys = ConstantUtils.mkSet [ispecsMax, ispecsMin]
 +
 +ipolicyParameters :: FrozenSet String
 +ipolicyParameters =
 +  ConstantUtils.mkSet [ConstantUtils.ipolicyVcpuRatio,
 +                       ConstantUtils.ipolicySpindleRatio]
 +
 +ipolicyAllKeys :: FrozenSet String
 +ipolicyAllKeys =
 +  ConstantUtils.union ipolicyParameters $
 +  ConstantUtils.mkSet [ConstantUtils.ipolicyDts,
 +                       ConstantUtils.ispecsMinmax,
 +                       ispecsStd]
 +
 +-- | Node parameter names
 +
 +ndExclusiveStorage :: String
 +ndExclusiveStorage = "exclusive_storage"
 +
 +ndOobProgram :: String
 +ndOobProgram = "oob_program"
 +
 +ndSpindleCount :: String
 +ndSpindleCount = "spindle_count"
 +
 +ndOvs :: String
 +ndOvs = "ovs"
 +
 +ndOvsLink :: String
 +ndOvsLink = "ovs_link"
 +
 +ndOvsName :: String
 +ndOvsName = "ovs_name"
 +
 +ndsParameterTypes :: Map String VType
 +ndsParameterTypes =
 +  Map.fromList
 +  [(ndExclusiveStorage, VTypeBool),
 +   (ndOobProgram, VTypeString),
 +   (ndOvs, VTypeBool),
 +   (ndOvsLink, VTypeMaybeString),
 +   (ndOvsName, VTypeMaybeString),
 +   (ndSpindleCount, VTypeInt)]
 +
 +ndsParameters :: FrozenSet String
 +ndsParameters = ConstantUtils.mkSet (Map.keys ndsParameterTypes)
 +
 +ndsParameterTitles :: Map String String
 +ndsParameterTitles =
 +  Map.fromList
 +  [(ndExclusiveStorage, "ExclusiveStorage"),
 +   (ndOobProgram, "OutOfBandProgram"),
 +   (ndOvs, "OpenvSwitch"),
 +   (ndOvsLink, "OpenvSwitchLink"),
 +   (ndOvsName, "OpenvSwitchName"),
 +   (ndSpindleCount, "SpindleCount")]
 +
 +-- * Logical Disks parameters
 +
 +ldpAccess :: String
 +ldpAccess = "access"
 +
 +ldpBarriers :: String
 +ldpBarriers = "disabled-barriers"
 +
 +ldpDefaultMetavg :: String
 +ldpDefaultMetavg = "default-metavg"
 +
 +ldpDelayTarget :: String
 +ldpDelayTarget = "c-delay-target"
 +
 +ldpDiskCustom :: String
 +ldpDiskCustom = "disk-custom"
 +
 +ldpDynamicResync :: String
 +ldpDynamicResync = "dynamic-resync"
 +
 +ldpFillTarget :: String
 +ldpFillTarget = "c-fill-target"
 +
 +ldpMaxRate :: String
 +ldpMaxRate = "c-max-rate"
 +
 +ldpMinRate :: String
 +ldpMinRate = "c-min-rate"
 +
 +ldpNetCustom :: String
 +ldpNetCustom = "net-custom"
 +
 +ldpNoMetaFlush :: String
 +ldpNoMetaFlush = "disable-meta-flush"
 +
 +ldpPlanAhead :: String
 +ldpPlanAhead = "c-plan-ahead"
 +
 +ldpPool :: String
 +ldpPool = "pool"
 +
 +ldpProtocol :: String
 +ldpProtocol = "protocol"
 +
 +ldpResyncRate :: String
 +ldpResyncRate = "resync-rate"
 +
 +ldpStripes :: String
 +ldpStripes = "stripes"
 +
 +diskLdTypes :: Map String VType
 +diskLdTypes =
 +  Map.fromList
 +  [(ldpAccess, VTypeString),
 +   (ldpResyncRate, VTypeInt),
 +   (ldpStripes, VTypeInt),
 +   (ldpBarriers, VTypeString),
 +   (ldpNoMetaFlush, VTypeBool),
 +   (ldpDefaultMetavg, VTypeString),
 +   (ldpDiskCustom, VTypeString),
 +   (ldpNetCustom, VTypeString),
 +   (ldpProtocol, VTypeString),
 +   (ldpDynamicResync, VTypeBool),
 +   (ldpPlanAhead, VTypeInt),
 +   (ldpFillTarget, VTypeInt),
 +   (ldpDelayTarget, VTypeInt),
 +   (ldpMaxRate, VTypeInt),
 +   (ldpMinRate, VTypeInt),
 +   (ldpPool, VTypeString)]
 +
 +diskLdParameters :: FrozenSet String
 +diskLdParameters = ConstantUtils.mkSet (Map.keys diskLdTypes)
 +
 +-- * Disk template parameters
 +--
 +-- Disk template parameters can be set/changed by the user via
 +-- gnt-cluster and gnt-group)
 +
 +drbdResyncRate :: String
 +drbdResyncRate = "resync-rate"
 +
 +drbdDataStripes :: String
 +drbdDataStripes = "data-stripes"
 +
 +drbdMetaStripes :: String
 +drbdMetaStripes = "meta-stripes"
 +
 +drbdDiskBarriers :: String
 +drbdDiskBarriers = "disk-barriers"
 +
 +drbdMetaBarriers :: String
 +drbdMetaBarriers = "meta-barriers"
 +
 +drbdDefaultMetavg :: String
 +drbdDefaultMetavg = "metavg"
 +
 +drbdDiskCustom :: String
 +drbdDiskCustom = "disk-custom"
 +
 +drbdNetCustom :: String
 +drbdNetCustom = "net-custom"
 +
 +drbdProtocol :: String
 +drbdProtocol = "protocol"
 +
 +drbdDynamicResync :: String
 +drbdDynamicResync = "dynamic-resync"
 +
 +drbdPlanAhead :: String
 +drbdPlanAhead = "c-plan-ahead"
 +
 +drbdFillTarget :: String
 +drbdFillTarget = "c-fill-target"
 +
 +drbdDelayTarget :: String
 +drbdDelayTarget = "c-delay-target"
 +
 +drbdMaxRate :: String
 +drbdMaxRate = "c-max-rate"
 +
 +drbdMinRate :: String
 +drbdMinRate = "c-min-rate"
 +
 +lvStripes :: String
 +lvStripes = "stripes"
 +
 +rbdAccess :: String
 +rbdAccess = "access"
 +
 +rbdPool :: String
 +rbdPool = "pool"
 +
 +diskDtTypes :: Map String VType
 +diskDtTypes =
 +  Map.fromList [(drbdResyncRate, VTypeInt),
 +                (drbdDataStripes, VTypeInt),
 +                (drbdMetaStripes, VTypeInt),
 +                (drbdDiskBarriers, VTypeString),
 +                (drbdMetaBarriers, VTypeBool),
 +                (drbdDefaultMetavg, VTypeString),
 +                (drbdDiskCustom, VTypeString),
 +                (drbdNetCustom, VTypeString),
 +                (drbdProtocol, VTypeString),
 +                (drbdDynamicResync, VTypeBool),
 +                (drbdPlanAhead, VTypeInt),
 +                (drbdFillTarget, VTypeInt),
 +                (drbdDelayTarget, VTypeInt),
 +                (drbdMaxRate, VTypeInt),
 +                (drbdMinRate, VTypeInt),
 +                (lvStripes, VTypeInt),
 +                (rbdAccess, VTypeString),
 +                (rbdPool, VTypeString)]
 +
 +diskDtParameters :: FrozenSet String
 +diskDtParameters = ConstantUtils.mkSet (Map.keys diskDtTypes)
 +
 +-- * Dynamic disk parameters
 +
 +ddpLocalIp :: String
 +ddpLocalIp = "local-ip"
 +
 +ddpRemoteIp :: String
 +ddpRemoteIp = "remote-ip"
 +
 +ddpPort :: String
 +ddpPort = "port"
 +
 +ddpLocalMinor :: String
 +ddpLocalMinor = "local-minor"
 +
 +ddpRemoteMinor :: String
 +ddpRemoteMinor = "remote-minor"
 +
 +-- * OOB supported commands
 +
 +oobPowerOn :: String
 +oobPowerOn = Types.oobCommandToRaw OobPowerOn
 +
 +oobPowerOff :: String
 +oobPowerOff = Types.oobCommandToRaw OobPowerOff
 +
 +oobPowerCycle :: String
 +oobPowerCycle = Types.oobCommandToRaw OobPowerCycle
 +
 +oobPowerStatus :: String
 +oobPowerStatus = Types.oobCommandToRaw OobPowerStatus
 +
 +oobHealth :: String
 +oobHealth = Types.oobCommandToRaw OobHealth
 +
 +oobCommands :: FrozenSet String
 +oobCommands = ConstantUtils.mkSet $ map Types.oobCommandToRaw [minBound..]
 +
 +oobPowerStatusPowered :: String
 +oobPowerStatusPowered = "powered"
 +
 +-- | 60 seconds
 +oobTimeout :: Int
 +oobTimeout = 60
 +
 +-- | 2 seconds
 +oobPowerDelay :: Double
 +oobPowerDelay = 2.0
 +
 +oobStatusCritical :: String
 +oobStatusCritical = Types.oobStatusToRaw OobStatusCritical
 +
 +oobStatusOk :: String
 +oobStatusOk = Types.oobStatusToRaw OobStatusOk
 +
 +oobStatusUnknown :: String
 +oobStatusUnknown = Types.oobStatusToRaw OobStatusUnknown
 +
 +oobStatusWarning :: String
 +oobStatusWarning = Types.oobStatusToRaw OobStatusWarning
 +
 +oobStatuses :: FrozenSet String
 +oobStatuses = ConstantUtils.mkSet $ map Types.oobStatusToRaw [minBound..]
 +
 +-- | Instance Parameters Profile
 +ppDefault :: String
 +ppDefault = "default"
 +
 +-- * nic* constants are used inside the ganeti config
 +
 +nicLink :: String
 +nicLink = "link"
 +
 +nicMode :: String
 +nicMode = "mode"
 +
 +nicVlan :: String
 +nicVlan = "vlan"
 +
 +nicsParameterTypes :: Map String VType
 +nicsParameterTypes =
 +  Map.fromList [(nicMode, vtypeString),
 +                (nicLink, vtypeString),
 +                (nicVlan, vtypeString)]
 +
 +nicsParameters :: FrozenSet String
 +nicsParameters = ConstantUtils.mkSet (Map.keys nicsParameterTypes)
 +
 +nicModeBridged :: String
 +nicModeBridged = Types.nICModeToRaw NMBridged
 +
 +nicModeRouted :: String
 +nicModeRouted = Types.nICModeToRaw NMRouted
 +
 +nicModeOvs :: String
 +nicModeOvs = Types.nICModeToRaw NMOvs
 +
 +nicIpPool :: String
 +nicIpPool = Types.nICModeToRaw NMPool
 +
 +nicValidModes :: FrozenSet String
 +nicValidModes = ConstantUtils.mkSet $ map Types.nICModeToRaw [minBound..]
 +
 +releaseAction :: String
 +releaseAction = "release"
 +
 +reserveAction :: String
 +reserveAction = "reserve"
 +
 +-- * idisk* constants are used in opcodes, to create/change disks
 +
 +idiskAdopt :: String
 +idiskAdopt = "adopt"
 +
 +idiskMetavg :: String
 +idiskMetavg = "metavg"
 +
 +idiskMode :: String
 +idiskMode = "mode"
 +
 +idiskName :: String
 +idiskName = "name"
 +
 +idiskSize :: String
 +idiskSize = "size"
 +
 +idiskSpindles :: String
 +idiskSpindles = "spindles"
 +
 +idiskVg :: String
 +idiskVg = "vg"
 +
 +idiskProvider :: String
 +idiskProvider = "provider"
 +
 +idiskParamsTypes :: Map String VType
 +idiskParamsTypes =
 +  Map.fromList [(idiskSize, VTypeSize),
 +                (idiskSpindles, VTypeInt),
 +                (idiskMode, VTypeString),
 +                (idiskAdopt, VTypeString),
 +                (idiskVg, VTypeString),
 +                (idiskMetavg, VTypeString),
 +                (idiskProvider, VTypeString),
 +                (idiskName, VTypeMaybeString)]
 +
 +idiskParams :: FrozenSet String
 +idiskParams = ConstantUtils.mkSet (Map.keys idiskParamsTypes)
 +
++modifiableIdiskParamsTypes :: Map String VType
++modifiableIdiskParamsTypes =
++  Map.fromList [(idiskMode, VTypeString),
++                (idiskName, VTypeString)]
++
++modifiableIdiskParams :: FrozenSet String
++modifiableIdiskParams =
++  ConstantUtils.mkSet (Map.keys modifiableIdiskParamsTypes)
++
 +-- * inic* constants are used in opcodes, to create/change nics
 +
 +inicBridge :: String
 +inicBridge = "bridge"
 +
 +inicIp :: String
 +inicIp = "ip"
 +
 +inicLink :: String
 +inicLink = "link"
 +
 +inicMac :: String
 +inicMac = "mac"
 +
 +inicMode :: String
 +inicMode = "mode"
 +
 +inicName :: String
 +inicName = "name"
 +
 +inicNetwork :: String
 +inicNetwork = "network"
 +
 +inicVlan :: String
 +inicVlan = "vlan"
 +
 +inicParamsTypes :: Map String VType
 +inicParamsTypes =
 +  Map.fromList [(inicBridge, VTypeMaybeString),
 +                (inicIp, VTypeMaybeString),
 +                (inicLink, VTypeString),
 +                (inicMac, VTypeString),
 +                (inicMode, VTypeString),
 +                (inicName, VTypeMaybeString),
 +                (inicNetwork, VTypeMaybeString),
 +                (inicVlan, VTypeMaybeString)]
 +
 +inicParams :: FrozenSet String
 +inicParams = ConstantUtils.mkSet (Map.keys inicParamsTypes)
 +
 +-- * Hypervisor constants
 +
 +htXenPvm :: String
 +htXenPvm = Types.hypervisorToRaw XenPvm
 +
 +htFake :: String
 +htFake = Types.hypervisorToRaw Fake
 +
 +htXenHvm :: String
 +htXenHvm = Types.hypervisorToRaw XenHvm
 +
 +htKvm :: String
 +htKvm = Types.hypervisorToRaw Kvm
 +
 +htChroot :: String
 +htChroot = Types.hypervisorToRaw Chroot
 +
 +htLxc :: String
 +htLxc = Types.hypervisorToRaw Lxc
 +
 +hyperTypes :: FrozenSet String
 +hyperTypes = ConstantUtils.mkSet $ map Types.hypervisorToRaw [minBound..]
 +
 +htsReqPort :: FrozenSet String
 +htsReqPort = ConstantUtils.mkSet [htXenHvm, htKvm]
 +
 +vncBasePort :: Int
 +vncBasePort = 5900
 +
 +vncDefaultBindAddress :: String
 +vncDefaultBindAddress = ip4AddressAny
 +
 +-- * NIC types
 +
 +htNicE1000 :: String
 +htNicE1000 = "e1000"
 +
 +htNicI82551 :: String
 +htNicI82551 = "i82551"
 +
 +htNicI8259er :: String
 +htNicI8259er = "i82559er"
 +
 +htNicI85557b :: String
 +htNicI85557b = "i82557b"
 +
 +htNicNe2kIsa :: String
 +htNicNe2kIsa = "ne2k_isa"
 +
 +htNicNe2kPci :: String
 +htNicNe2kPci = "ne2k_pci"
 +
 +htNicParavirtual :: String
 +htNicParavirtual = "paravirtual"
 +
 +htNicPcnet :: String
 +htNicPcnet = "pcnet"
 +
 +htNicRtl8139 :: String
 +htNicRtl8139 = "rtl8139"
 +
 +htHvmValidNicTypes :: FrozenSet String
 +htHvmValidNicTypes =
 +  ConstantUtils.mkSet [htNicE1000,
 +                       htNicNe2kIsa,
 +                       htNicNe2kPci,
 +                       htNicParavirtual,
 +                       htNicRtl8139]
 +
 +htKvmValidNicTypes :: FrozenSet String
 +htKvmValidNicTypes =
 +  ConstantUtils.mkSet [htNicE1000,
 +                       htNicI82551,
 +                       htNicI8259er,
 +                       htNicI85557b,
 +                       htNicNe2kIsa,
 +                       htNicNe2kPci,
 +                       htNicParavirtual,
 +                       htNicPcnet,
 +                       htNicRtl8139]
 +
 +-- * Vif types
 +
 +-- | Default vif type in xen-hvm
 +htHvmVifIoemu :: String
 +htHvmVifIoemu = "ioemu"
 +
 +htHvmVifVif :: String
 +htHvmVifVif = "vif"
 +
 +htHvmValidVifTypes :: FrozenSet String
 +htHvmValidVifTypes = ConstantUtils.mkSet [htHvmVifIoemu, htHvmVifVif]
 +
 +-- * Disk types
 +
 +htDiskIde :: String
 +htDiskIde = "ide"
 +
 +htDiskIoemu :: String
 +htDiskIoemu = "ioemu"
 +
 +htDiskMtd :: String
 +htDiskMtd = "mtd"
 +
 +htDiskParavirtual :: String
 +htDiskParavirtual = "paravirtual"
 +
 +htDiskPflash :: String
 +htDiskPflash = "pflash"
 +
 +htDiskScsi :: String
 +htDiskScsi = "scsi"
 +
 +htDiskSd :: String
 +htDiskSd = "sd"
 +
 +htHvmValidDiskTypes :: FrozenSet String
 +htHvmValidDiskTypes = ConstantUtils.mkSet [htDiskIoemu, htDiskParavirtual]
 +
 +htKvmValidDiskTypes :: FrozenSet String
 +htKvmValidDiskTypes =
 +  ConstantUtils.mkSet [htDiskIde,
 +                       htDiskMtd,
 +                       htDiskParavirtual,
 +                       htDiskPflash,
 +                       htDiskScsi,
 +                       htDiskSd]
 +
 +htCacheDefault :: String
 +htCacheDefault = "default"
 +
 +htCacheNone :: String
 +htCacheNone = "none"
 +
 +htCacheWback :: String
 +htCacheWback = "writeback"
 +
 +htCacheWthrough :: String
 +htCacheWthrough = "writethrough"
 +
 +htValidCacheTypes :: FrozenSet String
 +htValidCacheTypes =
 +  ConstantUtils.mkSet [htCacheDefault,
 +                       htCacheNone,
 +                       htCacheWback,
 +                       htCacheWthrough]
 +
 +-- * Mouse types
 +
 +htMouseMouse :: String
 +htMouseMouse = "mouse"
 +
 +htMouseTablet :: String
 +htMouseTablet = "tablet"
 +
 +htKvmValidMouseTypes :: FrozenSet String
 +htKvmValidMouseTypes = ConstantUtils.mkSet [htMouseMouse, htMouseTablet]
 +
 +-- * Boot order
 +
 +htBoCdrom :: String
 +htBoCdrom = "cdrom"
 +
 +htBoDisk :: String
 +htBoDisk = "disk"
 +
 +htBoFloppy :: String
 +htBoFloppy = "floppy"
 +
 +htBoNetwork :: String
 +htBoNetwork = "network"
 +
 +htKvmValidBoTypes :: FrozenSet String
 +htKvmValidBoTypes =
 +  ConstantUtils.mkSet [htBoCdrom, htBoDisk, htBoFloppy, htBoNetwork]
 +
 +-- * SPICE lossless image compression options
 +
 +htKvmSpiceLosslessImgComprAutoGlz :: String
 +htKvmSpiceLosslessImgComprAutoGlz = "auto_glz"
 +
 +htKvmSpiceLosslessImgComprAutoLz :: String
 +htKvmSpiceLosslessImgComprAutoLz = "auto_lz"
 +
 +htKvmSpiceLosslessImgComprGlz :: String
 +htKvmSpiceLosslessImgComprGlz = "glz"
 +
 +htKvmSpiceLosslessImgComprLz :: String
 +htKvmSpiceLosslessImgComprLz = "lz"
 +
 +htKvmSpiceLosslessImgComprOff :: String
 +htKvmSpiceLosslessImgComprOff = "off"
 +
 +htKvmSpiceLosslessImgComprQuic :: String
 +htKvmSpiceLosslessImgComprQuic = "quic"
 +
 +htKvmSpiceValidLosslessImgComprOptions :: FrozenSet String
 +htKvmSpiceValidLosslessImgComprOptions =
 +  ConstantUtils.mkSet [htKvmSpiceLosslessImgComprAutoGlz,
 +                       htKvmSpiceLosslessImgComprAutoLz,
 +                       htKvmSpiceLosslessImgComprGlz,
 +                       htKvmSpiceLosslessImgComprLz,
 +                       htKvmSpiceLosslessImgComprOff,
 +                       htKvmSpiceLosslessImgComprQuic]
 +
 +htKvmSpiceLossyImgComprAlways :: String
 +htKvmSpiceLossyImgComprAlways = "always"
 +
 +htKvmSpiceLossyImgComprAuto :: String
 +htKvmSpiceLossyImgComprAuto = "auto"
 +
 +htKvmSpiceLossyImgComprNever :: String
 +htKvmSpiceLossyImgComprNever = "never"
 +
 +htKvmSpiceValidLossyImgComprOptions :: FrozenSet String
 +htKvmSpiceValidLossyImgComprOptions =
 +  ConstantUtils.mkSet [htKvmSpiceLossyImgComprAlways,
 +                       htKvmSpiceLossyImgComprAuto,
 +                       htKvmSpiceLossyImgComprNever]
 +
 +-- * SPICE video stream detection
 +
 +htKvmSpiceVideoStreamDetectionAll :: String
 +htKvmSpiceVideoStreamDetectionAll = "all"
 +
 +htKvmSpiceVideoStreamDetectionFilter :: String
 +htKvmSpiceVideoStreamDetectionFilter = "filter"
 +
 +htKvmSpiceVideoStreamDetectionOff :: String
 +htKvmSpiceVideoStreamDetectionOff = "off"
 +
 +htKvmSpiceValidVideoStreamDetectionOptions :: FrozenSet String
 +htKvmSpiceValidVideoStreamDetectionOptions =
 +  ConstantUtils.mkSet [htKvmSpiceVideoStreamDetectionAll,
 +                       htKvmSpiceVideoStreamDetectionFilter,
 +                       htKvmSpiceVideoStreamDetectionOff]
 +
 +-- * Security models
 +
 +htSmNone :: String
 +htSmNone = "none"
 +
 +htSmPool :: String
 +htSmPool = "pool"
 +
 +htSmUser :: String
 +htSmUser = "user"
 +
 +htKvmValidSmTypes :: FrozenSet String
 +htKvmValidSmTypes = ConstantUtils.mkSet [htSmNone, htSmPool, htSmUser]
 +
 +-- * Kvm flag values
 +
 +htKvmDisabled :: String
 +htKvmDisabled = "disabled"
 +
 +htKvmEnabled :: String
 +htKvmEnabled = "enabled"
 +
 +htKvmFlagValues :: FrozenSet String
 +htKvmFlagValues = ConstantUtils.mkSet [htKvmDisabled, htKvmEnabled]
 +
 +-- * Migration type
 +
 +htMigrationLive :: String
 +htMigrationLive = Types.migrationModeToRaw MigrationLive
 +
 +htMigrationNonlive :: String
 +htMigrationNonlive = Types.migrationModeToRaw MigrationNonLive
 +
 +htMigrationModes :: FrozenSet String
 +htMigrationModes =
 +  ConstantUtils.mkSet $ map Types.migrationModeToRaw [minBound..]
 +
 +-- * Cluster verify steps
 +
 +verifyNplusoneMem :: String
 +verifyNplusoneMem = Types.verifyOptionalChecksToRaw VerifyNPlusOneMem
 +
 +verifyOptionalChecks :: FrozenSet String
 +verifyOptionalChecks =
 +  ConstantUtils.mkSet $ map Types.verifyOptionalChecksToRaw [minBound..]
 +
 +-- * Cluster Verify error classes
 +
 +cvTcluster :: String
 +cvTcluster = "cluster"
 +
 +cvTgroup :: String
 +cvTgroup = "group"
 +
 +cvTnode :: String
 +cvTnode = "node"
 +
 +cvTinstance :: String
 +cvTinstance = "instance"
 +
 +-- * Cluster Verify error codes and documentation
 +
 +cvEclustercert :: (String, String, String)
 +cvEclustercert =
 +  ("cluster",
 +   Types.cVErrorCodeToRaw CvECLUSTERCERT,
 +   "Cluster certificate files verification failure")
 +
 +cvEclustercfg :: (String, String, String)
 +cvEclustercfg =
 +  ("cluster",
 +   Types.cVErrorCodeToRaw CvECLUSTERCFG,
 +   "Cluster configuration verification failure")
 +
 +cvEclusterdanglinginst :: (String, String, String)
 +cvEclusterdanglinginst =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvECLUSTERDANGLINGINST,
 +   "Some instances have a non-existing primary node")
 +
 +cvEclusterdanglingnodes :: (String, String, String)
 +cvEclusterdanglingnodes =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvECLUSTERDANGLINGNODES,
 +   "Some nodes belong to non-existing groups")
 +
 +cvEclusterfilecheck :: (String, String, String)
 +cvEclusterfilecheck =
 +  ("cluster",
 +   Types.cVErrorCodeToRaw CvECLUSTERFILECHECK,
 +   "Cluster configuration verification failure")
 +
 +cvEgroupdifferentpvsize :: (String, String, String)
 +cvEgroupdifferentpvsize =
 +  ("group",
 +   Types.cVErrorCodeToRaw CvEGROUPDIFFERENTPVSIZE,
 +   "PVs in the group have different sizes")
 +
 +cvEinstancebadnode :: (String, String, String)
 +cvEinstancebadnode =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEBADNODE,
 +   "Instance marked as running lives on an offline node")
 +
 +cvEinstancedown :: (String, String, String)
 +cvEinstancedown =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEDOWN,
 +   "Instance not running on its primary node")
 +
 +cvEinstancefaultydisk :: (String, String, String)
 +cvEinstancefaultydisk =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEFAULTYDISK,
 +   "Impossible to retrieve status for a disk")
 +
 +cvEinstancelayout :: (String, String, String)
 +cvEinstancelayout =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCELAYOUT,
 +   "Instance has multiple secondary nodes")
 +
 +cvEinstancemissingcfgparameter :: (String, String, String)
 +cvEinstancemissingcfgparameter =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEMISSINGCFGPARAMETER,
 +   "A configuration parameter for an instance is missing")
 +
 +cvEinstancemissingdisk :: (String, String, String)
 +cvEinstancemissingdisk =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEMISSINGDISK,
 +   "Missing volume on an instance")
 +
 +cvEinstancepolicy :: (String, String, String)
 +cvEinstancepolicy =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEPOLICY,
 +   "Instance does not meet policy")
 +
 +cvEinstancesplitgroups :: (String, String, String)
 +cvEinstancesplitgroups =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCESPLITGROUPS,
 +   "Instance with primary and secondary nodes in different groups")
 +
 +cvEinstanceunsuitablenode :: (String, String, String)
 +cvEinstanceunsuitablenode =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEUNSUITABLENODE,
 +   "Instance running on nodes that are not suitable for it")
 +
 +cvEinstancewrongnode :: (String, String, String)
 +cvEinstancewrongnode =
 +  ("instance",
 +   Types.cVErrorCodeToRaw CvEINSTANCEWRONGNODE,
 +   "Instance running on the wrong node")
 +
 +cvEnodedrbd :: (String, String, String)
 +cvEnodedrbd =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEDRBD,
 +   "Error parsing the DRBD status file")
 +
 +cvEnodedrbdhelper :: (String, String, String)
 +cvEnodedrbdhelper =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEDRBDHELPER,
 +   "Error caused by the DRBD helper")
 +
 +cvEnodedrbdversion :: (String, String, String)
 +cvEnodedrbdversion =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEDRBDVERSION,
 +   "DRBD version mismatch within a node group")
 +
 +cvEnodefilecheck :: (String, String, String)
 +cvEnodefilecheck =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEFILECHECK,
 +   "Error retrieving the checksum of the node files")
 +
 +cvEnodefilestoragepaths :: (String, String, String)
 +cvEnodefilestoragepaths =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEFILESTORAGEPATHS,
 +   "Detected bad file storage paths")
 +
 +cvEnodefilestoragepathunusable :: (String, String, String)
 +cvEnodefilestoragepathunusable =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEFILESTORAGEPATHUNUSABLE,
 +   "File storage path unusable")
 +
 +cvEnodehooks :: (String, String, String)
 +cvEnodehooks =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEHOOKS,
 +   "Communication failure in hooks execution")
 +
 +cvEnodehv :: (String, String, String)
 +cvEnodehv =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEHV,
 +   "Hypervisor parameters verification failure")
 +
 +cvEnodelvm :: (String, String, String)
 +cvEnodelvm =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODELVM,
 +   "LVM-related node error")
 +
 +cvEnoden1 :: (String, String, String)
 +cvEnoden1 =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEN1,
 +   "Not enough memory to accommodate instance failovers")
 +
 +cvEnodenet :: (String, String, String)
 +cvEnodenet =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODENET,
 +   "Network-related node error")
 +
 +cvEnodeoobpath :: (String, String, String)
 +cvEnodeoobpath =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEOOBPATH,
 +   "Invalid Out Of Band path")
 +
 +cvEnodeorphaninstance :: (String, String, String)
 +cvEnodeorphaninstance =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEORPHANINSTANCE,
 +   "Unknown intance running on a node")
 +
 +cvEnodeorphanlv :: (String, String, String)
 +cvEnodeorphanlv =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEORPHANLV,
 +   "Unknown LVM logical volume")
 +
 +cvEnodeos :: (String, String, String)
 +cvEnodeos =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEOS,
 +   "OS-related node error")
 +
 +cvEnoderpc :: (String, String, String)
 +cvEnoderpc =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODERPC,
 +   "Error during connection to the primary node of an instance")
 +
 +cvEnodesetup :: (String, String, String)
 +cvEnodesetup =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODESETUP,
 +   "Node setup error")
 +
 +cvEnodesharedfilestoragepathunusable :: (String, String, String)
 +cvEnodesharedfilestoragepathunusable =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODESHAREDFILESTORAGEPATHUNUSABLE,
 +   "Shared file storage path unusable")
 +
 +cvEnodessh :: (String, String, String)
 +cvEnodessh =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODESSH,
 +   "SSH-related node error")
 +
 +cvEnodetime :: (String, String, String)
 +cvEnodetime =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODETIME,
 +   "Node returned invalid time")
 +
 +cvEnodeuserscripts :: (String, String, String)
 +cvEnodeuserscripts =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEUSERSCRIPTS,
 +   "User scripts not present or not executable")
 +
 +cvEnodeversion :: (String, String, String)
 +cvEnodeversion =
 +  ("node",
 +   Types.cVErrorCodeToRaw CvENODEVERSION,
 +   "Protocol version mismatch or Ganeti version mismatch")
 +
 +cvAllEcodes :: FrozenSet (String, String, String)
 +cvAllEcodes =
 +  ConstantUtils.mkSet
 +  [cvEclustercert,
 +   cvEclustercfg,
 +   cvEclusterdanglinginst,
 +   cvEclusterdanglingnodes,
 +   cvEclusterfilecheck,
 +   cvEgroupdifferentpvsize,
 +   cvEinstancebadnode,
 +   cvEinstancedown,
 +   cvEinstancefaultydisk,
 +   cvEinstancelayout,
 +   cvEinstancemissingcfgparameter,
 +   cvEinstancemissingdisk,
 +   cvEinstancepolicy,
 +   cvEinstancesplitgroups,
 +   cvEinstanceunsuitablenode,
 +   cvEinstancewrongnode,
 +   cvEnodedrbd,
 +   cvEnodedrbdhelper,
 +   cvEnodedrbdversion,
 +   cvEnodefilecheck,
 +   cvEnodefilestoragepaths,
 +   cvEnodefilestoragepathunusable,
 +   cvEnodehooks,
 +   cvEnodehv,
 +   cvEnodelvm,
 +   cvEnoden1,
 +   cvEnodenet,
 +   cvEnodeoobpath,
 +   cvEnodeorphaninstance,
 +   cvEnodeorphanlv,
 +   cvEnodeos,
 +   cvEnoderpc,
 +   cvEnodesetup,
 +   cvEnodesharedfilestoragepathunusable,
 +   cvEnodessh,
 +   cvEnodetime,
 +   cvEnodeuserscripts,
 +   cvEnodeversion]
 +
 +cvAllEcodesStrings :: FrozenSet String
 +cvAllEcodesStrings =
 +  ConstantUtils.mkSet $ map Types.cVErrorCodeToRaw [minBound..]
 +
 +-- * Node verify constants
 +
 +nvBridges :: String
 +nvBridges = "bridges"
 +
 +nvDrbdhelper :: String
 +nvDrbdhelper = "drbd-helper"
 +
 +nvDrbdversion :: String
 +nvDrbdversion = "drbd-version"
 +
 +nvDrbdlist :: String
 +nvDrbdlist = "drbd-list"
 +
 +nvExclusivepvs :: String
 +nvExclusivepvs = "exclusive-pvs"
 +
 +nvFilelist :: String
 +nvFilelist = "filelist"
 +
 +nvAcceptedStoragePaths :: String
 +nvAcceptedStoragePaths = "allowed-file-storage-paths"
 +
 +nvFileStoragePath :: String
 +nvFileStoragePath = "file-storage-path"
 +
 +nvSharedFileStoragePath :: String
 +nvSharedFileStoragePath = "shared-file-storage-path"
 +
 +nvHvinfo :: String
 +nvHvinfo = "hvinfo"
 +
 +nvHvparams :: String
 +nvHvparams = "hvparms"
 +
 +nvHypervisor :: String
 +nvHypervisor = "hypervisor"
 +
 +nvInstancelist :: String
 +nvInstancelist = "instancelist"
 +
 +nvLvlist :: String
 +nvLvlist = "lvlist"
 +
 +nvMasterip :: String
 +nvMasterip = "master-ip"
 +
 +nvNodelist :: String
 +nvNodelist = "nodelist"
 +
 +nvNodenettest :: String
 +nvNodenettest = "node-net-test"
 +
 +nvNodesetup :: String
 +nvNodesetup = "nodesetup"
 +
 +nvOobPaths :: String
 +nvOobPaths = "oob-paths"
 +
 +nvOslist :: String
 +nvOslist = "oslist"
 +
 +nvPvlist :: String
 +nvPvlist = "pvlist"
 +
 +nvTime :: String
 +nvTime = "time"
 +
 +nvUserscripts :: String
 +nvUserscripts = "user-scripts"
 +
 +nvVersion :: String
 +nvVersion = "version"
 +
 +nvVglist :: String
 +nvVglist = "vglist"
 +
 +nvVmnodes :: String
 +nvVmnodes = "vmnodes"
 +
 +-- * Instance status
 +
 +inststAdmindown :: String
 +inststAdmindown = Types.instanceStatusToRaw StatusDown
 +
 +inststAdminoffline :: String
 +inststAdminoffline = Types.instanceStatusToRaw StatusOffline
 +
 +inststErrordown :: String
 +inststErrordown = Types.instanceStatusToRaw ErrorDown
 +
 +inststErrorup :: String
 +inststErrorup = Types.instanceStatusToRaw ErrorUp
 +
 +inststNodedown :: String
 +inststNodedown = Types.instanceStatusToRaw NodeDown
 +
 +inststNodeoffline :: String
 +inststNodeoffline = Types.instanceStatusToRaw NodeOffline
 +
 +inststRunning :: String
 +inststRunning = Types.instanceStatusToRaw Running
 +
 +inststWrongnode :: String
 +inststWrongnode = Types.instanceStatusToRaw WrongNode
 +
 +inststAll :: FrozenSet String
 +inststAll = ConstantUtils.mkSet $ map Types.instanceStatusToRaw [minBound..]
 +
 +-- * Admin states
 +
 +adminstDown :: String
 +adminstDown = Types.adminStateToRaw AdminDown
 +
 +adminstOffline :: String
 +adminstOffline = Types.adminStateToRaw AdminOffline
 +
 +adminstUp :: String
 +adminstUp = Types.adminStateToRaw AdminUp
 +
 +adminstAll :: FrozenSet String
 +adminstAll = ConstantUtils.mkSet $ map Types.adminStateToRaw [minBound..]
 +
 +-- * Node roles
 +
 +nrDrained :: String
 +nrDrained = Types.nodeRoleToRaw NRDrained
 +
 +nrMaster :: String
 +nrMaster = Types.nodeRoleToRaw NRMaster
 +
 +nrMcandidate :: String
 +nrMcandidate = Types.nodeRoleToRaw NRCandidate
 +
 +nrOffline :: String
 +nrOffline = Types.nodeRoleToRaw NROffline
 +
 +nrRegular :: String
 +nrRegular = Types.nodeRoleToRaw NRRegular
 +
 +nrAll :: FrozenSet String
 +nrAll = ConstantUtils.mkSet $ map Types.nodeRoleToRaw [minBound..]
 +
 +-- * SSL certificate check constants (in days)
 +
 +sslCertExpirationError :: Int
 +sslCertExpirationError = 7
 +
 +sslCertExpirationWarn :: Int
 +sslCertExpirationWarn = 30
 +
 +-- * Allocator framework constants
 +
 +iallocatorVersion :: Int
 +iallocatorVersion = 2
 +
 +iallocatorDirIn :: String
 +iallocatorDirIn = Types.iAllocatorTestDirToRaw IAllocatorDirIn
 +
 +iallocatorDirOut :: String
 +iallocatorDirOut = Types.iAllocatorTestDirToRaw IAllocatorDirOut
 +
 +validIallocatorDirections :: FrozenSet String
 +validIallocatorDirections =
 +  ConstantUtils.mkSet $ map Types.iAllocatorTestDirToRaw [minBound..]
 +
 +iallocatorModeAlloc :: String
 +iallocatorModeAlloc = Types.iAllocatorModeToRaw IAllocatorAlloc
 +
 +iallocatorModeChgGroup :: String
 +iallocatorModeChgGroup = Types.iAllocatorModeToRaw IAllocatorChangeGroup
 +
 +iallocatorModeMultiAlloc :: String
 +iallocatorModeMultiAlloc = Types.iAllocatorModeToRaw IAllocatorMultiAlloc
 +
 +iallocatorModeNodeEvac :: String
 +iallocatorModeNodeEvac = Types.iAllocatorModeToRaw IAllocatorNodeEvac
 +
 +iallocatorModeReloc :: String
 +iallocatorModeReloc = Types.iAllocatorModeToRaw IAllocatorReloc
 +
 +validIallocatorModes :: FrozenSet String
 +validIallocatorModes =
 +  ConstantUtils.mkSet $ map Types.iAllocatorModeToRaw [minBound..]
 +
 +iallocatorSearchPath :: [String]
 +iallocatorSearchPath = AutoConf.iallocatorSearchPath
 +
 +defaultIallocatorShortcut :: String
 +defaultIallocatorShortcut = "."
 +
 +-- * Node evacuation
 +
 +nodeEvacPri :: String
 +nodeEvacPri = Types.evacModeToRaw ChangePrimary
 +
 +nodeEvacSec :: String
 +nodeEvacSec = Types.evacModeToRaw ChangeSecondary
 +
 +nodeEvacAll :: String
 +nodeEvacAll = Types.evacModeToRaw ChangeAll
 +
 +nodeEvacModes :: FrozenSet String
 +nodeEvacModes = ConstantUtils.mkSet $ map Types.evacModeToRaw [minBound..]
 +
 +-- * Job queue
 +
 +jobQueueVersion :: Int
 +jobQueueVersion = 1
 +
 +jobQueueSizeHardLimit :: Int
 +jobQueueSizeHardLimit = 5000
 +
 +jobQueueFilesPerms :: Int
 +jobQueueFilesPerms = 0o640
 +
 +-- * Unchanged job return
 +
 +jobNotchanged :: String
 +jobNotchanged = "nochange"
 +
 +-- * Job status
 +
 +jobStatusQueued :: String
 +jobStatusQueued = Types.jobStatusToRaw JOB_STATUS_QUEUED
 +
 +jobStatusWaiting :: String
 +jobStatusWaiting = Types.jobStatusToRaw JOB_STATUS_WAITING
 +
 +jobStatusCanceling :: String
 +jobStatusCanceling = Types.jobStatusToRaw JOB_STATUS_CANCELING
 +
 +jobStatusRunning :: String
 +jobStatusRunning = Types.jobStatusToRaw JOB_STATUS_RUNNING
 +
 +jobStatusCanceled :: String
 +jobStatusCanceled = Types.jobStatusToRaw JOB_STATUS_CANCELED
 +
 +jobStatusSuccess :: String
 +jobStatusSuccess = Types.jobStatusToRaw JOB_STATUS_SUCCESS
 +
 +jobStatusError :: String
 +jobStatusError = Types.jobStatusToRaw JOB_STATUS_ERROR
 +
 +jobsPending :: FrozenSet String
 +jobsPending =
 +  ConstantUtils.mkSet [jobStatusQueued, jobStatusWaiting, jobStatusCanceling]
 +
 +jobsFinalized :: FrozenSet String
 +jobsFinalized =
 +  ConstantUtils.mkSet $ map Types.finalizedJobStatusToRaw [minBound..]
 +
 +jobStatusAll :: FrozenSet String
 +jobStatusAll = ConstantUtils.mkSet $ map Types.jobStatusToRaw [minBound..]
 +
 +-- * OpCode status
 +
 +-- ** Not yet finalized opcodes
 +
 +opStatusCanceling :: String
 +opStatusCanceling = "canceling"
 +
 +opStatusQueued :: String
 +opStatusQueued = "queued"
 +
 +opStatusRunning :: String
 +opStatusRunning = "running"
 +
 +opStatusWaiting :: String
 +opStatusWaiting = "waiting"
 +
 +-- ** Finalized opcodes
 +
 +opStatusCanceled :: String
 +opStatusCanceled = "canceled"
 +
 +opStatusError :: String
 +opStatusError = "error"
 +
 +opStatusSuccess :: String
 +opStatusSuccess = "success"
 +
 +opsFinalized :: FrozenSet String
 +opsFinalized =
 +  ConstantUtils.mkSet [opStatusCanceled, opStatusError, opStatusSuccess]
 +
 +-- * OpCode priority
 +
 +opPrioLowest :: Int
 +opPrioLowest = 19
 +
 +opPrioHighest :: Int
 +opPrioHighest = -20
 +
 +opPrioLow :: Int
 +opPrioLow = Types.opSubmitPriorityToRaw OpPrioLow
 +
 +opPrioNormal :: Int
 +opPrioNormal = Types.opSubmitPriorityToRaw OpPrioNormal
 +
 +opPrioHigh :: Int
 +opPrioHigh = Types.opSubmitPriorityToRaw OpPrioHigh
 +
 +opPrioSubmitValid :: FrozenSet Int
 +opPrioSubmitValid = ConstantUtils.mkSet [opPrioLow, opPrioNormal, opPrioHigh]
 +
 +opPrioDefault :: Int
 +opPrioDefault = opPrioNormal
 +
 +-- * Lock recalculate mode
 +
 +locksAppend :: String
 +locksAppend = "append"
 +
 +locksReplace :: String
 +locksReplace = "replace"
 +
 +-- * Lock timeout
 +--
 +-- The lock timeout (sum) before we transition into blocking acquire
 +-- (this can still be reset by priority change).  Computed as max time
 +-- (10 hours) before we should actually go into blocking acquire,
 +-- given that we start from the default priority level.
 +
 +lockAttemptsMaxwait :: Double
 +lockAttemptsMaxwait = 15.0
 +
 +lockAttemptsMinwait :: Double
 +lockAttemptsMinwait = 1.0
 +
 +lockAttemptsTimeout :: Int
 +lockAttemptsTimeout = (10 * 3600) `div` (opPrioDefault - opPrioHighest)
 +
 +-- * Execution log types
 +
 +elogMessage :: String
 +elogMessage = Types.eLogTypeToRaw ELogMessage
 +
 +elogRemoteImport :: String
 +elogRemoteImport = Types.eLogTypeToRaw ELogRemoteImport
 +
 +elogJqueueTest :: String
 +elogJqueueTest = Types.eLogTypeToRaw ELogJqueueTest
 +
 +-- * /etc/hosts modification
 +
 +etcHostsAdd :: String
 +etcHostsAdd = "add"
 +
 +etcHostsRemove :: String
 +etcHostsRemove = "remove"
 +
 +-- * Job queue test
 +
 +jqtMsgprefix :: String
 +jqtMsgprefix = "TESTMSG="
 +
 +jqtExec :: String
 +jqtExec = "exec"
 +
 +jqtExpandnames :: String
 +jqtExpandnames = "expandnames"
 +
 +jqtLogmsg :: String
 +jqtLogmsg = "logmsg"
 +
 +jqtStartmsg :: String
 +jqtStartmsg = "startmsg"
 +
 +jqtAll :: FrozenSet String
 +jqtAll = ConstantUtils.mkSet [jqtExec, jqtExpandnames, jqtLogmsg, jqtStartmsg]
 +
 +-- * Query resources
 +
 +qrCluster :: String
 +qrCluster = "cluster"
 +
 +qrExport :: String
 +qrExport = "export"
 +
 +qrExtstorage :: String
 +qrExtstorage = "extstorage"
 +
 +qrGroup :: String
 +qrGroup = "group"
 +
 +qrInstance :: String
 +qrInstance = "instance"
 +
 +qrJob :: String
 +qrJob = "job"
 +
 +qrLock :: String
 +qrLock = "lock"
 +
 +qrNetwork :: String
 +qrNetwork = "network"
 +
 +qrNode :: String
 +qrNode = "node"
 +
 +qrOs :: String
 +qrOs = "os"
 +
 +-- | List of resources which can be queried using 'Ganeti.OpCodes.OpQuery'
 +qrViaOp :: FrozenSet String
 +qrViaOp =
 +  ConstantUtils.mkSet [qrCluster,
 +                       qrInstance,
 +                       qrNode,
 +                       qrGroup,
 +                       qrOs,
 +                       qrExport,
 +                       qrNetwork,
 +                       qrExtstorage]
 +
 +-- | List of resources which can be queried using Local UniX Interface
 +qrViaLuxi :: FrozenSet String
 +qrViaLuxi = ConstantUtils.mkSet [qrLock, qrJob]
 +
 +-- | List of resources which can be queried using RAPI
 +qrViaRapi :: FrozenSet String
 +qrViaRapi = qrViaLuxi
 +
 +-- * Query field types
 +
 +qftBool :: String
 +qftBool = "bool"
 +
 +qftNumber :: String
 +qftNumber = "number"
 +
 +qftOther :: String
 +qftOther = "other"
 +
 +qftText :: String
 +qftText = "text"
 +
 +qftTimestamp :: String
 +qftTimestamp = "timestamp"
 +
 +qftUnit :: String
 +qftUnit = "unit"
 +
 +qftUnknown :: String
 +qftUnknown = "unknown"
 +
 +qftAll :: FrozenSet String
 +qftAll =
 +  ConstantUtils.mkSet [qftBool,
 +                       qftNumber,
 +                       qftOther,
 +                       qftText,
 +                       qftTimestamp,
 +                       qftUnit,
 +                       qftUnknown]
 +
 +-- * Query result field status
 +--
 +-- Don't change or reuse values as they're used by clients.
 +--
 +-- FIXME: link with 'Ganeti.Query.Language.ResultStatus'
 +
 +-- | No data (e.g. RPC error), can be used instead of 'rsOffline'
 +rsNodata :: Int
 +rsNodata = 2
 +
 +rsNormal :: Int
 +rsNormal = 0
 +
 +-- | Resource marked offline
 +rsOffline :: Int
 +rsOffline = 4
 +
 +-- | Value unavailable/unsupported for item; if this field is
 +-- supported but we cannot get the data for the moment, 'rsNodata' or
 +-- 'rsOffline' should be used
 +rsUnavail :: Int
 +rsUnavail = 3
 +
 +rsUnknown :: Int
 +rsUnknown = 1
 +
 +rsAll :: FrozenSet Int
 +rsAll =
 +  ConstantUtils.mkSet [rsNodata,
 +                       rsNormal,
 +                       rsOffline,
 +                       rsUnavail,
 +                       rsUnknown]
 +
 +-- | Special field cases and their verbose/terse formatting
 +rssDescription :: Map Int (String, String)
 +rssDescription =
 +  Map.fromList [(rsUnknown, ("(unknown)", "??")),
 +                (rsNodata, ("(nodata)", "?")),
 +                (rsOffline, ("(offline)", "*")),
 +                (rsUnavail, ("(unavail)", "-"))]
 +
 +-- * Max dynamic devices
 +
 +maxDisks :: Int
 +maxDisks = Types.maxDisks
 +
 +maxNics :: Int
 +maxNics = Types.maxNics
 +
 +-- | SSCONF file prefix
 +ssconfFileprefix :: String
 +ssconfFileprefix = "ssconf_"
 +
 +-- * SSCONF keys
 +
 +ssClusterName :: String
 +ssClusterName = "cluster_name"
 +
 +ssClusterTags :: String
 +ssClusterTags = "cluster_tags"
 +
 +ssFileStorageDir :: String
 +ssFileStorageDir = "file_storage_dir"
 +
 +ssSharedFileStorageDir :: String
 +ssSharedFileStorageDir = "shared_file_storage_dir"
 +
 +ssMasterCandidates :: String
 +ssMasterCandidates = "master_candidates"
 +
 +ssMasterCandidatesIps :: String
 +ssMasterCandidatesIps = "master_candidates_ips"
 +
 +ssMasterIp :: String
 +ssMasterIp = "master_ip"
 +
 +ssMasterNetdev :: String
 +ssMasterNetdev = "master_netdev"
 +
 +ssMasterNetmask :: String
 +ssMasterNetmask = "master_netmask"
 +
 +ssMasterNode :: String
 +ssMasterNode = "master_node"
 +
 +ssNodeList :: String
 +ssNodeList = "node_list"
 +
 +ssNodePrimaryIps :: String
 +ssNodePrimaryIps = "node_primary_ips"
 +
 +ssNodeSecondaryIps :: String
 +ssNodeSecondaryIps = "node_secondary_ips"
 +
 +ssOfflineNodes :: String
 +ssOfflineNodes = "offline_nodes"
 +
 +ssOnlineNodes :: String
 +ssOnlineNodes = "online_nodes"
 +
 +ssPrimaryIpFamily :: String
 +ssPrimaryIpFamily = "primary_ip_family"
 +
 +ssInstanceList :: String
 +ssInstanceList = "instance_list"
 +
 +ssReleaseVersion :: String
 +ssReleaseVersion = "release_version"
 +
 +ssHypervisorList :: String
 +ssHypervisorList = "hypervisor_list"
 +
 +ssMaintainNodeHealth :: String
 +ssMaintainNodeHealth = "maintain_node_health"
 +
 +ssUidPool :: String
 +ssUidPool = "uid_pool"
 +
 +ssNodegroups :: String
 +ssNodegroups = "nodegroups"
 +
 +ssNetworks :: String
 +ssNetworks = "networks"
 +
 +-- | This is not a complete SSCONF key, but the prefix for the
 +-- hypervisor keys
 +ssHvparamsPref :: String
 +ssHvparamsPref = "hvparams_"
 +
 +-- * Hvparams keys
 +
 +ssHvparamsXenChroot :: String
 +ssHvparamsXenChroot = ssHvparamsPref ++ htChroot
 +
 +ssHvparamsXenFake :: String
 +ssHvparamsXenFake = ssHvparamsPref ++ htFake
 +
 +ssHvparamsXenHvm :: String
 +ssHvparamsXenHvm = ssHvparamsPref ++ htXenHvm
 +
 +ssHvparamsXenKvm :: String
 +ssHvparamsXenKvm = ssHvparamsPref ++ htKvm
 +
 +ssHvparamsXenLxc :: String
 +ssHvparamsXenLxc = ssHvparamsPref ++ htLxc
 +
 +ssHvparamsXenPvm :: String
 +ssHvparamsXenPvm = ssHvparamsPref ++ htXenPvm
 +
 +validSsHvparamsKeys :: FrozenSet String
 +validSsHvparamsKeys =
 +  ConstantUtils.mkSet [ssHvparamsXenChroot,
 +                       ssHvparamsXenLxc,
 +                       ssHvparamsXenFake,
 +                       ssHvparamsXenHvm,
 +                       ssHvparamsXenKvm,
 +                       ssHvparamsXenPvm]
 +
 +ssFilePerms :: Int
 +ssFilePerms = 0o444
 +
 +-- | Cluster wide default parameters
 +defaultEnabledHypervisor :: String
 +defaultEnabledHypervisor = htXenPvm
 +
 +hvcDefaults :: Map Hypervisor (Map String PyValueEx)
 +hvcDefaults =
 +  Map.fromList
 +  [ (XenPvm, Map.fromList
 +             [ (hvUseBootloader,  PyValueEx False)
 +             , (hvBootloaderPath, PyValueEx xenBootloader)
 +             , (hvBootloaderArgs, PyValueEx "")
 +             , (hvKernelPath,     PyValueEx xenKernel)
 +             , (hvInitrdPath,     PyValueEx "")
 +             , (hvRootPath,       PyValueEx "/dev/xvda1")
 +             , (hvKernelArgs,     PyValueEx "ro")
 +             , (hvMigrationPort,  PyValueEx (8002 :: Int))
 +             , (hvMigrationMode,  PyValueEx htMigrationLive)
 +             , (hvBlockdevPrefix, PyValueEx "sd")
 +             , (hvRebootBehavior, PyValueEx instanceRebootAllowed)
 +             , (hvCpuMask,        PyValueEx cpuPinningAll)
 +             , (hvCpuCap,         PyValueEx (0 :: Int))
 +             , (hvCpuWeight,      PyValueEx (256 :: Int))
 +             , (hvVifScript,      PyValueEx "")
 +             , (hvXenCmd,         PyValueEx xenCmdXm)
 +             , (hvXenCpuid,       PyValueEx "")
 +             , (hvSoundhw,        PyValueEx "")
 +             ])
 +  , (XenHvm, Map.fromList
 +             [ (hvBootOrder,      PyValueEx "cd")
 +             , (hvCdromImagePath, PyValueEx "")
 +             , (hvNicType,        PyValueEx htNicRtl8139)
 +             , (hvDiskType,       PyValueEx htDiskParavirtual)
 +             , (hvVncBindAddress, PyValueEx ip4AddressAny)
 +             , (hvAcpi,           PyValueEx True)
 +             , (hvPae,            PyValueEx True)
 +             , (hvKernelPath,     PyValueEx "/usr/lib/xen/boot/hvmloader")
 +             , (hvDeviceModel,    PyValueEx "/usr/lib/xen/bin/qemu-dm")
 +             , (hvMigrationPort,  PyValueEx (8002 :: Int))
 +             , (hvMigrationMode,  PyValueEx htMigrationNonlive)
 +             , (hvUseLocaltime,   PyValueEx False)
 +             , (hvBlockdevPrefix, PyValueEx "hd")
 +             , (hvPassthrough,    PyValueEx "")
 +             , (hvRebootBehavior, PyValueEx instanceRebootAllowed)
 +             , (hvCpuMask,        PyValueEx cpuPinningAll)
 +             , (hvCpuCap,         PyValueEx (0 :: Int))
 +             , (hvCpuWeight,      PyValueEx (256 :: Int))
 +             , (hvVifType,        PyValueEx htHvmVifIoemu)
 +             , (hvVifScript,      PyValueEx "")
 +             , (hvViridian,       PyValueEx False)
 +             , (hvXenCmd,         PyValueEx xenCmdXm)
 +             , (hvXenCpuid,       PyValueEx "")
 +             , (hvSoundhw,        PyValueEx "")
 +             ])
 +  , (Kvm, Map.fromList
 +          [ (hvKvmPath,                         PyValueEx kvmPath)
 +          , (hvKernelPath,                      PyValueEx kvmKernel)
 +          , (hvInitrdPath,                      PyValueEx "")
 +          , (hvKernelArgs,                      PyValueEx "ro")
 +          , (hvRootPath,                        PyValueEx "/dev/vda1")
 +          , (hvAcpi,                            PyValueEx True)
 +          , (hvSerialConsole,                   PyValueEx True)
 +          , (hvSerialSpeed,                     PyValueEx (38400 :: Int))
 +          , (hvVncBindAddress,                  PyValueEx "")
 +          , (hvVncTls,                          PyValueEx False)
 +          , (hvVncX509,                         PyValueEx "")
 +          , (hvVncX509Verify,                   PyValueEx False)
 +          , (hvVncPasswordFile,                 PyValueEx "")
 +          , (hvKvmSpiceBind,                    PyValueEx "")
 +          , (hvKvmSpiceIpVersion,           PyValueEx ifaceNoIpVersionSpecified)
 +          , (hvKvmSpicePasswordFile,            PyValueEx "")
 +          , (hvKvmSpiceLosslessImgCompr,        PyValueEx "")
 +          , (hvKvmSpiceJpegImgCompr,            PyValueEx "")
 +          , (hvKvmSpiceZlibGlzImgCompr,         PyValueEx "")
 +          , (hvKvmSpiceStreamingVideoDetection, PyValueEx "")
 +          , (hvKvmSpiceAudioCompr,              PyValueEx True)
 +          , (hvKvmSpiceUseTls,                  PyValueEx False)
 +          , (hvKvmSpiceTlsCiphers,              PyValueEx opensslCiphers)
 +          , (hvKvmSpiceUseVdagent,              PyValueEx True)
 +          , (hvKvmFloppyImagePath,              PyValueEx "")
 +          , (hvCdromImagePath,                  PyValueEx "")
 +          , (hvKvmCdrom2ImagePath,              PyValueEx "")
 +          , (hvBootOrder,                       PyValueEx htBoDisk)
 +          , (hvNicType,                         PyValueEx htNicParavirtual)
 +          , (hvDiskType,                        PyValueEx htDiskParavirtual)
 +          , (hvKvmCdromDiskType,                PyValueEx "")
 +          , (hvUsbMouse,                        PyValueEx "")
 +          , (hvKeymap,                          PyValueEx "")
 +          , (hvMigrationPort,                   PyValueEx (8102 :: Int))
 +          , (hvMigrationBandwidth,              PyValueEx (32 :: Int))
 +          , (hvMigrationDowntime,               PyValueEx (30 :: Int))
 +          , (hvMigrationMode,                   PyValueEx htMigrationLive)
 +          , (hvUseLocaltime,                    PyValueEx False)
 +          , (hvDiskCache,                       PyValueEx htCacheDefault)
 +          , (hvSecurityModel,                   PyValueEx htSmNone)
 +          , (hvSecurityDomain,                  PyValueEx "")
 +          , (hvKvmFlag,                         PyValueEx "")
 +          , (hvVhostNet,                        PyValueEx False)
 +          , (hvKvmUseChroot,                    PyValueEx False)
 +          , (hvMemPath,                         PyValueEx "")
 +          , (hvRebootBehavior,                  PyValueEx instanceRebootAllowed)
 +          , (hvCpuMask,                         PyValueEx cpuPinningAll)
 +          , (hvCpuType,                         PyValueEx "")
 +          , (hvCpuCores,                        PyValueEx (0 :: Int))
 +          , (hvCpuThreads,                      PyValueEx (0 :: Int))
 +          , (hvCpuSockets,                      PyValueEx (0 :: Int))
 +          , (hvSoundhw,                         PyValueEx "")
 +          , (hvUsbDevices,                      PyValueEx "")
 +          , (hvVga,                             PyValueEx "")
 +          , (hvKvmExtra,                        PyValueEx "")
 +          , (hvKvmMachineVersion,               PyValueEx "")
 +          , (hvVnetHdr,                         PyValueEx True)])
 +  , (Fake, Map.fromList [(hvMigrationMode, PyValueEx htMigrationLive)])
 +  , (Chroot, Map.fromList [(hvInitScript, PyValueEx "/ganeti-chroot")])
 +  , (Lxc, Map.fromList [(hvCpuMask, PyValueEx "")])
 +  ]
 +
 +hvcGlobals :: FrozenSet String
 +hvcGlobals =
 +  ConstantUtils.mkSet [hvMigrationBandwidth,
 +                       hvMigrationMode,
 +                       hvMigrationPort,
 +                       hvXenCmd]
 +
 +becDefaults :: Map String PyValueEx
 +becDefaults =
 +  Map.fromList
 +  [ (beMinmem, PyValueEx (128 :: Int))
 +  , (beMaxmem, PyValueEx (128 :: Int))
 +  , (beVcpus, PyValueEx (1 :: Int))
 +  , (beAutoBalance, PyValueEx True)
 +  , (beAlwaysFailover, PyValueEx False)
 +  , (beSpindleUse, PyValueEx (1 :: Int))
 +  ]
 +
 +ndcDefaults :: Map String PyValueEx
 +ndcDefaults =
 +  Map.fromList
 +  [ (ndOobProgram,       PyValueEx "")
 +  , (ndSpindleCount,     PyValueEx (1 :: Int))
 +  , (ndExclusiveStorage, PyValueEx False)
 +  , (ndOvs,              PyValueEx False)
 +  , (ndOvsName,          PyValueEx defaultOvs)
 +  , (ndOvsLink,          PyValueEx "")
 +  ]
 +
 +ndcGlobals :: FrozenSet String
 +ndcGlobals = ConstantUtils.mkSet [ndExclusiveStorage]
 +
 +-- | Default delay target measured in sectors
 +defaultDelayTarget :: Int
 +defaultDelayTarget = 1
 +
 +defaultDiskCustom :: String
 +defaultDiskCustom = ""
 +
 +defaultDiskResync :: Bool
 +defaultDiskResync = False
 +
 +-- | Default fill target measured in sectors
 +defaultFillTarget :: Int
 +defaultFillTarget = 0
 +
 +-- | Default mininum rate measured in KiB/s
 +defaultMinRate :: Int
 +defaultMinRate = 4 * 1024
 +
 +defaultNetCustom :: String
 +defaultNetCustom = ""
 +
 +-- | Default plan ahead measured in sectors
 +--
 +-- The default values for the DRBD dynamic resync speed algorithm are
 +-- taken from the drbsetup 8.3.11 man page, except for c-plan-ahead
 +-- (that we don't need to set to 0, because we have a separate option
 +-- to enable it) and for c-max-rate, that we cap to the default value
 +-- for the static resync rate.
 +defaultPlanAhead :: Int
 +defaultPlanAhead = 20
 +
 +defaultRbdPool :: String
 +defaultRbdPool = "rbd"
 +
 +diskLdDefaults :: Map DiskTemplate (Map String PyValueEx)
 +diskLdDefaults =
 +  Map.fromList
 +  [ (DTBlock, Map.empty)
 +  , (DTDrbd8, Map.fromList
 +              [ (ldpBarriers,      PyValueEx drbdBarriers)
 +              , (ldpDefaultMetavg, PyValueEx defaultVg)
 +              , (ldpDelayTarget,   PyValueEx defaultDelayTarget)
 +              , (ldpDiskCustom,    PyValueEx defaultDiskCustom)
 +              , (ldpDynamicResync, PyValueEx defaultDiskResync)
 +              , (ldpFillTarget,    PyValueEx defaultFillTarget)
 +              , (ldpMaxRate,       PyValueEx classicDrbdSyncSpeed)
 +              , (ldpMinRate,       PyValueEx defaultMinRate)
 +              , (ldpNetCustom,     PyValueEx defaultNetCustom)
 +              , (ldpNoMetaFlush,   PyValueEx drbdNoMetaFlush)
 +              , (ldpPlanAhead,     PyValueEx defaultPlanAhead)
 +              , (ldpProtocol,      PyValueEx drbdDefaultNetProtocol)
 +              , (ldpResyncRate,    PyValueEx classicDrbdSyncSpeed)
 +              ])
 +  , (DTExt, Map.empty)
 +  , (DTFile, Map.empty)
 +  , (DTPlain, Map.fromList [(ldpStripes, PyValueEx lvmStripecount)])
 +  , (DTRbd, Map.fromList
 +            [ (ldpPool, PyValueEx defaultRbdPool)
 +            , (ldpAccess, PyValueEx diskKernelspace)
 +            ])
 +  , (DTSharedFile, Map.empty)
 +  ]
 +
 +diskDtDefaults :: Map DiskTemplate (Map String PyValueEx)
 +diskDtDefaults =
 +  Map.fromList
 +  [ (DTBlock,      Map.empty)
 +  , (DTDiskless,   Map.empty)
 +  , (DTDrbd8,      Map.fromList
 +                   [ (drbdDataStripes,   PyValueEx lvmStripecount)
 +                   , (drbdDefaultMetavg, PyValueEx defaultVg)
 +                   , (drbdDelayTarget,   PyValueEx defaultDelayTarget)
 +                   , (drbdDiskBarriers,  PyValueEx drbdBarriers)
 +                   , (drbdDiskCustom,    PyValueEx defaultDiskCustom)
 +                   , (drbdDynamicResync, PyValueEx defaultDiskResync)
 +                   , (drbdFillTarget,    PyValueEx defaultFillTarget)
 +                   , (drbdMaxRate,       PyValueEx classicDrbdSyncSpeed)
 +                   , (drbdMetaBarriers,  PyValueEx drbdNoMetaFlush)
 +                   , (drbdMetaStripes,   PyValueEx lvmStripecount)
 +                   , (drbdMinRate,       PyValueEx defaultMinRate)
 +                   , (drbdNetCustom,     PyValueEx defaultNetCustom)
 +                   , (drbdPlanAhead,     PyValueEx defaultPlanAhead)
 +                   , (drbdProtocol,      PyValueEx drbdDefaultNetProtocol)
 +                   , (drbdResyncRate,    PyValueEx classicDrbdSyncSpeed)
 +                   ])
 +  , (DTExt,        Map.empty)
 +  , (DTFile,       Map.empty)
 +  , (DTPlain,      Map.fromList [(lvStripes, PyValueEx lvmStripecount)])
 +  , (DTRbd,        Map.fromList
 +                   [ (rbdPool, PyValueEx defaultRbdPool)
 +                   , (rbdAccess, PyValueEx diskKernelspace)
 +                   ])
 +  , (DTSharedFile, Map.empty)
 +  ]
 +
 +niccDefaults :: Map String PyValueEx
 +niccDefaults =
 +  Map.fromList
 +  [ (nicMode, PyValueEx nicModeBridged)
 +  , (nicLink, PyValueEx defaultBridge)
 +  , (nicVlan, PyValueEx "")
 +  ]
 +
 +-- | All of the following values are quite arbitrary - there are no
 +-- "good" defaults, these must be customised per-site
 +ispecsMinmaxDefaults :: Map String (Map String Int)
 +ispecsMinmaxDefaults =
 +  Map.fromList
 +  [(ispecsMin,
 +    Map.fromList
 +    [(ConstantUtils.ispecMemSize, Types.iSpecMemorySize Types.defMinISpec),
 +     (ConstantUtils.ispecCpuCount, Types.iSpecCpuCount Types.defMinISpec),
 +     (ConstantUtils.ispecDiskCount, Types.iSpecDiskCount Types.defMinISpec),
 +     (ConstantUtils.ispecDiskSize, Types.iSpecDiskSize Types.defMinISpec),
 +     (ConstantUtils.ispecNicCount, Types.iSpecNicCount Types.defMinISpec),
 +     (ConstantUtils.ispecSpindleUse, Types.iSpecSpindleUse Types.defMinISpec)]),
 +   (ispecsMax,
 +    Map.fromList
 +    [(ConstantUtils.ispecMemSize, Types.iSpecMemorySize Types.defMaxISpec),
 +     (ConstantUtils.ispecCpuCount, Types.iSpecCpuCount Types.defMaxISpec),
 +     (ConstantUtils.ispecDiskCount, Types.iSpecDiskCount Types.defMaxISpec),
 +     (ConstantUtils.ispecDiskSize, Types.iSpecDiskSize Types.defMaxISpec),
 +     (ConstantUtils.ispecNicCount, Types.iSpecNicCount Types.defMaxISpec),
 +     (ConstantUtils.ispecSpindleUse, Types.iSpecSpindleUse Types.defMaxISpec)])]
 +
 +ipolicyDefaults :: Map String PyValueEx
 +ipolicyDefaults =
 +  Map.fromList
 +  [ (ispecsMinmax,        PyValueEx [ispecsMinmaxDefaults])
 +  , (ispecsStd,           PyValueEx (Map.fromList
 +                                     [ (ispecMemSize,    128)
 +                                     , (ispecCpuCount,   1)
 +                                     , (ispecDiskCount,  1)
 +                                     , (ispecDiskSize,   1024)
 +                                     , (ispecNicCount,   1)
 +                                     , (ispecSpindleUse, 1)
 +                                     ] :: Map String Int))
 +  , (ipolicyDts,          PyValueEx (ConstantUtils.toList diskTemplates))
 +  , (ipolicyVcpuRatio,    PyValueEx (4.0 :: Double))
 +  , (ipolicySpindleRatio, PyValueEx (32.0 :: Double))
 +  ]
 +
 +masterPoolSizeDefault :: Int
 +masterPoolSizeDefault = 10
 +
 +-- * Exclusive storage
 +
 +-- | Error margin used to compare physical disks
 +partMargin :: Double
 +partMargin = 0.01
 +
 +-- | Space reserved when creating instance disks
 +partReserved :: Double
 +partReserved = 0.02
 +
 +-- * Confd
 +
 +confdProtocolVersion :: Int
 +confdProtocolVersion = ConstantUtils.confdProtocolVersion
 +
 +-- Confd request type
 +
 +confdReqPing :: Int
 +confdReqPing = Types.confdRequestTypeToRaw ReqPing
 +
 +confdReqNodeRoleByname :: Int
 +confdReqNodeRoleByname = Types.confdRequestTypeToRaw ReqNodeRoleByName
 +
 +confdReqNodePipByInstanceIp :: Int
 +confdReqNodePipByInstanceIp = Types.confdRequestTypeToRaw ReqNodePipByInstPip
 +
 +confdReqClusterMaster :: Int
 +confdReqClusterMaster = Types.confdRequestTypeToRaw ReqClusterMaster
 +
 +confdReqNodePipList :: Int
 +confdReqNodePipList = Types.confdRequestTypeToRaw ReqNodePipList
 +
 +confdReqMcPipList :: Int
 +confdReqMcPipList = Types.confdRequestTypeToRaw ReqMcPipList
 +
 +confdReqInstancesIpsList :: Int
 +confdReqInstancesIpsList = Types.confdRequestTypeToRaw ReqInstIpsList
 +
 +confdReqNodeDrbd :: Int
 +confdReqNodeDrbd = Types.confdRequestTypeToRaw ReqNodeDrbd
 +
 +confdReqNodeInstances :: Int
 +confdReqNodeInstances = Types.confdRequestTypeToRaw ReqNodeInstances
 +
 +confdReqs :: FrozenSet Int
 +confdReqs =
 +  ConstantUtils.mkSet .
 +  map Types.confdRequestTypeToRaw $
 +  [minBound..] \\ [ReqNodeInstances]
 +
 +-- * Confd request type
 +
 +confdReqfieldName :: Int
 +confdReqfieldName = Types.confdReqFieldToRaw ReqFieldName
 +
 +confdReqfieldIp :: Int
 +confdReqfieldIp = Types.confdReqFieldToRaw ReqFieldIp
 +
 +confdReqfieldMnodePip :: Int
 +confdReqfieldMnodePip = Types.confdReqFieldToRaw ReqFieldMNodePip
 +
 +-- * Confd repl status
 +
 +confdReplStatusOk :: Int
 +confdReplStatusOk = Types.confdReplyStatusToRaw ReplyStatusOk
 +
 +confdReplStatusError :: Int
 +confdReplStatusError = Types.confdReplyStatusToRaw ReplyStatusError
 +
 +confdReplStatusNotimplemented :: Int
 +confdReplStatusNotimplemented = Types.confdReplyStatusToRaw ReplyStatusNotImpl
 +
 +confdReplStatuses :: FrozenSet Int
 +confdReplStatuses =
 +  ConstantUtils.mkSet $ map Types.confdReplyStatusToRaw [minBound..]
 +
 +-- * Confd node role
 +
 +confdNodeRoleMaster :: Int
 +confdNodeRoleMaster = Types.confdNodeRoleToRaw NodeRoleMaster
 +
 +confdNodeRoleCandidate :: Int
 +confdNodeRoleCandidate = Types.confdNodeRoleToRaw NodeRoleCandidate
 +
 +confdNodeRoleOffline :: Int
 +confdNodeRoleOffline = Types.confdNodeRoleToRaw NodeRoleOffline
 +
 +confdNodeRoleDrained :: Int
 +confdNodeRoleDrained = Types.confdNodeRoleToRaw NodeRoleDrained
 +
 +confdNodeRoleRegular :: Int
 +confdNodeRoleRegular = Types.confdNodeRoleToRaw NodeRoleRegular
 +
 +-- * A few common errors for confd
 +
 +confdErrorUnknownEntry :: Int
 +confdErrorUnknownEntry = Types.confdErrorTypeToRaw ConfdErrorUnknownEntry
 +
 +confdErrorInternal :: Int
 +confdErrorInternal = Types.confdErrorTypeToRaw ConfdErrorInternal
 +
 +confdErrorArgument :: Int
 +confdErrorArgument = Types.confdErrorTypeToRaw ConfdErrorArgument
 +
 +-- * Confd request query fields
 +
 +confdReqqLink :: String
 +confdReqqLink = ConstantUtils.confdReqqLink
 +
 +confdReqqIp :: String
 +confdReqqIp = ConstantUtils.confdReqqIp
 +
 +confdReqqIplist :: String
 +confdReqqIplist = ConstantUtils.confdReqqIplist
 +
 +confdReqqFields :: String
 +confdReqqFields = ConstantUtils.confdReqqFields
 +
 +-- | Each request is "salted" by the current timestamp.
 +--
 +-- This constant decides how many seconds of skew to accept.
 +--
 +-- TODO: make this a default and allow the value to be more
 +-- configurable
 +confdMaxClockSkew :: Int
 +confdMaxClockSkew = 2 * nodeMaxClockSkew
 +
 +-- | When we haven't reloaded the config for more than this amount of
 +-- seconds, we force a test to see if inotify is betraying us. Using a
 +-- prime number to ensure we get less chance of 'same wakeup' with
 +-- other processes.
 +confdConfigReloadTimeout :: Int
 +confdConfigReloadTimeout = 17
 +
 +-- | If we receive more than one update in this amount of
 +-- microseconds, we move to polling every RATELIMIT seconds, rather
 +-- than relying on inotify, to be able to serve more requests.
 +confdConfigReloadRatelimit :: Int
 +confdConfigReloadRatelimit = 250000
 +
 +-- | Magic number prepended to all confd queries.
 +--
 +-- This allows us to distinguish different types of confd protocols
 +-- and handle them. For example by changing this we can move the whole
 +-- payload to be compressed, or move away from json.
 +confdMagicFourcc :: String
 +confdMagicFourcc = "plj0"
 +
 +-- | By default a confd request is sent to the minimum between this
 +-- number and all MCs. 6 was chosen because even in the case of a
 +-- disastrous 50% response rate, we should have enough answers to be
 +-- able to compare more than one.
 +confdDefaultReqCoverage :: Int
 +confdDefaultReqCoverage = 6
 +
 +-- | Timeout in seconds to expire pending query request in the confd
 +-- client library. We don't actually expect any answer more than 10
 +-- seconds after we sent a request.
 +confdClientExpireTimeout :: Int
 +confdClientExpireTimeout = 10
 +
 +-- | Maximum UDP datagram size.
 +--
 +-- On IPv4: 64K - 20 (ip header size) - 8 (udp header size) = 65507
 +-- On IPv6: 64K - 40 (ip6 header size) - 8 (udp header size) = 65487
 +--   (assuming we can't use jumbo frames)
 +-- We just set this to 60K, which should be enough
 +maxUdpDataSize :: Int
 +maxUdpDataSize = 61440
 +
 +-- * User-id pool minimum/maximum acceptable user-ids
 +
 +uidpoolUidMin :: Int
 +uidpoolUidMin = 0
 +
 +-- | Assuming 32 bit user-ids
 +uidpoolUidMax :: Integer
 +uidpoolUidMax = 2 ^ 32 - 1
 +
 +-- | Name or path of the pgrep command
 +pgrep :: String
 +pgrep = "pgrep"
 +
 +-- | Name of the node group that gets created at cluster init or
 +-- upgrade
 +initialNodeGroupName :: String
 +initialNodeGroupName = "default"
 +
 +-- * Possible values for NodeGroup.alloc_policy
 +
 +allocPolicyLastResort :: String
 +allocPolicyLastResort = Types.allocPolicyToRaw AllocLastResort
 +
 +allocPolicyPreferred :: String
 +allocPolicyPreferred = Types.allocPolicyToRaw AllocPreferred
 +
 +allocPolicyUnallocable :: String
 +allocPolicyUnallocable = Types.allocPolicyToRaw AllocUnallocable
 +
 +validAllocPolicies :: [String]
 +validAllocPolicies = map Types.allocPolicyToRaw [minBound..]
 +
 +-- | Temporary external/shared storage parameters
 +blockdevDriverManual :: String
 +blockdevDriverManual = Types.blockDriverToRaw BlockDrvManual
 +
 +-- | 'qemu-img' path, required for 'ovfconverter'
 +qemuimgPath :: String
 +qemuimgPath = AutoConf.qemuimgPath
 +
 +-- | Whether htools was enabled at compilation time
 +--
 +-- FIXME: this should be moved next to the other enable constants,
 +-- such as, 'enableConfd', and renamed to 'enableHtools'.
 +htools :: Bool
 +htools = AutoConf.htools
 +
 +-- | The hail iallocator
 +iallocHail :: String
 +iallocHail = "hail"
 +
 +-- * Fake opcodes for functions that have hooks attached to them via
 +-- backend.RunLocalHooks
 +
 +fakeOpMasterTurndown :: String
 +fakeOpMasterTurndown = "OP_CLUSTER_IP_TURNDOWN"
 +
 +fakeOpMasterTurnup :: String
 +fakeOpMasterTurnup = "OP_CLUSTER_IP_TURNUP"
 +
 +-- * SSH key types
 +
 +sshkDsa :: String
 +sshkDsa = "dsa"
 +
 +sshkRsa :: String
 +sshkRsa = "rsa"
 +
 +sshkAll :: FrozenSet String
 +sshkAll = ConstantUtils.mkSet [sshkRsa, sshkDsa]
 +
 +-- * SSH authorized key types
 +
 +sshakDss :: String
 +sshakDss = "ssh-dss"
 +
 +sshakRsa :: String
 +sshakRsa = "ssh-rsa"
 +
 +sshakAll :: FrozenSet String
 +sshakAll = ConstantUtils.mkSet [sshakDss, sshakRsa]
 +
 +-- * SSH setup
 +
 +sshsClusterName :: String
 +sshsClusterName = "cluster_name"
 +
 +sshsSshHostKey :: String
 +sshsSshHostKey = "ssh_host_key"
 +
 +sshsSshRootKey :: String
 +sshsSshRootKey = "ssh_root_key"
 +
 +sshsNodeDaemonCertificate :: String
 +sshsNodeDaemonCertificate = "node_daemon_certificate"
 +
 +-- * Key files for SSH daemon
 +
 +sshHostDsaPriv :: String
 +sshHostDsaPriv = sshConfigDir ++ "/ssh_host_dsa_key"
 +
 +sshHostDsaPub :: String
 +sshHostDsaPub = sshHostDsaPriv ++ ".pub"
 +
 +sshHostRsaPriv :: String
 +sshHostRsaPriv = sshConfigDir ++ "/ssh_host_rsa_key"
 +
 +sshHostRsaPub :: String
 +sshHostRsaPub = sshHostRsaPriv ++ ".pub"
 +
 +sshDaemonKeyfiles :: Map String (String, String)
 +sshDaemonKeyfiles =
 +  Map.fromList [ (sshkRsa, (sshHostRsaPriv, sshHostRsaPub))
 +               , (sshkDsa, (sshHostDsaPriv, sshHostDsaPub))
 +               ]
 +
 +-- * Node daemon setup
 +
 +ndsClusterName :: String
 +ndsClusterName = "cluster_name"
 +
 +ndsNodeDaemonCertificate :: String
 +ndsNodeDaemonCertificate = "node_daemon_certificate"
 +
 +ndsSsconf :: String
 +ndsSsconf = "ssconf"
 +
 +ndsStartNodeDaemon :: String
 +ndsStartNodeDaemon = "start_node_daemon"
 +
 +-- * The source reasons for the execution of an OpCode
 +
 +opcodeReasonSrcClient :: String
 +opcodeReasonSrcClient = "gnt:client"
 +
 +opcodeReasonSrcNoded :: String
 +opcodeReasonSrcNoded = "gnt:daemon:noded"
 +
 +opcodeReasonSrcOpcode :: String
 +opcodeReasonSrcOpcode = "gnt:opcode"
 +
 +opcodeReasonSrcRlib2 :: String
 +opcodeReasonSrcRlib2 = "gnt:library:rlib2"
 +
 +opcodeReasonSrcUser :: String
 +opcodeReasonSrcUser = "gnt:user"
 +
 +opcodeReasonSources :: FrozenSet String
 +opcodeReasonSources =
 +  ConstantUtils.mkSet [opcodeReasonSrcClient,
 +                       opcodeReasonSrcNoded,
 +                       opcodeReasonSrcOpcode,
 +                       opcodeReasonSrcRlib2,
 +                       opcodeReasonSrcUser]
 +
 +-- | Path generating random UUID
 +randomUuidFile :: String
 +randomUuidFile = ConstantUtils.randomUuidFile
 +
 +-- * Auto-repair tag prefixes
 +
 +autoRepairTagPrefix :: String
 +autoRepairTagPrefix = "ganeti:watcher:autorepair:"
 +
 +autoRepairTagEnabled :: String
 +autoRepairTagEnabled = autoRepairTagPrefix
 +
 +autoRepairTagPending :: String
 +autoRepairTagPending = autoRepairTagPrefix ++ "pending:"
 +
 +autoRepairTagResult :: String
 +autoRepairTagResult = autoRepairTagPrefix ++ "result:"
 +
 +autoRepairTagSuspended :: String
 +autoRepairTagSuspended = autoRepairTagPrefix ++ "suspend:"
 +
 +-- * Auto-repair levels
 +
 +autoRepairFailover :: String
 +autoRepairFailover = Types.autoRepairTypeToRaw ArFailover
 +
 +autoRepairFixStorage :: String
 +autoRepairFixStorage = Types.autoRepairTypeToRaw ArFixStorage
 +
 +autoRepairMigrate :: String
 +autoRepairMigrate = Types.autoRepairTypeToRaw ArMigrate
 +
 +autoRepairReinstall :: String
 +autoRepairReinstall = Types.autoRepairTypeToRaw ArReinstall
 +
 +autoRepairAllTypes :: FrozenSet String
 +autoRepairAllTypes =
 +  ConstantUtils.mkSet [autoRepairFailover,
 +                       autoRepairFixStorage,
 +                       autoRepairMigrate,
 +                       autoRepairReinstall]
 +
 +-- * Auto-repair results
 +
 +autoRepairEnoperm :: String
 +autoRepairEnoperm = Types.autoRepairResultToRaw ArEnoperm
 +
 +autoRepairFailure :: String
 +autoRepairFailure = Types.autoRepairResultToRaw ArFailure
 +
 +autoRepairSuccess :: String
 +autoRepairSuccess = Types.autoRepairResultToRaw ArSuccess
 +
 +autoRepairAllResults :: FrozenSet String
 +autoRepairAllResults =
 +  ConstantUtils.mkSet [autoRepairEnoperm, autoRepairFailure, autoRepairSuccess]
 +
 +-- | The version identifier for builtin data collectors
 +builtinDataCollectorVersion :: String
 +builtinDataCollectorVersion = "B"
 +
 +-- | The reason trail opcode parameter name
 +opcodeReason :: String
 +opcodeReason = "reason"
 +
 +diskstatsFile :: String
 +diskstatsFile = "/proc/diskstats"
 +
 +-- *  CPU load collector
 +
 +statFile :: String
 +statFile = "/proc/stat"
 +
 +cpuavgloadBufferSize :: Int
 +cpuavgloadBufferSize = 150
 +
 +cpuavgloadWindowSize :: Int
 +cpuavgloadWindowSize = 600
 +
 +-- * Monitoring daemon
 +
 +-- | Mond's variable for periodical data collection
 +mondTimeInterval :: Int
 +mondTimeInterval = 5
 +
 +-- | Mond's latest API version
 +mondLatestApiVersion :: Int
 +mondLatestApiVersion = 1
 +
 +-- * Disk access modes
 +
 +diskUserspace :: String
 +diskUserspace = Types.diskAccessModeToRaw DiskUserspace
 +
 +diskKernelspace :: String
 +diskKernelspace = Types.diskAccessModeToRaw DiskKernelspace
 +
 +diskValidAccessModes :: FrozenSet String
 +diskValidAccessModes =
 +  ConstantUtils.mkSet $ map Types.diskAccessModeToRaw [minBound..]
 +
 +-- | Timeout for queue draining in upgrades
 +upgradeQueueDrainTimeout :: Int
 +upgradeQueueDrainTimeout = 36 * 60 * 60 -- 1.5 days
 +
 +-- | Intervall at which the queue is polled during upgrades
 +upgradeQueuePollInterval :: Int
 +upgradeQueuePollInterval  = 10
 +
 +-- * Hotplug Actions
 +
 +hotplugActionAdd :: String
 +hotplugActionAdd = Types.hotplugActionToRaw HAAdd
 +
 +hotplugActionRemove :: String
 +hotplugActionRemove = Types.hotplugActionToRaw HARemove
 +
 +hotplugActionModify :: String
 +hotplugActionModify = Types.hotplugActionToRaw HAMod
 +
 +hotplugAllActions :: FrozenSet String
 +hotplugAllActions =
 +  ConstantUtils.mkSet $ map Types.hotplugActionToRaw [minBound..]
 +
 +-- * Hotplug Device Targets
 +
 +hotplugTargetNic :: String
 +hotplugTargetNic = Types.hotplugTargetToRaw HTNic
 +
 +hotplugTargetDisk :: String
 +hotplugTargetDisk = Types.hotplugTargetToRaw HTDisk
 +
 +hotplugAllTargets :: FrozenSet String
 +hotplugAllTargets =
 +  ConstantUtils.mkSet $ map Types.hotplugTargetToRaw [minBound..]
 +
 +-- | Timeout for disk removal (seconds)
 +diskRemoveRetryTimeout :: Int
 +diskRemoveRetryTimeout = 30
 +
 +-- | Interval between disk removal retries (seconds)
 +diskRemoveRetryInterval :: Int
 +diskRemoveRetryInterval  = 3
 +
 +-- * UUID regex
 +
 +uuidRegex :: String
 +uuidRegex = "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
 +
 +-- * Luxi constants
 +
++luxiSocketPerms :: Int
++luxiSocketPerms = 0o660
++
 +luxiKeyMethod :: String
 +luxiKeyMethod = "method"
 +
 +luxiKeyArgs :: String
 +luxiKeyArgs = "args"
 +
 +luxiKeySuccess :: String
 +luxiKeySuccess = "success"
 +
 +luxiKeyResult :: String
 +luxiKeyResult = "result"
 +
 +luxiKeyVersion :: String
 +luxiKeyVersion = "version"
 +
 +luxiReqSubmitJob :: String
 +luxiReqSubmitJob = "SubmitJob"
 +
 +luxiReqSubmitJobToDrainedQueue :: String
 +luxiReqSubmitJobToDrainedQueue = "SubmitJobToDrainedQueue"
 +
 +luxiReqSubmitManyJobs :: String
 +luxiReqSubmitManyJobs = "SubmitManyJobs"
 +
 +luxiReqWaitForJobChange :: String
 +luxiReqWaitForJobChange = "WaitForJobChange"
 +
 +luxiReqCancelJob :: String
 +luxiReqCancelJob = "CancelJob"
 +
 +luxiReqArchiveJob :: String
 +luxiReqArchiveJob = "ArchiveJob"
 +
 +luxiReqChangeJobPriority :: String
 +luxiReqChangeJobPriority = "ChangeJobPriority"
 +
 +luxiReqAutoArchiveJobs :: String
 +luxiReqAutoArchiveJobs = "AutoArchiveJobs"
 +
 +luxiReqQuery :: String
 +luxiReqQuery = "Query"
 +
 +luxiReqQueryFields :: String
 +luxiReqQueryFields = "QueryFields"
 +
 +luxiReqQueryJobs :: String
 +luxiReqQueryJobs = "QueryJobs"
 +
 +luxiReqQueryInstances :: String
 +luxiReqQueryInstances = "QueryInstances"
 +
 +luxiReqQueryNodes :: String
 +luxiReqQueryNodes = "QueryNodes"
 +
 +luxiReqQueryGroups :: String
 +luxiReqQueryGroups = "QueryGroups"
 +
 +luxiReqQueryNetworks :: String
 +luxiReqQueryNetworks = "QueryNetworks"
 +
 +luxiReqQueryExports :: String
 +luxiReqQueryExports = "QueryExports"
 +
 +luxiReqQueryConfigValues :: String
 +luxiReqQueryConfigValues = "QueryConfigValues"
 +
 +luxiReqQueryClusterInfo :: String
 +luxiReqQueryClusterInfo = "QueryClusterInfo"
 +
 +luxiReqQueryTags :: String
 +luxiReqQueryTags = "QueryTags"
 +
 +luxiReqSetDrainFlag :: String
 +luxiReqSetDrainFlag = "SetDrainFlag"
 +
 +luxiReqSetWatcherPause :: String
 +luxiReqSetWatcherPause = "SetWatcherPause"
 +
 +luxiReqAll :: FrozenSet String
 +luxiReqAll =
 +  ConstantUtils.mkSet
 +  [ luxiReqArchiveJob
 +  , luxiReqAutoArchiveJobs
 +  , luxiReqCancelJob
 +  , luxiReqChangeJobPriority
 +  , luxiReqQuery
 +  , luxiReqQueryClusterInfo
 +  , luxiReqQueryConfigValues
 +  , luxiReqQueryExports
 +  , luxiReqQueryFields
 +  , luxiReqQueryGroups
 +  , luxiReqQueryInstances
 +  , luxiReqQueryJobs
 +  , luxiReqQueryNodes
 +  , luxiReqQueryNetworks
 +  , luxiReqQueryTags
 +  , luxiReqSetDrainFlag
 +  , luxiReqSetWatcherPause
 +  , luxiReqSubmitJob
 +  , luxiReqSubmitJobToDrainedQueue
 +  , luxiReqSubmitManyJobs
 +  , luxiReqWaitForJobChange
 +  ]
 +
 +luxiDefCtmo :: Int
 +luxiDefCtmo = 10
 +
 +luxiDefRwto :: Int
 +luxiDefRwto = 60
 +
 +-- | 'WaitForJobChange' timeout
 +luxiWfjcTimeout :: Int
 +luxiWfjcTimeout = (luxiDefRwto - 1) `div` 2
 +
 +-- * Query language constants
 +
 +-- ** Logic operators with one or more operands, each of which is a
 +-- filter on its own
 +
 +qlangOpAnd :: String
 +qlangOpAnd = "&"
 +
 +qlangOpOr :: String
 +qlangOpOr = "|"
 +
 +-- ** Unary operators with exactly one operand
 +
 +qlangOpNot :: String
 +qlangOpNot = "!"
 +
 +qlangOpTrue :: String
 +qlangOpTrue = "?"
 +
 +-- ** Binary operators with exactly two operands, the field name and
 +-- an operator-specific value
 +
 +qlangOpContains :: String
 +qlangOpContains = "=[]"
 +
 +qlangOpEqual :: String
 +qlangOpEqual = "="
 +
 +qlangOpGe :: String
 +qlangOpGe = ">="
 +
 +qlangOpGt :: String
 +qlangOpGt = ">"
 +
 +qlangOpLe :: String
 +qlangOpLe = "<="
 +
 +qlangOpLt :: String
 +qlangOpLt = "<"
 +
 +qlangOpNotEqual :: String
 +qlangOpNotEqual = "!="
 +
 +qlangOpRegexp :: String
 +qlangOpRegexp = "=~"
 +
 +-- | Characters used for detecting user-written filters (see
 +-- L{_CheckFilter})
 +
 +qlangFilterDetectionChars :: FrozenSet String
 +qlangFilterDetectionChars =
 +  ConstantUtils.mkSet ["!", " ", "\"", "\'",
 +                       ")", "(", "\x0b", "\n",
 +                       "\r", "\x0c", "/", "<",
 +                       "\t", ">", "=", "\\", "~"]
 +
 +-- | Characters used to detect globbing filters
 +qlangGlobDetectionChars :: FrozenSet String
 +qlangGlobDetectionChars = ConstantUtils.mkSet ["*", "?"]
 +
 +-- * Error related constants
 +--
 +-- 'OpPrereqError' failure types
 +
 +-- | Environment error (e.g. node disk error)
 +errorsEcodeEnviron :: String
 +errorsEcodeEnviron = "environment_error"
 +
 +-- | Entity already exists
 +errorsEcodeExists :: String
 +errorsEcodeExists = "already_exists"
 +
 +-- | Internal cluster error
 +errorsEcodeFault :: String
 +errorsEcodeFault = "internal_error"
 +
 +-- | Wrong arguments (at syntax level)
 +errorsEcodeInval :: String
 +errorsEcodeInval = "wrong_input"
 +
 +-- | Entity not found
 +errorsEcodeNoent :: String
 +errorsEcodeNoent = "unknown_entity"
 +
 +-- | Not enough resources (iallocator failure, disk space, memory, etc)
 +errorsEcodeNores :: String
 +errorsEcodeNores = "insufficient_resources"
 +
 +-- | Resource not unique (e.g. MAC or IP duplication)
 +errorsEcodeNotunique :: String
 +errorsEcodeNotunique = "resource_not_unique"
 +
 +-- | Resolver errors
 +errorsEcodeResolver :: String
 +errorsEcodeResolver = "resolver_error"
 +
 +-- | Wrong entity state
 +errorsEcodeState :: String
 +errorsEcodeState = "wrong_state"
 +
 +-- | Temporarily out of resources; operation can be tried again
 +errorsEcodeTempNores :: String
 +errorsEcodeTempNores = "temp_insufficient_resources"
 +
 +errorsEcodeAll :: FrozenSet String
 +errorsEcodeAll =
 +  ConstantUtils.mkSet [ errorsEcodeNores
 +                      , errorsEcodeExists
 +                      , errorsEcodeState
 +                      , errorsEcodeNotunique
 +                      , errorsEcodeTempNores
 +                      , errorsEcodeNoent
 +                      , errorsEcodeFault
 +                      , errorsEcodeResolver
 +                      , errorsEcodeInval
 +                      , errorsEcodeEnviron
 +                      ]
 +
 +-- * Jstore related constants
 +
 +jstoreJobsPerArchiveDirectory :: Int
 +jstoreJobsPerArchiveDirectory = 10000
Simple merge
Simple merge
@@@ -149,8 -166,11 +153,11 @@@ isIpV6 = (':' `elem`
  prepareUrl :: (RpcCall a) => Node -> a -> String
  prepareUrl node call =
    let node_ip = nodePrimaryIp node
+       node_address = if isIpV6 node_ip
+                      then "[" ++ node_ip ++ "]"
+                      else node_ip
 -      port = snd C.daemonsPortsGanetiNoded
 +      port = C.defaultNodedPort
-       path_prefix = "https://" ++ node_ip ++ ":" ++ show port
+       path_prefix = "https://" ++ node_address ++ ":" ++ show port
    in path_prefix ++ "/" ++ rpcCallName call
  
  -- | Create HTTP request for a given node provided it is online,
index cba364e,0000000..51bc04e
mode 100644,000000..100644
--- /dev/null
@@@ -1,2359 -1,0 +1,2359 @@@
 +#!/usr/bin/python
 +#
 +
 +# Copyright (C) 2008, 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.
 +
 +
 +"""Tests for LUInstance*
 +
 +"""
 +
 +import copy
 +import itertools
 +import unittest
 +import mock
 +import operator
 +
 +from ganeti import compat
 +from ganeti import constants
 +from ganeti import errors
 +from ganeti import ht
 +from ganeti import opcodes
 +from ganeti import objects
 +from ganeti import rpc
 +from ganeti import utils
 +from ganeti.cmdlib import instance
 +
 +from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
 +
 +from testsupport import *
 +
 +import testutils
 +
 +
 +class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
 +  def test(self):
 +    ispec = {
 +      constants.ISPEC_MEM_SIZE: 2048,
 +      constants.ISPEC_CPU_COUNT: 2,
 +      constants.ISPEC_DISK_COUNT: 1,
 +      constants.ISPEC_DISK_SIZE: [512],
 +      constants.ISPEC_NIC_COUNT: 0,
 +      constants.ISPEC_SPINDLE_USE: 1,
 +      }
 +    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
 +                                            constants.DT_PLAIN)
 +    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
 +                                                        constants.DT_PLAIN,
 +                                                        _compute_fn=stub)
 +    self.assertEqual(ret, [])
 +
 +
 +class TestLUInstanceCreate(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceCreate, self).setUp()
 +
 +    self.net = self.cfg.AddNewNetwork()
 +    self.cfg.ConnectNetworkToGroup(self.net, self.group)
 +
 +    self.node1 = self.cfg.AddNewNode()
 +    self.node2 = self.cfg.AddNewNode()
 +
 +    self.rpc.call_os_get.side_effect = \
 +      lambda node, _: self.RpcResultsBuilder() \
 +                        .CreateSuccessfulNodeResult(node, self.os)
 +
 +    hv_info = ("bootid",
 +               [{
 +                 "type": constants.ST_LVM_VG,
 +                 "storage_free": 10000
 +               }],
 +               ({"memory_free": 10000}, ))
 +    self.rpc.call_node_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, hv_info) \
 +        .AddSuccessfulNode(self.node1, hv_info) \
 +        .AddSuccessfulNode(self.node2, hv_info) \
 +        .Build()
 +
 +    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
 +      lambda node, _: self.RpcResultsBuilder() \
 +                        .CreateSuccessfulNodeResult(node, [])
 +
 +    self.iallocator_cls.return_value.result = [self.node1.name, self.node2.name]
 +
 +    self.diskless_op = opcodes.OpInstanceCreate(
 +      instance_name="diskless.test.com",
 +      pnode=self.master.name,
 +      disk_template=constants.DT_DISKLESS,
 +      mode=constants.INSTANCE_CREATE,
 +      nics=[{}],
 +      disks=[],
 +      os_type=self.os_name_variant)
 +
 +    self.plain_op = opcodes.OpInstanceCreate(
 +      instance_name="plain.test.com",
 +      pnode=self.master.name,
 +      disk_template=constants.DT_PLAIN,
 +      mode=constants.INSTANCE_CREATE,
 +      nics=[{}],
 +      disks=[{
 +        constants.IDISK_SIZE: 1024
 +      }],
 +      os_type=self.os_name_variant)
 +
 +    self.block_op = opcodes.OpInstanceCreate(
 +      instance_name="block.test.com",
 +      pnode=self.master.name,
 +      disk_template=constants.DT_BLOCK,
 +      mode=constants.INSTANCE_CREATE,
 +      nics=[{}],
 +      disks=[{
 +        constants.IDISK_SIZE: 1024,
 +        constants.IDISK_ADOPT: "/dev/disk/block0"
 +      }],
 +      os_type=self.os_name_variant)
 +
 +    self.drbd_op = opcodes.OpInstanceCreate(
 +      instance_name="drbd.test.com",
 +      pnode=self.node1.name,
 +      snode=self.node2.name,
 +      disk_template=constants.DT_DRBD8,
 +      mode=constants.INSTANCE_CREATE,
 +      nics=[{}],
 +      disks=[{
 +        constants.IDISK_SIZE: 1024
 +      }],
 +      os_type=self.os_name_variant)
 +
 +    self.file_op = opcodes.OpInstanceCreate(
 +      instance_name="file.test.com",
 +      pnode=self.node1.name,
 +      disk_template=constants.DT_FILE,
 +      mode=constants.INSTANCE_CREATE,
 +      nics=[{}],
 +      disks=[{
 +        constants.IDISK_SIZE: 1024
 +      }],
 +      os_type=self.os_name_variant)
 +
 +  def testSimpleCreate(self):
 +    op = self.CopyOpCode(self.diskless_op)
 +    self.ExecOpCode(op)
 +
 +  def testStrangeHostnameResolve(self):
 +    op = self.CopyOpCode(self.diskless_op)
 +    self.netutils_mod.GetHostname.return_value = \
 +      HostnameMock("random.host.example.com", "203.0.113.1")
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Resolved hostname .* does not look the same as given hostname")
 +
 +  def testOpportunisticLockingNoIAllocator(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         opportunistic_locking=True,
 +                         iallocator=None)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Opportunistic locking is only available in combination with an"
 +          " instance allocator")
 +
 +  def testNicWithNetAndMode(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_NETWORK: self.net.name,
 +                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "If network is given, no mode or link is allowed to be passed")
 +
 +  def testAutoIpNoNameCheck(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: constants.VALUE_AUTO
 +                         }],
 +                         ip_check=False,
 +                         name_check=False)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "IP address set to auto but name checks have been skipped")
 +
 +  def testAutoIp(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: constants.VALUE_AUTO
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testPoolIpNoNetwork(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: constants.NIC_IP_POOL
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "if ip=pool, parameter network must be passed too")
 +
 +  def testValidIp(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: "203.0.113.1"
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testRoutedNoIp(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_MODE: constants.NIC_MODE_ROUTED
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Routed nic mode requires an ip address")
 +
 +  def testValicMac(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testValidNicParams(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
 +                           constants.INIC_LINK: "br_mock"
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testValidNicParamsOpenVSwitch(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_MODE: constants.NIC_MODE_OVS,
 +                           constants.INIC_VLAN: "1"
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testNicNoneName(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_NAME: constants.VALUE_NONE
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testConflictingIP(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: self.net.gateway[:-1] + "2"
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "The requested IP address .* belongs to network .*, but the target"
 +          " NIC does not.")
 +
 +  def testVLanFormat(self):
 +    for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
 +      self.ResetMocks()
 +      op = self.CopyOpCode(self.diskless_op,
 +                           nics=[{
 +                             constants.INIC_VLAN: vlan
 +                           }])
 +      self.ExecOpCodeExpectOpPrereqError(
 +        op, "Specified VLAN parameter is invalid")
 +
 +  def testPoolIp(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: constants.NIC_IP_POOL,
 +                           constants.INIC_NETWORK: self.net.name
 +                         }])
 +    self.ExecOpCode(op)
 +
 +  def testPoolIpUnconnectedNetwork(self):
 +    net = self.cfg.AddNewNetwork()
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: constants.NIC_IP_POOL,
 +                           constants.INIC_NETWORK: net.name
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "No netparams found for network .*.")
 +
 +  def testIpNotInNetwork(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         nics=[{
 +                           constants.INIC_IP: "203.0.113.1",
 +                           constants.INIC_NETWORK: self.net.name
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "IP address .* already in use or does not belong to network .*")
 +
 +  def testMixAdoptAndNotAdopt(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=constants.DT_PLAIN,
 +                         disks=[{
 +                           constants.IDISK_ADOPT: "lv1"
 +                         }, {}])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Either all disks are adopted or none is")
 +
 +  def testMustAdoptWithoutAdopt(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=constants.DT_BLOCK,
 +                         disks=[{}])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk template blockdev requires disk adoption, but no 'adopt'"
 +          " parameter given")
 +
 +  def testDontAdoptWithAdopt(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=constants.DT_DRBD8,
 +                         disks=[{
 +                           constants.IDISK_ADOPT: "lv1"
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk adoption is not supported for the 'drbd' disk template")
 +
 +  def testAdoptWithIAllocator(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=constants.DT_PLAIN,
 +                         disks=[{
 +                           constants.IDISK_ADOPT: "lv1"
 +                         }],
 +                         iallocator="mock")
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk adoption not allowed with an iallocator script")
 +
 +  def testAdoptWithImport(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=constants.DT_PLAIN,
 +                         disks=[{
 +                           constants.IDISK_ADOPT: "lv1"
 +                         }],
 +                         mode=constants.INSTANCE_IMPORT)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk adoption not allowed for instance import")
 +
 +  def testArgumentCombinations(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         # start flag will be flipped
 +                         no_install=True,
 +                         start=True,
 +                         # no allowed combination
 +                         ip_check=True,
 +                         name_check=False)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Cannot do IP address check without a name check")
 +
 +  def testInvalidFileDriver(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         file_driver="invalid_file_driver")
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
 +
 +  def testMissingSecondaryNode(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         pnode=self.master.name,
 +                         disk_template=constants.DT_DRBD8)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "The networked disk templates need a mirror node")
 +
 +  def testIgnoredSecondaryNode(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         pnode=self.master.name,
 +                         snode=self.node1.name,
 +                         disk_template=constants.DT_PLAIN)
 +    try:
 +      self.ExecOpCode(op)
 +    except Exception:
 +      pass
 +    self.mcpu.assertLogContainsRegex(
 +      "Secondary node will be ignored on non-mirrored disk template")
 +
 +  def testMissingOsType(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         os_type=self.REMOVE)
 +    self.ExecOpCodeExpectOpPrereqError(op, "No guest OS specified")
 +
 +  def testBlacklistedOs(self):
 +    self.cluster.blacklisted_os = [self.os_name_variant]
 +    op = self.CopyOpCode(self.diskless_op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Guest OS .* is not allowed for installation")
 +
 +  def testMissingDiskTemplate(self):
 +    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
 +    op = self.CopyOpCode(self.diskless_op,
 +                         disk_template=self.REMOVE)
 +    self.ExecOpCode(op)
 +
 +  def testExistingInstance(self):
 +    inst = self.cfg.AddNewInstance()
 +    op = self.CopyOpCode(self.diskless_op,
 +                         instance_name=inst.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Instance .* is already in the cluster")
 +
 +  def testPlainInstance(self):
 +    op = self.CopyOpCode(self.plain_op)
 +    self.ExecOpCode(op)
 +
 +  def testPlainIAllocator(self):
 +    op = self.CopyOpCode(self.plain_op,
 +                         pnode=self.REMOVE,
 +                         iallocator="mock")
 +    self.ExecOpCode(op)
 +
 +  def testIAllocatorOpportunisticLocking(self):
 +    op = self.CopyOpCode(self.plain_op,
 +                         pnode=self.REMOVE,
 +                         iallocator="mock",
 +                         opportunistic_locking=True)
 +    self.ExecOpCode(op)
 +
 +  def testFailingIAllocator(self):
 +    self.iallocator_cls.return_value.success = False
 +    op = self.CopyOpCode(self.plain_op,
 +                         pnode=self.REMOVE,
 +                         iallocator="mock")
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Can't compute nodes using iallocator")
 +
 +  def testDrbdInstance(self):
 +    op = self.CopyOpCode(self.drbd_op)
 +    self.ExecOpCode(op)
 +
 +  def testDrbdIAllocator(self):
 +    op = self.CopyOpCode(self.drbd_op,
 +                         pnode=self.REMOVE,
 +                         snode=self.REMOVE,
 +                         iallocator="mock")
 +    self.ExecOpCode(op)
 +
 +  def testFileInstance(self):
 +    op = self.CopyOpCode(self.file_op)
 +    self.ExecOpCode(op)
 +
 +  def testFileInstanceNoClusterStorage(self):
 +    self.cluster.file_storage_dir = None
 +    op = self.CopyOpCode(self.file_op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Cluster file storage dir not defined")
 +
 +  def testFileInstanceAdditionalPath(self):
 +    op = self.CopyOpCode(self.file_op,
 +                         file_storage_dir="mock_dir")
 +    self.ExecOpCode(op)
 +
 +  def testIdentifyDefaults(self):
 +    op = self.CopyOpCode(self.plain_op,
 +                         hvparams={
 +                           constants.HV_BOOT_ORDER: "cd"
 +                         },
 +                         beparams=constants.BEC_DEFAULTS.copy(),
 +                         nics=[{
 +                           constants.NIC_MODE: constants.NIC_MODE_BRIDGED
 +                         }],
 +                         osparams={
 +                           self.os_name_variant: {}
 +                         },
 +                         identify_defaults=True)
 +    self.ExecOpCode(op)
 +
 +    inst = self.cfg.GetAllInstancesInfo().values()[0]
 +    self.assertEqual(0, len(inst.hvparams))
 +    self.assertEqual(0, len(inst.beparams))
 +    assert self.os_name_variant not in inst.osparams or \
 +            len(inst.osparams[self.os_name_variant]) == 0
 +
 +  def testOfflineNode(self):
 +    self.node1.offline = True
 +    op = self.CopyOpCode(self.diskless_op,
 +                         pnode=self.node1.name)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
 +
 +  def testDrainedNode(self):
 +    self.node1.drained = True
 +    op = self.CopyOpCode(self.diskless_op,
 +                         pnode=self.node1.name)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
 +
 +  def testNonVmCapableNode(self):
 +    self.node1.vm_capable = False
 +    op = self.CopyOpCode(self.diskless_op,
 +                         pnode=self.node1.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Cannot use non-vm_capable primary node")
 +
 +  def testNonEnabledHypervisor(self):
 +    self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
 +    op = self.CopyOpCode(self.diskless_op,
 +                         hypervisor=constants.HT_FAKE)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Selected hypervisor .* not enabled in the cluster")
 +
 +  def testAddTag(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         tags=["tag"])
 +    self.ExecOpCode(op)
 +
 +  def testInvalidTag(self):
 +    op = self.CopyOpCode(self.diskless_op,
 +                         tags=["too_long" * 20])
 +    self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
 +
 +  def testPingableInstanceName(self):
 +    self.netutils_mod.TcpPing.return_value = True
 +    op = self.CopyOpCode(self.diskless_op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "IP .* of instance diskless.test.com already in use")
 +
 +  def testPrimaryIsSecondaryNode(self):
 +    op = self.CopyOpCode(self.drbd_op,
 +                         snode=self.drbd_op.pnode)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "The secondary node cannot be the primary node")
 +
 +  def testPrimarySecondaryDifferentNodeGroups(self):
 +    group = self.cfg.AddNewNodeGroup()
 +    self.node2.group = group.uuid
 +    op = self.CopyOpCode(self.drbd_op)
 +    self.ExecOpCode(op)
 +    self.mcpu.assertLogContainsRegex(
 +      "The primary and secondary nodes are in two different node groups")
 +
 +  def testExclusiveStorageUnsupportedDiskTemplate(self):
 +    self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
 +    op = self.CopyOpCode(self.drbd_op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk template drbd not supported with exclusive storage")
 +
 +  def testAdoptPlain(self):
 +    self.rpc.call_lv_list.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {
 +          "xenvg/mock_disk_1": (10000, None, False)
 +        }) \
 +        .Build()
 +    op = self.CopyOpCode(self.plain_op)
 +    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
 +    self.ExecOpCode(op)
 +
 +  def testAdoptPlainMissingLv(self):
 +    self.rpc.call_lv_list.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {}) \
 +        .Build()
 +    op = self.CopyOpCode(self.plain_op)
 +    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
 +    self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
 +
 +  def testAdoptPlainOnlineLv(self):
 +    self.rpc.call_lv_list.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {
 +          "xenvg/mock_disk_1": (10000, None, True)
 +        }) \
 +        .Build()
 +    op = self.CopyOpCode(self.plain_op)
 +    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Online logical volumes found, cannot adopt")
 +
 +  def testAdoptBlock(self):
 +    self.rpc.call_bdev_sizes.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {
 +          "/dev/disk/block0": 10000
 +        }) \
 +        .Build()
 +    op = self.CopyOpCode(self.block_op)
 +    self.ExecOpCode(op)
 +
 +  def testAdoptBlockDuplicateNames(self):
 +    op = self.CopyOpCode(self.block_op,
 +                         disks=[{
 +                           constants.IDISK_SIZE: 0,
 +                           constants.IDISK_ADOPT: "/dev/disk/block0"
 +                         }, {
 +                           constants.IDISK_SIZE: 0,
 +                           constants.IDISK_ADOPT: "/dev/disk/block0"
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Duplicate disk names given for adoption")
 +
 +  def testAdoptBlockInvalidNames(self):
 +    op = self.CopyOpCode(self.block_op,
 +                         disks=[{
 +                           constants.IDISK_SIZE: 0,
 +                           constants.IDISK_ADOPT: "/invalid/block0"
 +                         }])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Device node.* lie outside .* and cannot be adopted")
 +
 +  def testAdoptBlockMissingDisk(self):
 +    self.rpc.call_bdev_sizes.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {}) \
 +        .Build()
 +    op = self.CopyOpCode(self.block_op)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Missing block device")
 +
 +  def testNoWaitForSyncDrbd(self):
 +    op = self.CopyOpCode(self.drbd_op,
 +                         wait_for_sync=False)
 +    self.ExecOpCode(op)
 +
 +  def testNoWaitForSyncPlain(self):
 +    op = self.CopyOpCode(self.plain_op,
 +                         wait_for_sync=False)
 +    self.ExecOpCode(op)
 +
 +  def testImportPlainFromGivenSrcNode(self):
 +    exp_info = """
 +[export]
 +version=0
 +os=mock_os
 +[instance]
 +name=old_name.example.com
 +"""
 +
 +    self.rpc.call_export_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, exp_info)
 +    op = self.CopyOpCode(self.plain_op,
 +                         mode=constants.INSTANCE_IMPORT,
 +                         src_node=self.master.name)
 +    self.ExecOpCode(op)
 +
 +  def testImportPlainWithoutSrcNodeNotFound(self):
 +    op = self.CopyOpCode(self.plain_op,
 +                         mode=constants.INSTANCE_IMPORT)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "No export found for relative path")
 +
 +  def testImportPlainWithoutSrcNode(self):
 +    exp_info = """
 +[export]
 +version=0
 +os=mock_os
 +[instance]
 +name=old_name.example.com
 +"""
 +
 +    self.rpc.call_export_list.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master, {"mock_path": {}}) \
 +        .Build()
 +    self.rpc.call_export_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, exp_info)
 +
 +    op = self.CopyOpCode(self.plain_op,
 +                         mode=constants.INSTANCE_IMPORT,
 +                         src_path="mock_path")
 +    self.ExecOpCode(op)
 +
 +  def testImportPlainCorruptExportInfo(self):
 +    exp_info = ""
 +    self.rpc.call_export_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, exp_info)
 +    op = self.CopyOpCode(self.plain_op,
 +                         mode=constants.INSTANCE_IMPORT,
 +                         src_node=self.master.name)
 +    self.ExecOpCodeExpectException(op, errors.ProgrammerError,
 +                                   "Corrupted export config")
 +
 +  def testImportPlainWrongExportInfoVersion(self):
 +    exp_info = """
 +[export]
 +version=1
 +"""
 +    self.rpc.call_export_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, exp_info)
 +    op = self.CopyOpCode(self.plain_op,
 +                         mode=constants.INSTANCE_IMPORT,
 +                         src_node=self.master.name)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Wrong export version")
 +
 +  def testImportPlainWithParametersAndImport(self):
 +    exp_info = """
 +[export]
 +version=0
 +os=mock_os
 +[instance]
 +name=old_name.example.com
 +disk0_size=1024
 +disk1_size=1500
 +disk1_dump=mock_path
 +nic0_mode=bridged
 +nic0_link=br_mock
 +nic0_mac=f6:ab:f4:45:d1:af
 +nic0_ip=192.0.2.1
 +tags=tag1 tag2
 +hypervisor=xen-hvm
 +[hypervisor]
 +boot_order=cd
 +[backend]
 +memory=1024
 +vcpus=8
 +[os]
 +param1=val1
 +"""
 +
 +    self.rpc.call_export_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, exp_info)
 +    self.rpc.call_import_start.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, "daemon_name")
 +    self.rpc.call_impexp_status.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master,
 +                                    [
 +                                      objects.ImportExportStatus(exit_status=0)
 +                                    ])
 +    self.rpc.call_impexp_cleanup.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, True)
 +
 +    op = self.CopyOpCode(self.plain_op,
 +                         disks=[],
 +                         nics=[],
 +                         tags=[],
 +                         hypervisor=None,
 +                         hvparams={},
 +                         mode=constants.INSTANCE_IMPORT,
 +                         src_node=self.master.name)
 +    self.ExecOpCode(op)
 +
 +
 +class TestCheckOSVariant(CmdlibTestCase):
 +  def testNoVariantsSupported(self):
 +    os = self.cfg.CreateOs(supported_variants=[])
 +    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
 +                      os, "os+variant")
 +
 +  def testNoVariantGiven(self):
 +    os = self.cfg.CreateOs(supported_variants=["default"])
 +    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
 +                      os, "os")
 +
 +  def testWrongVariantGiven(self):
 +    os = self.cfg.CreateOs(supported_variants=["default"])
 +    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
 +                      os, "os+wrong_variant")
 +
 +  def testOkWithVariant(self):
 +    os = self.cfg.CreateOs(supported_variants=["default"])
 +    instance._CheckOSVariant(os, "os+default")
 +
 +  def testOkWithoutVariant(self):
 +    os = self.cfg.CreateOs(supported_variants=[])
 +    instance._CheckOSVariant(os, "os")
 +
 +
 +class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
 +  def setUp(self):
 +    super(TestCheckTargetNodeIPolicy, self).setUp()
 +
 +    self.op = self.diskless_op
 +
 +    self.instance = self.cfg.AddNewInstance()
 +    self.target_group = self.cfg.AddNewNodeGroup()
 +    self.target_node = self.cfg.AddNewNode(group=self.target_group)
 +
 +  @withLockedLU
 +  def testNoViolation(self, lu):
 +    compute_recoder = mock.Mock(return_value=[])
 +    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
 +                                    self.target_node, NotImplemented,
 +                                    _compute_fn=compute_recoder)
 +    self.assertTrue(compute_recoder.called)
 +    self.mcpu.assertLogIsEmpty()
 +
 +  @withLockedLU
 +  def testNoIgnore(self, lu):
 +    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
 +    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
 +                      lu, NotImplemented, self.instance,
 +                      self.target_node, NotImplemented,
 +                      _compute_fn=compute_recoder)
 +    self.assertTrue(compute_recoder.called)
 +    self.mcpu.assertLogIsEmpty()
 +
 +  @withLockedLU
 +  def testIgnoreViolation(self, lu):
 +    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
 +    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
 +                                    self.target_node, NotImplemented,
 +                                    ignore=True, _compute_fn=compute_recoder)
 +    self.assertTrue(compute_recoder.called)
 +    msg = ("Instance does not meet target node group's .* instance policy:"
 +           " mem_size not in range")
 +    self.mcpu.assertLogContainsRegex(msg)
 +
 +
 +class TestApplyContainerMods(unittest.TestCase):
 +  def testEmptyContainer(self):
 +    container = []
 +    chgdesc = []
 +    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
 +                                 None)
 +    self.assertEqual(container, [])
 +    self.assertEqual(chgdesc, [])
 +
 +  def testAdd(self):
 +    container = []
 +    chgdesc = []
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_ADD, -1, "Hello"),
 +      (constants.DDM_ADD, -1, "World"),
 +      (constants.DDM_ADD, 0, "Start"),
 +      (constants.DDM_ADD, -1, "End"),
 +      ], None)
 +    instance._ApplyContainerMods("test", container, chgdesc, mods,
 +                                 None, None, None)
 +    self.assertEqual(container, ["Start", "Hello", "World", "End"])
 +    self.assertEqual(chgdesc, [])
 +
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_ADD, 0, "zero"),
 +      (constants.DDM_ADD, 3, "Added"),
 +      (constants.DDM_ADD, 5, "four"),
 +      (constants.DDM_ADD, 7, "xyz"),
 +      ], None)
 +    instance._ApplyContainerMods("test", container, chgdesc, mods,
 +                                 None, None, None)
 +    self.assertEqual(container,
 +                     ["zero", "Start", "Hello", "Added", "World", "four",
 +                      "End", "xyz"])
 +    self.assertEqual(chgdesc, [])
 +
 +    for idx in [-2, len(container) + 1]:
 +      mods = instance._PrepareContainerMods([
 +        (constants.DDM_ADD, idx, "error"),
 +        ], None)
 +      self.assertRaises(IndexError, instance._ApplyContainerMods,
 +                        "test", container, None, mods, None, None, None)
 +
 +  def testRemoveError(self):
 +    for idx in [0, 1, 2, 100, -1, -4]:
 +      mods = instance._PrepareContainerMods([
 +        (constants.DDM_REMOVE, idx, None),
 +        ], None)
 +      self.assertRaises(IndexError, instance._ApplyContainerMods,
 +                        "test", [], None, mods, None, None, None)
 +
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_REMOVE, 0, object()),
 +      ], None)
 +    self.assertRaises(AssertionError, instance._ApplyContainerMods,
 +                      "test", [""], None, mods, None, None, None)
 +
 +  def testAddError(self):
 +    for idx in range(-100, -1) + [100]:
 +      mods = instance._PrepareContainerMods([
 +        (constants.DDM_ADD, idx, None),
 +        ], None)
 +      self.assertRaises(IndexError, instance._ApplyContainerMods,
 +                        "test", [], None, mods, None, None, None)
 +
 +  def testRemove(self):
 +    container = ["item 1", "item 2"]
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_ADD, -1, "aaa"),
 +      (constants.DDM_REMOVE, -1, None),
 +      (constants.DDM_ADD, -1, "bbb"),
 +      ], None)
 +    chgdesc = []
 +    instance._ApplyContainerMods("test", container, chgdesc, mods,
 +                                 None, None, None)
 +    self.assertEqual(container, ["item 1", "item 2", "bbb"])
 +    self.assertEqual(chgdesc, [
 +      ("test/2", "remove"),
 +      ])
 +
 +  def testModify(self):
 +    container = ["item 1", "item 2"]
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_MODIFY, -1, "a"),
 +      (constants.DDM_MODIFY, 0, "b"),
 +      (constants.DDM_MODIFY, 1, "c"),
 +      ], None)
 +    chgdesc = []
 +    instance._ApplyContainerMods("test", container, chgdesc, mods,
 +                                 None, None, None)
 +    self.assertEqual(container, ["item 1", "item 2"])
 +    self.assertEqual(chgdesc, [])
 +
 +    for idx in [-2, len(container) + 1]:
 +      mods = instance._PrepareContainerMods([
 +        (constants.DDM_MODIFY, idx, "error"),
 +        ], None)
 +      self.assertRaises(IndexError, instance._ApplyContainerMods,
 +                        "test", container, None, mods, None, None, None)
 +
 +  @staticmethod
 +  def _CreateTestFn(idx, params, private):
 +    private.data = ("add", idx, params)
 +    return ((100 * idx, params), [
 +      ("test/%s" % idx, hex(idx)),
 +      ])
 +
 +  @staticmethod
 +  def _ModifyTestFn(idx, item, params, private):
 +    private.data = ("modify", idx, params)
 +    return [
 +      ("test/%s" % idx, "modify %s" % params),
 +      ]
 +
 +  @staticmethod
 +  def _RemoveTestFn(idx, item, private):
 +    private.data = ("remove", idx, item)
 +
 +  def testAddWithCreateFunction(self):
 +    container = []
 +    chgdesc = []
 +    mods = instance._PrepareContainerMods([
 +      (constants.DDM_ADD, -1, "Hello"),
 +      (constants.DDM_ADD, -1, "World"),
 +      (constants.DDM_ADD, 0, "Start"),
 +      (constants.DDM_ADD, -1, "End"),
 +      (constants.DDM_REMOVE, 2, None),
 +      (constants.DDM_MODIFY, -1, "foobar"),
 +      (constants.DDM_REMOVE, 2, None),
 +      (constants.DDM_ADD, 1, "More"),
 +      ], mock.Mock)
 +    instance._ApplyContainerMods("test", container, chgdesc, mods,
 +                                 self._CreateTestFn, self._ModifyTestFn,
 +                                 self._RemoveTestFn)
 +    self.assertEqual(container, [
 +      (000, "Start"),
 +      (100, "More"),
 +      (000, "Hello"),
 +      ])
 +    self.assertEqual(chgdesc, [
 +      ("test/0", "0x0"),
 +      ("test/1", "0x1"),
 +      ("test/0", "0x0"),
 +      ("test/3", "0x3"),
 +      ("test/2", "remove"),
 +      ("test/2", "modify foobar"),
 +      ("test/2", "remove"),
 +      ("test/1", "0x1")
 +      ])
 +    self.assertTrue(compat.all(op == private.data[0]
 +                               for (op, _, _, private) in mods))
 +    self.assertEqual([private.data for (op, _, _, private) in mods], [
 +      ("add", 0, "Hello"),
 +      ("add", 1, "World"),
 +      ("add", 0, "Start"),
 +      ("add", 3, "End"),
 +      ("remove", 2, (100, "World")),
 +      ("modify", 2, "foobar"),
 +      ("remove", 2, (300, "End")),
 +      ("add", 1, "More"),
 +      ])
 +
 +
 +class _FakeConfigForGenDiskTemplate(ConfigMock):
 +  def __init__(self):
 +    super(_FakeConfigForGenDiskTemplate, self).__init__()
 +
 +    self._unique_id = itertools.count()
 +    self._drbd_minor = itertools.count(20)
 +    self._port = itertools.count(constants.FIRST_DRBD_PORT)
 +    self._secret = itertools.count()
 +
 +  def GenerateUniqueID(self, ec_id):
 +    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
 +
 +  def AllocateDRBDMinor(self, nodes, instance):
 +    return [self._drbd_minor.next()
 +            for _ in nodes]
 +
 +  def AllocatePort(self):
 +    return self._port.next()
 +
 +  def GenerateDRBDSecret(self, ec_id):
 +    return "ec%s-secret%s" % (ec_id, self._secret.next())
 +
 +
 +class TestGenerateDiskTemplate(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestGenerateDiskTemplate, self).setUp()
 +
 +    self.cfg = _FakeConfigForGenDiskTemplate()
 +    self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
 +
 +    self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
 +
 +    self.lu = self.GetMockLU()
 +
 +  @staticmethod
 +  def GetDiskParams():
 +    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
 +
 +  def testWrongDiskTemplate(self):
 +    gdt = instance.GenerateDiskTemplate
 +    disk_template = "##unknown##"
 +
 +    assert disk_template not in constants.DISK_TEMPLATES
 +
 +    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
 +                      "inst26831.example.com", "node30113.example.com", [], [],
 +                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
 +                      self.GetDiskParams())
 +
 +  def testDiskless(self):
 +    gdt = instance.GenerateDiskTemplate
 +
 +    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
 +                 "node30113.example.com", [], [],
 +                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
 +                 self.GetDiskParams())
 +    self.assertEqual(result, [])
 +
 +  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
 +                       file_storage_dir=NotImplemented,
 +                       file_driver=NotImplemented):
 +    gdt = instance.GenerateDiskTemplate
 +
 +    map(lambda params: utils.ForceDictType(params,
 +                                           constants.IDISK_PARAMS_TYPES),
 +        disk_info)
 +
 +    # Check if non-empty list of secondaries is rejected
 +    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
 +                      template, "inst25088.example.com",
 +                      "node185.example.com", ["node323.example.com"], [],
 +                      NotImplemented, NotImplemented, base_index,
 +                      self.lu.LogInfo, self.GetDiskParams())
 +
 +    result = gdt(self.lu, template, "inst21662.example.com",
 +                 "node21741.example.com", [],
 +                 disk_info, file_storage_dir, file_driver, base_index,
 +                 self.lu.LogInfo, self.GetDiskParams())
 +
 +    for (idx, disk) in enumerate(result):
 +      self.assertTrue(isinstance(disk, objects.Disk))
 +      self.assertEqual(disk.dev_type, exp_dev_type)
 +      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
 +      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
 +      self.assertTrue(disk.children is None)
 +
 +    self._CheckIvNames(result, base_index, base_index + len(disk_info))
 +    instance._UpdateIvNames(base_index, result)
 +    self._CheckIvNames(result, base_index, base_index + len(disk_info))
 +
 +    return result
 +
 +  def _CheckIvNames(self, disks, base_index, end_index):
 +    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
 +                     ["disk/%s" % i for i in range(base_index, end_index)])
 +
 +  def testPlain(self):
 +    disk_info = [{
 +      constants.IDISK_SIZE: 1024,
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      }, {
 +      constants.IDISK_SIZE: 4096,
 +      constants.IDISK_VG: "othervg",
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      }]
 +
 +    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
 +                                   constants.DT_PLAIN)
 +
 +    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
 +      ("xenvg", "ec1-uq0.disk3"),
 +      ("othervg", "ec1-uq1.disk4"),
 +      ])
 +
 +  def testFile(self):
 +    # anything != DT_FILE would do here
 +    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
 +    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
 +                      constants.DT_FILE, [], 0, NotImplemented)
 +    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
 +                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
 +
 +    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
 +      disk_info = [{
 +        constants.IDISK_SIZE: 80 * 1024,
 +        constants.IDISK_MODE: constants.DISK_RDONLY,
 +        }, {
 +        constants.IDISK_SIZE: 4096,
 +        constants.IDISK_MODE: constants.DISK_RDWR,
 +        }, {
 +        constants.IDISK_SIZE: 6 * 1024,
 +        constants.IDISK_MODE: constants.DISK_RDWR,
 +        }]
 +
 +      self.cluster.enabled_disk_templates = [disk_template]
 +      result = self._TestTrivialDisk(
 +        disk_template, disk_info, 2, disk_template,
 +        file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
 +
 +      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
 +        (constants.FD_BLKTAP, "/tmp/disk2"),
 +        (constants.FD_BLKTAP, "/tmp/disk3"),
 +        (constants.FD_BLKTAP, "/tmp/disk4"),
 +        ])
 +
 +  def testBlock(self):
 +    disk_info = [{
 +      constants.IDISK_SIZE: 8 * 1024,
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      constants.IDISK_ADOPT: "/tmp/some/block/dev",
 +      }]
 +
 +    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
 +                                   constants.DT_BLOCK)
 +
 +    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
 +      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
 +      ])
 +
 +  def testRbd(self):
 +    disk_info = [{
 +      constants.IDISK_SIZE: 8 * 1024,
 +      constants.IDISK_MODE: constants.DISK_RDONLY,
 +      }, {
 +      constants.IDISK_SIZE: 100 * 1024,
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      }]
 +
 +    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
 +                                   constants.DT_RBD)
 +
 +    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
 +      ("rbd", "ec1-uq0.rbd.disk0"),
 +      ("rbd", "ec1-uq1.rbd.disk1"),
 +      ])
 +
 +  def testDrbd8(self):
 +    gdt = instance.GenerateDiskTemplate
 +    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
 +    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
 +
 +    disk_info = [{
 +      constants.IDISK_SIZE: 1024,
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      }, {
 +      constants.IDISK_SIZE: 100 * 1024,
 +      constants.IDISK_MODE: constants.DISK_RDONLY,
 +      constants.IDISK_METAVG: "metavg",
 +      }, {
 +      constants.IDISK_SIZE: 4096,
 +      constants.IDISK_MODE: constants.DISK_RDWR,
 +      constants.IDISK_VG: "vgxyz",
 +      },
 +      ]
 +
 +    exp_logical_ids = [
 +      [
 +        (self.lu.cfg.GetVGName(), "ec1-uq0.disk0_data"),
 +        (drbd8_default_metavg, "ec1-uq0.disk0_meta"),
 +      ], [
 +        (self.lu.cfg.GetVGName(), "ec1-uq1.disk1_data"),
 +        ("metavg", "ec1-uq1.disk1_meta"),
 +      ], [
 +        ("vgxyz", "ec1-uq2.disk2_data"),
 +        (drbd8_default_metavg, "ec1-uq2.disk2_meta"),
 +      ]]
 +
 +    assert len(exp_logical_ids) == len(disk_info)
 +
 +    map(lambda params: utils.ForceDictType(params,
 +                                           constants.IDISK_PARAMS_TYPES),
 +        disk_info)
 +
 +    # Check if empty list of secondaries is rejected
 +    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
 +                      "inst827.example.com", "node1334.example.com", [],
 +                      disk_info, NotImplemented, NotImplemented, 0,
 +                      self.lu.LogInfo, self.GetDiskParams())
 +
 +    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
 +                 "node1334.example.com", ["node12272.example.com"],
 +                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
 +                 self.GetDiskParams())
 +
 +    for (idx, disk) in enumerate(result):
 +      self.assertTrue(isinstance(disk, objects.Disk))
 +      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
 +      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
 +      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
 +
 +      for child in disk.children:
 +        self.assertTrue(isinstance(disk, objects.Disk))
 +        self.assertEqual(child.dev_type, constants.DT_PLAIN)
 +        self.assertTrue(child.children is None)
 +
 +      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
 +                       exp_logical_ids[idx])
 +
 +      self.assertEqual(len(disk.children), 2)
 +      self.assertEqual(disk.children[0].size, disk.size)
 +      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
 +
 +    self._CheckIvNames(result, 0, len(disk_info))
 +    instance._UpdateIvNames(0, result)
 +    self._CheckIvNames(result, 0, len(disk_info))
 +
 +    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
 +      ("node1334.example.com", "node12272.example.com",
 +       constants.FIRST_DRBD_PORT, 20, 21, "ec1-secret0"),
 +      ("node1334.example.com", "node12272.example.com",
 +       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec1-secret1"),
 +      ("node1334.example.com", "node12272.example.com",
 +       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec1-secret2"),
 +      ])
 +
 +
 +class _DiskPauseTracker:
 +  def __init__(self):
 +    self.history = []
 +
 +  def __call__(self, (disks, instance), pause):
 +    assert not (set(disks) - set(instance.disks))
 +
 +    self.history.extend((i.logical_id, i.size, pause)
 +                        for i in disks)
 +
 +    return (True, [True] * len(disks))
 +
 +
 +class _ConfigForDiskWipe:
 +  def __init__(self, exp_node_uuid):
 +    self._exp_node_uuid = exp_node_uuid
 +
 +  def GetNodeName(self, node_uuid):
 +    assert node_uuid == self._exp_node_uuid
 +    return "name.of.expected.node"
 +
 +
 +class _RpcForDiskWipe:
 +  def __init__(self, exp_node, pause_cb, wipe_cb):
 +    self._exp_node = exp_node
 +    self._pause_cb = pause_cb
 +    self._wipe_cb = wipe_cb
 +
 +  def call_blockdev_pause_resume_sync(self, node, disks, pause):
 +    assert node == self._exp_node
 +    return rpc.RpcResult(data=self._pause_cb(disks, pause))
 +
 +  def call_blockdev_wipe(self, node, bdev, offset, size):
 +    assert node == self._exp_node
 +    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
 +
 +
 +class _DiskWipeProgressTracker:
 +  def __init__(self, start_offset):
 +    self._start_offset = start_offset
 +    self.progress = {}
 +
 +  def __call__(self, (disk, _), offset, size):
 +    assert isinstance(offset, (long, int))
 +    assert isinstance(size, (long, int))
 +
 +    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
 +
 +    assert offset >= self._start_offset
 +    assert (offset + size) <= disk.size
 +
 +    assert size > 0
 +    assert size <= constants.MAX_WIPE_CHUNK
 +    assert size <= max_chunk_size
 +
 +    assert offset == self._start_offset or disk.logical_id in self.progress
 +
 +    # Keep track of progress
 +    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
 +
 +    assert cur_progress == offset
 +
 +    # Record progress
 +    self.progress[disk.logical_id] += size
 +
 +    return (True, None)
 +
 +
 +class TestWipeDisks(unittest.TestCase):
 +  def _FailingPauseCb(self, (disks, _), pause):
 +    self.assertEqual(len(disks), 3)
 +    self.assertTrue(pause)
 +    # Simulate an RPC error
 +    return (False, "error")
 +
 +  def testPauseFailure(self):
 +    node_name = "node1372.example.com"
 +
 +    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
 +                                     NotImplemented),
 +                 cfg=_ConfigForDiskWipe(node_name))
 +
 +    disks = [
 +      objects.Disk(dev_type=constants.DT_PLAIN),
 +      objects.Disk(dev_type=constants.DT_PLAIN),
 +      objects.Disk(dev_type=constants.DT_PLAIN),
 +      ]
 +
 +    inst = objects.Instance(name="inst21201",
 +                            primary_node=node_name,
 +                            disk_template=constants.DT_PLAIN,
 +                            disks=disks)
 +
 +    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
 +
 +  def _FailingWipeCb(self, (disk, _), offset, size):
 +    # This should only ever be called for the first disk
 +    self.assertEqual(disk.logical_id, "disk0")
 +    return (False, None)
 +
 +  def testFailingWipe(self):
 +    node_uuid = "node13445-uuid"
 +    pt = _DiskPauseTracker()
 +
 +    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
 +                 cfg=_ConfigForDiskWipe(node_uuid))
 +
 +    disks = [
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
 +                   size=100 * 1024),
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
 +                   size=500 * 1024),
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
 +      ]
 +
 +    inst = objects.Instance(name="inst562",
 +                            primary_node=node_uuid,
 +                            disk_template=constants.DT_PLAIN,
 +                            disks=disks)
 +
 +    try:
 +      instance.WipeDisks(lu, inst)
 +    except errors.OpExecError, err:
 +      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
 +    else:
 +      self.fail("Did not raise exception")
 +
 +    # Check if all disks were paused and resumed
 +    self.assertEqual(pt.history, [
 +      ("disk0", 100 * 1024, True),
 +      ("disk1", 500 * 1024, True),
 +      ("disk2", 256, True),
 +      ("disk0", 100 * 1024, False),
 +      ("disk1", 500 * 1024, False),
 +      ("disk2", 256, False),
 +      ])
 +
 +  def _PrepareWipeTest(self, start_offset, disks):
 +    node_name = "node-with-offset%s.example.com" % start_offset
 +    pauset = _DiskPauseTracker()
 +    progresst = _DiskWipeProgressTracker(start_offset)
 +
 +    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
 +                 cfg=_ConfigForDiskWipe(node_name))
 +
 +    instance = objects.Instance(name="inst3560",
 +                                primary_node=node_name,
 +                                disk_template=constants.DT_PLAIN,
 +                                disks=disks)
 +
 +    return (lu, instance, pauset, progresst)
 +
 +  def testNormalWipe(self):
 +    disks = [
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
 +                   size=500 * 1024),
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
 +      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
 +                   size=constants.MAX_WIPE_CHUNK),
 +      ]
 +
 +    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
 +
 +    instance.WipeDisks(lu, inst)
 +
 +    self.assertEqual(pauset.history, [
 +      ("disk0", 1024, True),
 +      ("disk1", 500 * 1024, True),
 +      ("disk2", 128, True),
 +      ("disk3", constants.MAX_WIPE_CHUNK, True),
 +      ("disk0", 1024, False),
 +      ("disk1", 500 * 1024, False),
 +      ("disk2", 128, False),
 +      ("disk3", constants.MAX_WIPE_CHUNK, False),
 +      ])
 +
 +    # Ensure the complete disk has been wiped
 +    self.assertEqual(progresst.progress,
 +                     dict((i.logical_id, i.size) for i in disks))
 +
 +  def testWipeWithStartOffset(self):
 +    for start_offset in [0, 280, 8895, 1563204]:
 +      disks = [
 +        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
 +                     size=128),
 +        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
 +                     size=start_offset + (100 * 1024)),
 +        ]
 +
 +      (lu, inst, pauset, progresst) = \
 +        self._PrepareWipeTest(start_offset, disks)
 +
 +      # Test start offset with only one disk
 +      instance.WipeDisks(lu, inst,
 +                         disks=[(1, disks[1], start_offset)])
 +
 +      # Only the second disk may have been paused and wiped
 +      self.assertEqual(pauset.history, [
 +        ("disk1", start_offset + (100 * 1024), True),
 +        ("disk1", start_offset + (100 * 1024), False),
 +        ])
 +      self.assertEqual(progresst.progress, {
 +        "disk1": disks[1].size,
 +        })
 +
 +
 +class TestCheckOpportunisticLocking(unittest.TestCase):
 +  class OpTest(opcodes.OpCode):
 +    OP_PARAMS = [
 +      ("opportunistic_locking", False, ht.TBool, None),
 +      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
 +      ]
 +
 +  @classmethod
 +  def _MakeOp(cls, **kwargs):
 +    op = cls.OpTest(**kwargs)
 +    op.Validate(True)
 +    return op
 +
 +  def testMissingAttributes(self):
 +    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
 +                      object())
 +
 +  def testDefaults(self):
 +    op = self._MakeOp()
 +    instance._CheckOpportunisticLocking(op)
 +
 +  def test(self):
 +    for iallocator in [None, "something", "other"]:
 +      for opplock in [False, True]:
 +        op = self._MakeOp(iallocator=iallocator,
 +                          opportunistic_locking=opplock)
 +        if opplock and not iallocator:
 +          self.assertRaises(errors.OpPrereqError,
 +                            instance._CheckOpportunisticLocking, op)
 +        else:
 +          instance._CheckOpportunisticLocking(op)
 +
 +
 +class TestLUInstanceRemove(CmdlibTestCase):
 +  def testRemoveMissingInstance(self):
 +    op = opcodes.OpInstanceRemove(instance_name="missing.inst")
 +    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
 +
 +  def testRemoveInst(self):
 +    inst = self.cfg.AddNewInstance(disks=[])
 +    op = opcodes.OpInstanceRemove(instance_name=inst.name)
 +    self.ExecOpCode(op)
 +
 +
 +class TestLUInstanceMove(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceMove, self).setUp()
 +
 +    self.node = self.cfg.AddNewNode()
 +
 +    self.rpc.call_blockdev_assemble.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.node, ("/dev/mocked_path",
 +                                    "/var/run/ganeti/instance-disks/mocked_d"))
 +    self.rpc.call_blockdev_export.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, "")
 +    self.rpc.call_blockdev_remove.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, "")
 +
 +  def testMissingInstance(self):
 +    op = opcodes.OpInstanceMove(instance_name="missing.inst",
 +                                target_node=self.node.name)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
 +
 +  def testUncopyableDiskTemplate(self):
 +    inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk template sharedfile not suitable for copying")
 +
 +  def testAlreadyOnTargetNode(self):
 +    inst = self.cfg.AddNewInstance()
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.master.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Instance .* is already on the node .*")
 +
 +  def testMoveStoppedInstance(self):
 +    inst = self.cfg.AddNewInstance()
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCode(op)
 +
 +  def testMoveRunningInstance(self):
 +    self.rpc.call_node_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.node,
 +                           (NotImplemented, NotImplemented,
 +                            ({"memory_free": 10000}, ))) \
 +        .Build()
 +    self.rpc.call_instance_start.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.node, "")
 +
 +    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCode(op)
 +
 +  def testMoveFailingStart(self):
 +    self.rpc.call_node_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.node,
 +                           (NotImplemented, NotImplemented,
 +                            ({"memory_free": 10000}, ))) \
 +        .Build()
 +    self.rpc.call_instance_start.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateFailedNodeResult(self.node)
 +
 +    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCodeExpectOpExecError(
 +      op, "Could not start instance .* on node .*")
 +
 +  def testMoveFailingBlockdevAssemble(self):
 +    inst = self.cfg.AddNewInstance()
 +    self.rpc.call_blockdev_assemble.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateFailedNodeResult(self.node)
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
 +
 +  def testMoveFailingBlockdevExport(self):
 +    inst = self.cfg.AddNewInstance()
 +    self.rpc.call_blockdev_export.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateFailedNodeResult(self.node)
 +    op = opcodes.OpInstanceMove(instance_name=inst.name,
 +                                target_node=self.node.name)
 +    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
 +
 +
 +class TestLUInstanceRename(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceRename, self).setUp()
 +
 +    self.inst = self.cfg.AddNewInstance()
 +
 +    self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
 +                                       new_name="new_name.example.com")
 +
 +  def testIpCheckWithoutNameCheck(self):
 +    op = self.CopyOpCode(self.op,
 +                         ip_check=True,
 +                         name_check=False)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "IP address check requires a name check")
 +
 +  def testIpAlreadyInUse(self):
 +    self.netutils_mod.TcpPing.return_value = True
 +    op = self.CopyOpCode(self.op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "IP .* of instance .* already in use")
 +
 +  def testExistingInstanceName(self):
 +    self.cfg.AddNewInstance(name="new_name.example.com")
 +    op = self.CopyOpCode(self.op)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Instance .* is already in the cluster")
 +
 +  def testFileInstance(self):
 +    self.rpc.call_blockdev_assemble.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, (None, None))
 +    self.rpc.call_blockdev_shutdown.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, None)
 +
 +    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name)
 +    self.ExecOpCode(op)
 +
 +
 +class TestLUInstanceMultiAlloc(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceMultiAlloc, self).setUp()
 +
 +    self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
 +                                            disk_template=constants.DT_DRBD8,
 +                                            disks=[],
 +                                            nics=[],
 +                                            os_type="mock_os",
 +                                            hypervisor=constants.HT_XEN_HVM,
 +                                            mode=constants.INSTANCE_CREATE)
 +
 +  def testInstanceWithIAllocator(self):
 +    inst = self.CopyOpCode(self.inst_op,
 +                           iallocator="mock")
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "iallocator are not allowed to be set on instance objects")
 +
 +  def testOnlySomeNodesGiven(self):
 +    inst1 = self.CopyOpCode(self.inst_op,
 +                            pnode=self.master.name)
 +    inst2 = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "There are instance objects providing pnode/snode while others"
 +          " do not")
 +
 +  def testMissingIAllocator(self):
 +    self.cluster.default_iallocator = None
 +    inst = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "No iallocator or nodes on the instances given and no cluster-wide"
 +          " default iallocator found")
 +
 +  def testDuplicateInstanceNames(self):
 +    inst1 = self.CopyOpCode(self.inst_op)
 +    inst2 = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "There are duplicate instance names")
 +
 +  def testWithGivenNodes(self):
 +    snode = self.cfg.AddNewNode()
 +    inst = self.CopyOpCode(self.inst_op,
 +                           pnode=self.master.name,
 +                           snode=snode.name)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
 +    self.ExecOpCode(op)
 +
 +  def testDryRun(self):
 +    snode = self.cfg.AddNewNode()
 +    inst = self.CopyOpCode(self.inst_op,
 +                           pnode=self.master.name,
 +                           snode=snode.name)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
 +                                      dry_run=True)
 +    self.ExecOpCode(op)
 +
 +  def testWithIAllocator(self):
 +    snode = self.cfg.AddNewNode()
 +    self.iallocator_cls.return_value.result = \
 +      ([("inst.example.com", [self.master.name, snode.name])], [])
 +
 +    inst = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
 +                                      iallocator="mock_ialloc")
 +    self.ExecOpCode(op)
 +
 +  def testWithIAllocatorOpportunisticLocking(self):
 +    snode = self.cfg.AddNewNode()
 +    self.iallocator_cls.return_value.result = \
 +      ([("inst.example.com", [self.master.name, snode.name])], [])
 +
 +    inst = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
 +                                      iallocator="mock_ialloc",
 +                                      opportunistic_locking=True)
 +    self.ExecOpCode(op)
 +
 +  def testFailingIAllocator(self):
 +    self.iallocator_cls.return_value.success = False
 +
 +    inst = self.CopyOpCode(self.inst_op)
 +    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
 +                                      iallocator="mock_ialloc")
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Can't compute nodes using iallocator")
 +
 +
 +class TestLUInstanceSetParams(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceSetParams, self).setUp()
 +
 +    self.inst = self.cfg.AddNewInstance()
 +    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
 +
 +    self.running_inst = \
 +      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
 +    self.running_op = \
 +      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
 +
 +    self.snode = self.cfg.AddNewNode()
 +
 +    self.mocked_storage_type = constants.ST_LVM_VG
 +    self.mocked_storage_free = 10000
 +    self.mocked_master_cpu_total = 16
 +    self.mocked_master_memory_free = 2048
 +    self.mocked_snode_cpu_total = 16
 +    self.mocked_snode_memory_free = 512
 +
 +    self.mocked_running_inst_memory = 1024
 +    self.mocked_running_inst_vcpus = 8
 +    self.mocked_running_inst_state = "running"
 +    self.mocked_running_inst_time = 10938474
 +
 +    bootid = "mock_bootid"
 +    storage_info = [
 +      {
 +        "type": self.mocked_storage_type,
 +        "storage_free": self.mocked_storage_free
 +      }
 +    ]
 +    hv_info_master = {
 +      "cpu_total": self.mocked_master_cpu_total,
 +      "memory_free": self.mocked_master_memory_free
 +    }
 +    hv_info_snode = {
 +      "cpu_total": self.mocked_snode_cpu_total,
 +      "memory_free": self.mocked_snode_memory_free
 +    }
 +
 +    self.rpc.call_node_info.return_value = \
 +      self.RpcResultsBuilder() \
 +        .AddSuccessfulNode(self.master,
 +                           (bootid, storage_info, (hv_info_master, ))) \
 +        .AddSuccessfulNode(self.snode,
 +                           (bootid, storage_info, (hv_info_snode, ))) \
 +        .Build()
 +
 +    def _InstanceInfo(_, instance, __, ___):
 +      if instance == self.inst.name:
 +        return self.RpcResultsBuilder() \
 +          .CreateSuccessfulNodeResult(self.master, None)
 +      elif instance == self.running_inst.name:
 +        return self.RpcResultsBuilder() \
 +          .CreateSuccessfulNodeResult(
 +            self.master, {
 +              "memory": self.mocked_running_inst_memory,
 +              "vcpus": self.mocked_running_inst_vcpus,
 +              "state": self.mocked_running_inst_state,
 +              "time": self.mocked_running_inst_time
 +            })
 +      else:
 +        raise AssertionError()
 +    self.rpc.call_instance_info.side_effect = _InstanceInfo
 +
 +    self.rpc.call_bridges_exist.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, True)
 +
 +    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
 +      lambda node, _: self.RpcResultsBuilder() \
 +                        .CreateSuccessfulNodeResult(node, [])
 +
 +    self.rpc.call_blockdev_shutdown.side_effect = \
 +      lambda node, _: self.RpcResultsBuilder() \
 +                        .CreateSuccessfulNodeResult(node, [])
 +
 +  def testNoChanges(self):
 +    op = self.CopyOpCode(self.op)
 +    self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
 +
 +  def testGlobalHvparams(self):
 +    op = self.CopyOpCode(self.op,
 +                         hvparams={constants.HV_MIGRATION_PORT: 1234})
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "hypervisor parameters are global and cannot be customized")
 +
 +  def testHvparams(self):
 +    op = self.CopyOpCode(self.op,
 +                         hvparams={constants.HV_BOOT_ORDER: "cd"})
 +    self.ExecOpCode(op)
 +
 +  def testDisksAndDiskTemplate(self):
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_PLAIN,
 +                         disks=[[constants.DDM_ADD, -1, {}]])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk template conversion and other disk changes not supported at"
 +          " the same time")
 +
 +  def testDiskTemplateToMirroredNoRemoteNode(self):
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_DRBD8)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Changing the disk template to a mirrored one requires specifying"
 +          " a secondary node")
 +
 +  def testPrimaryNodeToOldPrimaryNode(self):
 +    op = self.CopyOpCode(self.op,
 +                         pnode=self.master.name)
 +    self.ExecOpCode(op)
 +
 +  def testPrimaryNodeChange(self):
 +    node = self.cfg.AddNewNode()
 +    op = self.CopyOpCode(self.op,
 +                         pnode=node.name)
 +    self.ExecOpCode(op)
 +
 +  def testPrimaryNodeChangeRunningInstance(self):
 +    node = self.cfg.AddNewNode()
 +    op = self.CopyOpCode(self.running_op,
 +                         pnode=node.name)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
 +
 +  def testOsChange(self):
 +    os = self.cfg.CreateOs(supported_variants=[])
 +    self.rpc.call_os_get.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, os)
 +    op = self.CopyOpCode(self.op,
 +                         os_name=os.name)
 +    self.ExecOpCode(op)
 +
 +  def testVCpuChange(self):
 +    op = self.CopyOpCode(self.op,
 +                         beparams={
 +                           constants.BE_VCPUS: 4
 +                         })
 +    self.ExecOpCode(op)
 +
 +  def testWrongCpuMask(self):
 +    op = self.CopyOpCode(self.op,
 +                         beparams={
 +                           constants.BE_VCPUS: 4
 +                         },
 +                         hvparams={
 +                           constants.HV_CPU_MASK: "1,2:3,4"
 +                         })
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Number of vCPUs .* does not match the CPU mask .*")
 +
 +  def testCorrectCpuMask(self):
 +    op = self.CopyOpCode(self.op,
 +                         beparams={
 +                           constants.BE_VCPUS: 4
 +                         },
 +                         hvparams={
 +                           constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
 +                         })
 +    self.ExecOpCode(op)
 +
 +  def testOsParams(self):
 +    op = self.CopyOpCode(self.op,
 +                         osparams={
 +                           self.os.supported_parameters[0]: "test_param_val"
 +                         })
 +    self.ExecOpCode(op)
 +
 +  def testIncreaseMemoryTooMuch(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         beparams={
 +                           constants.BE_MAXMEM:
 +                             self.mocked_master_memory_free * 2
 +                         })
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "This change will prevent the instance from starting")
 +
 +  def testIncreaseMemory(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         beparams={
 +                           constants.BE_MAXMEM: self.mocked_master_memory_free
 +                         })
 +    self.ExecOpCode(op)
 +
 +  def testIncreaseMemoryTooMuchForSecondary(self):
 +    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
 +                                   disk_template=constants.DT_DRBD8,
 +                                   secondary_node=self.snode)
 +    self.rpc.call_instance_info.side_effect = [
 +      self.RpcResultsBuilder()
 +        .CreateSuccessfulNodeResult(self.master,
 +                                    {
 +                                      "memory":
 +                                        self.mocked_snode_memory_free * 2,
 +                                      "vcpus": self.mocked_running_inst_vcpus,
 +                                      "state": self.mocked_running_inst_state,
 +                                      "time": self.mocked_running_inst_time
 +                                    })]
 +
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         beparams={
 +                           constants.BE_MAXMEM:
 +                             self.mocked_snode_memory_free * 2,
 +                           constants.BE_AUTO_BALANCE: True
 +                         })
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "This change will prevent the instance from failover to its"
 +          " secondary node")
 +
 +  def testInvalidRuntimeMemory(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         runtime_mem=self.mocked_master_memory_free * 2)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Instance .* must have memory between .* and .* of memory")
 +
 +  def testIncreaseRuntimeMemory(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         runtime_mem=self.mocked_master_memory_free,
 +                         beparams={
 +                           constants.BE_MAXMEM: self.mocked_master_memory_free
 +                         })
 +    self.ExecOpCode(op)
 +
 +  def testAddNicWithPoolIpNoNetwork(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1,
 +                                {
 +                                  constants.INIC_IP: constants.NIC_IP_POOL
 +                                })])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "If ip=pool, parameter network cannot be none")
 +
 +  def testAddNicWithPoolIp(self):
 +    net = self.cfg.AddNewNetwork()
 +    self.cfg.ConnectNetworkToGroup(net, self.group)
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1,
 +                                {
 +                                  constants.INIC_IP: constants.NIC_IP_POOL,
 +                                  constants.INIC_NETWORK: net.name
 +                                })])
 +    self.ExecOpCode(op)
 +
 +  def testAddNicWithInvalidIp(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1,
 +                                {
 +                                  constants.INIC_IP: "invalid"
 +                                })])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Invalid IP address")
 +
 +  def testAddNic(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1, {})])
 +    self.ExecOpCode(op)
 +
 +  def testNoHotplugSupport(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1, {})],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateFailedNodeResult(self.master)
 +    self.ExecOpCodeExpectOpPrereqError(op, "Hotplug is not possible")
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +
 +  def testHotplugIfPossible(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1, {})],
 +                         hotplug_if_possible=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateFailedNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertFalse(self.rpc.call_hotplug_device.called)
 +
 +  def testHotAddNic(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1, {})],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertTrue(self.rpc.call_hotplug_device.called)
 +
 +  def testAddNicWithIp(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_ADD, -1,
 +                                {
 +                                  constants.INIC_IP: "2.3.1.4"
 +                                })])
 +    self.ExecOpCode(op)
 +
 +  def testModifyNicRoutedWithoutIp(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, 0,
 +                                {
 +                                  constants.INIC_MODE: constants.NIC_MODE_ROUTED
 +                                })])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Cannot set the NIC IP address to None on a routed NIC")
 +
 +  def testModifyNicSetMac(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, 0,
 +                                {
 +                                  constants.INIC_MAC: "0a:12:95:15:bf:75"
 +                                })])
 +    self.ExecOpCode(op)
 +
 +  def testModifyNicWithPoolIpNoNetwork(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, -1,
 +                                {
 +                                  constants.INIC_IP: constants.NIC_IP_POOL
 +                                })])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "ip=pool, but no network found")
 +
 +  def testModifyNicSetNet(self):
 +    old_net = self.cfg.AddNewNetwork()
 +    self.cfg.ConnectNetworkToGroup(old_net, self.group)
 +    inst = self.cfg.AddNewInstance(nics=[
 +      self.cfg.CreateNic(network=old_net,
 +                         ip="198.51.100.2")])
 +
 +    new_net = self.cfg.AddNewNetwork(mac_prefix="be")
 +    self.cfg.ConnectNetworkToGroup(new_net, self.group)
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         nics=[(constants.DDM_MODIFY, 0,
 +                                {
 +                                  constants.INIC_NETWORK: new_net.name
 +                                })])
 +    self.ExecOpCode(op)
 +
 +  def testModifyNicSetLinkWhileConnected(self):
 +    old_net = self.cfg.AddNewNetwork()
 +    self.cfg.ConnectNetworkToGroup(old_net, self.group)
 +    inst = self.cfg.AddNewInstance(nics=[
 +      self.cfg.CreateNic(network=old_net)])
 +
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         nics=[(constants.DDM_MODIFY, 0,
 +                                {
 +                                  constants.INIC_LINK: "mock_link"
 +                                })])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Not allowed to change link or mode of a NIC that is connected"
 +          " to a network")
 +
 +  def testModifyNicSetNetAndIp(self):
 +    net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
 +    self.cfg.ConnectNetworkToGroup(net, self.group)
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, 0,
 +                                {
 +                                  constants.INIC_NETWORK: net.name,
 +                                  constants.INIC_IP: "123.123.123.1"
 +                                })])
 +    self.ExecOpCode(op)
 +
 +  def testModifyNic(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, 0, {})])
 +    self.ExecOpCode(op)
 +
 +  def testHotModifyNic(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_MODIFY, 0, {})],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertTrue(self.rpc.call_hotplug_device.called)
 +
 +  def testRemoveLastNic(self):
 +    op = self.CopyOpCode(self.op,
 +                         nics=[(constants.DDM_REMOVE, 0, {})])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "violates policy")
 +
 +  def testRemoveNic(self):
 +    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
 +                                         self.cfg.CreateNic()])
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         nics=[(constants.DDM_REMOVE, 0, {})])
 +    self.ExecOpCode(op)
 +
 +  def testHotRemoveNic(self):
 +    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
 +                                         self.cfg.CreateNic()])
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         nics=[(constants.DDM_REMOVE, 0, {})],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertTrue(self.rpc.call_hotplug_device.called)
 +
 +  def testSetOffline(self):
 +    op = self.CopyOpCode(self.op,
 +                         offline=True)
 +    self.ExecOpCode(op)
 +
 +  def testUnsetOffline(self):
 +    op = self.CopyOpCode(self.op,
 +                         offline=False)
 +    self.ExecOpCode(op)
 +
 +  def testAddDiskInvalidMode(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_MODE: "invalid"
 +                                 }]])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Invalid disk access mode 'invalid'")
 +
 +  def testAddDiskMissingSize(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1, {}]])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Required disk parameter 'size' missing")
 +
 +  def testAddDiskInvalidSize(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: "invalid"
 +                                 }]])
 +    self.ExecOpCodeExpectException(
 +      op, errors.TypeEnforcementError, "is not a valid size")
 +
 +  def testAddDiskRunningInstanceNoWaitForSync(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024
 +                                 }]],
 +                         wait_for_sync=False)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Can't add a disk to an instance with activated disks"
 +          " and --no-wait-for-sync given.")
 +
 +  def testAddDiskDownInstance(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024
 +                                 }]])
 +    self.ExecOpCode(op)
 +
 +    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
 +
 +  def testAddDiskRunningInstance(self):
 +    op = self.CopyOpCode(self.running_op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024
 +                                 }]])
 +    self.ExecOpCode(op)
 +
 +    self.assertFalse(self.rpc.call_blockdev_shutdown.called)
 +
 +  def testAddDiskNoneName(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024,
 +                                   constants.IDISK_NAME: constants.VALUE_NONE
 +                                 }]])
 +    self.ExecOpCode(op)
 +
 +  def testHotAddDisk(self):
 +    self.rpc.call_blockdev_assemble.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, ("/dev/mocked_path",
 +                                    "/var/run/ganeti/instance-disks/mocked_d"))
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_ADD, -1,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024,
 +                                 }]],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertTrue(self.rpc.call_blockdev_create.called)
 +    self.assertTrue(self.rpc.call_blockdev_assemble.called)
 +    self.assertTrue(self.rpc.call_hotplug_device.called)
 +
 +  def testHotRemoveDisk(self):
 +    inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
 +                                          self.cfg.CreateDisk()])
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name,
 +                         disks=[[constants.DDM_REMOVE, -1,
 +                                 {}]],
 +                         hotplug=True)
 +    self.rpc.call_hotplug_supported.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.ExecOpCode(op)
 +    self.assertTrue(self.rpc.call_hotplug_supported.called)
 +    self.assertTrue(self.rpc.call_hotplug_device.called)
 +    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
 +    self.assertTrue(self.rpc.call_blockdev_remove.called)
 +
 +  def testModifyDiskWithSize(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_MODIFY, 0,
 +                                 {
 +                                   constants.IDISK_SIZE: 1024
 +                                 }]])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk size change not possible, use grow-disk")
 +
 +  def testModifyDiskWithRandomParams(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_MODIFY, 0,
 +                                 {
 +                                   constants.IDISK_METAVG: "new_meta_vg",
 +                                   constants.IDISK_MODE: "invalid",
 +                                   constants.IDISK_NAME: "new_name"
 +                                 }]])
-     self.ExecOpCodeExpectOpPrereqError(
-       op, "Disk modification doesn't support additional arbitrary parameters")
++    self.ExecOpCodeExpectException(op, errors.TypeEnforcementError,
++                                   "Unknown parameter 'metavg'")
 +
 +  def testModifyDiskUnsetName(self):
 +    op = self.CopyOpCode(self.op,
 +                         disks=[[constants.DDM_MODIFY, 0,
 +                                  {
 +                                    constants.IDISK_NAME: constants.VALUE_NONE
 +                                  }]])
 +    self.ExecOpCode(op)
 +
 +  def testSetOldDiskTemplate(self):
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=self.inst.disk_template)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Instance already has disk template")
 +
 +  def testSetDisabledDiskTemplate(self):
 +    self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_EXT)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Disk template .* is not enabled for this cluster")
 +
 +  def testInvalidDiskTemplateConversion(self):
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_EXT)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Unsupported disk template conversion from .* to .*")
 +
 +  def testConvertToDRBDWithSecondarySameAsPrimary(self):
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_DRBD8,
 +                         remote_node=self.master.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Given new secondary node .* is the same as the primary node"
 +          " of the instance")
 +
 +  def testConvertPlainToDRBD(self):
 +    self.rpc.call_blockdev_shutdown.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, True)
 +    self.rpc.call_blockdev_getmirrorstatus.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
 +
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_DRBD8,
 +                         remote_node=self.snode.name)
 +    self.ExecOpCode(op)
 +
 +  def testConvertDRBDToPlain(self):
 +    self.inst.disks = [self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
 +                                           primary_node=self.master,
 +                                           secondary_node=self.snode)]
 +    self.inst.disk_template = constants.DT_DRBD8
 +    self.rpc.call_blockdev_shutdown.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, True)
 +    self.rpc.call_blockdev_remove.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master)
 +    self.rpc.call_blockdev_getmirrorstatus.return_value = \
 +      self.RpcResultsBuilder() \
 +        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
 +
 +    op = self.CopyOpCode(self.op,
 +                         disk_template=constants.DT_PLAIN)
 +    self.ExecOpCode(op)
 +
 +
 +class TestLUInstanceChangeGroup(CmdlibTestCase):
 +  def setUp(self):
 +    super(TestLUInstanceChangeGroup, self).setUp()
 +
 +    self.group2 = self.cfg.AddNewNodeGroup()
 +    self.node2 = self.cfg.AddNewNode(group=self.group2)
 +    self.inst = self.cfg.AddNewInstance()
 +    self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
 +
 +  def testTargetGroupIsInstanceGroup(self):
 +    op = self.CopyOpCode(self.op,
 +                         target_groups=[self.group.name])
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Can't use group\(s\) .* as targets, they are used by the"
 +          " instance .*")
 +
 +  def testNoTargetGroups(self):
 +    inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
 +                                   primary_node=self.master,
 +                                   secondary_node=self.node2)
 +    op = self.CopyOpCode(self.op,
 +                         instance_name=inst.name)
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "There are no possible target groups")
 +
 +  def testFailingIAllocator(self):
 +    self.iallocator_cls.return_value.success = False
 +    op = self.CopyOpCode(self.op)
 +
 +    self.ExecOpCodeExpectOpPrereqError(
 +      op, "Can't compute solution for changing group of instance .*"
 +          " using iallocator .*")
 +
 +  def testChangeGroup(self):
 +    self.iallocator_cls.return_value.success = True
 +    self.iallocator_cls.return_value.result = ([], [], [])
 +    op = self.CopyOpCode(self.op)
 +
 +    self.ExecOpCode(op)
 +
 +
 +if __name__ == "__main__":
 +  testutils.GanetiTestProgram()