diff --git a/htools/Ganeti/HTools/Cluster.hs b/htools/Ganeti/HTools/Cluster.hs index 37ad970aebcfe3f4dcfc278a28070d987d51c299..9e946936658352701d4f56753686cb069f62a65f 100644 --- a/htools/Ganeti/HTools/Cluster.hs +++ b/htools/Ganeti/HTools/Cluster.hs @@ -922,49 +922,48 @@ nodeEvacInstance nl il ChangeSecondary ops = iMoveToJob nl' il' idx (ReplaceSecondary ndx) return (nl', il', ops) +-- The algorithm for ChangeAll is as follows: +-- +-- * generate all (primary, secondary) node pairs for the target groups +-- * for each pair, execute the needed moves (r:s, f, r:s) and compute +-- the final node list state and group score +-- * select the best choice via a foldl that uses the same Either +-- String solution as the ChangeSecondary mode nodeEvacInstance nl il ChangeAll inst@(Instance.Instance {Instance.diskTemplate = DTDrbd8}) gdx avail_nodes = do - let primary = Container.find (Instance.pNode inst) nl - idx = Instance.idx inst - no_nodes = Left "no nodes available" - -- if the primary is offline, then we first failover - (nl1, inst1, ops1) <- - if Node.offline primary - then do - (nl', inst', _, _) <- - annotateResult "Failing over to the secondary" $ - opToResult $ applyMove nl inst Failover - return (nl', inst', [Failover]) - else return (nl, inst, []) - -- we now need to execute a replace secondary to the future - -- primary node - (nl2, inst2, _, new_pdx) <- annotateResult "Searching for a new primary" $ - eitherToResult $ - foldl' (evacDrbdSecondaryInner nl1 inst1 gdx) - no_nodes avail_nodes - let ops2 = ReplaceSecondary new_pdx:ops1 - -- since we chose the new primary, we remove it from the list of - -- available nodes - let avail_nodes_sec = new_pdx `delete` avail_nodes - -- we now execute another failover, the primary stays fixed now - (nl3, inst3, _, _) <- annotateResult "Failing over to new primary" $ - opToResult $ applyMove nl2 inst2 Failover - let ops3 = Failover:ops2 - -- and finally another replace secondary, to the final secondary - (nl4, inst4, _, new_sdx) <- - annotateResult "Searching for a new secondary" $ + let no_nodes = Left "no nodes available" + node_pairs = [(p,s) | p <- avail_nodes, s <- avail_nodes, p /= s] + (nl', il', ops, _) <- + annotateResult "Can't find any good nodes for relocation" $ eitherToResult $ - foldl' (evacDrbdSecondaryInner nl3 inst3 gdx) no_nodes avail_nodes_sec - let ops4 = ReplaceSecondary new_sdx:ops3 - il' = Container.add idx inst4 il - ops = concatMap (iMoveToJob nl4 il' idx) $ reverse ops4 - return (nl4, il', ops) + foldl' + (\accu nodes -> case evacDrbdAllInner nl il inst gdx nodes of + Bad msg -> + case accu of + Right _ -> accu + -- we don't need more details (which + -- nodes, etc.) as we only selected + -- this group if we can allocate on + -- it, hence failures will not + -- propagate out of this fold loop + Left _ -> Left $ "Allocation failed: " ++ msg + Ok result@(_, _, _, new_cv) -> + let new_accu = Right result in + case accu of + Left _ -> new_accu + Right (_, _, _, old_cv) -> + if old_cv < new_cv + then accu + else new_accu + ) no_nodes node_pairs + + return (nl', il', ops) -- | Inner fold function for changing secondary of a DRBD instance. -- --- The "running" solution is either a @Left String@, which means we +-- The running solution is either a @Left String@, which means we -- don't have yet a working solution, or a @Right (...)@, which -- represents a valid solution; it holds the modified node list, the -- modified instance (after evacuation), the score of that solution, @@ -1003,6 +1002,66 @@ evacDrbdSecondaryInner nl inst gdx accu ndx = then accu else new_accu +-- | Compute result of changing all nodes of a DRBD instance. +-- +-- Given the target primary and secondary node (which might be in a +-- different group or not), this function will 'execute' all the +-- required steps and assuming all operations succceed, will return +-- the modified node and instance lists, the opcodes needed for this +-- and the new group score. +evacDrbdAllInner :: Node.List -- ^ Cluster node list + -> Instance.List -- ^ Cluster instance list + -> Instance.Instance -- ^ The instance to be moved + -> Gdx -- ^ The target group index + -- (which can differ from the + -- current group of the + -- instance) + -> (Ndx, Ndx) -- ^ Tuple of new + -- primary\/secondary nodes + -> Result (Node.List, Instance.List, [OpCodes.OpCode], Score) +evacDrbdAllInner nl il inst gdx (t_pdx, t_sdx) = + do + let primary = Container.find (Instance.pNode inst) nl + idx = Instance.idx inst + -- if the primary is offline, then we first failover + (nl1, inst1, ops1) <- + if Node.offline primary + then do + (nl', inst', _, _) <- + annotateResult "Failing over to the secondary" $ + opToResult $ applyMove nl inst Failover + return (nl', inst', [Failover]) + else return (nl, inst, []) + let (o1, o2, o3) = (ReplaceSecondary t_pdx, + Failover, + ReplaceSecondary t_sdx) + -- we now need to execute a replace secondary to the future + -- primary node + (nl2, inst2, _, _) <- + annotateResult "Changing secondary to new primary" $ + opToResult $ + applyMove nl1 inst1 o1 + let ops2 = o1:ops1 + -- we now execute another failover, the primary stays fixed now + (nl3, inst3, _, _) <- annotateResult "Failing over to new primary" $ + opToResult $ applyMove nl2 inst2 o2 + let ops3 = o2:ops2 + -- and finally another replace secondary, to the final secondary + (nl4, inst4, _, _) <- + annotateResult "Changing secondary to final secondary" $ + opToResult $ + applyMove nl3 inst3 o3 + let ops4 = o3:ops3 + il' = Container.add idx inst4 il + ops = concatMap (iMoveToJob nl4 il' idx) $ reverse ops4 + let nodes = Container.elems nl4 + -- The fromJust below is ugly (it can fail nastily), but + -- at this point we should have any internal mismatches, + -- and adding a monad here would be quite involved + grpnodes = fromJust (gdx `lookup` Node.computeGroups nodes) + new_cv = compCVNodes grpnodes + return (nl4, il', ops, new_cv) + -- | Computes the nodes in a given group which are available for -- allocation. availableGroupNodes :: [(Gdx, [Ndx])] -- ^ Group index/node index assoc list