def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
memory, vcpus, nics, disk_template, disks,
- bep, hvp, hypervisor_name):
+ bep, hvp, hypervisor_name, tags):
"""Builds instance related env variables for hooks
This builds the hook environment from individual variables.
@param hvp: the hypervisor parameters for the instance
@type hypervisor_name: string
@param hypervisor_name: the hypervisor for the instance
+ @type tags: list
+ @param tags: list of instance tags as strings
@rtype: dict
@return: the hook environment for this instance
env["INSTANCE_DISK_COUNT"] = disk_count
+ if not tags:
+ tags = []
+
+ env["INSTANCE_TAGS"] = " ".join(tags)
+
for source, kind in [(bep, "BE"), (hvp, "HV")]:
for key, value in source.items():
env["INSTANCE_%s_%s" % (kind, key)] = value
'bep': bep,
'hvp': hvp,
'hypervisor_name': instance.hypervisor,
+ 'tags': instance.tags,
}
if override:
args.update(override)
def _VerifyCertificate(filename):
- """Verifies a certificate for LUClusterVerifyConfig.
+ """Verifies a certificate for L{LUClusterVerifyConfig}.
@type filename: string
@param filename: Path to PEM file
"""Verifies the cluster config.
"""
- REQ_BGL = False
+ REQ_BGL = True
def _VerifyHVP(self, hvp_data):
"""Verifies locally the syntax of the hypervisor parameters.
self._ErrorIf(True, self.ECLUSTERCFG, None, msg % str(err))
def ExpandNames(self):
+ # Information can be safely retrieved as the BGL is acquired in exclusive
+ # mode
self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
self.all_node_info = self.cfg.GetAllNodesInfo()
self.all_inst_info = self.cfg.GetAllInstancesInfo()
feedback_fn("* Verifying all nodes belong to an existing group")
# We do this verification here because, should this bogus circumstance
- # occur, it would never be catched by VerifyGroup, which only acts on
+ # occur, it would never be caught by VerifyGroup, which only acts on
# nodes/instances reachable from existing node groups.
dangling_nodes = set(node.name for node in self.all_node_info.values()
# This raises errors.OpPrereqError on its own:
self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
- all_node_info = self.cfg.GetAllNodesInfo()
- all_inst_info = self.cfg.GetAllInstancesInfo()
-
- node_names = set(node.name
- for node in all_node_info.values()
- if node.group == self.group_uuid)
-
- inst_names = [inst.name
- for inst in all_inst_info.values()
- if inst.primary_node in node_names]
-
- # In Exec(), we warn about mirrored instances that have primary and
- # secondary living in separate node groups. To fully verify that
- # volumes for these instances are healthy, we will need to do an
- # extra call to their secondaries. We ensure here those nodes will
- # be locked.
- for inst in inst_names:
- if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
- node_names.update(all_inst_info[inst].secondary_nodes)
+ # Get instances in node group; this is unsafe and needs verification later
+ inst_names = self.cfg.GetNodeGroupInstances(self.group_uuid)
self.needed_locks = {
- locking.LEVEL_NODEGROUP: [self.group_uuid],
- locking.LEVEL_NODE: list(node_names),
locking.LEVEL_INSTANCE: inst_names,
- }
+ locking.LEVEL_NODEGROUP: [self.group_uuid],
+ locking.LEVEL_NODE: [],
+ }
self.share_locks = dict.fromkeys(locking.LEVELS, 1)
- def CheckPrereq(self):
- self.all_node_info = self.cfg.GetAllNodesInfo()
- self.all_inst_info = self.cfg.GetAllInstancesInfo()
+ def DeclareLocks(self, level):
+ if level == locking.LEVEL_NODE:
+ # Get members of node group; this is unsafe and needs verification later
+ nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
+
+ all_inst_info = self.cfg.GetAllInstancesInfo()
+
+ # In Exec(), we warn about mirrored instances that have primary and
+ # secondary living in separate node groups. To fully verify that
+ # volumes for these instances are healthy, we will need to do an
+ # extra call to their secondaries. We ensure here those nodes will
+ # be locked.
+ for inst in self.glm.list_owned(locking.LEVEL_INSTANCE):
+ # Important: access only the instances whose lock is owned
+ if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
+ nodes.update(all_inst_info[inst].secondary_nodes)
- group_nodes = set(node.name
- for node in self.all_node_info.values()
- if node.group == self.group_uuid)
+ self.needed_locks[locking.LEVEL_NODE] = nodes
- group_instances = set(inst.name
- for inst in self.all_inst_info.values()
- if inst.primary_node in group_nodes)
+ def CheckPrereq(self):
+ group_nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
+ group_instances = self.cfg.GetNodeGroupInstances(self.group_uuid)
unlocked_nodes = \
group_nodes.difference(self.glm.list_owned(locking.LEVEL_NODE))
group_instances.difference(self.glm.list_owned(locking.LEVEL_INSTANCE))
if unlocked_nodes:
- raise errors.OpPrereqError("missing lock for nodes: %s" %
+ raise errors.OpPrereqError("Missing lock for nodes: %s" %
utils.CommaJoin(unlocked_nodes))
if unlocked_instances:
- raise errors.OpPrereqError("missing lock for instances: %s" %
+ raise errors.OpPrereqError("Missing lock for instances: %s" %
utils.CommaJoin(unlocked_instances))
+ self.all_node_info = self.cfg.GetAllNodesInfo()
+ self.all_inst_info = self.cfg.GetAllInstancesInfo()
+
self.my_node_names = utils.NiceSort(group_nodes)
self.my_inst_names = utils.NiceSort(group_instances)
# All or no nodes
errorif(missing_file and missing_file != node_names,
cls.ECLUSTERFILECHECK, None,
- "File %s is optional, but it must exist on all or no nodes (not"
- " found on %s)",
+ "File %s is optional, but it must exist on all or no"
+ " nodes (not found on %s)",
filename, utils.CommaJoin(utils.NiceSort(missing_file)))
else:
errorif(missing_file, cls.ECLUSTERFILECHECK, None,
"""Build hooks nodes.
"""
- assert self.my_node_names, ("Node list not gathered,"
- " has CheckPrereq been executed?")
return ([], self.my_node_names)
def Exec(self, feedback_fn):
"""
# This method has too many local variables. pylint: disable-msg=R0914
+
+ if not self.my_node_names:
+ # empty node group
+ feedback_fn("* Empty node group, skipping verification")
+ return True
+
self.bad = False
_ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
verbose = self.op.verbose
all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
node_verify_param,
self.cfg.GetClusterName())
+ nvinfo_endtime = time.time()
+
if self.extra_lv_nodes and vg_name is not None:
extra_lv_nvinfo = \
self.rpc.call_node_verify(self.extra_lv_nodes,
self.cfg.GetClusterName())
else:
extra_lv_nvinfo = {}
- nvinfo_endtime = time.time()
all_drbd_map = self.cfg.ComputeDRBDMap()
and hook results
"""
- # We only really run POST phase hooks, and are only interested in
- # their results
- if phase == constants.HOOKS_PHASE_POST:
+ # We only really run POST phase hooks, only for non-empty groups,
+ # and are only interested in their results
+ if not self.my_node_names:
+ # empty node group
+ pass
+ elif phase == constants.HOOKS_PHASE_POST:
# Used to change hooks' output to proper indentation
feedback_fn("* Hooks Results")
assert hooks_results, "invalid result from hooks"
feedback_fn("%s" % output)
lu_result = 0
- return lu_result
+ return lu_result
class LUClusterVerifyDisks(NoHooksLU):
_StartInstanceDisks(self, instance, force)
result = self.rpc.call_instance_start(node_current, instance,
- self.op.hvparams, self.op.beparams)
+ self.op.hvparams, self.op.beparams,
+ self.op.startup_paused)
msg = result.fail_msg
if msg:
_ShutdownInstanceDisks(self, instance)
self.LogInfo("Instance %s was already stopped, starting now",
instance.name)
_StartInstanceDisks(self, instance, ignore_secondaries)
- result = self.rpc.call_instance_start(node_current, instance, None, None)
+ result = self.rpc.call_instance_start(node_current, instance,
+ None, None, False)
msg = result.fail_msg
if msg:
_ShutdownInstanceDisks(self, instance)
"""Recreate the disks.
"""
- # change primary node, if needed
- if self.op.nodes:
- self.instance.primary_node = self.op.nodes[0]
- self.LogWarning("Changing the instance's nodes, you will have to"
- " remove any disks left on the older nodes manually")
+ instance = self.instance
to_skip = []
- for idx, disk in enumerate(self.instance.disks):
+ mods = [] # keeps track of needed logical_id changes
+
+ for idx, disk in enumerate(instance.disks):
if idx not in self.op.disks: # disk idx has not been passed in
to_skip.append(idx)
continue
# update secondaries for disks, if needed
if self.op.nodes:
if disk.dev_type == constants.LD_DRBD8:
- # need to update the nodes
+ # need to update the nodes and minors
assert len(self.op.nodes) == 2
- logical_id = list(disk.logical_id)
- logical_id[0] = self.op.nodes[0]
- logical_id[1] = self.op.nodes[1]
- disk.logical_id = tuple(logical_id)
+ assert len(disk.logical_id) == 6 # otherwise disk internals
+ # have changed
+ (_, _, old_port, _, _, old_secret) = disk.logical_id
+ new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
+ new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
+ new_minors[0], new_minors[1], old_secret)
+ assert len(disk.logical_id) == len(new_id)
+ mods.append((idx, new_id))
+
+ # now that we have passed all asserts above, we can apply the mods
+ # in a single run (to avoid partial changes)
+ for idx, new_id in mods:
+ instance.disks[idx].logical_id = new_id
+ # change primary node, if needed
if self.op.nodes:
- self.cfg.Update(self.instance, feedback_fn)
+ instance.primary_node = self.op.nodes[0]
+ self.LogWarning("Changing the instance's nodes, you will have to"
+ " remove any disks left on the older nodes manually")
- _CreateDisks(self, self.instance, to_skip=to_skip)
+ if self.op.nodes:
+ self.cfg.Update(instance, feedback_fn)
+
+ _CreateDisks(self, instance, to_skip=to_skip)
class LUInstanceRename(LogicalUnit):
old_name = inst.name
rename_file_storage = False
- if (inst.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE) and
+ if (inst.disk_template in constants.DTS_FILEBASED and
self.op.new_name != inst.name):
old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
rename_file_storage = True
_ShutdownInstanceDisks(self, instance)
raise errors.OpExecError("Can't activate the instance's disks")
- result = self.rpc.call_instance_start(target_node, instance, None, None)
+ result = self.rpc.call_instance_start(target_node, instance,
+ None, None, False)
msg = result.fail_msg
if msg:
_ShutdownInstanceDisks(self, instance)
pnode = target_node
all_nodes = [pnode]
- if instance.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
+ if instance.disk_template in constants.DTS_FILEBASED:
file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
raise errors.OpPrereqError("Invalid file driver name '%s'" %
self.op.file_driver, errors.ECODE_INVAL)
- if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
- raise errors.OpPrereqError("File storage directory path not absolute",
- errors.ECODE_INVAL)
+ if self.op.disk_template == constants.DT_FILE:
+ opcodes.RequireFileStorage()
+ elif self.op.disk_template == constants.DT_SHARED_FILE:
+ opcodes.RequireSharedFileStorage()
### Node/iallocator related checks
_CheckIAllocatorOrNode(self, "iallocator", "pnode")
mode=constants.IALLOCATOR_MODE_ALLOC,
name=self.op.instance_name,
disk_template=self.op.disk_template,
- tags=[],
+ tags=self.op.tags,
os=self.op.os_type,
vcpus=self.be_full[constants.BE_VCPUS],
memory=self.be_full[constants.BE_MEMORY],
bep=self.be_full,
hvp=self.hv_full,
hypervisor_name=self.op.hypervisor,
+ tags=self.op.tags,
))
return env
nics.append(ndict)
self.op.nics = nics
+ if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
+ self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
+
if (self.op.hypervisor is None and
einfo.has_option(constants.INISECT_INS, "hypervisor")):
self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
+
if einfo.has_section(constants.INISECT_HYP):
# use the export parameters but do not override the ones
# specified by the user
if name in os_defs and os_defs[name] == self.op.osparams[name]:
del self.op.osparams[name]
+ def _CalculateFileStorageDir(self):
+ """Calculate final instance file storage dir.
+
+ """
+ # file storage dir calculation/check
+ self.instance_file_storage_dir = None
+ if self.op.disk_template in constants.DTS_FILEBASED:
+ # build the full file storage dir path
+ joinargs = []
+
+ if self.op.disk_template == constants.DT_SHARED_FILE:
+ get_fsd_fn = self.cfg.GetSharedFileStorageDir
+ else:
+ get_fsd_fn = self.cfg.GetFileStorageDir
+
+ cfg_storagedir = get_fsd_fn()
+ if not cfg_storagedir:
+ raise errors.OpPrereqError("Cluster file storage dir not defined")
+ joinargs.append(cfg_storagedir)
+
+ if self.op.file_storage_dir is not None:
+ joinargs.append(self.op.file_storage_dir)
+
+ joinargs.append(self.op.instance_name)
+
+ # pylint: disable-msg=W0142
+ self.instance_file_storage_dir = utils.PathJoin(*joinargs)
+
def CheckPrereq(self):
"""Check prerequisites.
"""
+ self._CalculateFileStorageDir()
+
if self.op.mode == constants.INSTANCE_IMPORT:
export_info = self._ReadExportInfo()
self._ReadExportParams(export_info)
",".join(enabled_hvs)),
errors.ECODE_STATE)
+ # Check tag validity
+ for tag in self.op.tags:
+ objects.TaggableObject.ValidateTag(tag)
+
# check hypervisor parameter syntax (locally)
utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
else:
network_port = None
- if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
- # this is needed because os.path.join does not accept None arguments
- if self.op.file_storage_dir is None:
- string_file_storage_dir = ""
- else:
- string_file_storage_dir = self.op.file_storage_dir
-
- # build the full file storage dir path
- if self.op.disk_template == constants.DT_SHARED_FILE:
- get_fsd_fn = self.cfg.GetSharedFileStorageDir
- else:
- get_fsd_fn = self.cfg.GetFileStorageDir
-
- file_storage_dir = utils.PathJoin(get_fsd_fn(),
- string_file_storage_dir, instance)
- else:
- file_storage_dir = ""
-
disks = _GenerateDiskTemplate(self,
self.op.disk_template,
instance, pnode_name,
self.secondaries,
self.disks,
- file_storage_dir,
+ self.instance_file_storage_dir,
self.op.file_driver,
0,
feedback_fn)
osparams=self.op.osparams,
)
+ if self.op.tags:
+ for tag in self.op.tags:
+ iobj.AddTag(tag)
+
if self.adopt_disks:
if self.op.disk_template == constants.DT_PLAIN:
# rename LVs to the newly-generated names; we need to construct
self.cfg.Update(iobj, feedback_fn)
logging.info("Starting instance %s on node %s", instance, pnode_name)
feedback_fn("* starting instance...")
- result = self.rpc.call_instance_start(pnode_name, iobj, None, None)
+ result = self.rpc.call_instance_start(pnode_name, iobj,
+ None, None, False)
result.Raise("Could not start instance")
return list(iobj.all_nodes)
(node_name, self.instance.name))
def _CreateNewStorage(self, node_name):
+ """Create new storage on the primary or secondary node.
+
+ This is only used for same-node replaces, not for changing the
+ secondary node, hence we don't want to modify the existing disk.
+
+ """
iv_names = {}
for idx, dev in enumerate(self.instance.disks):
logical_id=(vg_meta, names[1]))
new_lvs = [lv_data, lv_meta]
- old_lvs = dev.children
+ old_lvs = [child.Copy() for child in dev.children]
iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
# we pass force_create=True to force the LVM creation
self.lu.LogWarning("Can't remove old LV: %s" % msg,
hint="remove unused LVs manually")
- def _ExecDrbd8DiskOnly(self, feedback_fn):
+ def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable-msg=W0613
"""Replace a disk on the primary or secondary for DRBD 8.
The algorithm for replace is quite complicated:
rename_new_to_old)
result.Raise("Can't rename new LVs on node %s" % self.target_node)
+ # Intermediate steps of in memory modifications
for old, new in zip(old_lvs, new_lvs):
new.logical_id = old.logical_id
self.cfg.SetDiskID(new, self.target_node)
+ # We need to modify old_lvs so that removal later removes the
+ # right LVs, not the newly added ones; note that old_lvs is a
+ # copy here
for disk in old_lvs:
disk.logical_id = ren_fn(disk, temp_suffix)
self.cfg.SetDiskID(disk, self.target_node)
"volumes"))
raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
- dev.children = new_lvs
-
- self.cfg.Update(self.instance, feedback_fn)
-
cstep = 5
if self.early_release:
self.lu.LogStep(cstep, steps_total, "Removing old storage")
(self.op.name, self.op.node_name))
-class LUNodeEvacStrategy(NoHooksLU):
- """Computes the node evacuation strategy.
+class LUNodeEvacuate(NoHooksLU):
+ """Evacuates instances off a list of nodes.
"""
REQ_BGL = False
_CheckIAllocatorOrNode(self, "iallocator", "remote_node")
def ExpandNames(self):
- self.op.nodes = _GetWantedNodes(self, self.op.nodes)
- self.needed_locks = locks = {}
+ self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
+
+ if self.op.remote_node is not None:
+ self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
+ assert self.op.remote_node
+
+ if self.op.remote_node == self.op.node_name:
+ raise errors.OpPrereqError("Can not use evacuated node as a new"
+ " secondary node", errors.ECODE_INVAL)
+
+ if self.op.mode != constants.IALLOCATOR_NEVAC_SEC:
+ raise errors.OpPrereqError("Without the use of an iallocator only"
+ " secondary instances can be evacuated",
+ errors.ECODE_INVAL)
+
+ # Declare locks
+ self.share_locks = dict.fromkeys(locking.LEVELS, 1)
+ self.needed_locks = {
+ locking.LEVEL_INSTANCE: [],
+ locking.LEVEL_NODEGROUP: [],
+ locking.LEVEL_NODE: [],
+ }
+
if self.op.remote_node is None:
- locks[locking.LEVEL_NODE] = locking.ALL_SET
+ # Iallocator will choose any node(s) in the same group
+ group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
else:
- self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
- locks[locking.LEVEL_NODE] = self.op.nodes + [self.op.remote_node]
+ group_nodes = frozenset([self.op.remote_node])
- def Exec(self, feedback_fn):
- instances = []
- for node in self.op.nodes:
- instances.extend(_GetNodeSecondaryInstances(self.cfg, node))
- if not instances:
- return []
+ # Determine nodes to be locked
+ self.lock_nodes = set([self.op.node_name]) | group_nodes
+
+ def _DetermineInstances(self):
+ """Builds list of instances to operate on.
+
+ """
+ assert self.op.mode in constants.IALLOCATOR_NEVAC_MODES
+
+ if self.op.mode == constants.IALLOCATOR_NEVAC_PRI:
+ # Primary instances only
+ inst_fn = _GetNodePrimaryInstances
+ assert self.op.remote_node is None, \
+ "Evacuating primary instances requires iallocator"
+ elif self.op.mode == constants.IALLOCATOR_NEVAC_SEC:
+ # Secondary instances only
+ inst_fn = _GetNodeSecondaryInstances
+ else:
+ # All instances
+ assert self.op.mode == constants.IALLOCATOR_NEVAC_ALL
+ inst_fn = _GetNodeInstances
+
+ return inst_fn(self.cfg, self.op.node_name)
+
+ def DeclareLocks(self, level):
+ if level == locking.LEVEL_INSTANCE:
+ # Lock instances optimistically, needs verification once node and group
+ # locks have been acquired
+ self.needed_locks[locking.LEVEL_INSTANCE] = \
+ set(i.name for i in self._DetermineInstances())
+
+ elif level == locking.LEVEL_NODEGROUP:
+ # Lock node groups optimistically, needs verification once nodes have
+ # been acquired
+ self.needed_locks[locking.LEVEL_NODEGROUP] = \
+ self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
+
+ elif level == locking.LEVEL_NODE:
+ self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
+
+ def CheckPrereq(self):
+ # Verify locks
+ owned_instances = self.glm.list_owned(locking.LEVEL_INSTANCE)
+ owned_nodes = self.glm.list_owned(locking.LEVEL_NODE)
+ owned_groups = self.glm.list_owned(locking.LEVEL_NODEGROUP)
+
+ assert owned_nodes == self.lock_nodes
+
+ wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
+ if owned_groups != wanted_groups:
+ raise errors.OpExecError("Node groups changed since locks were acquired,"
+ " current groups are '%s', used to be '%s'" %
+ (utils.CommaJoin(wanted_groups),
+ utils.CommaJoin(owned_groups)))
+
+ # Determine affected instances
+ self.instances = self._DetermineInstances()
+ self.instance_names = [i.name for i in self.instances]
+
+ if set(self.instance_names) != owned_instances:
+ raise errors.OpExecError("Instances on node '%s' changed since locks"
+ " were acquired, current instances are '%s',"
+ " used to be '%s'" %
+ (self.op.node_name,
+ utils.CommaJoin(self.instance_names),
+ utils.CommaJoin(owned_instances)))
+
+ if self.instance_names:
+ self.LogInfo("Evacuating instances from node '%s': %s",
+ self.op.node_name,
+ utils.CommaJoin(utils.NiceSort(self.instance_names)))
+ else:
+ self.LogInfo("No instances to evacuate from node '%s'",
+ self.op.node_name)
if self.op.remote_node is not None:
- result = []
- for i in instances:
+ for i in self.instances:
if i.primary_node == self.op.remote_node:
raise errors.OpPrereqError("Node %s is the primary node of"
" instance %s, cannot use it as"
" secondary" %
(self.op.remote_node, i.name),
errors.ECODE_INVAL)
- result.append([i.name, self.op.remote_node])
- else:
- ial = IAllocator(self.cfg, self.rpc,
- mode=constants.IALLOCATOR_MODE_MEVAC,
- evac_nodes=self.op.nodes)
- ial.Run(self.op.iallocator, validate=True)
+
+ def Exec(self, feedback_fn):
+ assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
+
+ if not self.instance_names:
+ # No instances to evacuate
+ jobs = []
+
+ elif self.op.iallocator is not None:
+ # TODO: Implement relocation to other group
+ ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
+ evac_mode=self.op.mode,
+ instances=list(self.instance_names))
+
+ ial.Run(self.op.iallocator)
+
if not ial.success:
- raise errors.OpExecError("No valid evacuation solution: %s" % ial.info,
- errors.ECODE_NORES)
- result = ial.result
- return result
+ raise errors.OpPrereqError("Can't compute node evacuation using"
+ " iallocator '%s': %s" %
+ (self.op.iallocator, ial.info),
+ errors.ECODE_NORES)
+
+ jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
+
+ elif self.op.remote_node is not None:
+ assert self.op.mode == constants.IALLOCATOR_NEVAC_SEC
+ jobs = [
+ [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
+ remote_node=self.op.remote_node,
+ disks=[],
+ mode=constants.REPLACE_DISK_CHG,
+ early_release=self.op.early_release)]
+ for instance_name in self.instance_names
+ ]
+
+ else:
+ raise errors.ProgrammerError("No iallocator or remote node")
+
+ return ResultWithJobs(jobs)
+
+
+def _SetOpEarlyRelease(early_release, op):
+ """Sets C{early_release} flag on opcodes if available.
+
+ """
+ try:
+ op.early_release = early_release
+ except AttributeError:
+ assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
+
+ return op
+
+
+def _NodeEvacDest(use_nodes, group, nodes):
+ """Returns group or nodes depending on caller's choice.
+
+ """
+ if use_nodes:
+ return utils.CommaJoin(nodes)
+ else:
+ return group
+
+
+def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
+ """Unpacks the result of change-group and node-evacuate iallocator requests.
+
+ Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
+ L{constants.IALLOCATOR_MODE_CHG_GROUP}.
+
+ @type lu: L{LogicalUnit}
+ @param lu: Logical unit instance
+ @type alloc_result: tuple/list
+ @param alloc_result: Result from iallocator
+ @type early_release: bool
+ @param early_release: Whether to release locks early if possible
+ @type use_nodes: bool
+ @param use_nodes: Whether to display node names instead of groups
+
+ """
+ (moved, failed, jobs) = alloc_result
+
+ if failed:
+ lu.LogWarning("Unable to evacuate instances %s",
+ utils.CommaJoin("%s (%s)" % (name, reason)
+ for (name, reason) in failed))
+
+ if moved:
+ lu.LogInfo("Instances to be moved: %s",
+ utils.CommaJoin("%s (to %s)" %
+ (name, _NodeEvacDest(use_nodes, group, nodes))
+ for (name, group, nodes) in moved))
+
+ return [map(compat.partial(_SetOpEarlyRelease, early_release),
+ map(opcodes.OpCode.LoadOpCode, ops))
+ for ops in jobs]
class LUInstanceGrowDisk(LogicalUnit):
not self.op.remove_instance):
assert not activate_disks
feedback_fn("Starting instance %s" % instance.name)
- result = self.rpc.call_instance_start(src_node, instance, None, None)
+ result = self.rpc.call_instance_start(src_node, instance,
+ None, None, False)
msg = result.fail_msg
if msg:
feedback_fn("Failed to start instance: %s" % msg)
return self.op.new_name
+class LUGroupEvacuate(LogicalUnit):
+ HPATH = "group-evacuate"
+ HTYPE = constants.HTYPE_GROUP
+ REQ_BGL = False
+
+ def ExpandNames(self):
+ # This raises errors.OpPrereqError on its own:
+ self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
+
+ if self.op.target_groups:
+ self.req_target_uuids = map(self.cfg.LookupNodeGroup,
+ self.op.target_groups)
+ else:
+ self.req_target_uuids = []
+
+ if self.group_uuid in self.req_target_uuids:
+ raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
+ " as a target group (targets are %s)" %
+ (self.group_uuid,
+ utils.CommaJoin(self.req_target_uuids)),
+ errors.ECODE_INVAL)
+
+ if not self.op.iallocator:
+ # Use default iallocator
+ self.op.iallocator = self.cfg.GetDefaultIAllocator()
+
+ if not self.op.iallocator:
+ raise errors.OpPrereqError("No iallocator was specified, neither in the"
+ " opcode nor as a cluster-wide default",
+ errors.ECODE_INVAL)
+
+ self.share_locks = dict.fromkeys(locking.LEVELS, 1)
+ self.needed_locks = {
+ locking.LEVEL_INSTANCE: [],
+ locking.LEVEL_NODEGROUP: [],
+ locking.LEVEL_NODE: [],
+ }
+
+ def DeclareLocks(self, level):
+ if level == locking.LEVEL_INSTANCE:
+ assert not self.needed_locks[locking.LEVEL_INSTANCE]
+
+ # Lock instances optimistically, needs verification once node and group
+ # locks have been acquired
+ self.needed_locks[locking.LEVEL_INSTANCE] = \
+ self.cfg.GetNodeGroupInstances(self.group_uuid)
+
+ elif level == locking.LEVEL_NODEGROUP:
+ assert not self.needed_locks[locking.LEVEL_NODEGROUP]
+
+ if self.req_target_uuids:
+ lock_groups = set([self.group_uuid] + self.req_target_uuids)
+
+ # Lock all groups used by instances optimistically; this requires going
+ # via the node before it's locked, requiring verification later on
+ lock_groups.update(group_uuid
+ for instance_name in
+ self.glm.list_owned(locking.LEVEL_INSTANCE)
+ for group_uuid in
+ self.cfg.GetInstanceNodeGroups(instance_name))
+ 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:
+ # This will only lock the nodes in the group to be evacuated which
+ # contain actual instances
+ self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
+ self._LockInstancesNodes()
+
+ # Lock all nodes in group to be evacuated
+ assert self.group_uuid in self.glm.list_owned(locking.LEVEL_NODEGROUP)
+ member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
+ self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
+
+ def CheckPrereq(self):
+ owned_instances = frozenset(self.glm.list_owned(locking.LEVEL_INSTANCE))
+ owned_groups = frozenset(self.glm.list_owned(locking.LEVEL_NODEGROUP))
+ owned_nodes = frozenset(self.glm.list_owned(locking.LEVEL_NODE))
+
+ assert owned_groups.issuperset(self.req_target_uuids)
+ assert self.group_uuid in owned_groups
+
+ # Check if locked instances are still correct
+ wanted_instances = self.cfg.GetNodeGroupInstances(self.group_uuid)
+ if owned_instances != wanted_instances:
+ raise errors.OpPrereqError("Instances in node group to be evacuated (%s)"
+ " changed since locks were acquired, wanted"
+ " %s, have %s; retry the operation" %
+ (self.group_uuid,
+ utils.CommaJoin(wanted_instances),
+ utils.CommaJoin(owned_instances)),
+ errors.ECODE_STATE)
+
+ # Get instance information
+ self.instances = dict((name, self.cfg.GetInstanceInfo(name))
+ for name in owned_instances)
+
+ # Check if node groups for locked instances are still correct
+ for instance_name in owned_instances:
+ inst = self.instances[instance_name]
+ assert self.group_uuid in self.cfg.GetInstanceNodeGroups(instance_name), \
+ "Instance %s has no node in group %s" % (instance_name, self.group_uuid)
+ assert owned_nodes.issuperset(inst.all_nodes), \
+ "Instance %s's nodes changed while we kept the lock" % instance_name
+
+ inst_groups = self.cfg.GetInstanceNodeGroups(instance_name)
+ if not owned_groups.issuperset(inst_groups):
+ raise errors.OpPrereqError("Instance's node groups changed since locks"
+ " were acquired, current groups are '%s',"
+ " owning groups '%s'; retry the operation" %
+ (utils.CommaJoin(inst_groups),
+ utils.CommaJoin(owned_groups)),
+ errors.ECODE_STATE)
+
+ if self.req_target_uuids:
+ # User requested specific target groups
+ self.target_uuids = self.req_target_uuids
+ else:
+ # All groups except the one to be evacuated are potential targets
+ self.target_uuids = [group_uuid for group_uuid in owned_groups
+ if group_uuid != self.group_uuid]
+
+ if not self.target_uuids:
+ raise errors.OpExecError("There are no possible target groups")
+
+ def BuildHooksEnv(self):
+ """Build hooks env.
+
+ """
+ return {
+ "GROUP_NAME": self.op.group_name,
+ "TARGET_GROUPS": " ".join(self.target_uuids),
+ }
+
+ def BuildHooksNodes(self):
+ """Build hooks nodes.
+
+ """
+ mn = self.cfg.GetMasterNode()
+
+ assert self.group_uuid in self.glm.list_owned(locking.LEVEL_NODEGROUP)
+
+ run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
+
+ return (run_nodes, run_nodes)
+
+ def Exec(self, feedback_fn):
+ instances = list(self.glm.list_owned(locking.LEVEL_INSTANCE))
+
+ assert self.group_uuid not in self.target_uuids
+
+ ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
+ instances=instances, target_groups=self.target_uuids)
+
+ ial.Run(self.op.iallocator)
+
+ if not ial.success:
+ raise errors.OpPrereqError("Can't compute group evacuation using"
+ " iallocator '%s': %s" %
+ (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 evacuating node group %s",
+ len(jobs), self.op.group_name)
+
+ return ResultWithJobs(jobs)
+
+
class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
"""Generic tags LU.
self.name = None
self.evac_nodes = None
self.instances = None
- self.reloc_mode = None
+ self.evac_mode = None
self.target_groups = []
# computed fields
self.required_nodes = None
self.success = self.info = self.result = None
try:
- (fn, keyset, self._result_check) = self._MODE_DATA[self.mode]
+ (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
except KeyError:
raise errors.ProgrammerError("Unknown mode '%s' passed to the"
" IAllocator" % self.mode)
+ keyset = [n for (n, _) in keydata]
+
for key in kwargs:
if key not in keyset:
raise errors.ProgrammerError("Invalid input parameter '%s' to"
if key not in kwargs:
raise errors.ProgrammerError("Missing input parameter '%s' to"
" IAllocator" % key)
- self._BuildInputData(compat.partial(fn, self))
+ self._BuildInputData(compat.partial(fn, self), keydata)
def _ComputeClusterData(self):
"""Compute the generic allocator input data.
hypervisor_name = self.hypervisor
elif self.mode == constants.IALLOCATOR_MODE_RELOC:
hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
- elif self.mode in (constants.IALLOCATOR_MODE_MEVAC,
- constants.IALLOCATOR_MODE_MRELOC):
+ else:
hypervisor_name = cluster_info.enabled_hypervisors[0]
node_data = self.rpc.call_node_info(node_list, cfg.GetVGName(),
}
return request
- def _AddMultiRelocate(self):
- """Get data for multi-relocate requests.
+ def _AddNodeEvacuate(self):
+ """Get data for node-evacuate requests.
+
+ """
+ return {
+ "instances": self.instances,
+ "evac_mode": self.evac_mode,
+ }
+
+ def _AddChangeGroup(self):
+ """Get data for node-evacuate requests.
"""
return {
"instances": self.instances,
- "reloc_mode": self.reloc_mode,
"target_groups": self.target_groups,
}
- def _BuildInputData(self, fn):
+ def _BuildInputData(self, fn, keydata):
"""Build input data structures.
"""
request = fn()
request["type"] = self.mode
+ for keyname, keytype in keydata:
+ if keyname not in request:
+ raise errors.ProgrammerError("Request parameter %s is missing" %
+ keyname)
+ val = request[keyname]
+ if not keytype(val):
+ raise errors.ProgrammerError("Request parameter %s doesn't pass"
+ " validation, value %s, expected"
+ " type %s" % (keyname, val, keytype))
self.in_data["request"] = request
self.in_text = serializer.Dump(self.in_data)
+ _STRING_LIST = ht.TListOf(ht.TString)
+ _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
+ # pylint: disable-msg=E1101
+ # Class '...' has no 'OP_ID' member
+ "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
+ opcodes.OpInstanceMigrate.OP_ID,
+ opcodes.OpInstanceReplaceDisks.OP_ID])
+ })))
+
+ _NEVAC_MOVED = \
+ ht.TListOf(ht.TAnd(ht.TIsLength(3),
+ ht.TItems([ht.TNonEmptyString,
+ ht.TNonEmptyString,
+ ht.TListOf(ht.TNonEmptyString),
+ ])))
+ _NEVAC_FAILED = \
+ ht.TListOf(ht.TAnd(ht.TIsLength(2),
+ ht.TItems([ht.TNonEmptyString,
+ ht.TMaybeString,
+ ])))
+ _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
+ ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
+
_MODE_DATA = {
constants.IALLOCATOR_MODE_ALLOC:
(_AddNewInstance,
- ["name", "memory", "disks", "disk_template", "os", "tags", "nics",
- "vcpus", "hypervisor"], ht.TList),
+ [
+ ("name", ht.TString),
+ ("memory", ht.TInt),
+ ("disks", ht.TListOf(ht.TDict)),
+ ("disk_template", ht.TString),
+ ("os", ht.TString),
+ ("tags", _STRING_LIST),
+ ("nics", ht.TListOf(ht.TDict)),
+ ("vcpus", ht.TInt),
+ ("hypervisor", ht.TString),
+ ], ht.TList),
constants.IALLOCATOR_MODE_RELOC:
- (_AddRelocateInstance, ["name", "relocate_from"], ht.TList),
+ (_AddRelocateInstance,
+ [("name", ht.TString), ("relocate_from", _STRING_LIST)],
+ ht.TList),
constants.IALLOCATOR_MODE_MEVAC:
- (_AddEvacuateNodes, ["evac_nodes"],
- ht.TListOf(ht.TAnd(ht.TIsLength(2),
- ht.TListOf(ht.TString)))),
- constants.IALLOCATOR_MODE_MRELOC:
- (_AddMultiRelocate, ["instances", "reloc_mode", "target_groups"],
- ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
- # pylint: disable-msg=E1101
- # Class '...' has no 'OP_ID' member
- "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
- opcodes.OpInstanceMigrate.OP_ID,
- opcodes.OpInstanceReplaceDisks.OP_ID])
- })))),
+ (_AddEvacuateNodes, [("evac_nodes", _STRING_LIST)],
+ ht.TListOf(ht.TAnd(ht.TIsLength(2), _STRING_LIST))),
+ constants.IALLOCATOR_MODE_NODE_EVAC:
+ (_AddNodeEvacuate, [
+ ("instances", _STRING_LIST),
+ ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
+ ], _NEVAC_RESULT),
+ constants.IALLOCATOR_MODE_CHG_GROUP:
+ (_AddChangeGroup, [
+ ("instances", _STRING_LIST),
+ ("target_groups", _STRING_LIST),
+ ], _NEVAC_RESULT),
}
def Run(self, name, validate=True, call_fn=None):
else:
raise errors.ProgrammerError("Unhandled mode '%s'" % self.mode)
+ elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
+ assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
+
self.out_data = rdict
@staticmethod
if not hasattr(self.op, "evac_nodes"):
raise errors.OpPrereqError("Missing attribute 'evac_nodes' on"
" opcode input", errors.ECODE_INVAL)
- elif self.op.mode == constants.IALLOCATOR_MODE_MRELOC:
- if self.op.instances:
- self.op.instances = _GetWantedInstances(self, self.op.instances)
- else:
- raise errors.OpPrereqError("Missing instances to relocate",
- errors.ECODE_INVAL)
+ elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
+ constants.IALLOCATOR_MODE_NODE_EVAC):
+ if not self.op.instances:
+ raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
+ self.op.instances = _GetWantedInstances(self, self.op.instances)
else:
raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
self.op.mode, errors.ECODE_INVAL)
ial = IAllocator(self.cfg, self.rpc,
mode=self.op.mode,
evac_nodes=self.op.evac_nodes)
- elif self.op.mode == constants.IALLOCATOR_MODE_MRELOC:
+ elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
ial = IAllocator(self.cfg, self.rpc,
mode=self.op.mode,
instances=self.op.instances,
- reloc_mode=self.op.reloc_mode,
target_groups=self.op.target_groups)
+ elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
+ ial = IAllocator(self.cfg, self.rpc,
+ mode=self.op.mode,
+ instances=self.op.instances,
+ evac_mode=self.op.evac_mode)
else:
raise errors.ProgrammerError("Uncatched mode %s in"
" LUTestAllocator.Exec", self.op.mode)