module Ganeti.HTools.Cluster
(
AllocDetails(..)
, Table(..)
, CStats(..)
, AllocNodes
, AllocResult
, AllocMethod
, GenericAllocSolutionList
, AllocSolutionList
, totalResources
, computeAllocationDelta
, hasRequiredNetworks
, computeBadItems
, printSolutionLine
, formatCmds
, involvedNodes
, getMoves
, splitJobs
, printNodes
, printInsts
, doNextBalance
, tryBalance
, iMoveToJob
, genAllocNodes
, tryAlloc
, tryGroupAlloc
, tryMGAlloc
, filterMGResults
, sortMGResults
, tryChangeGroup
, allocList
, iterateAlloc
, tieredAlloc
, instanceGroup
, findSplitInstances
) where
import Prelude ()
import Ganeti.Prelude
import Control.Applicative (liftA2)
import Control.Arrow ((&&&))
import Control.Monad (unless)
import qualified Data.IntSet as IntSet
import qualified Data.Set as Set
import Data.List ( nub
, sortBy
, foldl'
, intersect
, partition
, (\\)
, sort
, intercalate)
import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
import Data.Ord (comparing)
import Text.Printf (printf)
import Ganeti.BasicTypes
import Ganeti.HTools.AlgorithmParams (AlgorithmOptions(..), defaultOptions)
import qualified Ganeti.HTools.Container as Container
import Ganeti.HTools.Cluster.AllocatePrimitives ( allocateOnSingle
, allocateOnPair)
import Ganeti.HTools.Cluster.AllocationSolution
( GenericAllocSolution(..) , AllocSolution, emptyAllocSolution
, sumAllocs, extractNl, updateIl
, annotateSolution, solutionDescription, collapseFailures
, emptyAllocCollection, concatAllocCollections, collectionToSolution )
import Ganeti.HTools.Cluster.Evacuate ( EvacSolution(..), emptyEvacSolution
, updateEvacSolution, reverseEvacSolution
, nodeEvacInstance)
import Ganeti.HTools.Cluster.Metrics (compCV, compClusterStatistics)
import Ganeti.HTools.Cluster.Moves (applyMoveEx)
import Ganeti.HTools.Cluster.Utils (splitCluster, instancePriGroup
, availableGroupNodes, iMoveToJob)
import Ganeti.HTools.GlobalN1 (allocGlobalN1, redundant)
import qualified Ganeti.HTools.Instance as Instance
import qualified Ganeti.HTools.Nic as Nic
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Group as Group
import Ganeti.HTools.Types
import Ganeti.Compat
import Ganeti.Utils
import Ganeti.Types (EvacMode(..))
data AllocDetails = AllocDetails Int (Maybe String)
deriving (Show)
type AllocResult = (FailStats, Node.List, Instance.List,
[Instance.Instance], [CStats])
type GenericAllocSolutionList a =
[(Instance.Instance, GenericAllocSolution a)]
type AllocSolutionList = GenericAllocSolutionList Score
type AllocNodes = Either [Ndx] [(Ndx, [Ndx])]
data Table = Table Node.List Instance.List Score [Placement]
deriving (Show)
data CStats = CStats
{ csFmem :: Integer
, csFdsk :: Integer
, csFspn :: Integer
, csAmem :: Integer
, csAdsk :: Integer
, csAcpu :: Integer
, csMmem :: Integer
, csMdsk :: Integer
, csMcpu :: Integer
, csImem :: Integer
, csIdsk :: Integer
, csIspn :: Integer
, csIcpu :: Integer
, csTmem :: Double
, csTdsk :: Double
, csTspn :: Double
, csTcpu :: Double
, csVcpu :: Integer
, csNcpu :: Double
, csXmem :: Integer
, csNmem :: Integer
, csScore :: Score
, csNinst :: Int
} deriving (Show)
type AllocMethod = Node.List
-> Instance.List
-> Maybe Int
-> Instance.Instance
-> AllocNodes
-> [Instance.Instance]
-> [CStats]
-> Result AllocResult
verifyN1 :: [Node.Node] -> [Node.Node]
verifyN1 = filter Node.failN1
computeBadItems :: Node.List -> Instance.List ->
([Node.Node], [Instance.Instance])
computeBadItems nl il =
let bad_nodes = verifyN1 $ getOnline nl
bad_instances = map (`Container.find` il) .
sort . nub $
concatMap (\ n -> Node.sList n ++ Node.pList n) bad_nodes
in
(bad_nodes, bad_instances)
emptyCStats :: CStats
emptyCStats = CStats 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
updateCStats :: CStats -> Node.Node -> CStats
updateCStats cs node =
let CStats { csFmem = x_fmem, csFdsk = x_fdsk,
csAmem = x_amem, csAcpu = x_acpu, csAdsk = x_adsk,
csMmem = x_mmem, csMdsk = x_mdsk, csMcpu = x_mcpu,
csImem = x_imem, csIdsk = x_idsk, csIcpu = x_icpu,
csTmem = x_tmem, csTdsk = x_tdsk, csTcpu = x_tcpu,
csVcpu = x_vcpu, csNcpu = x_ncpu,
csXmem = x_xmem, csNmem = x_nmem, csNinst = x_ninst,
csFspn = x_fspn, csIspn = x_ispn, csTspn = x_tspn
}
= cs
inc_amem = Node.fMem node Node.rMem node
inc_amem' = if inc_amem > 0 then inc_amem else 0
inc_adsk = Node.availDisk node
inc_imem = truncate (Node.tMem node) Node.nMem node
Node.xMem node Node.fMem node
inc_icpu = Node.uCpu node
inc_idsk = truncate (Node.tDsk node) Node.fDsk node
inc_ispn = Node.tSpindles node Node.fSpindles node
inc_vcpu = Node.hiCpu node
inc_acpu = Node.availCpu node
inc_ncpu = fromIntegral (Node.uCpu node) /
iPolicyVcpuRatio (Node.iPolicy node)
in cs { csFmem = x_fmem + fromIntegral (Node.fMem node)
, csFdsk = x_fdsk + fromIntegral (Node.fDsk node)
, csFspn = x_fspn + fromIntegral (Node.fSpindles node)
, csAmem = x_amem + fromIntegral inc_amem'
, csAdsk = x_adsk + fromIntegral inc_adsk
, csAcpu = x_acpu + fromIntegral inc_acpu
, csMmem = max x_mmem (fromIntegral inc_amem')
, csMdsk = max x_mdsk (fromIntegral inc_adsk)
, csMcpu = max x_mcpu (fromIntegral inc_acpu)
, csImem = x_imem + fromIntegral inc_imem
, csIdsk = x_idsk + fromIntegral inc_idsk
, csIspn = x_ispn + fromIntegral inc_ispn
, csIcpu = x_icpu + fromIntegral inc_icpu
, csTmem = x_tmem + Node.tMem node
, csTdsk = x_tdsk + Node.tDsk node
, csTspn = x_tspn + fromIntegral (Node.tSpindles node)
, csTcpu = x_tcpu + Node.tCpu node
, csVcpu = x_vcpu + fromIntegral inc_vcpu
, csNcpu = x_ncpu + inc_ncpu
, csXmem = x_xmem + fromIntegral (Node.xMem node)
, csNmem = x_nmem + fromIntegral (Node.nMem node)
, csNinst = x_ninst + length (Node.pList node)
}
totalResources :: Node.List -> CStats
totalResources nl =
let cs = foldl' updateCStats emptyCStats . Container.elems $ nl
in cs { csScore = compCV nl }
computeAllocationDelta :: CStats -> CStats -> AllocStats
computeAllocationDelta cini cfin =
let CStats {csImem = i_imem, csIdsk = i_idsk, csIcpu = i_icpu,
csNcpu = i_ncpu, csIspn = i_ispn } = cini
CStats {csImem = f_imem, csIdsk = f_idsk, csIcpu = f_icpu,
csTmem = t_mem, csTdsk = t_dsk, csVcpu = f_vcpu,
csNcpu = f_ncpu, csTcpu = f_tcpu,
csIspn = f_ispn, csTspn = t_spn } = cfin
rini = AllocInfo { allocInfoVCpus = fromIntegral i_icpu
, allocInfoNCpus = i_ncpu
, allocInfoMem = fromIntegral i_imem
, allocInfoDisk = fromIntegral i_idsk
, allocInfoSpn = fromIntegral i_ispn
}
rfin = AllocInfo { allocInfoVCpus = fromIntegral (f_icpu i_icpu)
, allocInfoNCpus = f_ncpu i_ncpu
, allocInfoMem = fromIntegral (f_imem i_imem)
, allocInfoDisk = fromIntegral (f_idsk i_idsk)
, allocInfoSpn = fromIntegral (f_ispn i_ispn)
}
runa = AllocInfo { allocInfoVCpus = fromIntegral (f_vcpu f_icpu)
, allocInfoNCpus = f_tcpu f_ncpu
, allocInfoMem = truncate t_mem fromIntegral f_imem
, allocInfoDisk = truncate t_dsk fromIntegral f_idsk
, allocInfoSpn = truncate t_spn fromIntegral f_ispn
}
in (rini, rfin, runa)
getOnline :: Node.List -> [Node.Node]
getOnline = filter (not . Node.offline) . Container.elems
compareTables :: Table -> Table -> Table
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
if a_cv > b_cv then b else a
checkSingleStep :: Bool
-> Table
-> Instance.Instance
-> Table
-> IMove
-> Table
checkSingleStep force ini_tbl target cur_tbl move =
let Table ini_nl ini_il _ ini_plc = ini_tbl
tmp_resu = applyMoveEx force ini_nl target move
in case tmp_resu of
Bad _ -> cur_tbl
Ok (upd_nl, new_inst, pri_idx, sec_idx) ->
let tgt_idx = Instance.idx target
upd_cvar = compCV upd_nl
upd_il = Container.add tgt_idx new_inst ini_il
upd_plc = (tgt_idx, pri_idx, sec_idx, move, upd_cvar):ini_plc
upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
in compareTables cur_tbl upd_tbl
migrationMoves :: MirrorType
-> Bool
-> [Ndx]
-> [IMove]
migrationMoves MirrorNone _ _ = []
migrationMoves MirrorInternal False _ = []
migrationMoves MirrorInternal True _ = [Failover]
migrationMoves MirrorExternal _ nodes_idx = map FailoverToAny nodes_idx
diskMoves :: MirrorType
-> Bool
-> Bool
-> (Bool, Bool)
-> [Ndx]
-> [IMove]
diskMoves MirrorNone _ _ _ _ = []
diskMoves MirrorExternal _ _ _ _ = []
diskMoves MirrorInternal valid_sec inst_moves restr nodes_idx =
concatMap (intMirrSingleDiskMove valid_sec inst_moves restr) nodes_idx
where
intMirrSingleDiskMove _ False _ tdx =
[ReplaceSecondary tdx]
intMirrSingleDiskMove _ _ (True, False) tdx =
[ReplaceSecondary tdx]
intMirrSingleDiskMove True True (False, _) tdx =
[ ReplaceSecondary tdx
, ReplaceAndFailover tdx
, ReplacePrimary tdx
, FailoverAndReplace tdx
]
intMirrSingleDiskMove True True (True, True) tdx =
[ ReplaceSecondary tdx
, ReplaceAndFailover tdx
, FailoverAndReplace tdx
]
intMirrSingleDiskMove False True _ tdx =
[ ReplaceSecondary tdx
, ReplaceAndFailover tdx
]
checkInstanceMove :: AlgorithmOptions
-> [Ndx]
-> Table
-> Instance.Instance
-> (Table, Table)
checkInstanceMove opts nodes_idx ini_tbl@(Table nl _ _ _) target =
let force = algIgnoreSoftErrors opts
inst_moves = algInstanceMoves opts
rest_mig = algRestrictedMigration opts
opdx = Instance.pNode target
osdx = Instance.sNode target
bad_nodes = [opdx, osdx]
nodes = filter (`notElem` bad_nodes) nodes_idx
mir_type = Instance.mirrorType target
use_secondary = elem osdx nodes_idx && inst_moves
primary_drained = Node.offline
. flip Container.find nl
$ Instance.pNode target
migrations = migrationMoves mir_type use_secondary nodes
disk_moves = diskMoves mir_type use_secondary inst_moves
(rest_mig, primary_drained) nodes
best_migr_tbl =
if inst_moves
then foldl' (checkSingleStep force ini_tbl target) ini_tbl migrations
else ini_tbl
best_tbl =
foldl' (checkSingleStep force ini_tbl target) best_migr_tbl disk_moves
in (best_migr_tbl, best_tbl)
checkMove :: AlgorithmOptions
-> [Ndx]
-> Table
-> [Instance.Instance]
-> Table
checkMove opts nodes_idx ini_tbl@(Table _ _ ini_cv _) victims =
let disk_moves = algDiskMoves opts
disk_moves_f = algDiskMovesFactor opts
table_pairs = parMap rwhnf (checkInstanceMove opts nodes_idx ini_tbl)
victims
best_migr_tbl@(Table _ _ best_migr_cv _) =
foldl' compareTables ini_tbl $ map fst table_pairs
best_tbl@(Table _ _ best_cv _) =
foldl' compareTables ini_tbl $ map snd table_pairs
in if not disk_moves
|| ini_cv best_cv <= (ini_cv best_migr_cv) * disk_moves_f
then best_migr_tbl
else best_tbl
doNextBalance :: Table
-> Int
-> Score
-> Bool
doNextBalance ini_tbl max_rounds min_score =
let Table _ _ ini_cv ini_plc = ini_tbl
ini_plc_len = length ini_plc
in (max_rounds < 0 || ini_plc_len < max_rounds) && ini_cv > min_score
tryBalance :: AlgorithmOptions
-> Table
-> Maybe Table
tryBalance opts ini_tbl =
let evac_mode = algEvacMode opts
mg_limit = algMinGainLimit opts
min_gain = algMinGain opts
Table ini_nl ini_il ini_cv _ = ini_tbl
all_inst = Container.elems ini_il
all_nodes = Container.elems ini_nl
(offline_nodes, online_nodes) = partition Node.offline all_nodes
all_inst' = if evac_mode
then let bad_nodes = map Node.idx offline_nodes
in filter (any (`elem` bad_nodes) .
Instance.allNodes) all_inst
else all_inst
reloc_inst = filter (\i -> Instance.movable i &&
Instance.autoBalance i) all_inst'
node_idx = map Node.idx online_nodes
allowed_node = maybe (const True) (flip Set.member)
$ algAllowedNodes opts
good_nidx = filter allowed_node node_idx
allowed_inst = liftA2 (&&) (allowed_node . Instance.pNode)
(liftA2 (||) allowed_node (< 0) . Instance.sNode)
good_reloc_inst = filter allowed_inst reloc_inst
fin_tbl = checkMove opts good_nidx ini_tbl good_reloc_inst
(Table _ _ fin_cv _) = fin_tbl
in
if fin_cv < ini_cv && (ini_cv > mg_limit || ini_cv fin_cv >= min_gain)
then Just fin_tbl
else Nothing
genAllocNodes :: AlgorithmOptions
-> Group.List
-> Node.List
-> Int
-> Bool
-> Result AllocNodes
genAllocNodes opts gl nl count drop_unalloc =
let filter_fn = if drop_unalloc
then filter (Group.isAllocable .
flip Container.find gl . Node.group)
else id
restrict_fn = maybe id (\ns -> filter (flip elem ns . Node.name))
$ algRestrictToNodes opts
all_nodes = restrict_fn . filter_fn $ getOnline nl
all_pairs = [(Node.idx p,
[Node.idx s | s <- all_nodes,
Node.idx p /= Node.idx s,
Node.group p == Node.group s]) |
p <- all_nodes]
in case count of
1 -> Ok (Left (map Node.idx all_nodes))
2 -> Ok (Right (filter (not . null . snd) all_pairs))
_ -> Bad "Unsupported number of nodes, only one or two supported"
tryAlloc :: (Monad m) =>
AlgorithmOptions
-> Node.List
-> Instance.List
-> Instance.Instance
-> AllocNodes
-> m AllocSolution
tryAlloc _ _ _ _ (Right []) = fail "Not enough online nodes"
tryAlloc opts nl il inst (Right ok_pairs) =
let cstat = compClusterStatistics $ Container.elems nl
n1pred = if algCapacity opts
then allocGlobalN1 opts nl il
else const True
psols = parMap rwhnf (\(p, ss) ->
collectionToSolution FailN1 n1pred $
foldl (\cstate ->
concatAllocCollections cstate
. allocateOnPair opts cstat nl inst p)
emptyAllocCollection ss) ok_pairs
sols = foldl' sumAllocs emptyAllocSolution psols
in return $ annotateSolution sols
tryAlloc _ _ _ _ (Left []) = fail "No online nodes"
tryAlloc opts nl il inst (Left all_nodes) =
let sols = foldl (\cstate ->
concatAllocCollections cstate
. allocateOnSingle opts nl inst
) emptyAllocCollection all_nodes
n1pred = if algCapacity opts
then allocGlobalN1 opts nl il
else const True
in return . annotateSolution
$ collectionToSolution FailN1 n1pred sols
filterMGResults :: [(Group.Group, Result (GenericAllocSolution a))]
-> [(Group.Group, GenericAllocSolution a)]
filterMGResults = foldl' fn []
where unallocable = not . Group.isAllocable
fn accu (grp, rasol) =
case rasol of
Bad _ -> accu
Ok sol | isNothing (asSolution sol) -> accu
| unallocable grp -> accu
| otherwise -> (grp, sol):accu
sortMGResults :: Ord a
=> [(Group.Group, GenericAllocSolution a)]
-> [(Group.Group, GenericAllocSolution a)]
sortMGResults sols =
let extractScore (_, _, _, x) = x
solScore (grp, sol) = (Group.allocPolicy grp,
(extractScore . fromJust . asSolution) sol)
in sortBy (comparing solScore) sols
hasRequiredNetworks :: Group.Group -> Instance.Instance -> Bool
hasRequiredNetworks ng = all hasNetwork . Instance.nics
where hasNetwork = maybe True (`elem` Group.networks ng) . Nic.network
filterValidGroups :: [(Group.Group, (Node.List, Instance.List))]
-> Instance.Instance
-> ([(Group.Group, (Node.List, Instance.List))], [String])
filterValidGroups [] _ = ([], [])
filterValidGroups (ng:ngs) inst =
let (valid_ngs, msgs) = filterValidGroups ngs inst
in if hasRequiredNetworks (fst ng) inst
then (ng:valid_ngs, msgs)
else (valid_ngs,
("group " ++ Group.name (fst ng) ++
" is not connected to a network required by instance " ++
Instance.name inst):msgs)
findAllocation :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> Gdx
-> Instance.Instance
-> Int
-> Result (AllocSolution, [String])
findAllocation opts mggl mgnl mgil gdx inst cnt = do
let belongsTo nl' nidx = nidx `elem` map Node.idx (Container.elems nl')
nl = Container.filter ((== gdx) . Node.group) mgnl
il = Container.filter (belongsTo nl . Instance.pNode) mgil
group' = Container.find gdx mggl
unless (hasRequiredNetworks group' inst) . failError
$ "The group " ++ Group.name group' ++ " is not connected to\
\ a network required by instance " ++ Instance.name inst
solution <- genAllocNodes opts mggl nl cnt False >>= tryAlloc opts nl il inst
return (solution, solutionDescription (group', return solution))
findBestAllocGroup :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> Maybe [Gdx]
-> Instance.Instance
-> Int
-> Result (Group.Group, AllocSolution, [String])
findBestAllocGroup opts mggl mgnl mgil allowed_gdxs inst cnt =
let groups_by_idx = splitCluster mgnl mgil
groups = map (\(gid, d) -> (Container.find gid mggl, d)) groups_by_idx
groups' = maybe groups
(\gs -> filter ((`elem` gs) . Group.idx . fst) groups)
allowed_gdxs
(groups'', filter_group_msgs) = filterValidGroups groups' inst
sols = map (\(gr, (nl, _)) ->
(gr, genAllocNodes opts mggl nl cnt False >>=
tryAlloc opts mgnl mgil inst))
groups''::[(Group.Group, Result AllocSolution)]
all_msgs = filter_group_msgs ++ concatMap solutionDescription sols
goodSols = filterMGResults sols
sortedSols = sortMGResults goodSols
in case sortedSols of
[] -> Bad $ if null groups'
then "no groups for evacuation: allowed groups was " ++
show allowed_gdxs ++ ", all groups: " ++
show (map fst groups)
else intercalate ", " all_msgs
(final_group, final_sol):_ -> return (final_group, final_sol, all_msgs)
tryMGAlloc :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> Instance.Instance
-> Int
-> Result AllocSolution
tryMGAlloc opts mggl mgnl mgil inst cnt = do
(best_group, solution, all_msgs) <-
findBestAllocGroup opts mggl mgnl mgil Nothing inst cnt
let group_name = Group.name best_group
selmsg = "Selected group: " ++ group_name
return $ solution { asLog = selmsg:all_msgs }
tryGroupAlloc :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> String
-> Instance.Instance
-> Int
-> Result AllocSolution
tryGroupAlloc opts mggl mgnl ngil gn inst cnt = do
gdx <- Group.idx <$> Container.findByName mggl gn
(solution, msgs) <- findAllocation opts mggl mgnl ngil gdx inst cnt
return $ solution { asLog = msgs }
allocList :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> [(Instance.Instance, AllocDetails)]
-> AllocSolutionList
-> Result (Node.List, Instance.List,
AllocSolutionList)
allocList _ _ nl il [] result = Ok (nl, il, result)
allocList opts gl nl il ((xi, AllocDetails xicnt mgn):xies) result = do
ares <- case mgn of
Nothing -> tryMGAlloc opts gl nl il xi xicnt
Just gn -> tryGroupAlloc opts gl nl il gn xi xicnt
let sol = asSolution ares
nl' = extractNl nl il sol
il' = updateIl il sol
allocList opts gl nl' il' xies ((xi, ares):result)
tryChangeGroup :: AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> [Gdx]
-> [Idx]
-> Result (Node.List, Instance.List, EvacSolution)
tryChangeGroup opts gl ini_nl ini_il gdxs idxs =
let evac_gdxs = nub $ map (instancePriGroup ini_nl .
flip Container.find ini_il) idxs
target_gdxs = (if null gdxs
then Container.keys gl
else gdxs) \\ evac_gdxs
offline = map Node.idx . filter Node.offline $ Container.elems ini_nl
excl_ndx = foldl' (flip IntSet.insert) IntSet.empty offline
group_ndx = map (\(gdx, (nl, _)) -> (gdx, map Node.idx
(Container.elems nl))) $
splitCluster ini_nl ini_il
(fin_nl, fin_il, esol) =
foldl' (\state@(nl, il, _) inst ->
let solution = do
let ncnt = Instance.requiredNodes $
Instance.diskTemplate inst
(grp, _, _) <- findBestAllocGroup opts gl nl il
(Just target_gdxs) inst ncnt
let gdx = Group.idx grp
av_nodes <- availableGroupNodes group_ndx
excl_ndx gdx
nodeEvacInstance defaultOptions
nl il ChangeAll inst gdx av_nodes
in updateEvacSolution state (Instance.idx inst) solution
)
(ini_nl, ini_il, emptyEvacSolution)
(map (`Container.find` ini_il) idxs)
in return (fin_nl, fin_il, reverseEvacSolution esol)
iterateAllocSmallStep :: AlgorithmOptions -> AllocMethod
iterateAllocSmallStep opts nl il limit newinst allocnodes ixes cstats =
let depth = length ixes
newname = printf "new-%d" depth::String
newidx = Container.size il
newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
newlimit = fmap (flip () 1) limit
opts' = if Instance.diskTemplate newi2 == DTDrbd8
then opts { algCapacity = False }
else opts
in case tryAlloc opts' nl il newi2 allocnodes of
Bad s -> Bad s
Ok (AllocSolution { asFailures = errs, asSolution = sols3 }) ->
let newsol = Ok (collapseFailures errs, nl, il, ixes, cstats) in
case sols3 of
Nothing -> newsol
Just (xnl, xi, _, _) ->
if limit == Just 0
then newsol
else iterateAllocSmallStep opts xnl (Container.add newidx xi il)
newlimit newinst allocnodes (xi:ixes)
(totalResources xnl:cstats)
guessBigstepSize :: Node.List -> Instance.Instance -> Int
guessBigstepSize nl inst =
let nodes = Container.elems nl
totalUnusedMemory = sum $ map Node.fMem nodes
reserved = round . maximum $ map Node.tMem nodes
capacity = (totalUnusedMemory reserved) `div` Instance.mem inst
guess = capacity Container.size nl
in if guess < 20 then 20 else guess
iterateAlloc' :: Bool -> AlgorithmOptions -> AllocMethod
iterateAlloc' tryHugestep opts nl il limit newinst allocnodes ixes cstats =
if not $ algCapacity opts
then iterateAllocSmallStep opts nl il limit newinst allocnodes ixes cstats
else let bigstepsize = if tryHugestep
then guessBigstepSize nl newinst
else 10
(limit', newlimit) = maybe (Just bigstepsize, Nothing)
(Just . min bigstepsize
&&& Just . max 0 . flip () bigstepsize)
limit
opts' = opts { algCapacity = False }
in case iterateAllocSmallStep opts' nl il limit'
newinst allocnodes ixes cstats of
Bad s -> Bad s
Ok res@(_, nl', il', ixes', cstats') | redundant opts nl' il' ->
if newlimit == Just 0 || length ixes' == length ixes
then return res
else iterateAlloc' tryHugestep opts nl' il' newlimit newinst
allocnodes ixes' cstats'
_ -> if tryHugestep
then iterateAlloc' False opts nl il limit newinst allocnodes
ixes cstats
else iterateAllocSmallStep opts nl il limit newinst
allocnodes ixes cstats
iterateAlloc :: AlgorithmOptions -> AllocMethod
iterateAlloc = iterateAlloc' True
sufficesShrinking :: (Instance.Instance -> AllocSolution) -> Instance.Instance
-> FailMode -> Maybe Instance.Instance
sufficesShrinking allocFn inst fm =
case dropWhile (isNothing . asSolution . fst)
. takeWhile (liftA2 (||) (elem fm . asFailures . fst)
(isJust . asSolution . fst))
. map (allocFn &&& id) $
iterateOk (`Instance.shrinkByType` fm) inst
of x:_ -> Just . snd $ x
_ -> Nothing
underlyingCause :: FailMode -> FailMode
underlyingCause FailN1 = FailMem
underlyingCause x = x
doShrink :: (Instance.Instance -> AllocSolution) -> Instance.Instance
-> FailMode -> Maybe Instance.Instance
doShrink allocFn inst fm =
let physRes = underlyingCause fm
getCount = runListHead 0 snd . filter ((==) physRes . fst)
. collapseFailures . map underlyingCause . asFailures
initialStat = getCount $ allocFn inst
hasChanged = ((/=) initialStat . getCount . fst)
lookAhead = 50
heuristics xs = if null (drop lookAhead xs)
then length xs `div` 2
else lookAhead
in fmap snd
. monotoneFind heuristics hasChanged
. map (allocFn &&& id)
$ iterateOk (`Instance.shrinkByType` physRes) inst
tieredAlloc :: AlgorithmOptions -> AllocMethod
tieredAlloc opts nl il limit newinst allocnodes ixes cstats =
case iterateAlloc opts nl il limit newinst allocnodes ixes cstats of
Bad s -> Bad s
Ok (errs, nl', il', ixes', cstats') ->
let newsol = Ok (errs, nl', il', ixes', cstats')
ixes_cnt = length ixes'
(stop, newlimit) = case limit of
Nothing -> (False, Nothing)
Just n -> (n <= ixes_cnt,
Just (n ixes_cnt))
sortedErrs = nub . map (underlyingCause . fst)
$ sortBy (flip $ comparing snd) errs
allocFn = fromMaybe emptyAllocSolution
. flip (tryAlloc opts nl' il') allocnodes
suffShrink = sufficesShrinking allocFn newinst
bigSteps = filter isJust . map suffShrink $ drop 1 sortedErrs
progress (Ok (_, _, _, newil', _)) (Ok (_, _, _, newil, _)) =
length newil' > length newil
progress _ _ = False
in if stop then newsol else
let newsol' = case map (doShrink allocFn newinst) sortedErrs of
Just newinst' : _ -> tieredAlloc opts nl' il' newlimit
newinst' allocnodes ixes' cstats'
_ -> newsol
in if progress newsol' newsol then newsol' else
case bigSteps of
Just newinst':_ -> tieredAlloc opts nl' il' newlimit
newinst' allocnodes ixes' cstats'
_ -> newsol
computeMoves :: Instance.Instance
-> String
-> IMove
-> String
-> String
-> (String, [String])
computeMoves i inam mv c d =
case mv of
Failover -> ("f", [mig])
FailoverToAny _ -> (printf "fa:%s" c, [mig_any])
FailoverAndReplace _ -> (printf "f r:%s" d, [mig, rep d])
ReplaceSecondary _ -> (printf "r:%s" d, [rep d])
ReplaceAndFailover _ -> (printf "r:%s f" c, [rep c, mig])
ReplacePrimary _ -> (printf "f r:%s f" c, [mig, rep c, mig])
where morf = if Instance.isRunning i then "migrate" else "failover"
mig = printf "%s -f %s" morf inam::String
mig_any = printf "%s -f -n %s %s" morf c inam::String
rep n = printf "replace-disks -n %s %s" n inam::String
printSolutionLine :: Node.List
-> Instance.List
-> Int
-> Int
-> Placement
-> Int
-> (String, [String])
printSolutionLine nl il nmlen imlen plc pos =
let pmlen = (2*nmlen + 1)
(i, p, s, mv, c) = plc
old_sec = Instance.sNode inst
inst = Container.find i il
inam = Instance.alias inst
npri = Node.alias $ Container.find p nl
nsec = Node.alias $ Container.find s nl
opri = Node.alias $ Container.find (Instance.pNode inst) nl
osec = Node.alias $ Container.find old_sec nl
(moves, cmds) = computeMoves inst inam mv npri nsec
ostr = if old_sec == Node.noSecondary
then printf "%s" opri::String
else printf "%s:%s" opri osec::String
nstr = if s == Node.noSecondary
then printf "%s" npri::String
else printf "%s:%s" npri nsec::String
in (printf " %3d. %-*s %-*s => %-*s %12.8f a=%s"
pos imlen inam pmlen ostr pmlen nstr c moves,
cmds)
involvedNodes :: Instance.List
-> Placement
-> [Ndx]
involvedNodes il plc =
let (i, np, ns, _, _) = plc
inst = Container.find i il
in nub . filter (>= 0) $ [np, ns] ++ Instance.allNodes inst
getMoves :: (Table, Table) -> [MoveJob]
getMoves (Table _ initial_il _ initial_plc, Table final_nl _ _ final_plc) =
let
plctoMoves (plc@(idx, p, s, mv, _)) =
let inst = Container.find idx initial_il
inst_name = Instance.name inst
affected = involvedNodes initial_il plc
np = Node.alias $ Container.find p final_nl
ns = Node.alias $ Container.find s final_nl
(_, cmds) = computeMoves inst inst_name mv np ns
in (affected, idx, mv, cmds)
in map plctoMoves . reverse . drop (length initial_plc) $ reverse final_plc
mergeJobs :: ([JobSet], [Ndx]) -> MoveJob -> ([JobSet], [Ndx])
mergeJobs ([], _) n@(ndx, _, _, _) = ([[n]], ndx)
mergeJobs (cjs@(j:js), nbuf) n@(ndx, _, _, _)
| null (ndx `intersect` nbuf) = ((n:j):js, ndx ++ nbuf)
| otherwise = ([n]:cjs, ndx)
splitJobs :: [MoveJob] -> [JobSet]
splitJobs = fst . foldl mergeJobs ([], [])
formatJob :: Int -> Int -> (Int, MoveJob) -> [String]
formatJob jsn jsl (sn, (_, _, _, cmds)) =
let out =
printf " echo job %d/%d" jsn sn:
printf " check":
map (" gnt-instance " ++) cmds
in if sn == 1
then ["", printf "echo jobset %d, %d jobs" jsn jsl] ++ out
else out
formatCmds :: [JobSet] -> String
formatCmds =
unlines .
concatMap (\(jsn, js) -> concatMap (formatJob jsn (length js))
(zip [1..] js)) .
zip [1..]
printNodes :: Node.List -> [String] -> String
printNodes nl fs =
let fields = case fs of
[] -> Node.defaultFields
"+":rest -> Node.defaultFields ++ rest
_ -> fs
snl = sortBy (comparing Node.idx) (Container.elems nl)
(header, isnum) = unzip $ map Node.showHeader fields
in printTable "" header (map (Node.list fields) snl) isnum
printInsts :: Node.List -> Instance.List -> String
printInsts nl il =
let sil = sortBy (comparing Instance.idx) (Container.elems il)
helper inst = [ if Instance.isRunning inst then "R" else " "
, Instance.name inst
, Container.nameOf nl (Instance.pNode inst)
, let sdx = Instance.sNode inst
in if sdx == Node.noSecondary
then ""
else Container.nameOf nl sdx
, if Instance.autoBalance inst then "Y" else "N"
, printf "%3d" $ Instance.vcpus inst
, printf "%5d" $ Instance.mem inst
, printf "%5d" $ Instance.dsk inst `div` 1024
, printf "%5.3f" lC
, printf "%5.3f" lM
, printf "%5.3f" lD
, printf "%5.3f" lN
]
where DynUtil lC lM lD lN = Instance.util inst
header = [ "F", "Name", "Pri_node", "Sec_node", "Auto_bal"
, "vcpu", "mem" , "dsk", "lCpu", "lMem", "lDsk", "lNet" ]
isnum = False:False:False:False:False:repeat True
in printTable "" header (map helper sil) isnum
instanceGroup :: Node.List -> Instance.Instance -> Result Gdx
instanceGroup nl i =
let sidx = Instance.sNode i
pnode = Container.find (Instance.pNode i) nl
snode = if sidx == Node.noSecondary
then pnode
else Container.find sidx nl
pgroup = Node.group pnode
sgroup = Node.group snode
in if pgroup /= sgroup
then fail ("Instance placed accross two node groups, primary " ++
show pgroup ++ ", secondary " ++ show sgroup)
else return pgroup
findSplitInstances :: Node.List -> Instance.List -> [Instance.Instance]
findSplitInstances nl =
filter (not . isOk . instanceGroup nl) . Container.elems