blob: 00413a6245f69d0562a2273daf26cd3f064cc2ba [file] [log] [blame]
{-| Implementation of special handling of dedicated clusters.
-}
{-
Copyright (C) 2014 Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-}
module Ganeti.HTools.Dedicated
( isDedicated
, testInstances
, allocationVector
, Metric
, lostAllocationsMetric
, allocateOnSingle
, allocateOnPair
, findAllocation
, runDedicatedAllocation
) where
import Prelude ()
import Ganeti.Prelude
import Control.Applicative (liftA2)
import Control.Arrow ((&&&))
import Control.Monad (unless, liftM, foldM, mplus)
import qualified Data.Foldable as F
import Data.Function (on)
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import Data.List (sortBy, intercalate)
import Ganeti.BasicTypes (iterateOk, Result, failError)
import qualified Ganeti.HTools.AlgorithmParams as Alg
import qualified Ganeti.HTools.Backend.IAlloc as IAlloc
import qualified Ganeti.HTools.Cluster as Cluster
import qualified Ganeti.HTools.Cluster.AllocationSolution as AllocSol
import qualified Ganeti.HTools.Cluster.Utils as ClusterUtils
import qualified Ganeti.HTools.Container as Container
import qualified Ganeti.HTools.Group as Group
import qualified Ganeti.HTools.Instance as Instance
import qualified Ganeti.HTools.Loader as Loader
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Types as T
-- | Given a cluster description and maybe a group name, decide
-- if that group, or all allocatable groups if no group is given,
-- is dedicated.
isDedicated :: Loader.ClusterData -> Maybe String -> Bool
isDedicated cdata maybeGroup =
let groups =
IntMap.keysSet
. IntMap.filter (maybe ((/=) T.AllocUnallocable . Group.allocPolicy)
(\name -> (==) name . Group.name) maybeGroup)
$ Loader.cdGroups cdata
in F.all (liftA2 (||) Node.exclStorage
$ not . (`IntSet.member` groups) . Node.group)
$ Loader.cdNodes cdata
-- | Given a specification interval, create an instance minimally fitting
-- into that interval. In other words create an instance from the lower bounds
-- of the specified interval.
minimallyCompliantInstance :: T.ISpec -> Instance.Instance
minimallyCompliantInstance spec =
Instance.create "minimalspecinstance"
(T.iSpecMemorySize spec)
(T.iSpecDiskSize spec)
[]
(T.iSpecCpuCount spec)
T.Running [] False Node.noSecondary Node.noSecondary T.DTPlain
(T.iSpecSpindleUse spec)
[] False
-- | From an instance policy get the list of test instances, in correct order,
-- for which the allocation count has to be determined for the lost allocations
-- metrics.
testInstances :: T.IPolicy -> [Instance.Instance]
testInstances =
map minimallyCompliantInstance
. sortBy (flip compare `on` T.iSpecDiskSize)
. map T.minMaxISpecsMinSpec
. T.iPolicyMinMaxISpecs
-- | Given the test instances, compute the allocations vector of a node
allocationVector :: [Instance.Instance] -> Node.Node -> [Int]
allocationVector insts node =
map (\ inst -> length $ iterateOk (`Node.addPri` inst) node) insts
-- | The metric do be used in dedicated allocation.
type Metric = ([Int], Int)
-- | Given the test instances and an instance to be placed, compute
-- the lost allocations metrics for that node, together with the
-- modified node. Return Bad if it is not possible to place the
-- instance on that node.
lostAllocationsMetric :: Alg.AlgorithmOptions
-> [Instance.Instance]
-> Instance.Instance
-> Node.Node
-> T.OpResult (Metric, Node.Node)
lostAllocationsMetric opts insts inst node = do
let allocVec = allocationVector insts
before = allocVec node
force = Alg.algIgnoreSoftErrors opts
node' <- Node.addPriEx force node inst
let after = allocVec node'
disk = Node.fDsk node'
return ((zipWith (-) before after, disk), node')
-- | Allocate an instance on a given node.
allocateOnSingle :: Alg.AlgorithmOptions
-> Node.List -> Instance.Instance -> T.Ndx
-> T.OpResult (AllocSol.GenericAllocElement Metric)
allocateOnSingle opts nl inst new_pdx = do
let primary = Container.find new_pdx nl
policy = Node.iPolicy primary
testInst = testInstances policy
excl = Node.exclStorage primary
new_inst = Instance.setBoth inst new_pdx Node.noSecondary
Instance.instMatchesPolicy inst policy excl
(metrics, new_p) <- lostAllocationsMetric opts testInst inst primary
let new_nl = Container.add new_pdx new_p nl
return (new_nl, new_inst, [new_p], metrics)
-- | Allocate an instance on a given pair of nodes.
allocateOnPair :: Alg.AlgorithmOptions
-> Node.List
-> Instance.Instance
-> T.Ndx
-> T.Ndx
-> T.OpResult (AllocSol.GenericAllocElement Metric)
allocateOnPair opts nl inst pdx sdx = do
let primary = Container.find pdx nl
secondary = Container.find sdx nl
policy = Node.iPolicy primary
testInst = testInstances policy
inst' = Instance.setBoth inst pdx sdx
Instance.instMatchesPolicy inst policy (Node.exclStorage primary)
((lAllP, dskP), primary') <- lostAllocationsMetric opts testInst inst' primary
secondary' <- Node.addSec secondary inst' pdx
let lAllS = zipWith (-) (allocationVector testInst secondary)
(allocationVector testInst secondary')
dskS = Node.fDsk secondary'
metric = (zipWith (+) lAllP lAllS, dskP + dskS)
nl' = Container.addTwo pdx primary' sdx secondary' nl
return (nl', inst', [primary', secondary'], metric)
-- | Find an allocation for an instance on a group.
findAllocation :: Alg.AlgorithmOptions
-> Group.List
-> Node.List
-> T.Gdx
-> Instance.Instance
-> Int
-> Result (AllocSol.GenericAllocSolution Metric, [String])
findAllocation opts mggl mgnl gdx inst count = do
let nl = Container.filter ((== gdx) . Node.group) mgnl
group = Container.find gdx mggl
unless (Cluster.hasRequiredNetworks group inst) . failError
$ "The group " ++ Group.name group ++ " is not connected to\
\ a network required by instance " ++ Instance.name inst
allocNodes <- Cluster.genAllocNodes opts mggl nl count False
solution <- case allocNodes of
(Right []) -> fail "Not enough online nodes"
(Right pairs) ->
let sols = foldl AllocSol.sumAllocs AllocSol.emptyAllocSolution
$ map (\(p, ss) -> foldl
(\cstate ->
AllocSol.concatAllocs cstate
. allocateOnPair opts nl inst p)
AllocSol.emptyAllocSolution ss)
pairs
in return $ AllocSol.genericAnnotateSolution show sols
(Left []) -> fail "No online nodes"
(Left nodes) ->
let sols = foldl (\cstate ->
AllocSol.concatAllocs cstate
. allocateOnSingle opts nl inst)
AllocSol.emptyAllocSolution nodes
in return $ AllocSol.genericAnnotateSolution show sols
return (solution, AllocSol.solutionDescription (group, return solution))
-- | Find an allocation in a suitable group.
findMGAllocation :: Alg.AlgorithmOptions
-> Group.List
-> Node.List
-> Instance.List
-> Instance.Instance
-> Int
-> Result (AllocSol.GenericAllocSolution Metric)
findMGAllocation opts gl nl il inst count = do
let groups_by_idx = ClusterUtils.splitCluster nl il
genSol (gdx, (nl', _)) =
liftM fst $ findAllocation opts gl nl' gdx inst count
sols = map (flip Container.find gl . fst &&& genSol) groups_by_idx
goodSols = Cluster.sortMGResults $ Cluster.filterMGResults sols
all_msgs = concatMap AllocSol.solutionDescription sols
case goodSols of
[] -> fail $ intercalate ", " all_msgs
(final_group, final_sol):_ ->
let sel_msg = "Selected group: " ++ Group.name final_group
in return $ final_sol { AllocSol.asLog = sel_msg : all_msgs }
-- | Handle allocation requests in the dedicated scenario.
runDedicatedAllocation :: Alg.AlgorithmOptions
-> Loader.Request
-> (Maybe (Node.List, Instance.List), String)
runDedicatedAllocation opts request =
let Loader.Request rqtype (Loader.ClusterData gl nl il _ _) = request
allocresult =
case rqtype of
Loader.Allocate inst (Cluster.AllocDetails count (Just gn)) rNds -> do
gdx <- Group.idx <$> Container.findByName gl gn
let opts' = opts { Alg.algRestrictToNodes =
Alg.algRestrictToNodes opts `mplus` rNds }
(solution, msgs) <- findAllocation opts' gl nl gdx inst count
IAlloc.formatAllocate il $ solution { AllocSol.asLog = msgs }
Loader.Allocate inst (Cluster.AllocDetails count Nothing) rNds ->
let opts' = opts { Alg.algRestrictToNodes =
Alg.algRestrictToNodes opts `mplus` rNds }
in findMGAllocation opts' gl nl il inst count
>>= IAlloc.formatAllocate il
Loader.MultiAllocate insts ->
IAlloc.formatMultiAlloc =<< foldM
(\(nl', il', res)
(inst, Cluster.AllocDetails count maybeGroup) -> do
ares <- maybe (findMGAllocation opts gl nl' il' inst count)
(\gn -> do
gdx <- Group.idx <$> Container.findByName gl gn
liftM fst
$ findAllocation opts gl nl gdx inst count)
maybeGroup
let sol = AllocSol.asSolution ares
nl'' = AllocSol.extractNl nl' il' sol
il'' = AllocSol.updateIl il' sol
return (nl'', il'', (inst, ares):res))
(nl, il, []) insts
_ -> fail "Dedicated Allocation only for proper allocation requests"
in IAlloc.formatIAllocResult allocresult