4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Logical units dealing with node groups."""
27 from ganeti import constants
28 from ganeti import errors
29 from ganeti import locking
30 from ganeti import objects
31 from ganeti import qlang
32 from ganeti import query
33 from ganeti import utils
34 from ganeti.masterd import iallocator
35 from ganeti.cmdlib.base import LogicalUnit, NoHooksLU, QueryBase, \
37 from ganeti.cmdlib.common import MergeAndVerifyHvState, \
38 MergeAndVerifyDiskState, GetWantedNodes, GetUpdatedParams, \
39 CheckNodeGroupInstances, GetUpdatedIPolicy, \
40 ComputeNewInstanceViolations, GetDefaultIAllocator, ShareAll, \
41 CheckInstancesNodeGroups, LoadNodeEvacResult, MapInstanceLvsToNodes, \
42 CheckIpolicyVsDiskTemplates
44 import ganeti.masterd.instance
47 class LUGroupAdd(LogicalUnit):
48 """Logical unit for creating node groups.
52 HTYPE = constants.HTYPE_GROUP
55 def ExpandNames(self):
56 # We need the new group's UUID here so that we can create and acquire the
57 # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
58 # that it should not check whether the UUID exists in the configuration.
59 self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
60 self.needed_locks = {}
61 self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
63 def _CheckIpolicy(self):
64 """Checks the group's ipolicy for consistency and validity.
68 cluster = self.cfg.GetClusterInfo()
69 full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
71 objects.InstancePolicy.CheckParameterSyntax(full_ipolicy, False)
72 except errors.ConfigurationError, err:
73 raise errors.OpPrereqError("Invalid instance policy: %s" % err,
75 CheckIpolicyVsDiskTemplates(full_ipolicy,
76 cluster.enabled_disk_templates)
78 def CheckPrereq(self):
79 """Check prerequisites.
81 This checks that the given group name is not an existing node group
86 existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
87 except errors.OpPrereqError:
90 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
91 " node group (UUID: %s)" %
92 (self.op.group_name, existing_uuid),
96 utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
99 self.new_hv_state = MergeAndVerifyHvState(self.op.hv_state, None)
101 self.new_hv_state = None
103 if self.op.disk_state:
104 self.new_disk_state = MergeAndVerifyDiskState(self.op.disk_state, None)
106 self.new_disk_state = None
108 if self.op.diskparams:
109 for templ in constants.DISK_TEMPLATES:
110 if templ in self.op.diskparams:
111 utils.ForceDictType(self.op.diskparams[templ],
112 constants.DISK_DT_TYPES)
113 self.new_diskparams = self.op.diskparams
115 utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
116 except errors.OpPrereqError, err:
117 raise errors.OpPrereqError("While verify diskparams options: %s" % err,
120 self.new_diskparams = {}
124 def BuildHooksEnv(self):
129 "GROUP_NAME": self.op.group_name,
132 def BuildHooksNodes(self):
133 """Build hooks nodes.
136 mn = self.cfg.GetMasterNode()
139 def Exec(self, feedback_fn):
140 """Add the node group to the cluster.
143 group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
144 uuid=self.group_uuid,
145 alloc_policy=self.op.alloc_policy,
146 ndparams=self.op.ndparams,
147 diskparams=self.new_diskparams,
148 ipolicy=self.op.ipolicy,
149 hv_state_static=self.new_hv_state,
150 disk_state_static=self.new_disk_state)
152 self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
153 del self.remove_locks[locking.LEVEL_NODEGROUP]
156 class LUGroupAssignNodes(NoHooksLU):
157 """Logical unit for assigning nodes to groups.
162 def ExpandNames(self):
163 # These raise errors.OpPrereqError on their own:
164 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
165 (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
167 # We want to lock all the affected nodes and groups. We have readily
168 # available the list of nodes, and the *destination* group. To gather the
169 # list of "source" groups, we need to fetch node information later on.
170 self.needed_locks = {
171 locking.LEVEL_NODEGROUP: set([self.group_uuid]),
172 locking.LEVEL_NODE: self.op.node_uuids,
175 def DeclareLocks(self, level):
176 if level == locking.LEVEL_NODEGROUP:
177 assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
179 # Try to get all affected nodes' groups without having the group or node
180 # lock yet. Needs verification later in the code flow.
181 groups = self.cfg.GetNodeGroupsFromNodes(self.op.node_uuids)
183 self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
185 def CheckPrereq(self):
186 """Check prerequisites.
189 assert self.needed_locks[locking.LEVEL_NODEGROUP]
190 assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
191 frozenset(self.op.node_uuids))
193 expected_locks = (set([self.group_uuid]) |
194 self.cfg.GetNodeGroupsFromNodes(self.op.node_uuids))
195 actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
196 if actual_locks != expected_locks:
197 raise errors.OpExecError("Nodes changed groups since locks were acquired,"
198 " current groups are '%s', used to be '%s'" %
199 (utils.CommaJoin(expected_locks),
200 utils.CommaJoin(actual_locks)))
202 self.node_data = self.cfg.GetAllNodesInfo()
203 self.group = self.cfg.GetNodeGroup(self.group_uuid)
204 instance_data = self.cfg.GetAllInstancesInfo()
206 if self.group is None:
207 raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
208 (self.op.group_name, self.group_uuid))
210 (new_splits, previous_splits) = \
211 self.CheckAssignmentForSplitInstances([(uuid, self.group_uuid)
212 for uuid in self.op.node_uuids],
213 self.node_data, instance_data)
216 fmt_new_splits = utils.CommaJoin(utils.NiceSort(
217 self.cfg.GetInstanceNames(new_splits)))
219 if not self.op.force:
220 raise errors.OpExecError("The following instances get split by this"
221 " change and --force was not given: %s" %
224 self.LogWarning("This operation will split the following instances: %s",
228 self.LogWarning("In addition, these already-split instances continue"
229 " to be split across groups: %s",
230 utils.CommaJoin(utils.NiceSort(
231 self.cfg.GetInstanceNames(previous_splits))))
233 def Exec(self, feedback_fn):
234 """Assign nodes to a new group.
237 mods = [(node_uuid, self.group_uuid) for node_uuid in self.op.node_uuids]
239 self.cfg.AssignGroupNodes(mods)
242 def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
243 """Check for split instances after a node assignment.
245 This method considers a series of node assignments as an atomic operation,
246 and returns information about split instances after applying the set of
249 In particular, it returns information about newly split instances, and
250 instances that were already split, and remain so after the change.
252 Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
255 @type changes: list of (node_uuid, new_group_uuid) pairs.
256 @param changes: list of node assignments to consider.
257 @param node_data: a dict with data for all nodes
258 @param instance_data: a dict with all instances to consider
260 @return: a list of instances that were previously okay and result split as a
261 consequence of this change, and a list of instances that were previously
262 split and this change does not fix.
265 changed_nodes = dict((uuid, group) for uuid, group in changes
266 if node_data[uuid].group != group)
268 all_split_instances = set()
269 previously_split_instances = set()
271 for inst in instance_data.values():
272 if inst.disk_template not in constants.DTS_INT_MIRROR:
275 if len(set(node_data[node_uuid].group
276 for node_uuid in inst.all_nodes)) > 1:
277 previously_split_instances.add(inst.uuid)
279 if len(set(changed_nodes.get(node_uuid, node_data[node_uuid].group)
280 for node_uuid in inst.all_nodes)) > 1:
281 all_split_instances.add(inst.uuid)
283 return (list(all_split_instances - previously_split_instances),
284 list(previously_split_instances & all_split_instances))
287 class GroupQuery(QueryBase):
288 FIELDS = query.GROUP_FIELDS
290 def ExpandNames(self, lu):
293 self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
294 self._cluster = lu.cfg.GetClusterInfo()
295 name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
298 self.wanted = [name_to_uuid[name]
299 for name in utils.NiceSort(name_to_uuid.keys())]
301 # Accept names to be either names or UUIDs.
304 all_uuid = frozenset(self._all_groups.keys())
306 for name in self.names:
308 self.wanted.append(name)
309 elif name in name_to_uuid:
310 self.wanted.append(name_to_uuid[name])
315 raise errors.OpPrereqError("Some groups do not exist: %s" %
316 utils.CommaJoin(missing),
319 def DeclareLocks(self, lu, level):
322 def _GetQueryData(self, lu):
323 """Computes the list of node groups and their attributes.
326 do_nodes = query.GQ_NODE in self.requested_data
327 do_instances = query.GQ_INST in self.requested_data
329 group_to_nodes = None
330 group_to_instances = None
332 # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
333 # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
334 # latter GetAllInstancesInfo() is not enough, for we have to go through
335 # instance->node. Hence, we will need to process nodes even if we only need
336 # instance information.
337 if do_nodes or do_instances:
338 all_nodes = lu.cfg.GetAllNodesInfo()
339 group_to_nodes = dict((uuid, []) for uuid in self.wanted)
342 for node in all_nodes.values():
343 if node.group in group_to_nodes:
344 group_to_nodes[node.group].append(node.uuid)
345 node_to_group[node.uuid] = node.group
348 all_instances = lu.cfg.GetAllInstancesInfo()
349 group_to_instances = dict((uuid, []) for uuid in self.wanted)
351 for instance in all_instances.values():
352 node = instance.primary_node
353 if node in node_to_group:
354 group_to_instances[node_to_group[node]].append(instance.uuid)
357 # Do not pass on node information if it was not requested.
358 group_to_nodes = None
360 return query.GroupQueryData(self._cluster,
361 [self._all_groups[uuid]
362 for uuid in self.wanted],
363 group_to_nodes, group_to_instances,
364 query.GQ_DISKPARAMS in self.requested_data)
367 class LUGroupQuery(NoHooksLU):
368 """Logical unit for querying node groups.
373 def CheckArguments(self):
374 self.gq = GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
375 self.op.output_fields, False)
377 def ExpandNames(self):
378 self.gq.ExpandNames(self)
380 def DeclareLocks(self, level):
381 self.gq.DeclareLocks(self, level)
383 def Exec(self, feedback_fn):
384 return self.gq.OldStyleQuery(self)
387 class LUGroupSetParams(LogicalUnit):
388 """Modifies the parameters of a node group.
391 HPATH = "group-modify"
392 HTYPE = constants.HTYPE_GROUP
395 def CheckArguments(self):
399 self.op.alloc_policy,
405 if all_changes.count(None) == len(all_changes):
406 raise errors.OpPrereqError("Please pass at least one modification",
409 def ExpandNames(self):
410 # This raises errors.OpPrereqError on its own:
411 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
413 self.needed_locks = {
414 locking.LEVEL_INSTANCE: [],
415 locking.LEVEL_NODEGROUP: [self.group_uuid],
418 self.share_locks[locking.LEVEL_INSTANCE] = 1
420 def DeclareLocks(self, level):
421 if level == locking.LEVEL_INSTANCE:
422 assert not self.needed_locks[locking.LEVEL_INSTANCE]
424 # Lock instances optimistically, needs verification once group lock has
426 self.needed_locks[locking.LEVEL_INSTANCE] = \
427 self.cfg.GetInstanceNames(
428 self.cfg.GetNodeGroupInstances(self.group_uuid))
431 def _UpdateAndVerifyDiskParams(old, new):
432 """Updates and verifies disk parameters.
435 new_params = GetUpdatedParams(old, new)
436 utils.ForceDictType(new_params, constants.DISK_DT_TYPES)
439 def _CheckIpolicy(self, cluster, owned_instance_names):
440 """Sanity checks for the ipolicy.
442 @type cluster: C{objects.Cluster}
443 @param cluster: the cluster's configuration
444 @type owned_instance_names: list of string
445 @param owned_instance_names: list of instances
449 self.new_ipolicy = GetUpdatedIPolicy(self.group.ipolicy,
453 new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
454 CheckIpolicyVsDiskTemplates(new_ipolicy,
455 cluster.enabled_disk_templates)
456 instances = self.cfg.GetMultiInstanceInfoByName(owned_instance_names)
457 gmi = ganeti.masterd.instance
459 ComputeNewInstanceViolations(gmi.CalculateGroupIPolicy(cluster,
461 new_ipolicy, instances, self.cfg)
464 self.LogWarning("After the ipolicy change the following instances"
466 utils.CommaJoin(violations))
468 def CheckPrereq(self):
469 """Check prerequisites.
472 owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
474 # Check if locked instances are still correct
475 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
477 self.group = self.cfg.GetNodeGroup(self.group_uuid)
478 cluster = self.cfg.GetClusterInfo()
480 if self.group is None:
481 raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
482 (self.op.group_name, self.group_uuid))
485 new_ndparams = GetUpdatedParams(self.group.ndparams, self.op.ndparams)
486 utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
487 self.new_ndparams = new_ndparams
489 if self.op.diskparams:
490 diskparams = self.group.diskparams
491 uavdp = self._UpdateAndVerifyDiskParams
492 # For each disktemplate subdict update and verify the values
493 new_diskparams = dict((dt,
494 uavdp(diskparams.get(dt, {}),
495 self.op.diskparams[dt]))
496 for dt in constants.DISK_TEMPLATES
497 if dt in self.op.diskparams)
498 # As we've all subdicts of diskparams ready, lets merge the actual
499 # dict with all updated subdicts
500 self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
502 utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
503 except errors.OpPrereqError, err:
504 raise errors.OpPrereqError("While verify diskparams options: %s" % err,
508 self.new_hv_state = MergeAndVerifyHvState(self.op.hv_state,
509 self.group.hv_state_static)
511 if self.op.disk_state:
512 self.new_disk_state = \
513 MergeAndVerifyDiskState(self.op.disk_state,
514 self.group.disk_state_static)
516 self._CheckIpolicy(cluster, owned_instance_names)
518 def BuildHooksEnv(self):
523 "GROUP_NAME": self.op.group_name,
524 "NEW_ALLOC_POLICY": self.op.alloc_policy,
527 def BuildHooksNodes(self):
528 """Build hooks nodes.
531 mn = self.cfg.GetMasterNode()
534 def Exec(self, feedback_fn):
535 """Modifies the node group.
541 self.group.ndparams = self.new_ndparams
542 result.append(("ndparams", str(self.group.ndparams)))
544 if self.op.diskparams:
545 self.group.diskparams = self.new_diskparams
546 result.append(("diskparams", str(self.group.diskparams)))
548 if self.op.alloc_policy:
549 self.group.alloc_policy = self.op.alloc_policy
552 self.group.hv_state_static = self.new_hv_state
554 if self.op.disk_state:
555 self.group.disk_state_static = self.new_disk_state
558 self.group.ipolicy = self.new_ipolicy
560 self.cfg.Update(self.group, feedback_fn)
564 class LUGroupRemove(LogicalUnit):
565 HPATH = "group-remove"
566 HTYPE = constants.HTYPE_GROUP
569 def ExpandNames(self):
570 # This will raises errors.OpPrereqError on its own:
571 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
572 self.needed_locks = {
573 locking.LEVEL_NODEGROUP: [self.group_uuid],
576 def CheckPrereq(self):
577 """Check prerequisites.
579 This checks that the given group name exists as a node group, that is
580 empty (i.e., contains no nodes), and that is not the last group of the
584 # Verify that the group is empty.
585 group_nodes = [node.uuid
586 for node in self.cfg.GetAllNodesInfo().values()
587 if node.group == self.group_uuid]
590 raise errors.OpPrereqError("Group '%s' not empty, has the following"
593 utils.CommaJoin(utils.NiceSort(group_nodes))),
596 # Verify the cluster would not be left group-less.
597 if len(self.cfg.GetNodeGroupList()) == 1:
598 raise errors.OpPrereqError("Group '%s' is the only group, cannot be"
599 " removed" % self.op.group_name,
602 def BuildHooksEnv(self):
607 "GROUP_NAME": self.op.group_name,
610 def BuildHooksNodes(self):
611 """Build hooks nodes.
614 mn = self.cfg.GetMasterNode()
617 def Exec(self, feedback_fn):
618 """Remove the node group.
622 self.cfg.RemoveNodeGroup(self.group_uuid)
623 except errors.ConfigurationError:
624 raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
625 (self.op.group_name, self.group_uuid))
627 self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
630 class LUGroupRename(LogicalUnit):
631 HPATH = "group-rename"
632 HTYPE = constants.HTYPE_GROUP
635 def ExpandNames(self):
636 # This raises errors.OpPrereqError on its own:
637 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
639 self.needed_locks = {
640 locking.LEVEL_NODEGROUP: [self.group_uuid],
643 def CheckPrereq(self):
644 """Check prerequisites.
646 Ensures requested new name is not yet used.
650 new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
651 except errors.OpPrereqError:
654 raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
655 " node group (UUID: %s)" %
656 (self.op.new_name, new_name_uuid),
659 def BuildHooksEnv(self):
664 "OLD_NAME": self.op.group_name,
665 "NEW_NAME": self.op.new_name,
668 def BuildHooksNodes(self):
669 """Build hooks nodes.
672 mn = self.cfg.GetMasterNode()
674 all_nodes = self.cfg.GetAllNodesInfo()
675 all_nodes.pop(mn, None)
678 run_nodes.extend(node.uuid for node in all_nodes.values()
679 if node.group == self.group_uuid)
681 return (run_nodes, run_nodes)
683 def Exec(self, feedback_fn):
684 """Rename the node group.
687 group = self.cfg.GetNodeGroup(self.group_uuid)
690 raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
691 (self.op.group_name, self.group_uuid))
693 group.name = self.op.new_name
694 self.cfg.Update(group, feedback_fn)
696 return self.op.new_name
699 class LUGroupEvacuate(LogicalUnit):
700 HPATH = "group-evacuate"
701 HTYPE = constants.HTYPE_GROUP
704 def ExpandNames(self):
705 # This raises errors.OpPrereqError on its own:
706 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
708 if self.op.target_groups:
709 self.req_target_uuids = map(self.cfg.LookupNodeGroup,
710 self.op.target_groups)
712 self.req_target_uuids = []
714 if self.group_uuid in self.req_target_uuids:
715 raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
716 " as a target group (targets are %s)" %
718 utils.CommaJoin(self.req_target_uuids)),
721 self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
723 self.share_locks = ShareAll()
724 self.needed_locks = {
725 locking.LEVEL_INSTANCE: [],
726 locking.LEVEL_NODEGROUP: [],
727 locking.LEVEL_NODE: [],
730 def DeclareLocks(self, level):
731 if level == locking.LEVEL_INSTANCE:
732 assert not self.needed_locks[locking.LEVEL_INSTANCE]
734 # Lock instances optimistically, needs verification once node and group
735 # locks have been acquired
736 self.needed_locks[locking.LEVEL_INSTANCE] = \
737 self.cfg.GetInstanceNames(
738 self.cfg.GetNodeGroupInstances(self.group_uuid))
740 elif level == locking.LEVEL_NODEGROUP:
741 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
743 if self.req_target_uuids:
744 lock_groups = set([self.group_uuid] + self.req_target_uuids)
746 # Lock all groups used by instances optimistically; this requires going
747 # via the node before it's locked, requiring verification later on
748 lock_groups.update(group_uuid
750 self.owned_locks(locking.LEVEL_INSTANCE)
752 self.cfg.GetInstanceNodeGroups(
753 self.cfg.GetInstanceInfoByName(instance_name)
756 # No target groups, need to lock all of them
757 lock_groups = locking.ALL_SET
759 self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
761 elif level == locking.LEVEL_NODE:
762 # This will only lock the nodes in the group to be evacuated which
763 # contain actual instances
764 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
765 self._LockInstancesNodes()
767 # Lock all nodes in group to be evacuated and target groups
768 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
769 assert self.group_uuid in owned_groups
770 member_node_uuids = [node_uuid
771 for group in owned_groups
773 self.cfg.GetNodeGroup(group).members]
774 self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
776 def CheckPrereq(self):
777 owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
778 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
779 owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
781 assert owned_groups.issuperset(self.req_target_uuids)
782 assert self.group_uuid in owned_groups
784 # Check if locked instances are still correct
785 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
787 # Get instance information
789 dict(self.cfg.GetMultiInstanceInfoByName(owned_instance_names))
791 # Check if node groups for locked instances are still correct
792 CheckInstancesNodeGroups(self.cfg, self.instances,
793 owned_groups, owned_node_uuids, self.group_uuid)
795 if self.req_target_uuids:
796 # User requested specific target groups
797 self.target_uuids = self.req_target_uuids
799 # All groups except the one to be evacuated are potential targets
800 self.target_uuids = [group_uuid for group_uuid in owned_groups
801 if group_uuid != self.group_uuid]
803 if not self.target_uuids:
804 raise errors.OpPrereqError("There are no possible target groups",
807 def BuildHooksEnv(self):
812 "GROUP_NAME": self.op.group_name,
813 "TARGET_GROUPS": " ".join(self.target_uuids),
816 def BuildHooksNodes(self):
817 """Build hooks nodes.
820 mn = self.cfg.GetMasterNode()
822 assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
824 run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
826 return (run_nodes, run_nodes)
828 def Exec(self, feedback_fn):
829 inst_names = list(self.owned_locks(locking.LEVEL_INSTANCE))
831 assert self.group_uuid not in self.target_uuids
833 req = iallocator.IAReqGroupChange(instances=inst_names,
834 target_groups=self.target_uuids)
835 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
837 ial.Run(self.op.iallocator)
840 raise errors.OpPrereqError("Can't compute group evacuation using"
841 " iallocator '%s': %s" %
842 (self.op.iallocator, ial.info),
845 jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
847 self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
848 len(jobs), self.op.group_name)
850 return ResultWithJobs(jobs)
853 class LUGroupVerifyDisks(NoHooksLU):
854 """Verifies the status of all disks in a node group.
859 def ExpandNames(self):
860 # Raises errors.OpPrereqError on its own if group can't be found
861 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
863 self.share_locks = ShareAll()
864 self.needed_locks = {
865 locking.LEVEL_INSTANCE: [],
866 locking.LEVEL_NODEGROUP: [],
867 locking.LEVEL_NODE: [],
869 # This opcode is acquires all node locks in a group. LUClusterVerifyDisks
870 # starts one instance of this opcode for every group, which means all
871 # nodes will be locked for a short amount of time, so it's better to
872 # acquire the node allocation lock as well.
873 locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
876 def DeclareLocks(self, level):
877 if level == locking.LEVEL_INSTANCE:
878 assert not self.needed_locks[locking.LEVEL_INSTANCE]
880 # Lock instances optimistically, needs verification once node and group
881 # locks have been acquired
882 self.needed_locks[locking.LEVEL_INSTANCE] = \
883 self.cfg.GetInstanceNames(
884 self.cfg.GetNodeGroupInstances(self.group_uuid))
886 elif level == locking.LEVEL_NODEGROUP:
887 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
889 self.needed_locks[locking.LEVEL_NODEGROUP] = \
890 set([self.group_uuid] +
891 # Lock all groups used by instances optimistically; this requires
892 # going via the node before it's locked, requiring verification
895 for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
897 self.cfg.GetInstanceNodeGroups(
898 self.cfg.GetInstanceInfoByName(instance_name).uuid)])
900 elif level == locking.LEVEL_NODE:
901 # This will only lock the nodes in the group to be verified which contain
903 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
904 self._LockInstancesNodes()
906 # Lock all nodes in group to be verified
907 assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
908 member_node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
909 self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
911 def CheckPrereq(self):
912 owned_inst_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
913 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
914 owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
916 assert self.group_uuid in owned_groups
918 # Check if locked instances are still correct
919 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_inst_names)
921 # Get instance information
922 self.instances = dict(self.cfg.GetMultiInstanceInfoByName(owned_inst_names))
924 # Check if node groups for locked instances are still correct
925 CheckInstancesNodeGroups(self.cfg, self.instances,
926 owned_groups, owned_node_uuids, self.group_uuid)
928 def _VerifyInstanceLvs(self, node_errors, offline_disk_instance_names,
930 node_lv_to_inst = MapInstanceLvsToNodes(
931 [inst for inst in self.instances.values() if inst.disks_active])
933 node_uuids = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
934 set(self.cfg.GetVmCapableNodeList()))
936 node_lvs = self.rpc.call_lv_list(node_uuids, [])
938 for (node_uuid, node_res) in node_lvs.items():
942 msg = node_res.fail_msg
944 logging.warning("Error enumerating LVs on node %s: %s",
945 self.cfg.GetNodeName(node_uuid), msg)
946 node_errors[node_uuid] = msg
949 for lv_name, (_, _, lv_online) in node_res.payload.items():
950 inst = node_lv_to_inst.pop((node_uuid, lv_name), None)
951 if not lv_online and inst is not None:
952 offline_disk_instance_names.add(inst.name)
954 # any leftover items in nv_dict are missing LVs, let's arrange the data
956 for key, inst in node_lv_to_inst.iteritems():
957 missing_disks.setdefault(inst.name, []).append(list(key))
959 def _VerifyDrbdStates(self, node_errors, offline_disk_instance_names):
961 for inst in self.instances.values():
962 if not inst.disks_active or inst.disk_template != constants.DT_DRBD8:
965 for node_uuid in itertools.chain([inst.primary_node],
966 inst.secondary_nodes):
967 node_to_inst.setdefault(node_uuid, []).append(inst)
969 nodes_ip = dict((uuid, node.secondary_ip) for (uuid, node)
970 in self.cfg.GetMultiNodeInfo(node_to_inst.keys()))
971 for (node_uuid, insts) in node_to_inst.items():
972 node_disks = [(inst.disks, inst) for inst in insts]
973 node_res = self.rpc.call_drbd_needs_activation(node_uuid, nodes_ip,
975 msg = node_res.fail_msg
977 logging.warning("Error getting DRBD status on node %s: %s",
978 self.cfg.GetNodeName(node_uuid), msg)
979 node_errors[node_uuid] = msg
982 faulty_disk_uuids = set(node_res.payload)
983 for inst in self.instances.values():
984 inst_disk_uuids = set([disk.uuid for disk in inst.disks])
985 if inst_disk_uuids.intersection(faulty_disk_uuids):
986 offline_disk_instance_names.add(inst.name)
988 def Exec(self, feedback_fn):
989 """Verify integrity of cluster disks.
991 @rtype: tuple of three items
992 @return: a tuple of (dict of node-to-node_error, list of instances
993 which need activate-disks, dict of instance: (node, volume) for
998 offline_disk_instance_names = set()
1001 self._VerifyInstanceLvs(node_errors, offline_disk_instance_names,
1003 self._VerifyDrbdStates(node_errors, offline_disk_instance_names)
1005 return (node_errors, list(offline_disk_instance_names), missing_disks)