blob: 4f469102a7f43cc82dcd830c882bfd9aa5e6809f [file] [log] [blame]
#
#
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Logical units dealing with instances."""
import logging
import os
from ganeti import compat
from ganeti import constants
from ganeti import errors
from ganeti import locking
from ganeti.masterd import iallocator
from ganeti import masterd
from ganeti import netutils
from ganeti import objects
from ganeti import utils
from ganeti.cmdlib.base import NoHooksLU, LogicalUnit, ResultWithJobs
from ganeti.cmdlib.common import \
INSTANCE_NOT_RUNNING, CheckNodeOnline, \
ShareAll, GetDefaultIAllocator, CheckInstanceNodeGroups, \
LoadNodeEvacResult, \
ExpandInstanceUuidAndName, \
CheckInstanceState, ExpandNodeUuidAndName, \
CheckDiskTemplateEnabled
from ganeti.cmdlib.instance_storage import CreateDisks, \
ComputeDisks, \
StartInstanceDisks, ShutdownInstanceDisks, \
AssembleInstanceDisks
from ganeti.cmdlib.instance_utils import \
BuildInstanceHookEnvByObject,\
CheckNodeNotDrained, RemoveInstance, CopyLockList, \
CheckNodeVmCapable, CheckTargetNodeIPolicy, \
GetInstanceInfoText, RemoveDisks, CheckNodeFreeMemory, \
CheckInstanceBridgesExist, \
CheckInstanceExistence, \
CheckHostnameSane, CheckOpportunisticLocking, ComputeFullBeParams, \
ComputeNics, CreateInstanceAllocRequest
import ganeti.masterd.instance
class LUInstanceRename(LogicalUnit):
"""Rename an instance.
"""
HPATH = "instance-rename"
HTYPE = constants.HTYPE_INSTANCE
REQ_BGL = False
def CheckArguments(self):
"""Check arguments.
"""
if self.op.ip_check and not self.op.name_check:
# TODO: make the ip check more flexible and not depend on the name check
raise errors.OpPrereqError("IP address check requires a name check",
errors.ECODE_INVAL)
self._new_name_resolved = False
def BuildHooksEnv(self):
"""Build hooks env.
This runs on master, primary and secondary nodes of the instance.
"""
env = BuildInstanceHookEnvByObject(self, self.instance)
env["INSTANCE_NEW_NAME"] = self.op.new_name
return env
def BuildHooksNodes(self):
"""Build hooks nodes.
"""
nl = [self.cfg.GetMasterNode()] + \
list(self.cfg.GetInstanceNodes(self.instance.uuid))
return (nl, nl)
def _PerformChecksAndResolveNewName(self):
"""Checks and resolves the new name, storing the FQDN, if permitted.
"""
if self._new_name_resolved or not self.op.name_check:
return
hostname = CheckHostnameSane(self, self.op.new_name)
self.op.new_name = hostname.name
if (self.op.ip_check and
netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
raise errors.OpPrereqError("IP %s of instance %s already in use" %
(hostname.ip, self.op.new_name),
errors.ECODE_NOTUNIQUE)
self._new_name_resolved = True
def CheckPrereq(self):
"""Check prerequisites.
This checks that the instance is in the cluster and is not running.
"""
(self.op.instance_uuid, self.op.instance_name) = \
ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
self.op.instance_name)
instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
assert instance is not None
# It should actually not happen that an instance is running with a disabled
# disk template, but in case it does, the renaming of file-based instances
# will fail horribly. Thus, we test it before.
for disk in self.cfg.GetInstanceDisks(instance.uuid):
if (disk.dev_type in constants.DTS_FILEBASED and
self.op.new_name != instance.name):
# TODO: when disks are separate objects, this should check for disk
# types, not disk templates.
CheckDiskTemplateEnabled(self.cfg.GetClusterInfo(), disk.dev_type)
CheckNodeOnline(self, instance.primary_node)
CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
msg="cannot rename")
self.instance = instance
self._PerformChecksAndResolveNewName()
if self.op.new_name != instance.name:
CheckInstanceExistence(self, self.op.new_name)
def ExpandNames(self):
self._ExpandAndLockInstance(allow_forthcoming=True)
# Note that this call might not resolve anything if name checks have been
# disabled in the opcode. In this case, we might have a renaming collision
# if a shortened name and a full name are used simultaneously, as we will
# have two different locks. However, at that point the user has taken away
# the tools necessary to detect this issue.
self._PerformChecksAndResolveNewName()
# Used to prevent instance namespace collisions.
if self.op.new_name != self.op.instance_name:
CheckInstanceExistence(self, self.op.new_name)
self.add_locks[locking.LEVEL_INSTANCE] = self.op.new_name
def Exec(self, feedback_fn):
"""Rename the instance.
"""
old_name = self.instance.name
rename_file_storage = False
disks = self.cfg.GetInstanceDisks(self.instance.uuid)
renamed_storage = [d for d in disks
if (d.dev_type in constants.DTS_FILEBASED and
d.dev_type != constants.DT_GLUSTER)]
if (renamed_storage and self.op.new_name != self.instance.name):
disks = self.cfg.GetInstanceDisks(self.instance.uuid)
old_file_storage_dir = os.path.dirname(disks[0].logical_id[1])
rename_file_storage = True
self.cfg.RenameInstance(self.instance.uuid, self.op.new_name)
# Assert that we have both the locks needed
assert old_name in self.owned_locks(locking.LEVEL_INSTANCE)
assert self.op.new_name in self.owned_locks(locking.LEVEL_INSTANCE)
# re-read the instance from the configuration after rename
renamed_inst = self.cfg.GetInstanceInfo(self.instance.uuid)
disks = self.cfg.GetInstanceDisks(renamed_inst.uuid)
if self.instance.forthcoming:
return renamed_inst.name
if rename_file_storage:
new_file_storage_dir = os.path.dirname(disks[0].logical_id[1])
result = self.rpc.call_file_storage_dir_rename(renamed_inst.primary_node,
old_file_storage_dir,
new_file_storage_dir)
result.Raise("Could not rename on node %s directory '%s' to '%s'"
" (but the instance has been renamed in Ganeti)" %
(self.cfg.GetNodeName(renamed_inst.primary_node),
old_file_storage_dir, new_file_storage_dir))
StartInstanceDisks(self, renamed_inst, None)
renamed_inst = self.cfg.GetInstanceInfo(renamed_inst.uuid)
# update info on disks
info = GetInstanceInfoText(renamed_inst)
for (idx, disk) in enumerate(disks):
for node_uuid in self.cfg.GetInstanceNodes(renamed_inst.uuid):
result = self.rpc.call_blockdev_setinfo(node_uuid,
(disk, renamed_inst), info)
result.Warn("Error setting info on node %s for disk %s" %
(self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
try:
result = self.rpc.call_instance_run_rename(renamed_inst.primary_node,
renamed_inst, old_name,
self.op.debug_level)
result.Warn("Could not run OS rename script for instance %s on node %s"
" (but the instance has been renamed in Ganeti)" %
(renamed_inst.name,
self.cfg.GetNodeName(renamed_inst.primary_node)),
self.LogWarning)
finally:
ShutdownInstanceDisks(self, renamed_inst)
return renamed_inst.name
class LUInstanceRemove(LogicalUnit):
"""Remove an instance.
"""
HPATH = "instance-remove"
HTYPE = constants.HTYPE_INSTANCE
REQ_BGL = False
def ExpandNames(self):
self._ExpandAndLockInstance(allow_forthcoming=True)
self.needed_locks[locking.LEVEL_NODE] = []
self.needed_locks[locking.LEVEL_NODE_RES] = []
self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
self.dont_collate_locks[locking.LEVEL_NODE] = True
self.dont_collate_locks[locking.LEVEL_NODE_RES] = True
def DeclareLocks(self, level):
if level == locking.LEVEL_NODE:
self._LockInstancesNodes()
elif level == locking.LEVEL_NODE_RES:
# Copy node locks
self.needed_locks[locking.LEVEL_NODE_RES] = \
CopyLockList(self.needed_locks[locking.LEVEL_NODE])
def BuildHooksEnv(self):
"""Build hooks env.
This runs on master, primary and secondary nodes of the instance.
"""
env = BuildInstanceHookEnvByObject(self, self.instance,
secondary_nodes=self.secondary_nodes,
disks=self.inst_disks)
env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
return env
def BuildHooksNodes(self):
"""Build hooks nodes.
"""
nl = [self.cfg.GetMasterNode()]
nl_post = list(self.cfg.GetInstanceNodes(self.instance.uuid)) + nl
return (nl, nl_post)
def CheckPrereq(self):
"""Check prerequisites.
This checks that the instance is in the cluster.
"""
self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
assert self.instance is not None, \
"Cannot retrieve locked instance %s" % self.op.instance_name
self.secondary_nodes = \
self.cfg.GetInstanceSecondaryNodes(self.instance.uuid)
self.inst_disks = self.cfg.GetInstanceDisks(self.instance.uuid)
def Exec(self, feedback_fn):
"""Remove the instance.
"""
assert (self.owned_locks(locking.LEVEL_NODE) ==
self.owned_locks(locking.LEVEL_NODE_RES))
assert not (set(self.cfg.GetInstanceNodes(self.instance.uuid)) -
self.owned_locks(locking.LEVEL_NODE)), \
"Not owning correct locks"
if not self.instance.forthcoming:
logging.info("Shutting down instance %s on node %s", self.instance.name,
self.cfg.GetNodeName(self.instance.primary_node))
result = self.rpc.call_instance_shutdown(self.instance.primary_node,
self.instance,
self.op.shutdown_timeout,
self.op.reason)
if self.op.ignore_failures:
result.Warn("Warning: can't shutdown instance", feedback_fn)
else:
result.Raise("Could not shutdown instance %s on node %s" %
(self.instance.name,
self.cfg.GetNodeName(self.instance.primary_node)))
else:
logging.info("Instance %s on node %s is forthcoming; not shutting down",
self.instance.name,
self.cfg.GetNodeName(self.instance.primary_node))
RemoveInstance(self, feedback_fn, self.instance, self.op.ignore_failures)
class LUInstanceMove(LogicalUnit):
"""Move an instance by data-copying.
This LU is only used if the instance needs to be moved by copying the data
from one node in the cluster to another. The instance is shut down and
the data is copied to the new node and the configuration change is propagated,
then the instance is started again.
See also:
L{LUInstanceFailover} for moving an instance on shared storage (no copying
required).
L{LUInstanceMigrate} for the live migration of an instance (no shutdown
required).
"""
HPATH = "instance-move"
HTYPE = constants.HTYPE_INSTANCE
REQ_BGL = False
def ExpandNames(self):
self._ExpandAndLockInstance()
(self.op.target_node_uuid, self.op.target_node) = \
ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
self.op.target_node)
self.needed_locks[locking.LEVEL_NODE] = [self.op.target_node_uuid]
self.needed_locks[locking.LEVEL_NODE_RES] = []
self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
def DeclareLocks(self, level):
if level == locking.LEVEL_NODE:
self._LockInstancesNodes(primary_only=True)
elif level == locking.LEVEL_NODE_RES:
# Copy node locks
self.needed_locks[locking.LEVEL_NODE_RES] = \
CopyLockList(self.needed_locks[locking.LEVEL_NODE])
def BuildHooksEnv(self):
"""Build hooks env.
This runs on master, primary and target nodes of the instance.
"""
env = {
"TARGET_NODE": self.op.target_node,
"SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
}
env.update(BuildInstanceHookEnvByObject(self, self.instance))
return env
def BuildHooksNodes(self):
"""Build hooks nodes.
"""
nl = [
self.cfg.GetMasterNode(),
self.instance.primary_node,
self.op.target_node_uuid,
]
return (nl, nl)
def CheckPrereq(self):
"""Check prerequisites.
This checks that the instance is in the cluster.
"""
self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
assert self.instance is not None, \
"Cannot retrieve locked instance %s" % self.op.instance_name
disks = self.cfg.GetInstanceDisks(self.instance.uuid)
for idx, dsk in enumerate(disks):
if dsk.dev_type not in constants.DTS_COPYABLE:
raise errors.OpPrereqError("Instance disk %d has disk type %s and is"
" not suitable for copying"
% (idx, dsk.dev_type), errors.ECODE_STATE)
target_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
assert target_node is not None, \
"Cannot retrieve locked node %s" % self.op.target_node
self.target_node_uuid = target_node.uuid
if target_node.uuid == self.instance.primary_node:
raise errors.OpPrereqError("Instance %s is already on the node %s" %
(self.instance.name, target_node.name),
errors.ECODE_STATE)
cluster = self.cfg.GetClusterInfo()
bep = cluster.FillBE(self.instance)
CheckNodeOnline(self, target_node.uuid)
CheckNodeNotDrained(self, target_node.uuid)
CheckNodeVmCapable(self, target_node.uuid)
group_info = self.cfg.GetNodeGroup(target_node.group)
ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
CheckTargetNodeIPolicy(self, ipolicy, self.instance, target_node, self.cfg,
ignore=self.op.ignore_ipolicy)
if self.instance.admin_state == constants.ADMINST_UP:
# check memory requirements on the target node
CheckNodeFreeMemory(
self, target_node.uuid, "failing over instance %s" %
self.instance.name, bep[constants.BE_MAXMEM],
self.instance.hypervisor,
cluster.hvparams[self.instance.hypervisor])
else:
self.LogInfo("Not checking memory on the secondary node as"
" instance will not be started")
# check bridge existance
CheckInstanceBridgesExist(self, self.instance, node_uuid=target_node.uuid)
def Exec(self, feedback_fn):
"""Move an instance.
The move is done by shutting it down on its present node, copying
the data over (slow) and starting it on the new node.
"""
source_node = self.cfg.GetNodeInfo(self.instance.primary_node)
target_node = self.cfg.GetNodeInfo(self.target_node_uuid)
self.LogInfo("Shutting down instance %s on source node %s",
self.instance.name, source_node.name)
assert (self.owned_locks(locking.LEVEL_NODE) ==
self.owned_locks(locking.LEVEL_NODE_RES))
result = self.rpc.call_instance_shutdown(source_node.uuid, self.instance,
self.op.shutdown_timeout,
self.op.reason)
if self.op.ignore_consistency:
result.Warn("Could not shutdown instance %s on node %s. Proceeding"
" anyway. Please make sure node %s is down. Error details" %
(self.instance.name, source_node.name, source_node.name),
self.LogWarning)
else:
result.Raise("Could not shutdown instance %s on node %s" %
(self.instance.name, source_node.name))
# create the target disks
try:
CreateDisks(self, self.instance, target_node_uuid=target_node.uuid)
except errors.OpExecError:
self.LogWarning("Device creation failed")
for disk_uuid in self.instance.disks:
self.cfg.ReleaseDRBDMinors(disk_uuid)
raise
errs = []
transfers = []
# activate, get path, create transfer jobs
disks = self.cfg.GetInstanceDisks(self.instance.uuid)
for idx, disk in enumerate(disks):
# FIXME: pass debug option from opcode to backend
dt = masterd.instance.DiskTransfer("disk/%s" % idx,
constants.IEIO_RAW_DISK,
(disk, self.instance),
constants.IEIO_RAW_DISK,
(disk, self.instance),
None)
transfers.append(dt)
self.cfg.Update(disk, feedback_fn)
import_result = \
masterd.instance.TransferInstanceData(self, feedback_fn,
source_node.uuid,
target_node.uuid,
target_node.secondary_ip,
self.op.compress,
self.instance, transfers)
if not compat.all(import_result):
errs.append("Failed to transfer instance data")
if errs:
self.LogWarning("Some disks failed to copy, aborting")
try:
RemoveDisks(self, self.instance, target_node_uuid=target_node.uuid)
finally:
for disk_uuid in self.instance.disks:
self.cfg.ReleaseDRBDMinors(disk_uuid)
raise errors.OpExecError("Errors during disk copy: %s" %
(",".join(errs),))
self.instance.primary_node = target_node.uuid
self.cfg.Update(self.instance, feedback_fn)
for disk in disks:
self.cfg.SetDiskNodes(disk.uuid, [target_node.uuid])
self.LogInfo("Removing the disks on the original node")
RemoveDisks(self, self.instance, target_node_uuid=source_node.uuid)
# Only start the instance if it's marked as up
if self.instance.admin_state == constants.ADMINST_UP:
self.LogInfo("Starting instance %s on node %s",
self.instance.name, target_node.name)
disks_ok, _, _ = AssembleInstanceDisks(self, self.instance,
ignore_secondaries=True)
if not disks_ok:
ShutdownInstanceDisks(self, self.instance)
raise errors.OpExecError("Can't activate the instance's disks")
result = self.rpc.call_instance_start(target_node.uuid,
(self.instance, None, None), False,
self.op.reason)
msg = result.fail_msg
if msg:
ShutdownInstanceDisks(self, self.instance)
raise errors.OpExecError("Could not start instance %s on node %s: %s" %
(self.instance.name, target_node.name, msg))
class LUInstanceMultiAlloc(NoHooksLU):
"""Allocates multiple instances at the same time.
"""
REQ_BGL = False
def CheckArguments(self):
"""Check arguments.
"""
nodes = []
for inst in self.op.instances:
if inst.iallocator is not None:
raise errors.OpPrereqError("iallocator are not allowed to be set on"
" instance objects", errors.ECODE_INVAL)
nodes.append(bool(inst.pnode))
if inst.disk_template in constants.DTS_INT_MIRROR:
nodes.append(bool(inst.snode))
has_nodes = compat.any(nodes)
if compat.all(nodes) ^ has_nodes:
raise errors.OpPrereqError("There are instance objects providing"
" pnode/snode while others do not",
errors.ECODE_INVAL)
if not has_nodes and self.op.iallocator is None:
default_iallocator = self.cfg.GetDefaultIAllocator()
if default_iallocator:
self.op.iallocator = default_iallocator
else:
raise errors.OpPrereqError("No iallocator or nodes on the instances"
" given and no cluster-wide default"
" iallocator found; please specify either"
" an iallocator or nodes on the instances"
" or set a cluster-wide default iallocator",
errors.ECODE_INVAL)
CheckOpportunisticLocking(self.op)
dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
if dups:
raise errors.OpPrereqError("There are duplicate instance names: %s" %
utils.CommaJoin(dups), errors.ECODE_INVAL)
def ExpandNames(self):
"""Calculate the locks.
"""
self.share_locks = ShareAll()
self.needed_locks = {}
if self.op.iallocator:
self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
if self.op.opportunistic_locking:
self.opportunistic_locks[locking.LEVEL_NODE] = True
self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
else:
nodeslist = []
for inst in self.op.instances:
(inst.pnode_uuid, inst.pnode) = \
ExpandNodeUuidAndName(self.cfg, inst.pnode_uuid, inst.pnode)
nodeslist.append(inst.pnode_uuid)
if inst.snode is not None:
(inst.snode_uuid, inst.snode) = \
ExpandNodeUuidAndName(self.cfg, inst.snode_uuid, inst.snode)
nodeslist.append(inst.snode_uuid)
self.needed_locks[locking.LEVEL_NODE] = nodeslist
# Lock resources of instance's primary and secondary nodes (copy to
# prevent accidential modification)
self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
def CheckPrereq(self):
"""Check prerequisite.
"""
if self.op.iallocator:
cluster = self.cfg.GetClusterInfo()
default_vg = self.cfg.GetVGName()
ec_id = self.proc.GetECId()
if self.op.opportunistic_locking:
# Only consider nodes for which a lock is held
node_whitelist = self.cfg.GetNodeNames(
set(self.owned_locks(locking.LEVEL_NODE)) &
set(self.owned_locks(locking.LEVEL_NODE_RES)))
else:
node_whitelist = None
insts = [CreateInstanceAllocRequest(op, ComputeDisks(op.disks,
op.disk_template,
default_vg),
ComputeNics(op, cluster, None,
self.cfg, ec_id),
ComputeFullBeParams(op, cluster),
node_whitelist)
for op in self.op.instances]
req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
ial = iallocator.IAllocator(self.cfg, self.rpc, req)
ial.Run(self.op.iallocator)
if not ial.success:
raise errors.OpPrereqError("Can't compute nodes using"
" iallocator '%s': %s" %
(self.op.iallocator, ial.info),
errors.ECODE_NORES)
self.ia_result = ial.result
if self.op.dry_run:
self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
constants.JOB_IDS_KEY: [],
})
def _ConstructPartialResult(self):
"""Contructs the partial result.
"""
if self.op.iallocator:
(allocatable, failed_insts) = self.ia_result
allocatable_insts = map(compat.fst, allocatable)
else:
allocatable_insts = [op.instance_name for op in self.op.instances]
failed_insts = []
return {
constants.ALLOCATABLE_KEY: allocatable_insts,
constants.FAILED_KEY: failed_insts,
}
def Exec(self, feedback_fn):
"""Executes the opcode.
"""
jobs = []
if self.op.iallocator:
op2inst = dict((op.instance_name, op) for op in self.op.instances)
(allocatable, failed) = self.ia_result
for (name, node_names) in allocatable:
op = op2inst.pop(name)
(op.pnode_uuid, op.pnode) = \
ExpandNodeUuidAndName(self.cfg, None, node_names[0])
if len(node_names) > 1:
(op.snode_uuid, op.snode) = \
ExpandNodeUuidAndName(self.cfg, None, node_names[1])
jobs.append([op])
missing = set(op2inst.keys()) - set(failed)
assert not missing, \
"Iallocator did return incomplete result: %s" % \
utils.CommaJoin(missing)
else:
jobs.extend([op] for op in self.op.instances)
return ResultWithJobs(jobs, **self._ConstructPartialResult())
class LUInstanceChangeGroup(LogicalUnit):
HPATH = "instance-change-group"
HTYPE = constants.HTYPE_INSTANCE
REQ_BGL = False
def ExpandNames(self):
self.share_locks = ShareAll()
self.needed_locks = {
locking.LEVEL_NODEGROUP: [],
locking.LEVEL_NODE: [],
}
self._ExpandAndLockInstance()
if self.op.target_groups:
self.req_target_uuids = map(self.cfg.LookupNodeGroup,
self.op.target_groups)
else:
self.req_target_uuids = None
self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
def DeclareLocks(self, level):
if level == locking.LEVEL_NODEGROUP:
assert not self.needed_locks[locking.LEVEL_NODEGROUP]
if self.req_target_uuids:
lock_groups = set(self.req_target_uuids)
# Lock all groups used by instance optimistically; this requires going
# via the node before it's locked, requiring verification later on
instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_uuid)
lock_groups.update(instance_groups)
else:
# No target groups, need to lock all of them
lock_groups = locking.ALL_SET
self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
elif level == locking.LEVEL_NODE:
if self.req_target_uuids:
# Lock all nodes used by instances
self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
self._LockInstancesNodes()
# Lock all nodes in all potential target groups
lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) |
self.cfg.GetInstanceNodeGroups(self.op.instance_uuid))
member_nodes = [node_uuid
for group in lock_groups
for node_uuid in self.cfg.GetNodeGroup(group).members]
self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
else:
# Lock all nodes as all groups are potential targets
self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
def CheckPrereq(self):
owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
assert (self.req_target_uuids is None or
owned_groups.issuperset(self.req_target_uuids))
assert owned_instance_names == set([self.op.instance_name])
# Get instance information
self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
# Check if node groups for locked instance are still correct
instance_all_nodes = self.cfg.GetInstanceNodes(self.instance.uuid)
assert owned_nodes.issuperset(instance_all_nodes), \
("Instance %s's nodes changed while we kept the lock" %
self.op.instance_name)
inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid,
owned_groups)
if self.req_target_uuids:
# User requested specific target groups
self.target_uuids = frozenset(self.req_target_uuids)
else:
# All groups except those used by the instance are potential targets
self.target_uuids = owned_groups - inst_groups
conflicting_groups = self.target_uuids & inst_groups
if conflicting_groups:
raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
" used by the instance '%s'" %
(utils.CommaJoin(conflicting_groups),
self.op.instance_name),
errors.ECODE_INVAL)
if not self.target_uuids:
raise errors.OpPrereqError("There are no possible target groups",
errors.ECODE_INVAL)
def BuildHooksEnv(self):
"""Build hooks env.
"""
assert self.target_uuids
env = {
"TARGET_GROUPS": " ".join(self.target_uuids),
}
env.update(BuildInstanceHookEnvByObject(self, self.instance))
return env
def BuildHooksNodes(self):
"""Build hooks nodes.
"""
mn = self.cfg.GetMasterNode()
return ([mn], [mn])
def Exec(self, feedback_fn):
instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
assert instances == [self.op.instance_name], "Instance not locked"
req = iallocator.IAReqGroupChange(instances=instances,
target_groups=list(self.target_uuids))
ial = iallocator.IAllocator(self.cfg, self.rpc, req)
ial.Run(self.op.iallocator)
if not ial.success:
raise errors.OpPrereqError("Can't compute solution for changing group of"
" instance '%s' using iallocator '%s': %s" %
(self.op.instance_name, self.op.iallocator,
ial.info), errors.ECODE_NORES)
jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
self.LogInfo("Iallocator returned %s job(s) for changing group of"
" instance '%s'", len(jobs), self.op.instance_name)
return ResultWithJobs(jobs)