("add_uids", None, ht.NoType),
("remove_uids", None, ht.NoType),
("maintain_node_health", None, ht.TMaybeBool),
+ ("prealloc_wipe_disks", None, ht.TMaybeBool),
("nicparams", None, ht.TOr(ht.TDict, ht.TNone)),
("drbd_helper", None, ht.TOr(ht.TString, ht.TNone)),
("default_iallocator", None, ht.TOr(ht.TString, ht.TNone)),
if self.op.maintain_node_health is not None:
self.cluster.maintain_node_health = self.op.maintain_node_health
+ if self.op.prealloc_wipe_disks is not None:
+ self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
+
if self.op.add_uids is not None:
uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
REQ_BGL = False
_SIMPLE_FIELDS = ["name", "serial_no", "ctime", "mtime", "uuid",
- "master_candidate", "offline", "drained"]
+ "master_candidate", "offline", "drained",
+ "master_capable", "vm_capable"]
_FIELDS_DYNAMIC = utils.FieldSet(
"dtotal", "dfree",
("primary_ip", None, ht.NoType),
("secondary_ip", None, ht.TMaybeString),
("readd", False, ht.TBool),
- ("nodegroup", None, ht.TMaybeString)
+ ("group", None, ht.TMaybeString)
]
def CheckArguments(self):
self.hostname = netutils.GetHostname(name=self.op.node_name,
family=self.primary_ip_family)
self.op.node_name = self.hostname.name
- if self.op.readd and self.op.nodegroup:
- raise errors.OpPrereqError("Cannot pass a nodegroup when a node is"
+ if self.op.readd and self.op.group:
+ raise errors.OpPrereqError("Cannot pass a node group when a node is"
" being readded", errors.ECODE_INVAL)
def BuildHooksEnv(self):
self.new_node = self.cfg.GetNodeInfo(node)
assert self.new_node is not None, "Can't retrieve locked node %s" % node
else:
- nodegroup = cfg.LookupNodeGroup(self.op.nodegroup)
+ node_group = cfg.LookupNodeGroup(self.op.group)
self.new_node = objects.Node(name=node,
primary_ip=primary_ip,
secondary_ip=secondary_ip,
master_candidate=self.master_candidate,
+ master_capable=True,
+ vm_capable=True,
offline=False, drained=False,
- nodegroup=nodegroup)
+ group=node_group)
def Exec(self, feedback_fn):
"""Adds the new node to the cluster.
class LUSetNodeParams(LogicalUnit):
"""Modifies the parameters of a node.
+ @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
+ to the node role (as _ROLE_*)
+ @cvar _R2F: a dictionary from node role to tuples of flags
+ @cvar _FLAGS: a list of attribute names corresponding to the flags
+
"""
HPATH = "node-modify"
HTYPE = constants.HTYPE_NODE
("offline", None, ht.TMaybeBool),
("drained", None, ht.TMaybeBool),
("auto_promote", False, ht.TBool),
+ ("master_capable", None, ht.TMaybeBool),
_PForce,
]
REQ_BGL = False
+ (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
+ _F2R = {
+ (True, False, False): _ROLE_CANDIDATE,
+ (False, True, False): _ROLE_DRAINED,
+ (False, False, True): _ROLE_OFFLINE,
+ (False, False, False): _ROLE_REGULAR,
+ }
+ _R2F = dict((v, k) for k, v in _F2R.items())
+ _FLAGS = ["master_candidate", "drained", "offline"]
def CheckArguments(self):
self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
- all_mods = [self.op.offline, self.op.master_candidate, self.op.drained]
- if all_mods.count(None) == 3:
+ all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
+ self.op.master_capable]
+ if all_mods.count(None) == len(all_mods):
raise errors.OpPrereqError("Please pass at least one modification",
errors.ECODE_INVAL)
if all_mods.count(True) > 1:
" state at the same time",
errors.ECODE_INVAL)
- # Boolean value that tells us whether we're offlining or draining the node
- self.offline_or_drain = (self.op.offline == True or
- self.op.drained == True)
- self.deoffline_or_drain = (self.op.offline == False or
- self.op.drained == False)
+ # Boolean value that tells us whether we might be demoting from MC
self.might_demote = (self.op.master_candidate == False or
- self.offline_or_drain)
+ self.op.offline == True or
+ self.op.drained == True or
+ self.op.master_capable == False)
self.lock_all = self.op.auto_promote and self.might_demote
-
def ExpandNames(self):
if self.lock_all:
self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
"MASTER_CANDIDATE": str(self.op.master_candidate),
"OFFLINE": str(self.op.offline),
"DRAINED": str(self.op.drained),
+ "MASTER_CAPABLE": str(self.op.master_capable),
}
nl = [self.cfg.GetMasterNode(),
self.op.node_name]
" only via master-failover",
errors.ECODE_INVAL)
+ if self.op.master_candidate and not node.master_capable:
+ raise errors.OpPrereqError("Node %s is not master capable, cannot make"
+ " it a master candidate" % node.name,
+ errors.ECODE_STATE)
if node.master_candidate and self.might_demote and not self.lock_all:
assert not self.op.auto_promote, "auto-promote set but lock_all not"
if mc_remaining < mc_should:
raise errors.OpPrereqError("Not enough master candidates, please"
" pass auto_promote to allow promotion",
- errors.ECODE_INVAL)
+ errors.ECODE_STATE)
- if (self.op.master_candidate == True and
- ((node.offline and not self.op.offline == False) or
- (node.drained and not self.op.drained == False))):
- raise errors.OpPrereqError("Node '%s' is offline or drained, can't set"
- " to master_candidate" % node.name,
- errors.ECODE_INVAL)
+ self.old_flags = old_flags = (node.master_candidate,
+ node.drained, node.offline)
+ assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
+ self.old_role = self._F2R[old_flags]
+
+ # Check for ineffective changes
+ for attr in self._FLAGS:
+ if (getattr(self.op, attr) == False and getattr(node, attr) == False):
+ self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
+ setattr(self.op, attr, None)
+
+ # Past this point, any flag change to False means a transition
+ # away from the respective state, as only real changes are kept
# If we're being deofflined/drained, we'll MC ourself if needed
- if (self.deoffline_or_drain and not self.offline_or_drain and not
- self.op.master_candidate == True and not node.master_candidate):
- self.op.master_candidate = _DecideSelfPromotion(self)
- if self.op.master_candidate:
- self.LogInfo("Autopromoting node to master candidate")
+ if (self.op.drained == False or self.op.offline == False or
+ (self.op.master_capable and not node.master_capable)):
+ if _DecideSelfPromotion(self):
+ self.op.master_candidate = True
+ self.LogInfo("Auto-promoting node to master candidate")
- return
+ # If we're no longer master capable, we'll demote ourselves from MC
+ if self.op.master_capable == False and node.master_candidate:
+ self.LogInfo("Demoting from master candidate")
+ self.op.master_candidate = False
def Exec(self, feedback_fn):
"""Modifies a node.
"""
node = self.node
+ old_role = self.old_role
+
+ assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
+
+ # compute new flags
+ if self.op.master_candidate:
+ new_role = self._ROLE_CANDIDATE
+ elif self.op.drained:
+ new_role = self._ROLE_DRAINED
+ elif self.op.offline:
+ new_role = self._ROLE_OFFLINE
+ elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
+ # False is still in new flags, which means we're un-setting (the
+ # only) True flag
+ new_role = self._ROLE_REGULAR
+ else: # no new flags, nothing, keep old role
+ new_role = old_role
result = []
- changed_mc = False
-
- if self.op.offline is not None:
- node.offline = self.op.offline
- result.append(("offline", str(self.op.offline)))
- if self.op.offline == True:
- if node.master_candidate:
- node.master_candidate = False
- changed_mc = True
- result.append(("master_candidate", "auto-demotion due to offline"))
- if node.drained:
- node.drained = False
- result.append(("drained", "clear drained status due to offline"))
-
- if self.op.master_candidate is not None:
- node.master_candidate = self.op.master_candidate
- changed_mc = True
- result.append(("master_candidate", str(self.op.master_candidate)))
- if self.op.master_candidate == False:
- rrc = self.rpc.call_node_demote_from_mc(node.name)
- msg = rrc.fail_msg
- if msg:
- self.LogWarning("Node failed to demote itself: %s" % msg)
-
- if self.op.drained is not None:
- node.drained = self.op.drained
- result.append(("drained", str(self.op.drained)))
- if self.op.drained == True:
- if node.master_candidate:
- node.master_candidate = False
- changed_mc = True
- result.append(("master_candidate", "auto-demotion due to drain"))
- rrc = self.rpc.call_node_demote_from_mc(node.name)
- msg = rrc.fail_msg
- if msg:
- self.LogWarning("Node failed to demote itself: %s" % msg)
- if node.offline:
- node.offline = False
- result.append(("offline", "clear offline status due to drain"))
+ changed_mc = [old_role, new_role].count(self._ROLE_CANDIDATE) == 1
+
+ if self.op.master_capable is not None:
+ node.master_capable = self.op.master_capable
+ result.append(("master_capable", str(self.op.master_capable)))
+
+ # Tell the node to demote itself, if no longer MC and not offline
+ if (old_role == self._ROLE_CANDIDATE and
+ new_role != self._ROLE_OFFLINE and new_role != old_role):
+ msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
+ if msg:
+ self.LogWarning("Node failed to demote itself: %s", msg)
+
+ new_flags = self._R2F[new_role]
+ for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
+ if of != nf:
+ result.append((desc, str(nf)))
+ (node.master_candidate, node.drained, node.offline) = new_flags
# we locked all nodes, we adjust the CP before updating this node
if self.lock_all:
return "originstname+%s" % instance.name
+def _CalcEta(time_taken, written, total_size):
+ """Calculates the ETA based on size written and total size.
+
+ @param time_taken: The time taken so far
+ @param written: amount written so far
+ @param total_size: The total size of data to be written
+ @return: The remaining time in seconds
+
+ """
+ avg_time = time_taken / float(written)
+ return (total_size - written) * avg_time
+
+
+def _WipeDisks(lu, instance):
+ """Wipes instance disks.
+
+ @type lu: L{LogicalUnit}
+ @param lu: the logical unit on whose behalf we execute
+ @type instance: L{objects.Instance}
+ @param instance: the instance whose disks we should create
+ @return: the success of the wipe
+
+ """
+ node = instance.primary_node
+ for idx, device in enumerate(instance.disks):
+ lu.LogInfo("* Wiping disk %d", idx)
+ logging.info("Wiping disk %d for instance %s", idx, instance.name)
+
+ # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
+ # MAX_WIPE_CHUNK at max
+ wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
+ constants.MIN_WIPE_CHUNK_PERCENT)
+
+ offset = 0
+ size = device.size
+ last_output = 0
+ start_time = time.time()
+
+ while offset < size:
+ wipe_size = min(wipe_chunk_size, size - offset)
+ result = lu.rpc.call_blockdev_wipe(node, device, offset, wipe_size)
+ result.Raise("Could not wipe disk %d at offset %d for size %d" %
+ (idx, offset, wipe_size))
+ now = time.time()
+ offset += wipe_size
+ if now - last_output >= 60:
+ eta = _CalcEta(now - start_time, offset, size)
+ lu.LogInfo(" - done: %.1f%% ETA: %s" %
+ (offset / float(size) * 100, utils.FormatSeconds(eta)))
+ last_output = now
+
+
def _CreateDisks(lu, instance, to_skip=None, target_node=None):
"""Create all disks for an instance.
self.op.instance_name = self.hostname1.name
# used in CheckPrereq for ip ping check
self.check_ip = self.hostname1.ip
- elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
- raise errors.OpPrereqError("Remote imports require names to be checked" %
- errors.ECODE_INVAL)
else:
self.check_ip = None
self.cfg.ReleaseDRBDMinors(instance)
raise
+ if self.cfg.GetClusterInfo().prealloc_wipe_disks:
+ feedback_fn("* wiping instance disks...")
+ try:
+ _WipeDisks(self, iobj)
+ except errors.OpExecError:
+ self.LogWarning("Device wiping failed, reverting...")
+ try:
+ _RemoveDisks(self, iobj)
+ finally:
+ self.cfg.ReleaseDRBDMinors(instance)
+ raise
+
feedback_fn("adding instance %s to cluster config" % instance)
self.cfg.AddInstance(iobj, self.proc.GetECId())
if self.op.osparams:
i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
_CheckOSParams(self, True, nodelist, instance_os, i_osdict)
- self.os_new = cluster.SimpleFillOS(instance_os, i_osdict)
self.os_inst = i_osdict # the new dict (without defaults)
else:
- self.os_new = self.os_inst = {}
+ self.os_inst = {}
self.warn = []
i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
# node data
- node_results = {}
node_list = cfg.GetNodeList()
if self.mode == constants.IALLOCATOR_MODE_ALLOC:
node_iinfo = \
self.rpc.call_all_instances_info(node_list,
cluster_info.enabled_hypervisors)
+
+ data["nodegroups"] = self._ComputeNodeGroupData(cfg)
+
+ data["nodes"] = self._ComputeNodeData(cfg, node_data, node_iinfo, i_list)
+
+ data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
+
+ self.in_data = data
+
+ @staticmethod
+ def _ComputeNodeGroupData(cfg):
+ """Compute node groups data.
+
+ """
+ ng = {}
+ for guuid, gdata in cfg.GetAllNodeGroupsInfo().items():
+ ng[guuid] = { "name": gdata.name }
+ return ng
+
+ @staticmethod
+ def _ComputeNodeData(cfg, node_data, node_iinfo, i_list):
+ """Compute global node data.
+
+ """
+ node_results = {}
for nname, nresult in node_data.items():
# first fill in static (config-based) values
ninfo = cfg.GetNodeInfo(nname)
"offline": ninfo.offline,
"drained": ninfo.drained,
"master_candidate": ninfo.master_candidate,
+ "group": ninfo.group,
+ "master_capable": ninfo.master_capable,
+ "vm_capable": ninfo.vm_capable,
}
if not (ninfo.offline or ninfo.drained):
pnr.update(pnr_dyn)
node_results[nname] = pnr
- data["nodes"] = node_results
- # instance data
+ return node_results
+
+ @staticmethod
+ def _ComputeInstanceData(cluster_info, i_list):
+ """Compute global instance data.
+
+ """
instance_data = {}
for iinfo, beinfo in i_list:
nic_data = []
pir["disks"])
instance_data[iinfo.name] = pir
- data["instances"] = instance_data
-
- self.in_data = data
+ return instance_data
def _AddNewInstance(self):
"""Add new instance data to allocator structure.