Revision 9188aeef Ganeti/HTools/Cluster.hs
b/Ganeti/HTools/Cluster.hs | ||
---|---|---|
47 | 47 |
import Ganeti.HTools.Types |
48 | 48 |
import Ganeti.HTools.Utils |
49 | 49 |
|
50 |
-- | A separate name for the cluster score type |
|
50 |
-- * Types |
|
51 |
|
|
52 |
-- | A separate name for the cluster score type. |
|
51 | 53 |
type Score = Double |
52 | 54 |
|
53 | 55 |
-- | The description of an instance placement. |
54 | 56 |
type Placement = (Idx, Ndx, Ndx, Score) |
55 | 57 |
|
56 |
{- | A cluster solution described as the solution delta and the list |
|
57 |
of placements. |
|
58 |
|
|
59 |
-} |
|
58 |
-- | A cluster solution described as the solution delta and the list |
|
59 |
-- of placements. |
|
60 | 60 |
data Solution = Solution Int [Placement] |
61 | 61 |
deriving (Eq, Ord, Show) |
62 | 62 |
|
63 |
-- | Returns the delta of a solution or -1 for Nothing |
|
64 |
solutionDelta :: Maybe Solution -> Int |
|
65 |
solutionDelta sol = case sol of |
|
66 |
Just (Solution d _) -> d |
|
67 |
_ -> -1 |
|
68 |
|
|
69 | 63 |
-- | A removal set. |
70 | 64 |
data Removal = Removal Node.List [Instance.Instance] |
71 | 65 |
|
... | ... | |
81 | 75 |
data Table = Table Node.List Instance.List Score [Placement] |
82 | 76 |
deriving (Show) |
83 | 77 |
|
84 |
-- General functions |
|
78 |
-- * Utility functions |
|
79 |
|
|
80 |
-- | Returns the delta of a solution or -1 for Nothing. |
|
81 |
solutionDelta :: Maybe Solution -> Int |
|
82 |
solutionDelta sol = case sol of |
|
83 |
Just (Solution d _) -> d |
|
84 |
_ -> -1 |
|
85 | 85 |
|
86 | 86 |
-- | Cap the removal list if needed. |
87 | 87 |
capRemovals :: [a] -> Int -> [a] |
... | ... | |
99 | 99 |
verifyN1 :: [Node.Node] -> [Node.Node] |
100 | 100 |
verifyN1 nl = filter Node.failN1 nl |
101 | 101 |
|
102 |
{-| Add an instance and return the new node and instance maps. -} |
|
102 |
{-| Computes the pair of bad nodes and instances. |
|
103 |
|
|
104 |
The bad node list is computed via a simple 'verifyN1' check, and the |
|
105 |
bad instance list is the list of primary and secondary instances of |
|
106 |
those nodes. |
|
107 |
|
|
108 |
-} |
|
109 |
computeBadItems :: Node.List -> Instance.List -> |
|
110 |
([Node.Node], [Instance.Instance]) |
|
111 |
computeBadItems nl il = |
|
112 |
let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl |
|
113 |
bad_instances = map (\idx -> Container.find idx il) $ |
|
114 |
sort $ nub $ concat $ |
|
115 |
map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes |
|
116 |
in |
|
117 |
(bad_nodes, bad_instances) |
|
118 |
|
|
119 |
-- | Compute the total free disk and memory in the cluster. |
|
120 |
totalResources :: Container.Container Node.Node -> (Int, Int) |
|
121 |
totalResources nl = |
|
122 |
foldl' |
|
123 |
(\ (mem, dsk) node -> (mem + (Node.f_mem node), |
|
124 |
dsk + (Node.f_dsk node))) |
|
125 |
(0, 0) (Container.elems nl) |
|
126 |
|
|
127 |
-- | Compute the mem and disk covariance. |
|
128 |
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double) |
|
129 |
compDetailedCV nl = |
|
130 |
let |
|
131 |
all_nodes = Container.elems nl |
|
132 |
(offline, nodes) = partition Node.offline all_nodes |
|
133 |
mem_l = map Node.p_mem nodes |
|
134 |
dsk_l = map Node.p_dsk nodes |
|
135 |
mem_cv = varianceCoeff mem_l |
|
136 |
dsk_cv = varianceCoeff dsk_l |
|
137 |
n1_l = length $ filter Node.failN1 nodes |
|
138 |
n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes) |
|
139 |
res_l = map Node.p_rem nodes |
|
140 |
res_cv = varianceCoeff res_l |
|
141 |
offline_inst = sum . map (\n -> (length . Node.plist $ n) + |
|
142 |
(length . Node.slist $ n)) $ offline |
|
143 |
online_inst = sum . map (\n -> (length . Node.plist $ n) + |
|
144 |
(length . Node.slist $ n)) $ nodes |
|
145 |
off_score = (fromIntegral offline_inst) / |
|
146 |
(fromIntegral $ online_inst + offline_inst) |
|
147 |
in (mem_cv, dsk_cv, n1_score, res_cv, off_score) |
|
148 |
|
|
149 |
-- | Compute the /total/ variance. |
|
150 |
compCV :: Node.List -> Double |
|
151 |
compCV nl = |
|
152 |
let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl |
|
153 |
in mem_cv + dsk_cv + n1_score + res_cv + off_score |
|
154 |
|
|
155 |
-- * hn1 functions |
|
156 |
|
|
157 |
-- | Add an instance and return the new node and instance maps. |
|
103 | 158 |
addInstance :: Node.List -> Instance.Instance -> |
104 | 159 |
Node.Node -> Node.Node -> Maybe Node.List |
105 | 160 |
addInstance nl idata pri sec = |
... | ... | |
128 | 183 |
removeInstances :: Node.List -> [Instance.Instance] -> Node.List |
129 | 184 |
removeInstances = foldl' removeInstance |
130 | 185 |
|
131 |
-- | Compute the total free disk and memory in the cluster. |
|
132 |
totalResources :: Container.Container Node.Node -> (Int, Int) |
|
133 |
totalResources nl = |
|
134 |
foldl' |
|
135 |
(\ (mem, dsk) node -> (mem + (Node.f_mem node), |
|
136 |
dsk + (Node.f_dsk node))) |
|
137 |
(0, 0) (Container.elems nl) |
|
138 | 186 |
|
139 |
{- | Compute a new version of a cluster given a solution.
|
|
187 |
{-| Compute a new version of a cluster given a solution. |
|
140 | 188 |
|
141 | 189 |
This is not used for computing the solutions, but for applying a |
142 | 190 |
(known-good) solution to the original cluster for final display. |
... | ... | |
161 | 209 |
) nc odxes |
162 | 210 |
|
163 | 211 |
|
164 |
-- First phase functions |
|
212 |
-- ** First phase functions
|
|
165 | 213 |
|
166 |
{- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
|
|
214 |
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2, |
|
167 | 215 |
[3..n]), ...] |
168 | 216 |
|
169 | 217 |
-} |
... | ... | |
190 | 238 |
in |
191 | 239 |
aux_fn count1 names1 [] |
192 | 240 |
|
193 |
{- | Computes the pair of bad nodes and instances. |
|
194 |
|
|
195 |
The bad node list is computed via a simple 'verifyN1' check, and the |
|
196 |
bad instance list is the list of primary and secondary instances of |
|
197 |
those nodes. |
|
198 |
|
|
199 |
-} |
|
200 |
computeBadItems :: Node.List -> Instance.List -> |
|
201 |
([Node.Node], [Instance.Instance]) |
|
202 |
computeBadItems nl il = |
|
203 |
let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl |
|
204 |
bad_instances = map (\idx -> Container.find idx il) $ |
|
205 |
sort $ nub $ concat $ |
|
206 |
map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes |
|
207 |
in |
|
208 |
(bad_nodes, bad_instances) |
|
209 |
|
|
210 |
|
|
211 |
{- | Checks if removal of instances results in N+1 pass. |
|
241 |
{-| Checks if removal of instances results in N+1 pass. |
|
212 | 242 |
|
213 | 243 |
Note: the check removal cannot optimize by scanning only the affected |
214 | 244 |
nodes, since the cluster is known to be not healthy; only the check |
... | ... | |
226 | 256 |
Just $ Removal nx victims |
227 | 257 |
|
228 | 258 |
|
229 |
-- | Computes the removals list for a given depth |
|
259 |
-- | Computes the removals list for a given depth.
|
|
230 | 260 |
computeRemovals :: Node.List |
231 | 261 |
-> [Instance.Instance] |
232 | 262 |
-> Int |
... | ... | |
234 | 264 |
computeRemovals nl bad_instances depth = |
235 | 265 |
map (checkRemoval nl) $ genNames depth bad_instances |
236 | 266 |
|
237 |
-- Second phase functions |
|
267 |
-- ** Second phase functions
|
|
238 | 268 |
|
239 |
-- | Single-node relocation cost |
|
269 |
-- | Single-node relocation cost.
|
|
240 | 270 |
nodeDelta :: Ndx -> Ndx -> Ndx -> Int |
241 | 271 |
nodeDelta i p s = |
242 | 272 |
if i == p || i == s then |
... | ... | |
244 | 274 |
else |
245 | 275 |
1 |
246 | 276 |
|
247 |
{-| Compute best solution. |
|
248 |
|
|
249 |
This function compares two solutions, choosing the minimum valid |
|
250 |
solution. |
|
251 |
-} |
|
277 |
-- | Compute best solution. |
|
278 |
-- |
|
279 |
-- This function compares two solutions, choosing the minimum valid |
|
280 |
-- solution. |
|
252 | 281 |
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution |
253 | 282 |
compareSolutions a b = case (a, b) of |
254 | 283 |
(Nothing, x) -> x |
255 | 284 |
(x, Nothing) -> x |
256 | 285 |
(x, y) -> min x y |
257 | 286 |
|
258 |
-- | Compute best table. Note that the ordering of the arguments is important. |
|
259 |
compareTables :: Table -> Table -> Table |
|
260 |
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) = |
|
261 |
if a_cv > b_cv then b else a |
|
262 |
|
|
263 | 287 |
-- | Check if a given delta is worse then an existing solution. |
264 | 288 |
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool |
265 | 289 |
tooHighDelta sol new_delta max_delta = |
... | ... | |
330 | 354 |
) accu_p nodes |
331 | 355 |
) prev_sol nodes |
332 | 356 |
|
333 |
-- | Apply a move |
|
357 |
{-| Auxiliary function for solution computation. |
|
358 |
|
|
359 |
We write this in an explicit recursive fashion in order to control |
|
360 |
early-abort in case we have met the min delta. We can't use foldr |
|
361 |
instead of explicit recursion since we need the accumulator for the |
|
362 |
abort decision. |
|
363 |
|
|
364 |
-} |
|
365 |
advanceSolution :: [Maybe Removal] -- ^ The removal to process |
|
366 |
-> Int -- ^ Minimum delta parameter |
|
367 |
-> Int -- ^ Maximum delta parameter |
|
368 |
-> Maybe Solution -- ^ Current best solution |
|
369 |
-> Maybe Solution -- ^ New best solution |
|
370 |
advanceSolution [] _ _ sol = sol |
|
371 |
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol |
|
372 |
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol = |
|
373 |
let new_sol = checkPlacement nx removed [] 0 prev_sol max_d |
|
374 |
new_delta = solutionDelta $! new_sol |
|
375 |
in |
|
376 |
if new_delta >= 0 && new_delta <= min_d then |
|
377 |
new_sol |
|
378 |
else |
|
379 |
advanceSolution xs min_d max_d new_sol |
|
380 |
|
|
381 |
-- | Computes the placement solution. |
|
382 |
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals |
|
383 |
-> Int -- ^ Minimum delta parameter |
|
384 |
-> Int -- ^ Maximum delta parameter |
|
385 |
-> Maybe Solution -- ^ The best solution found |
|
386 |
solutionFromRemovals removals min_delta max_delta = |
|
387 |
advanceSolution removals min_delta max_delta Nothing |
|
388 |
|
|
389 |
{-| Computes the solution at the given depth. |
|
390 |
|
|
391 |
This is a wrapper over both computeRemovals and |
|
392 |
solutionFromRemovals. In case we have no solution, we return Nothing. |
|
393 |
|
|
394 |
-} |
|
395 |
computeSolution :: Node.List -- ^ The original node data |
|
396 |
-> [Instance.Instance] -- ^ The list of /bad/ instances |
|
397 |
-> Int -- ^ The /depth/ of removals |
|
398 |
-> Int -- ^ Maximum number of removals to process |
|
399 |
-> Int -- ^ Minimum delta parameter |
|
400 |
-> Int -- ^ Maximum delta parameter |
|
401 |
-> Maybe Solution -- ^ The best solution found (or Nothing) |
|
402 |
computeSolution nl bad_instances depth max_removals min_delta max_delta = |
|
403 |
let |
|
404 |
removals = computeRemovals nl bad_instances depth |
|
405 |
removals' = capRemovals removals max_removals |
|
406 |
in |
|
407 |
solutionFromRemovals removals' min_delta max_delta |
|
408 |
|
|
409 |
-- * hbal functions |
|
410 |
|
|
411 |
-- | Compute best table. Note that the ordering of the arguments is important. |
|
412 |
compareTables :: Table -> Table -> Table |
|
413 |
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) = |
|
414 |
if a_cv > b_cv then b else a |
|
415 |
|
|
416 |
-- | Applies an instance move to a given node list and instance. |
|
334 | 417 |
applyMove :: Node.List -> Instance.Instance |
335 | 418 |
-> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx) |
336 | 419 |
-- Failover (f) |
... | ... | |
407 | 490 |
Container.addTwo old_sdx new_p old_pdx int_p nl |
408 | 491 |
in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx) |
409 | 492 |
|
493 |
-- | Tries to allocate an instance on one given node. |
|
410 | 494 |
allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node |
411 | 495 |
-> (Maybe Node.List, Instance.Instance) |
412 | 496 |
allocateOnSingle nl inst p = |
... | ... | |
415 | 499 |
return $ Container.add new_pdx new_p nl |
416 | 500 |
in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary) |
417 | 501 |
|
502 |
-- | Tries to allocate an instance on a given pair of nodes. |
|
418 | 503 |
allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node |
419 | 504 |
-> (Maybe Node.List, Instance.Instance) |
420 | 505 |
allocateOnPair nl inst tgt_p tgt_s = |
... | ... | |
426 | 511 |
return $ Container.addTwo new_pdx new_p new_sdx new_s nl |
427 | 512 |
in (new_nl, Instance.setBoth inst new_pdx new_sdx) |
428 | 513 |
|
514 |
-- | Tries to perform an instance move and returns the best table |
|
515 |
-- between the original one and the new one. |
|
429 | 516 |
checkSingleStep :: Table -- ^ The original table |
430 | 517 |
-> Instance.Instance -- ^ The instance to move |
431 | 518 |
-> Table -- ^ The current best table |
... | ... | |
502 | 589 |
else |
503 | 590 |
best_tbl |
504 | 591 |
|
505 |
{- | Auxiliary function for solution computation. |
|
506 |
|
|
507 |
We write this in an explicit recursive fashion in order to control |
|
508 |
early-abort in case we have met the min delta. We can't use foldr |
|
509 |
instead of explicit recursion since we need the accumulator for the |
|
510 |
abort decision. |
|
511 |
|
|
512 |
-} |
|
513 |
advanceSolution :: [Maybe Removal] -- ^ The removal to process |
|
514 |
-> Int -- ^ Minimum delta parameter |
|
515 |
-> Int -- ^ Maximum delta parameter |
|
516 |
-> Maybe Solution -- ^ Current best solution |
|
517 |
-> Maybe Solution -- ^ New best solution |
|
518 |
advanceSolution [] _ _ sol = sol |
|
519 |
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol |
|
520 |
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol = |
|
521 |
let new_sol = checkPlacement nx removed [] 0 prev_sol max_d |
|
522 |
new_delta = solutionDelta $! new_sol |
|
523 |
in |
|
524 |
if new_delta >= 0 && new_delta <= min_d then |
|
525 |
new_sol |
|
526 |
else |
|
527 |
advanceSolution xs min_d max_d new_sol |
|
528 |
|
|
529 |
-- | Computes the placement solution. |
|
530 |
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals |
|
531 |
-> Int -- ^ Minimum delta parameter |
|
532 |
-> Int -- ^ Maximum delta parameter |
|
533 |
-> Maybe Solution -- ^ The best solution found |
|
534 |
solutionFromRemovals removals min_delta max_delta = |
|
535 |
advanceSolution removals min_delta max_delta Nothing |
|
536 |
|
|
537 |
{- | Computes the solution at the given depth. |
|
538 |
|
|
539 |
This is a wrapper over both computeRemovals and |
|
540 |
solutionFromRemovals. In case we have no solution, we return Nothing. |
|
541 | 592 |
|
542 |
-} |
|
543 |
computeSolution :: Node.List -- ^ The original node data |
|
544 |
-> [Instance.Instance] -- ^ The list of /bad/ instances |
|
545 |
-> Int -- ^ The /depth/ of removals |
|
546 |
-> Int -- ^ Maximum number of removals to process |
|
547 |
-> Int -- ^ Minimum delta parameter |
|
548 |
-> Int -- ^ Maximum delta parameter |
|
549 |
-> Maybe Solution -- ^ The best solution found (or Nothing) |
|
550 |
computeSolution nl bad_instances depth max_removals min_delta max_delta = |
|
551 |
let |
|
552 |
removals = computeRemovals nl bad_instances depth |
|
553 |
removals' = capRemovals removals max_removals |
|
554 |
in |
|
555 |
solutionFromRemovals removals' min_delta max_delta |
|
556 |
|
|
557 |
-- Solution display functions (pure) |
|
593 |
-- * Formatting functions |
|
558 | 594 |
|
559 | 595 |
-- | Given the original and final nodes, computes the relocation description. |
560 | 596 |
computeMoves :: String -- ^ The instance name |
... | ... | |
600 | 636 |
printf "migrate -f %s" i, |
601 | 637 |
printf "replace-disks -n %s %s" d i]) |
602 | 638 |
|
603 |
{-| Converts a placement to string format -} |
|
604 |
printSolutionLine :: Node.List |
|
605 |
-> Instance.List |
|
606 |
-> Int |
|
607 |
-> Int |
|
608 |
-> Placement |
|
609 |
-> Int |
|
639 |
-- | Converts a placement to string format. |
|
640 |
printSolutionLine :: Node.List -- ^ The node list |
|
641 |
-> Instance.List -- ^ The instance list |
|
642 |
-> Int -- ^ Maximum node name length |
|
643 |
-> Int -- ^ Maximum instance name length |
|
644 |
-> Placement -- ^ The current placement |
|
645 |
-> Int -- ^ The index of the placement in |
|
646 |
-- the solution |
|
610 | 647 |
-> (String, [String]) |
611 | 648 |
printSolutionLine nl il nmlen imlen plc pos = |
612 | 649 |
let |
... | ... | |
627 | 664 |
pmlen nstr c moves, |
628 | 665 |
cmds) |
629 | 666 |
|
667 |
-- | Given a list of commands, prefix them with @gnt-instance@ and |
|
668 |
-- also beautify the display a little. |
|
630 | 669 |
formatCmds :: [[String]] -> String |
631 | 670 |
formatCmds cmd_strs = |
632 | 671 |
unlines $ |
... | ... | |
636 | 675 |
(map ("gnt-instance " ++) b)) $ |
637 | 676 |
zip [1..] cmd_strs |
638 | 677 |
|
639 |
{-| Converts a solution to string format -}
|
|
678 |
-- | Converts a solution to string format.
|
|
640 | 679 |
printSolution :: Node.List |
641 | 680 |
-> Instance.List |
642 | 681 |
-> [Placement] |
... | ... | |
663 | 702 |
"pri" "sec" "p_fmem" "p_fdsk" |
664 | 703 |
in unlines $ (header:map helper snl) |
665 | 704 |
|
666 |
-- | Compute the mem and disk covariance. |
|
667 |
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double) |
|
668 |
compDetailedCV nl = |
|
669 |
let |
|
670 |
all_nodes = Container.elems nl |
|
671 |
(offline, nodes) = partition Node.offline all_nodes |
|
672 |
mem_l = map Node.p_mem nodes |
|
673 |
dsk_l = map Node.p_dsk nodes |
|
674 |
mem_cv = varianceCoeff mem_l |
|
675 |
dsk_cv = varianceCoeff dsk_l |
|
676 |
n1_l = length $ filter Node.failN1 nodes |
|
677 |
n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes) |
|
678 |
res_l = map Node.p_rem nodes |
|
679 |
res_cv = varianceCoeff res_l |
|
680 |
offline_inst = sum . map (\n -> (length . Node.plist $ n) + |
|
681 |
(length . Node.slist $ n)) $ offline |
|
682 |
online_inst = sum . map (\n -> (length . Node.plist $ n) + |
|
683 |
(length . Node.slist $ n)) $ nodes |
|
684 |
off_score = (fromIntegral offline_inst) / |
|
685 |
(fromIntegral $ online_inst + offline_inst) |
|
686 |
in (mem_cv, dsk_cv, n1_score, res_cv, off_score) |
|
687 |
|
|
688 |
-- | Compute the 'total' variance. |
|
689 |
compCV :: Node.List -> Double |
|
690 |
compCV nl = |
|
691 |
let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl |
|
692 |
in mem_cv + dsk_cv + n1_score + res_cv + off_score |
|
693 |
|
|
705 |
-- | Shows statistics for a given node list. |
|
694 | 706 |
printStats :: Node.List -> String |
695 | 707 |
printStats nl = |
696 | 708 |
let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl |
Also available in: Unified diff