1 {-| Implementation of cluster-wide logic.
3 This module holds all pure cluster-logic; I\/O related functionality
4 goes into the "Main" module for the individual binaries.
10 Copyright (C) 2009 Google Inc.
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful, but
18 WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29 module Ganeti.HTools.Cluster
36 -- * Generic functions
38 , computeAllocationDelta
39 -- * First phase functions
41 -- * Second phase functions
46 -- * Display functions
49 -- * Balacing functions
56 -- * IAllocator functions
61 -- * Allocation functions
67 import Data.Ord (comparing)
68 import Text.Printf (printf)
71 import qualified Ganeti.HTools.Container as Container
72 import qualified Ganeti.HTools.Instance as Instance
73 import qualified Ganeti.HTools.Node as Node
74 import Ganeti.HTools.Types
75 import Ganeti.HTools.Utils
76 import qualified Ganeti.OpCodes as OpCodes
80 -- | Allocation\/relocation solution.
81 type AllocSolution = ([FailMode], Int, [(Score, Node.AllocElement)])
83 -- | The complete state for the balancing solution
84 data Table = Table Node.List Instance.List Score [Placement]
87 data CStats = CStats { csFmem :: Int -- ^ Cluster free mem
88 , csFdsk :: Int -- ^ Cluster free disk
89 , csAmem :: Int -- ^ Cluster allocatable mem
90 , csAdsk :: Int -- ^ Cluster allocatable disk
91 , csAcpu :: Int -- ^ Cluster allocatable cpus
92 , csMmem :: Int -- ^ Max node allocatable mem
93 , csMdsk :: Int -- ^ Max node allocatable disk
94 , csMcpu :: Int -- ^ Max node allocatable cpu
95 , csImem :: Int -- ^ Instance used mem
96 , csIdsk :: Int -- ^ Instance used disk
97 , csIcpu :: Int -- ^ Instance used cpu
98 , csTmem :: Double -- ^ Cluster total mem
99 , csTdsk :: Double -- ^ Cluster total disk
100 , csTcpu :: Double -- ^ Cluster total cpus
101 , csVcpu :: Int -- ^ Cluster virtual cpus (if
102 -- node pCpu has been set,
104 , csXmem :: Int -- ^ Unnacounted for mem
105 , csNmem :: Int -- ^ Node own memory
106 , csScore :: Score -- ^ The cluster score
107 , csNinst :: Int -- ^ The total number of instances
111 -- | Currently used, possibly to allocate, unallocable
112 type AllocStats = (RSpec, RSpec, RSpec)
114 -- * Utility functions
116 -- | Verifies the N+1 status and return the affected nodes.
117 verifyN1 :: [Node.Node] -> [Node.Node]
118 verifyN1 = filter Node.failN1
120 {-| Computes the pair of bad nodes and instances.
122 The bad node list is computed via a simple 'verifyN1' check, and the
123 bad instance list is the list of primary and secondary instances of
127 computeBadItems :: Node.List -> Instance.List ->
128 ([Node.Node], [Instance.Instance])
129 computeBadItems nl il =
130 let bad_nodes = verifyN1 $ getOnline nl
131 bad_instances = map (`Container.find` il) .
133 concatMap (\ n -> Node.sList n ++ Node.pList n) bad_nodes
135 (bad_nodes, bad_instances)
137 -- | Zero-initializer for the CStats type
138 emptyCStats :: CStats
139 emptyCStats = CStats 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
141 -- | Update stats with data from a new node
142 updateCStats :: CStats -> Node.Node -> CStats
143 updateCStats cs node =
144 let CStats { csFmem = x_fmem, csFdsk = x_fdsk,
145 csAmem = x_amem, csAcpu = x_acpu, csAdsk = x_adsk,
146 csMmem = x_mmem, csMdsk = x_mdsk, csMcpu = x_mcpu,
147 csImem = x_imem, csIdsk = x_idsk, csIcpu = x_icpu,
148 csTmem = x_tmem, csTdsk = x_tdsk, csTcpu = x_tcpu,
150 csXmem = x_xmem, csNmem = x_nmem, csNinst = x_ninst
153 inc_amem = Node.fMem node - Node.rMem node
154 inc_amem' = if inc_amem > 0 then inc_amem else 0
155 inc_adsk = Node.availDisk node
156 inc_imem = truncate (Node.tMem node) - Node.nMem node
157 - Node.xMem node - Node.fMem node
158 inc_icpu = Node.uCpu node
159 inc_idsk = truncate (Node.tDsk node) - Node.fDsk node
160 inc_vcpu = Node.hiCpu node
162 in cs { csFmem = x_fmem + Node.fMem node
163 , csFdsk = x_fdsk + Node.fDsk node
164 , csAmem = x_amem + inc_amem'
165 , csAdsk = x_adsk + inc_adsk
167 , csMmem = max x_mmem inc_amem'
168 , csMdsk = max x_mdsk inc_adsk
170 , csImem = x_imem + inc_imem
171 , csIdsk = x_idsk + inc_idsk
172 , csIcpu = x_icpu + inc_icpu
173 , csTmem = x_tmem + Node.tMem node
174 , csTdsk = x_tdsk + Node.tDsk node
175 , csTcpu = x_tcpu + Node.tCpu node
176 , csVcpu = x_vcpu + inc_vcpu
177 , csXmem = x_xmem + Node.xMem node
178 , csNmem = x_nmem + Node.nMem node
179 , csNinst = x_ninst + length (Node.pList node)
182 -- | Compute the total free disk and memory in the cluster.
183 totalResources :: Node.List -> CStats
185 let cs = foldl' updateCStats emptyCStats . Container.elems $ nl
186 in cs { csScore = compCV nl }
188 -- | Compute the delta between two cluster state.
190 -- This is used when doing allocations, to understand better the
191 -- available cluster resources. The return value is a triple of the
192 -- current used values, the delta that was still allocated, and what
193 -- was left unallocated.
194 computeAllocationDelta :: CStats -> CStats -> AllocStats
195 computeAllocationDelta cini cfin =
196 let CStats {csImem = i_imem, csIdsk = i_idsk, csIcpu = i_icpu} = cini
197 CStats {csImem = f_imem, csIdsk = f_idsk, csIcpu = f_icpu,
198 csTmem = t_mem, csTdsk = t_dsk, csVcpu = v_cpu } = cfin
199 rini = RSpec i_icpu i_imem i_idsk
200 rfin = RSpec (f_icpu - i_icpu) (f_imem - i_imem) (f_idsk - i_idsk)
201 un_cpu = v_cpu - f_icpu
202 runa = RSpec un_cpu (truncate t_mem - f_imem) (truncate t_dsk - f_idsk)
203 in (rini, rfin, runa)
205 -- | The names of the individual elements in the CV list
206 detailedCVNames :: [String]
207 detailedCVNames = [ "free_mem_cv"
221 -- | Compute the mem and disk covariance.
222 compDetailedCV :: Node.List -> [Double]
225 all_nodes = Container.elems nl
226 (offline, nodes) = partition Node.offline all_nodes
227 mem_l = map Node.pMem nodes
228 dsk_l = map Node.pDsk nodes
229 -- metric: memory covariance
230 mem_cv = varianceCoeff mem_l
231 -- metric: disk covariance
232 dsk_cv = varianceCoeff dsk_l
233 n1_l = length $ filter Node.failN1 nodes
234 -- metric: count of failN1 nodes
235 n1_score = fromIntegral n1_l::Double
236 res_l = map Node.pRem nodes
237 -- metric: reserved memory covariance
238 res_cv = varianceCoeff res_l
239 -- offline instances metrics
240 offline_ipri = sum . map (length . Node.pList) $ offline
241 offline_isec = sum . map (length . Node.sList) $ offline
242 -- metric: count of instances on offline nodes
243 off_score = fromIntegral (offline_ipri + offline_isec)::Double
244 -- metric: count of primary instances on offline nodes (this
245 -- helps with evacuation/failover of primary instances on
246 -- 2-node clusters with one node offline)
247 off_pri_score = fromIntegral offline_ipri::Double
248 cpu_l = map Node.pCpu nodes
249 -- metric: covariance of vcpu/pcpu ratio
250 cpu_cv = varianceCoeff cpu_l
251 -- metrics: covariance of cpu, memory, disk and network load
252 (c_load, m_load, d_load, n_load) = unzip4 $
254 let DynUtil c1 m1 d1 n1 = Node.utilLoad n
255 DynUtil c2 m2 d2 n2 = Node.utilPool n
256 in (c1/c2, m1/m2, d1/d2, n1/n2)
258 -- metric: conflicting instance count
259 pri_tags_inst = sum $ map Node.conflictingPrimaries nodes
260 pri_tags_score = fromIntegral pri_tags_inst::Double
261 in [ mem_cv, dsk_cv, n1_score, res_cv, off_score, off_pri_score, cpu_cv
262 , varianceCoeff c_load, varianceCoeff m_load
263 , varianceCoeff d_load, varianceCoeff n_load
266 -- | Compute the /total/ variance.
267 compCV :: Node.List -> Double
268 compCV = sum . compDetailedCV
270 -- | Compute online nodes from a Node.List
271 getOnline :: Node.List -> [Node.Node]
272 getOnline = filter (not . Node.offline) . Container.elems
276 -- | Compute best table. Note that the ordering of the arguments is important.
277 compareTables :: Table -> Table -> Table
278 compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
279 if a_cv > b_cv then b else a
281 -- | Applies an instance move to a given node list and instance.
282 applyMove :: Node.List -> Instance.Instance
283 -> IMove -> OpResult (Node.List, Instance.Instance, Ndx, Ndx)
285 applyMove nl inst Failover =
286 let old_pdx = Instance.pNode inst
287 old_sdx = Instance.sNode inst
288 old_p = Container.find old_pdx nl
289 old_s = Container.find old_sdx nl
290 int_p = Node.removePri old_p inst
291 int_s = Node.removeSec old_s inst
292 force_p = Node.offline old_p
293 new_nl = do -- Maybe monad
294 new_p <- Node.addPriEx force_p int_s inst
295 new_s <- Node.addSec int_p inst old_sdx
296 let new_inst = Instance.setBoth inst old_sdx old_pdx
297 return (Container.addTwo old_pdx new_s old_sdx new_p nl,
298 new_inst, old_sdx, old_pdx)
301 -- Replace the primary (f:, r:np, f)
302 applyMove nl inst (ReplacePrimary new_pdx) =
303 let old_pdx = Instance.pNode inst
304 old_sdx = Instance.sNode inst
305 old_p = Container.find old_pdx nl
306 old_s = Container.find old_sdx nl
307 tgt_n = Container.find new_pdx nl
308 int_p = Node.removePri old_p inst
309 int_s = Node.removeSec old_s inst
310 force_p = Node.offline old_p
311 new_nl = do -- Maybe monad
312 -- check that the current secondary can host the instance
313 -- during the migration
314 tmp_s <- Node.addPriEx force_p int_s inst
315 let tmp_s' = Node.removePri tmp_s inst
316 new_p <- Node.addPriEx force_p tgt_n inst
317 new_s <- Node.addSecEx force_p tmp_s' inst new_pdx
318 let new_inst = Instance.setPri inst new_pdx
319 return (Container.add new_pdx new_p $
320 Container.addTwo old_pdx int_p old_sdx new_s nl,
321 new_inst, new_pdx, old_sdx)
324 -- Replace the secondary (r:ns)
325 applyMove nl inst (ReplaceSecondary new_sdx) =
326 let old_pdx = Instance.pNode inst
327 old_sdx = Instance.sNode inst
328 old_s = Container.find old_sdx nl
329 tgt_n = Container.find new_sdx nl
330 int_s = Node.removeSec old_s inst
331 force_s = Node.offline old_s
332 new_inst = Instance.setSec inst new_sdx
333 new_nl = Node.addSecEx force_s tgt_n inst old_pdx >>=
334 \new_s -> return (Container.addTwo new_sdx
335 new_s old_sdx int_s nl,
336 new_inst, old_pdx, new_sdx)
339 -- Replace the secondary and failover (r:np, f)
340 applyMove nl inst (ReplaceAndFailover new_pdx) =
341 let old_pdx = Instance.pNode inst
342 old_sdx = Instance.sNode inst
343 old_p = Container.find old_pdx nl
344 old_s = Container.find old_sdx nl
345 tgt_n = Container.find new_pdx nl
346 int_p = Node.removePri old_p inst
347 int_s = Node.removeSec old_s inst
348 force_s = Node.offline old_s
349 new_nl = do -- Maybe monad
350 new_p <- Node.addPri tgt_n inst
351 new_s <- Node.addSecEx force_s int_p inst new_pdx
352 let new_inst = Instance.setBoth inst new_pdx old_pdx
353 return (Container.add new_pdx new_p $
354 Container.addTwo old_pdx new_s old_sdx int_s nl,
355 new_inst, new_pdx, old_pdx)
358 -- Failver and replace the secondary (f, r:ns)
359 applyMove nl inst (FailoverAndReplace new_sdx) =
360 let old_pdx = Instance.pNode inst
361 old_sdx = Instance.sNode inst
362 old_p = Container.find old_pdx nl
363 old_s = Container.find old_sdx nl
364 tgt_n = Container.find new_sdx nl
365 int_p = Node.removePri old_p inst
366 int_s = Node.removeSec old_s inst
367 force_p = Node.offline old_p
368 new_nl = do -- Maybe monad
369 new_p <- Node.addPriEx force_p int_s inst
370 new_s <- Node.addSecEx force_p tgt_n inst old_sdx
371 let new_inst = Instance.setBoth inst old_sdx new_sdx
372 return (Container.add new_sdx new_s $
373 Container.addTwo old_sdx new_p old_pdx int_p nl,
374 new_inst, old_sdx, new_sdx)
377 -- | Tries to allocate an instance on one given node.
378 allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
379 -> OpResult Node.AllocElement
380 allocateOnSingle nl inst p =
381 let new_pdx = Node.idx p
382 new_inst = Instance.setBoth inst new_pdx Node.noSecondary
383 new_nl = Node.addPri p inst >>= \new_p ->
384 return (Container.add new_pdx new_p nl, new_inst, [new_p])
387 -- | Tries to allocate an instance on a given pair of nodes.
388 allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
389 -> OpResult Node.AllocElement
390 allocateOnPair nl inst tgt_p tgt_s =
391 let new_pdx = Node.idx tgt_p
392 new_sdx = Node.idx tgt_s
393 new_nl = do -- Maybe monad
394 new_p <- Node.addPri tgt_p inst
395 new_s <- Node.addSec tgt_s inst new_pdx
396 let new_inst = Instance.setBoth inst new_pdx new_sdx
397 return (Container.addTwo new_pdx new_p new_sdx new_s nl, new_inst,
401 -- | Tries to perform an instance move and returns the best table
402 -- between the original one and the new one.
403 checkSingleStep :: Table -- ^ The original table
404 -> Instance.Instance -- ^ The instance to move
405 -> Table -- ^ The current best table
406 -> IMove -- ^ The move to apply
407 -> Table -- ^ The final best table
408 checkSingleStep ini_tbl target cur_tbl move =
410 Table ini_nl ini_il _ ini_plc = ini_tbl
411 tmp_resu = applyMove ini_nl target move
415 OpGood (upd_nl, new_inst, pri_idx, sec_idx) ->
416 let tgt_idx = Instance.idx target
417 upd_cvar = compCV upd_nl
418 upd_il = Container.add tgt_idx new_inst ini_il
419 upd_plc = (tgt_idx, pri_idx, sec_idx, move, upd_cvar):ini_plc
420 upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
422 compareTables cur_tbl upd_tbl
424 -- | Given the status of the current secondary as a valid new node and
425 -- the current candidate target node, generate the possible moves for
427 possibleMoves :: Bool -- ^ Whether the secondary node is a valid new node
428 -> Ndx -- ^ Target node candidate
429 -> [IMove] -- ^ List of valid result moves
430 possibleMoves True tdx =
431 [ReplaceSecondary tdx,
432 ReplaceAndFailover tdx,
434 FailoverAndReplace tdx]
436 possibleMoves False tdx =
437 [ReplaceSecondary tdx,
438 ReplaceAndFailover tdx]
440 -- | Compute the best move for a given instance.
441 checkInstanceMove :: [Ndx] -- ^ Allowed target node indices
442 -> Bool -- ^ Whether disk moves are allowed
443 -> Table -- ^ Original table
444 -> Instance.Instance -- ^ Instance to move
445 -> Table -- ^ Best new table for this instance
446 checkInstanceMove nodes_idx disk_moves ini_tbl target =
448 opdx = Instance.pNode target
449 osdx = Instance.sNode target
450 nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
451 use_secondary = elem osdx nodes_idx
452 aft_failover = if use_secondary -- if allowed to failover
453 then checkSingleStep ini_tbl target ini_tbl Failover
455 all_moves = if disk_moves
456 then concatMap (possibleMoves use_secondary) nodes
459 -- iterate over the possible nodes for this instance
460 foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
462 -- | Compute the best next move.
463 checkMove :: [Ndx] -- ^ Allowed target node indices
464 -> Bool -- ^ Whether disk moves are allowed
465 -> Table -- ^ The current solution
466 -> [Instance.Instance] -- ^ List of instances still to move
467 -> Table -- ^ The new solution
468 checkMove nodes_idx disk_moves ini_tbl victims =
469 let Table _ _ _ ini_plc = ini_tbl
470 -- iterate over all instances, computing the best move
474 compareTables step_tbl $
475 checkInstanceMove nodes_idx disk_moves ini_tbl em)
477 Table _ _ _ best_plc = best_tbl
478 in if length best_plc == length ini_plc
479 then ini_tbl -- no advancement
482 -- | Check if we are allowed to go deeper in the balancing
483 doNextBalance :: Table -- ^ The starting table
484 -> Int -- ^ Remaining length
485 -> Score -- ^ Score at which to stop
486 -> Bool -- ^ The resulting table and commands
487 doNextBalance ini_tbl max_rounds min_score =
488 let Table _ _ ini_cv ini_plc = ini_tbl
489 ini_plc_len = length ini_plc
490 in (max_rounds < 0 || ini_plc_len < max_rounds) && ini_cv > min_score
492 -- | Run a balance move
493 tryBalance :: Table -- ^ The starting table
494 -> Bool -- ^ Allow disk moves
495 -> Bool -- ^ Only evacuate moves
496 -> Maybe Table -- ^ The resulting table and commands
497 tryBalance ini_tbl disk_moves evac_mode =
498 let Table ini_nl ini_il ini_cv _ = ini_tbl
499 all_inst = Container.elems ini_il
500 all_inst' = if evac_mode
501 then let bad_nodes = map Node.idx . filter Node.offline $
502 Container.elems ini_nl
503 in filter (\e -> Instance.sNode e `elem` bad_nodes ||
504 Instance.pNode e `elem` bad_nodes)
507 reloc_inst = filter Instance.movable all_inst'
508 node_idx = map Node.idx . filter (not . Node.offline) $
509 Container.elems ini_nl
510 fin_tbl = checkMove node_idx disk_moves ini_tbl reloc_inst
511 (Table _ _ fin_cv _) = fin_tbl
514 then Just fin_tbl -- this round made success, return the new table
517 -- * Allocation functions
519 -- | Build failure stats out of a list of failures
520 collapseFailures :: [FailMode] -> FailStats
521 collapseFailures flst =
522 map (\k -> (k, length $ filter (k ==) flst)) [minBound..maxBound]
524 -- | Update current Allocation solution and failure stats with new
526 concatAllocs :: AllocSolution -> OpResult Node.AllocElement -> AllocSolution
527 concatAllocs (flst, cntok, sols) (OpFail reason) = (reason:flst, cntok, sols)
529 concatAllocs (flst, cntok, osols) (OpGood ns@(nl, _, _)) =
530 let nscore = compCV nl
531 -- Choose the old or new solution, based on the cluster score
532 nsols = case osols of
538 -- FIXME: here we simply concat to lists with more
539 -- than one element; we should instead abort, since
540 -- this is not a valid usage of this function
541 xs -> (nscore, ns):xs
543 -- Note: we force evaluation of nsols here in order to keep the
544 -- memory profile low - we know that we will need nsols for sure
545 -- in the next cycle, so we force evaluation of nsols, since the
546 -- foldl' in the caller will only evaluate the tuple, but not the
547 -- elements of the tuple
548 in nsols `seq` nsuc `seq` (flst, nsuc, nsols)
550 -- | Try to allocate an instance on the cluster.
551 tryAlloc :: (Monad m) =>
552 Node.List -- ^ The node list
553 -> Instance.List -- ^ The instance list
554 -> Instance.Instance -- ^ The instance to allocate
555 -> Int -- ^ Required number of nodes
556 -> m AllocSolution -- ^ Possible solution list
557 tryAlloc nl _ inst 2 =
558 let all_nodes = getOnline nl
559 all_pairs = liftM2 (,) all_nodes all_nodes
560 ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs
561 sols = foldl' (\cstate (p, s) ->
562 concatAllocs cstate $ allocateOnPair nl inst p s
563 ) ([], 0, []) ok_pairs
566 tryAlloc nl _ inst 1 =
567 let all_nodes = getOnline nl
568 sols = foldl' (\cstate ->
569 concatAllocs cstate . allocateOnSingle nl inst
570 ) ([], 0, []) all_nodes
573 tryAlloc _ _ _ reqn = fail $ "Unsupported number of allocation \
574 \destinations required (" ++ show reqn ++
575 "), only two supported"
577 -- | Try to allocate an instance on the cluster.
578 tryReloc :: (Monad m) =>
579 Node.List -- ^ The node list
580 -> Instance.List -- ^ The instance list
581 -> Idx -- ^ The index of the instance to move
582 -> Int -- ^ The number of nodes required
583 -> [Ndx] -- ^ Nodes which should not be used
584 -> m AllocSolution -- ^ Solution list
585 tryReloc nl il xid 1 ex_idx =
586 let all_nodes = getOnline nl
587 inst = Container.find xid il
588 ex_idx' = Instance.pNode inst:ex_idx
589 valid_nodes = filter (not . flip elem ex_idx' . Node.idx) all_nodes
590 valid_idxes = map Node.idx valid_nodes
591 sols1 = foldl' (\cstate x ->
594 applyMove nl inst (ReplaceSecondary x)
595 return (mnl, i, [Container.find x mnl])
596 in concatAllocs cstate em
597 ) ([], 0, []) valid_idxes
600 tryReloc _ _ _ reqn _ = fail $ "Unsupported number of relocation \
601 \destinations required (" ++ show reqn ++
602 "), only one supported"
604 -- | Try to evacuate a list of nodes.
605 tryEvac :: (Monad m) =>
606 Node.List -- ^ The node list
607 -> Instance.List -- ^ The instance list
608 -> [Ndx] -- ^ Nodes to be evacuated
609 -> m AllocSolution -- ^ Solution list
610 tryEvac nl il ex_ndx =
611 let ex_nodes = map (`Container.find` nl) ex_ndx
612 all_insts = nub . concatMap Node.sList $ ex_nodes
614 (_, sol) <- foldM (\(nl', (_, _, rsols)) idx -> do
615 -- FIXME: hardcoded one node here
616 (fm, cs, aes) <- tryReloc nl' il idx 1 ex_ndx
618 csol@(_, (nl'', _, _)):_ ->
619 return (nl'', (fm, cs, csol:rsols))
620 _ -> fail $ "Can't evacuate instance " ++
622 ) (nl, ([], 0, [])) all_insts
625 -- | Recursively place instances on the cluster until we're out of space
626 iterateAlloc :: Node.List
630 -> [Instance.Instance]
631 -> Result (FailStats, Node.List, [Instance.Instance])
632 iterateAlloc nl il newinst nreq ixes =
633 let depth = length ixes
634 newname = printf "new-%d" depth::String
635 newidx = length (Container.elems il) + depth
636 newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
637 in case tryAlloc nl il newi2 nreq of
639 Ok (errs, _, sols3) ->
641 [] -> Ok (collapseFailures errs, nl, ixes)
642 (_, (xnl, xi, _)):[] ->
643 iterateAlloc xnl il newinst nreq $! (xi:ixes)
644 _ -> Bad "Internal error: multiple solutions for single\
647 tieredAlloc :: Node.List
651 -> [Instance.Instance]
652 -> Result (FailStats, Node.List, [Instance.Instance])
653 tieredAlloc nl il newinst nreq ixes =
654 case iterateAlloc nl il newinst nreq ixes of
656 Ok (errs, nl', ixes') ->
657 case Instance.shrinkByType newinst . fst . last $
658 sortBy (comparing snd) errs of
659 Bad _ -> Ok (errs, nl', ixes')
661 tieredAlloc nl' il newinst' nreq ixes'
663 -- * Formatting functions
665 -- | Given the original and final nodes, computes the relocation description.
666 computeMoves :: Instance.Instance -- ^ The instance to be moved
667 -> String -- ^ The instance name
668 -> IMove -- ^ The move being performed
669 -> String -- ^ New primary
670 -> String -- ^ New secondary
671 -> (String, [String])
672 -- ^ Tuple of moves and commands list; moves is containing
673 -- either @/f/@ for failover or @/r:name/@ for replace
674 -- secondary, while the command list holds gnt-instance
675 -- commands (without that prefix), e.g \"@failover instance1@\"
676 computeMoves i inam mv c d =
678 Failover -> ("f", [mig])
679 FailoverAndReplace _ -> (printf "f r:%s" d, [mig, rep d])
680 ReplaceSecondary _ -> (printf "r:%s" d, [rep d])
681 ReplaceAndFailover _ -> (printf "r:%s f" c, [rep c, mig])
682 ReplacePrimary _ -> (printf "f r:%s f" c, [mig, rep c, mig])
683 where morf = if Instance.running i then "migrate" else "failover"
684 mig = printf "%s -f %s" morf inam::String
685 rep n = printf "replace-disks -n %s %s" n inam
687 -- | Converts a placement to string format.
688 printSolutionLine :: Node.List -- ^ The node list
689 -> Instance.List -- ^ The instance list
690 -> Int -- ^ Maximum node name length
691 -> Int -- ^ Maximum instance name length
692 -> Placement -- ^ The current placement
693 -> Int -- ^ The index of the placement in
695 -> (String, [String])
696 printSolutionLine nl il nmlen imlen plc pos =
698 pmlen = (2*nmlen + 1)
699 (i, p, s, mv, c) = plc
700 inst = Container.find i il
701 inam = Instance.alias inst
702 npri = Node.alias $ Container.find p nl
703 nsec = Node.alias $ Container.find s nl
704 opri = Node.alias $ Container.find (Instance.pNode inst) nl
705 osec = Node.alias $ Container.find (Instance.sNode inst) nl
706 (moves, cmds) = computeMoves inst inam mv npri nsec
707 ostr = printf "%s:%s" opri osec::String
708 nstr = printf "%s:%s" npri nsec::String
710 (printf " %3d. %-*s %-*s => %-*s %.8f a=%s"
711 pos imlen inam pmlen ostr
715 -- | Return the instance and involved nodes in an instance move.
716 involvedNodes :: Instance.List -> Placement -> [Ndx]
717 involvedNodes il plc =
718 let (i, np, ns, _, _) = plc
719 inst = Container.find i il
720 op = Instance.pNode inst
721 os = Instance.sNode inst
722 in nub [np, ns, op, os]
724 -- | Inner function for splitJobs, that either appends the next job to
725 -- the current jobset, or starts a new jobset.
726 mergeJobs :: ([JobSet], [Ndx]) -> MoveJob -> ([JobSet], [Ndx])
727 mergeJobs ([], _) n@(ndx, _, _, _) = ([[n]], ndx)
728 mergeJobs (cjs@(j:js), nbuf) n@(ndx, _, _, _)
729 | null (ndx `intersect` nbuf) = ((n:j):js, ndx ++ nbuf)
730 | otherwise = ([n]:cjs, ndx)
732 -- | Break a list of moves into independent groups. Note that this
733 -- will reverse the order of jobs.
734 splitJobs :: [MoveJob] -> [JobSet]
735 splitJobs = fst . foldl mergeJobs ([], [])
737 -- | Given a list of commands, prefix them with @gnt-instance@ and
738 -- also beautify the display a little.
739 formatJob :: Int -> Int -> (Int, MoveJob) -> [String]
740 formatJob jsn jsl (sn, (_, _, _, cmds)) =
742 printf " echo job %d/%d" jsn sn:
744 map (" gnt-instance " ++) cmds
746 then ["", printf "echo jobset %d, %d jobs" jsn jsl] ++ out
749 -- | Given a list of commands, prefix them with @gnt-instance@ and
750 -- also beautify the display a little.
751 formatCmds :: [JobSet] -> String
754 concatMap (\(jsn, js) -> concatMap (formatJob jsn (length js))
758 -- | Print the node list.
759 printNodes :: Node.List -> [String] -> String
761 let fields = case fs of
762 [] -> Node.defaultFields
763 "+":rest -> Node.defaultFields ++ rest
765 snl = sortBy (comparing Node.idx) (Container.elems nl)
766 (header, isnum) = unzip $ map Node.showHeader fields
767 in unlines . map ((:) ' ' . intercalate " ") $
768 formatTable (header:map (Node.list fields) snl) isnum
770 -- | Print the instance list.
771 printInsts :: Node.List -> Instance.List -> String
773 let sil = sortBy (comparing Instance.idx) (Container.elems il)
774 helper inst = [ if Instance.running inst then "R" else " "
776 , Container.nameOf nl (Instance.pNode inst)
777 , let sdx = Instance.sNode inst
778 in if sdx == Node.noSecondary
780 else Container.nameOf nl sdx
781 , printf "%3d" $ Instance.vcpus inst
782 , printf "%5d" $ Instance.mem inst
783 , printf "%5d" $ Instance.dsk inst `div` 1024
789 where DynUtil lC lM lD lN = Instance.util inst
790 header = [ "F", "Name", "Pri_node", "Sec_node", "vcpu", "mem"
791 , "dsk", "lCpu", "lMem", "lDsk", "lNet" ]
792 isnum = False:False:False:False:repeat True
793 in unlines . map ((:) ' ' . intercalate " ") $
794 formatTable (header:map helper sil) isnum
796 -- | Shows statistics for a given node list.
797 printStats :: Node.List -> String
799 let dcvs = compDetailedCV nl
800 hd = zip (detailedCVNames ++ repeat "unknown") dcvs
801 formatted = map (\(header, val) ->
802 printf "%s=%.8f" header val::String) hd
803 in intercalate ", " formatted
805 -- | Convert a placement into a list of OpCodes (basically a job).
806 iMoveToJob :: Node.List -> Instance.List
807 -> Idx -> IMove -> [OpCodes.OpCode]
808 iMoveToJob nl il idx move =
809 let inst = Container.find idx il
810 iname = Instance.name inst
811 lookNode = Just . Container.nameOf nl
812 opF = if Instance.running inst
813 then OpCodes.OpMigrateInstance iname True False
814 else OpCodes.OpFailoverInstance iname False
815 opR n = OpCodes.OpReplaceDisks iname (lookNode n)
816 OpCodes.ReplaceNewSecondary [] Nothing
819 ReplacePrimary np -> [ opF, opR np, opF ]
820 ReplaceSecondary ns -> [ opR ns ]
821 ReplaceAndFailover np -> [ opR np, opF ]
822 FailoverAndReplace ns -> [ opF, opR ns ]