Revision 266aea94 Ganeti/HTools/Cluster.hs
b/Ganeti/HTools/Cluster.hs | ||
---|---|---|
31 | 31 |
-- * Types |
32 | 32 |
Placement |
33 | 33 |
, AllocSolution |
34 |
, Solution(..) |
|
35 | 34 |
, Table(..) |
36 |
, Removal |
|
37 | 35 |
, Score |
38 | 36 |
, IMove(..) |
39 | 37 |
, CStats(..) |
... | ... | |
42 | 40 |
-- * First phase functions |
43 | 41 |
, computeBadItems |
44 | 42 |
-- * Second phase functions |
45 |
, computeSolution |
|
46 |
, applySolution |
|
47 | 43 |
, printSolution |
48 | 44 |
, printSolutionLine |
49 | 45 |
, formatCmds |
... | ... | |
83 | 79 |
-- | Allocation\/relocation solution. |
84 | 80 |
type AllocSolution = [(Maybe Node.List, Instance.Instance, [Node.Node])] |
85 | 81 |
|
86 |
-- | A cluster solution described as the solution delta and the list |
|
87 |
-- of placements. |
|
88 |
data Solution = Solution Int [Placement] |
|
89 |
deriving (Eq, Ord, Show) |
|
90 |
|
|
91 |
-- | A removal set. |
|
92 |
data Removal = Removal Node.List [Instance.Instance] |
|
93 |
|
|
94 | 82 |
-- | An instance move definition |
95 | 83 |
data IMove = Failover -- ^ Failover the instance (f) |
96 | 84 |
| ReplacePrimary Ndx -- ^ Replace primary (f, r:np, f) |
... | ... | |
115 | 103 |
|
116 | 104 |
-- * Utility functions |
117 | 105 |
|
118 |
-- | Returns the delta of a solution or -1 for Nothing. |
|
119 |
solutionDelta :: Maybe Solution -> Int |
|
120 |
solutionDelta sol = case sol of |
|
121 |
Just (Solution d _) -> d |
|
122 |
_ -> -1 |
|
123 |
|
|
124 |
-- | Cap the removal list if needed. |
|
125 |
capRemovals :: [a] -> Int -> [a] |
|
126 |
capRemovals removals max_removals = |
|
127 |
if max_removals > 0 then |
|
128 |
take max_removals removals |
|
129 |
else |
|
130 |
removals |
|
131 |
|
|
132 |
-- | Check if the given node list fails the N+1 check. |
|
133 |
verifyN1Check :: [Node.Node] -> Bool |
|
134 |
verifyN1Check nl = any Node.failN1 nl |
|
135 |
|
|
136 | 106 |
-- | Verifies the N+1 status and return the affected nodes. |
137 | 107 |
verifyN1 :: [Node.Node] -> [Node.Node] |
138 | 108 |
verifyN1 nl = filter Node.failN1 nl |
... | ... | |
224 | 194 |
getOnline :: Node.List -> [Node.Node] |
225 | 195 |
getOnline = filter (not . Node.offline) . Container.elems |
226 | 196 |
|
227 |
-- * hn1 functions |
|
228 |
|
|
229 |
-- | Add an instance and return the new node and instance maps. |
|
230 |
addInstance :: Node.List -> Instance.Instance -> |
|
231 |
Node.Node -> Node.Node -> Maybe Node.List |
|
232 |
addInstance nl idata pri sec = |
|
233 |
let pdx = Node.idx pri |
|
234 |
sdx = Node.idx sec |
|
235 |
in do |
|
236 |
pnode <- Node.addPri pri idata |
|
237 |
snode <- Node.addSec sec idata pdx |
|
238 |
new_nl <- return $ Container.addTwo sdx snode |
|
239 |
pdx pnode nl |
|
240 |
return new_nl |
|
241 |
|
|
242 |
-- | Remove an instance and return the new node and instance maps. |
|
243 |
removeInstance :: Node.List -> Instance.Instance -> Node.List |
|
244 |
removeInstance nl idata = |
|
245 |
let pnode = Instance.pnode idata |
|
246 |
snode = Instance.snode idata |
|
247 |
pn = Container.find pnode nl |
|
248 |
sn = Container.find snode nl |
|
249 |
new_nl = Container.addTwo |
|
250 |
pnode (Node.removePri pn idata) |
|
251 |
snode (Node.removeSec sn idata) nl in |
|
252 |
new_nl |
|
253 |
|
|
254 |
-- | Remove an instance and return the new node map. |
|
255 |
removeInstances :: Node.List -> [Instance.Instance] -> Node.List |
|
256 |
removeInstances = foldl' removeInstance |
|
257 |
|
|
258 |
|
|
259 |
{-| Compute a new version of a cluster given a solution. |
|
260 |
|
|
261 |
This is not used for computing the solutions, but for applying a |
|
262 |
(known-good) solution to the original cluster for final display. |
|
263 |
|
|
264 |
It first removes the relocated instances after which it places them on |
|
265 |
their new nodes. |
|
266 |
|
|
267 |
-} |
|
268 |
applySolution :: Node.List -> Instance.List -> [Placement] -> Node.List |
|
269 |
applySolution nl il sol = |
|
270 |
let odxes = map (\ (a, b, c, _) -> (Container.find a il, |
|
271 |
Node.idx (Container.find b nl), |
|
272 |
Node.idx (Container.find c nl)) |
|
273 |
) sol |
|
274 |
idxes = (\ (x, _, _) -> x) (unzip3 odxes) |
|
275 |
nc = removeInstances nl idxes |
|
276 |
in |
|
277 |
foldl' (\ nz (a, b, c) -> |
|
278 |
let new_p = Container.find b nz |
|
279 |
new_s = Container.find c nz in |
|
280 |
fromJust (addInstance nz a new_p new_s) |
|
281 |
) nc odxes |
|
282 |
|
|
283 |
|
|
284 |
-- ** First phase functions |
|
285 |
|
|
286 |
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2, |
|
287 |
[3..n]), ...] |
|
288 |
|
|
289 |
-} |
|
290 |
genParts :: [a] -> Int -> [(a, [a])] |
|
291 |
genParts l count = |
|
292 |
case l of |
|
293 |
[] -> [] |
|
294 |
x:xs -> |
|
295 |
if length l < count then |
|
296 |
[] |
|
297 |
else |
|
298 |
(x, xs) : (genParts xs count) |
|
299 |
|
|
300 |
-- | Generates combinations of count items from the names list. |
|
301 |
genNames :: Int -> [b] -> [[b]] |
|
302 |
genNames count1 names1 = |
|
303 |
let aux_fn count names current = |
|
304 |
case count of |
|
305 |
0 -> [current] |
|
306 |
_ -> |
|
307 |
concatMap |
|
308 |
(\ (x, xs) -> aux_fn (count - 1) xs (x:current)) |
|
309 |
(genParts names count) |
|
310 |
in |
|
311 |
aux_fn count1 names1 [] |
|
312 |
|
|
313 |
{-| Checks if removal of instances results in N+1 pass. |
|
314 |
|
|
315 |
Note: the check removal cannot optimize by scanning only the affected |
|
316 |
nodes, since the cluster is known to be not healthy; only the check |
|
317 |
placement can make this shortcut. |
|
318 |
|
|
319 |
-} |
|
320 |
checkRemoval :: Node.List -> [Instance.Instance] -> Maybe Removal |
|
321 |
checkRemoval nl victims = |
|
322 |
let nx = removeInstances nl victims |
|
323 |
failN1 = verifyN1Check (Container.elems nx) |
|
324 |
in |
|
325 |
if failN1 then |
|
326 |
Nothing |
|
327 |
else |
|
328 |
Just $ Removal nx victims |
|
329 |
|
|
330 |
|
|
331 |
-- | Computes the removals list for a given depth. |
|
332 |
computeRemovals :: Node.List |
|
333 |
-> [Instance.Instance] |
|
334 |
-> Int |
|
335 |
-> [Maybe Removal] |
|
336 |
computeRemovals nl bad_instances depth = |
|
337 |
map (checkRemoval nl) $ genNames depth bad_instances |
|
338 |
|
|
339 |
-- ** Second phase functions |
|
340 |
|
|
341 |
-- | Single-node relocation cost. |
|
342 |
nodeDelta :: Ndx -> Ndx -> Ndx -> Int |
|
343 |
nodeDelta i p s = |
|
344 |
if i == p || i == s then |
|
345 |
0 |
|
346 |
else |
|
347 |
1 |
|
348 |
|
|
349 |
-- | Compute best solution. |
|
350 |
-- |
|
351 |
-- This function compares two solutions, choosing the minimum valid |
|
352 |
-- solution. |
|
353 |
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution |
|
354 |
compareSolutions a b = case (a, b) of |
|
355 |
(Nothing, x) -> x |
|
356 |
(x, Nothing) -> x |
|
357 |
(x, y) -> min x y |
|
358 |
|
|
359 |
-- | Check if a given delta is worse then an existing solution. |
|
360 |
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool |
|
361 |
tooHighDelta sol new_delta max_delta = |
|
362 |
if new_delta > max_delta && max_delta >=0 then |
|
363 |
True |
|
364 |
else |
|
365 |
case sol of |
|
366 |
Nothing -> False |
|
367 |
Just (Solution old_delta _) -> old_delta <= new_delta |
|
368 |
|
|
369 |
{-| Check if placement of instances still keeps the cluster N+1 compliant. |
|
370 |
|
|
371 |
This is the workhorse of the allocation algorithm: given the |
|
372 |
current node and instance maps, the list of instances to be |
|
373 |
placed, and the current solution, this will return all possible |
|
374 |
solution by recursing until all target instances are placed. |
|
375 |
|
|
376 |
-} |
|
377 |
checkPlacement :: Node.List -- ^ The current node list |
|
378 |
-> [Instance.Instance] -- ^ List of instances still to place |
|
379 |
-> [Placement] -- ^ Partial solution until now |
|
380 |
-> Int -- ^ The delta of the partial solution |
|
381 |
-> Maybe Solution -- ^ The previous solution |
|
382 |
-> Int -- ^ Abort if the we go above this delta |
|
383 |
-> Maybe Solution -- ^ The new solution |
|
384 |
checkPlacement nl victims current current_delta prev_sol max_delta = |
|
385 |
let target = head victims |
|
386 |
opdx = Instance.pnode target |
|
387 |
osdx = Instance.snode target |
|
388 |
vtail = tail victims |
|
389 |
have_tail = (length vtail) > 0 |
|
390 |
nodes = Container.elems nl |
|
391 |
iidx = Instance.idx target |
|
392 |
in |
|
393 |
foldl' |
|
394 |
(\ accu_p pri -> |
|
395 |
let |
|
396 |
pri_idx = Node.idx pri |
|
397 |
upri_delta = current_delta + nodeDelta pri_idx opdx osdx |
|
398 |
new_pri = Node.addPri pri target |
|
399 |
fail_delta1 = tooHighDelta accu_p upri_delta max_delta |
|
400 |
in |
|
401 |
if fail_delta1 || isNothing(new_pri) then accu_p |
|
402 |
else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in |
|
403 |
foldl' |
|
404 |
(\ accu sec -> |
|
405 |
let |
|
406 |
sec_idx = Node.idx sec |
|
407 |
upd_delta = upri_delta + |
|
408 |
nodeDelta sec_idx opdx osdx |
|
409 |
fail_delta2 = tooHighDelta accu upd_delta max_delta |
|
410 |
new_sec = Node.addSec sec target pri_idx |
|
411 |
in |
|
412 |
if sec_idx == pri_idx || fail_delta2 || |
|
413 |
isNothing new_sec then accu |
|
414 |
else let |
|
415 |
nx = Container.add sec_idx (fromJust new_sec) pri_nl |
|
416 |
upd_cv = compCV nx |
|
417 |
plc = (iidx, pri_idx, sec_idx, upd_cv) |
|
418 |
c2 = plc:current |
|
419 |
result = |
|
420 |
if have_tail then |
|
421 |
checkPlacement nx vtail c2 upd_delta |
|
422 |
accu max_delta |
|
423 |
else |
|
424 |
Just (Solution upd_delta c2) |
|
425 |
in compareSolutions accu result |
|
426 |
) accu_p nodes |
|
427 |
) prev_sol nodes |
|
428 |
|
|
429 |
{-| Auxiliary function for solution computation. |
|
430 |
|
|
431 |
We write this in an explicit recursive fashion in order to control |
|
432 |
early-abort in case we have met the min delta. We can't use foldr |
|
433 |
instead of explicit recursion since we need the accumulator for the |
|
434 |
abort decision. |
|
435 |
|
|
436 |
-} |
|
437 |
advanceSolution :: [Maybe Removal] -- ^ The removal to process |
|
438 |
-> Int -- ^ Minimum delta parameter |
|
439 |
-> Int -- ^ Maximum delta parameter |
|
440 |
-> Maybe Solution -- ^ Current best solution |
|
441 |
-> Maybe Solution -- ^ New best solution |
|
442 |
advanceSolution [] _ _ sol = sol |
|
443 |
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol |
|
444 |
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol = |
|
445 |
let new_sol = checkPlacement nx removed [] 0 prev_sol max_d |
|
446 |
new_delta = solutionDelta $! new_sol |
|
447 |
in |
|
448 |
if new_delta >= 0 && new_delta <= min_d then |
|
449 |
new_sol |
|
450 |
else |
|
451 |
advanceSolution xs min_d max_d new_sol |
|
452 |
|
|
453 |
-- | Computes the placement solution. |
|
454 |
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals |
|
455 |
-> Int -- ^ Minimum delta parameter |
|
456 |
-> Int -- ^ Maximum delta parameter |
|
457 |
-> Maybe Solution -- ^ The best solution found |
|
458 |
solutionFromRemovals removals min_delta max_delta = |
|
459 |
advanceSolution removals min_delta max_delta Nothing |
|
460 |
|
|
461 |
{-| Computes the solution at the given depth. |
|
462 |
|
|
463 |
This is a wrapper over both computeRemovals and |
|
464 |
solutionFromRemovals. In case we have no solution, we return Nothing. |
|
465 |
|
|
466 |
-} |
|
467 |
computeSolution :: Node.List -- ^ The original node data |
|
468 |
-> [Instance.Instance] -- ^ The list of /bad/ instances |
|
469 |
-> Int -- ^ The /depth/ of removals |
|
470 |
-> Int -- ^ Maximum number of removals to process |
|
471 |
-> Int -- ^ Minimum delta parameter |
|
472 |
-> Int -- ^ Maximum delta parameter |
|
473 |
-> Maybe Solution -- ^ The best solution found (or Nothing) |
|
474 |
computeSolution nl bad_instances depth max_removals min_delta max_delta = |
|
475 |
let |
|
476 |
removals = computeRemovals nl bad_instances depth |
|
477 |
removals' = capRemovals removals max_removals |
|
478 |
in |
|
479 |
solutionFromRemovals removals' min_delta max_delta |
|
480 |
|
|
481 | 197 |
-- * hbal functions |
482 | 198 |
|
483 | 199 |
-- | Compute best table. Note that the ordering of the arguments is important. |
Also available in: Unified diff