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)
457 dict(self.cfg.GetMultiInstanceInfoByName(owned_instance_names))
458 gmi = ganeti.masterd.instance
460 ComputeNewInstanceViolations(gmi.CalculateGroupIPolicy(cluster,
462 new_ipolicy, instances.values(),
466 self.LogWarning("After the ipolicy change the following instances"
468 utils.CommaJoin(violations))
470 def CheckPrereq(self):
471 """Check prerequisites.
474 owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
476 # Check if locked instances are still correct
477 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
479 self.group = self.cfg.GetNodeGroup(self.group_uuid)
480 cluster = self.cfg.GetClusterInfo()
482 if self.group is None:
483 raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
484 (self.op.group_name, self.group_uuid))
487 new_ndparams = GetUpdatedParams(self.group.ndparams, self.op.ndparams)
488 utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
489 self.new_ndparams = new_ndparams
491 if self.op.diskparams:
492 diskparams = self.group.diskparams
493 uavdp = self._UpdateAndVerifyDiskParams
494 # For each disktemplate subdict update and verify the values
495 new_diskparams = dict((dt,
496 uavdp(diskparams.get(dt, {}),
497 self.op.diskparams[dt]))
498 for dt in constants.DISK_TEMPLATES
499 if dt in self.op.diskparams)
500 # As we've all subdicts of diskparams ready, lets merge the actual
501 # dict with all updated subdicts
502 self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
504 utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
505 except errors.OpPrereqError, err:
506 raise errors.OpPrereqError("While verify diskparams options: %s" % err,
510 self.new_hv_state = MergeAndVerifyHvState(self.op.hv_state,
511 self.group.hv_state_static)
513 if self.op.disk_state:
514 self.new_disk_state = \
515 MergeAndVerifyDiskState(self.op.disk_state,
516 self.group.disk_state_static)
518 self._CheckIpolicy(cluster, owned_instance_names)
520 def BuildHooksEnv(self):
525 "GROUP_NAME": self.op.group_name,
526 "NEW_ALLOC_POLICY": self.op.alloc_policy,
529 def BuildHooksNodes(self):
530 """Build hooks nodes.
533 mn = self.cfg.GetMasterNode()
536 def Exec(self, feedback_fn):
537 """Modifies the node group.
543 self.group.ndparams = self.new_ndparams
544 result.append(("ndparams", str(self.group.ndparams)))
546 if self.op.diskparams:
547 self.group.diskparams = self.new_diskparams
548 result.append(("diskparams", str(self.group.diskparams)))
550 if self.op.alloc_policy:
551 self.group.alloc_policy = self.op.alloc_policy
554 self.group.hv_state_static = self.new_hv_state
556 if self.op.disk_state:
557 self.group.disk_state_static = self.new_disk_state
560 self.group.ipolicy = self.new_ipolicy
562 self.cfg.Update(self.group, feedback_fn)
566 class LUGroupRemove(LogicalUnit):
567 HPATH = "group-remove"
568 HTYPE = constants.HTYPE_GROUP
571 def ExpandNames(self):
572 # This will raises errors.OpPrereqError on its own:
573 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
574 self.needed_locks = {
575 locking.LEVEL_NODEGROUP: [self.group_uuid],
578 def CheckPrereq(self):
579 """Check prerequisites.
581 This checks that the given group name exists as a node group, that is
582 empty (i.e., contains no nodes), and that is not the last group of the
586 # Verify that the group is empty.
587 group_nodes = [node.uuid
588 for node in self.cfg.GetAllNodesInfo().values()
589 if node.group == self.group_uuid]
592 raise errors.OpPrereqError("Group '%s' not empty, has the following"
595 utils.CommaJoin(utils.NiceSort(group_nodes))),
598 # Verify the cluster would not be left group-less.
599 if len(self.cfg.GetNodeGroupList()) == 1:
600 raise errors.OpPrereqError("Group '%s' is the only group, cannot be"
601 " removed" % self.op.group_name,
604 def BuildHooksEnv(self):
609 "GROUP_NAME": self.op.group_name,
612 def BuildHooksNodes(self):
613 """Build hooks nodes.
616 mn = self.cfg.GetMasterNode()
619 def Exec(self, feedback_fn):
620 """Remove the node group.
624 self.cfg.RemoveNodeGroup(self.group_uuid)
625 except errors.ConfigurationError:
626 raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
627 (self.op.group_name, self.group_uuid))
629 self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
632 class LUGroupRename(LogicalUnit):
633 HPATH = "group-rename"
634 HTYPE = constants.HTYPE_GROUP
637 def ExpandNames(self):
638 # This raises errors.OpPrereqError on its own:
639 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
641 self.needed_locks = {
642 locking.LEVEL_NODEGROUP: [self.group_uuid],
645 def CheckPrereq(self):
646 """Check prerequisites.
648 Ensures requested new name is not yet used.
652 new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
653 except errors.OpPrereqError:
656 raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
657 " node group (UUID: %s)" %
658 (self.op.new_name, new_name_uuid),
661 def BuildHooksEnv(self):
666 "OLD_NAME": self.op.group_name,
667 "NEW_NAME": self.op.new_name,
670 def BuildHooksNodes(self):
671 """Build hooks nodes.
674 mn = self.cfg.GetMasterNode()
676 all_nodes = self.cfg.GetAllNodesInfo()
677 all_nodes.pop(mn, None)
680 run_nodes.extend(node.uuid for node in all_nodes.values()
681 if node.group == self.group_uuid)
683 return (run_nodes, run_nodes)
685 def Exec(self, feedback_fn):
686 """Rename the node group.
689 group = self.cfg.GetNodeGroup(self.group_uuid)
692 raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
693 (self.op.group_name, self.group_uuid))
695 group.name = self.op.new_name
696 self.cfg.Update(group, feedback_fn)
698 return self.op.new_name
701 class LUGroupEvacuate(LogicalUnit):
702 HPATH = "group-evacuate"
703 HTYPE = constants.HTYPE_GROUP
706 def ExpandNames(self):
707 # This raises errors.OpPrereqError on its own:
708 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
710 if self.op.target_groups:
711 self.req_target_uuids = map(self.cfg.LookupNodeGroup,
712 self.op.target_groups)
714 self.req_target_uuids = []
716 if self.group_uuid in self.req_target_uuids:
717 raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
718 " as a target group (targets are %s)" %
720 utils.CommaJoin(self.req_target_uuids)),
723 self.op.iallocator = GetDefaultIAllocator(self.cfg, self.op.iallocator)
725 self.share_locks = ShareAll()
726 self.needed_locks = {
727 locking.LEVEL_INSTANCE: [],
728 locking.LEVEL_NODEGROUP: [],
729 locking.LEVEL_NODE: [],
732 def DeclareLocks(self, level):
733 if level == locking.LEVEL_INSTANCE:
734 assert not self.needed_locks[locking.LEVEL_INSTANCE]
736 # Lock instances optimistically, needs verification once node and group
737 # locks have been acquired
738 self.needed_locks[locking.LEVEL_INSTANCE] = \
739 self.cfg.GetInstanceNames(
740 self.cfg.GetNodeGroupInstances(self.group_uuid))
742 elif level == locking.LEVEL_NODEGROUP:
743 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
745 if self.req_target_uuids:
746 lock_groups = set([self.group_uuid] + self.req_target_uuids)
748 # Lock all groups used by instances optimistically; this requires going
749 # via the node before it's locked, requiring verification later on
750 lock_groups.update(group_uuid
752 self.owned_locks(locking.LEVEL_INSTANCE)
754 self.cfg.GetInstanceNodeGroups(
755 self.cfg.GetInstanceInfoByName(instance_name)
758 # No target groups, need to lock all of them
759 lock_groups = locking.ALL_SET
761 self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
763 elif level == locking.LEVEL_NODE:
764 # This will only lock the nodes in the group to be evacuated which
765 # contain actual instances
766 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
767 self._LockInstancesNodes()
769 # Lock all nodes in group to be evacuated and target groups
770 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
771 assert self.group_uuid in owned_groups
772 member_node_uuids = [node_uuid
773 for group in owned_groups
775 self.cfg.GetNodeGroup(group).members]
776 self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
778 def CheckPrereq(self):
779 owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
780 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
781 owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
783 assert owned_groups.issuperset(self.req_target_uuids)
784 assert self.group_uuid in owned_groups
786 # Check if locked instances are still correct
787 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instance_names)
789 # Get instance information
791 dict(self.cfg.GetMultiInstanceInfoByName(owned_instance_names))
793 # Check if node groups for locked instances are still correct
794 CheckInstancesNodeGroups(self.cfg, self.instances,
795 owned_groups, owned_node_uuids, self.group_uuid)
797 if self.req_target_uuids:
798 # User requested specific target groups
799 self.target_uuids = self.req_target_uuids
801 # All groups except the one to be evacuated are potential targets
802 self.target_uuids = [group_uuid for group_uuid in owned_groups
803 if group_uuid != self.group_uuid]
805 if not self.target_uuids:
806 raise errors.OpPrereqError("There are no possible target groups",
809 def BuildHooksEnv(self):
814 "GROUP_NAME": self.op.group_name,
815 "TARGET_GROUPS": " ".join(self.target_uuids),
818 def BuildHooksNodes(self):
819 """Build hooks nodes.
822 mn = self.cfg.GetMasterNode()
824 assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
826 run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
828 return (run_nodes, run_nodes)
830 def Exec(self, feedback_fn):
831 inst_names = list(self.owned_locks(locking.LEVEL_INSTANCE))
833 assert self.group_uuid not in self.target_uuids
835 req = iallocator.IAReqGroupChange(instances=inst_names,
836 target_groups=self.target_uuids)
837 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
839 ial.Run(self.op.iallocator)
842 raise errors.OpPrereqError("Can't compute group evacuation using"
843 " iallocator '%s': %s" %
844 (self.op.iallocator, ial.info),
847 jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
849 self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
850 len(jobs), self.op.group_name)
852 return ResultWithJobs(jobs)
855 class LUGroupVerifyDisks(NoHooksLU):
856 """Verifies the status of all disks in a node group.
861 def ExpandNames(self):
862 # Raises errors.OpPrereqError on its own if group can't be found
863 self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
865 self.share_locks = ShareAll()
866 self.needed_locks = {
867 locking.LEVEL_INSTANCE: [],
868 locking.LEVEL_NODEGROUP: [],
869 locking.LEVEL_NODE: [],
871 # This opcode is acquires all node locks in a group. LUClusterVerifyDisks
872 # starts one instance of this opcode for every group, which means all
873 # nodes will be locked for a short amount of time, so it's better to
874 # acquire the node allocation lock as well.
875 locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
878 def DeclareLocks(self, level):
879 if level == locking.LEVEL_INSTANCE:
880 assert not self.needed_locks[locking.LEVEL_INSTANCE]
882 # Lock instances optimistically, needs verification once node and group
883 # locks have been acquired
884 self.needed_locks[locking.LEVEL_INSTANCE] = \
885 self.cfg.GetInstanceNames(
886 self.cfg.GetNodeGroupInstances(self.group_uuid))
888 elif level == locking.LEVEL_NODEGROUP:
889 assert not self.needed_locks[locking.LEVEL_NODEGROUP]
891 self.needed_locks[locking.LEVEL_NODEGROUP] = \
892 set([self.group_uuid] +
893 # Lock all groups used by instances optimistically; this requires
894 # going via the node before it's locked, requiring verification
897 for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
899 self.cfg.GetInstanceNodeGroups(
900 self.cfg.GetInstanceInfoByName(instance_name).uuid)])
902 elif level == locking.LEVEL_NODE:
903 # This will only lock the nodes in the group to be verified which contain
905 self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
906 self._LockInstancesNodes()
908 # Lock all nodes in group to be verified
909 assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
910 member_node_uuids = self.cfg.GetNodeGroup(self.group_uuid).members
911 self.needed_locks[locking.LEVEL_NODE].extend(member_node_uuids)
913 def CheckPrereq(self):
914 owned_inst_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
915 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
916 owned_node_uuids = frozenset(self.owned_locks(locking.LEVEL_NODE))
918 assert self.group_uuid in owned_groups
920 # Check if locked instances are still correct
921 CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_inst_names)
923 # Get instance information
924 self.instances = dict(self.cfg.GetMultiInstanceInfoByName(owned_inst_names))
926 # Check if node groups for locked instances are still correct
927 CheckInstancesNodeGroups(self.cfg, self.instances,
928 owned_groups, owned_node_uuids, self.group_uuid)
930 def _VerifyInstanceLvs(self, node_errors, offline_disk_instance_names,
932 node_lv_to_inst = MapInstanceLvsToNodes(
933 [inst for inst in self.instances.values() if inst.disks_active])
935 node_uuids = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
936 set(self.cfg.GetVmCapableNodeList()))
938 node_lvs = self.rpc.call_lv_list(node_uuids, [])
940 for (node_uuid, node_res) in node_lvs.items():
944 msg = node_res.fail_msg
946 logging.warning("Error enumerating LVs on node %s: %s",
947 self.cfg.GetNodeName(node_uuid), msg)
948 node_errors[node_uuid] = msg
951 for lv_name, (_, _, lv_online) in node_res.payload.items():
952 inst = node_lv_to_inst.pop((node_uuid, lv_name), None)
953 if not lv_online and inst is not None:
954 offline_disk_instance_names.add(inst.name)
956 # any leftover items in nv_dict are missing LVs, let's arrange the data
958 for key, inst in node_lv_to_inst.iteritems():
959 missing_disks.setdefault(inst.name, []).append(list(key))
961 def _VerifyDrbdStates(self, node_errors, offline_disk_instance_names):
963 for inst in self.instances.values():
964 if not inst.disks_active or inst.disk_template != constants.DT_DRBD8:
967 for node_uuid in itertools.chain([inst.primary_node],
968 inst.secondary_nodes):
969 node_to_inst.setdefault(node_uuid, []).append(inst)
971 nodes_ip = dict((uuid, node.secondary_ip) for (uuid, node)
972 in self.cfg.GetMultiNodeInfo(node_to_inst.keys()))
973 for (node_uuid, insts) in node_to_inst.items():
974 node_disks = [(inst.disks, inst) for inst in insts]
975 node_res = self.rpc.call_drbd_needs_activation(node_uuid, nodes_ip,
977 msg = node_res.fail_msg
979 logging.warning("Error getting DRBD status on node %s: %s",
980 self.cfg.GetNodeName(node_uuid), msg)
981 node_errors[node_uuid] = msg
984 faulty_disk_uuids = set(node_res.payload)
985 for inst in self.instances.values():
986 inst_disk_uuids = set([disk.uuid for disk in inst.disks])
987 if inst_disk_uuids.intersection(faulty_disk_uuids):
988 offline_disk_instance_names.add(inst.name)
990 def Exec(self, feedback_fn):
991 """Verify integrity of cluster disks.
993 @rtype: tuple of three items
994 @return: a tuple of (dict of node-to-node_error, list of instances
995 which need activate-disks, dict of instance: (node, volume) for
1000 offline_disk_instance_names = set()
1003 self._VerifyInstanceLvs(node_errors, offline_disk_instance_names,
1005 self._VerifyDrbdStates(node_errors, offline_disk_instance_names)
1007 return (node_errors, list(offline_disk_instance_names), missing_disks)