module Test.Ganeti.HTools.Node
( testHTools_Node
, Node.Node(..)
, setInstanceSmallerThanNode
, genNode
, genOnlineNode
, genEmptyOnlineNode
, genNodeList
, genUniqueNodeList
) where
import Test.QuickCheck
import Test.HUnit
import Control.Monad
import qualified Data.Map as Map
import qualified Data.Graph as Graph
import Data.List
import Test.Ganeti.TestHelper
import Test.Ganeti.TestCommon
import Test.Ganeti.TestHTools
import Test.Ganeti.HTools.Instance ( genInstanceSmallerThanNode
, genInstanceList
, genInstanceOnNodeList)
import Ganeti.BasicTypes
import qualified Ganeti.HTools.Loader as Loader
import qualified Ganeti.HTools.Container as Container
import qualified Ganeti.HTools.Instance as Instance
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Types as Types
import qualified Ganeti.HTools.Graph as HGraph
genNode :: Maybe Int
-> Maybe Int
-> Gen Node.Node
genNode min_multiplier max_multiplier = do
let (base_mem, base_dsk, base_cpu, base_spindles) =
case min_multiplier of
Just mm -> (mm * Types.unitMem,
mm * Types.unitDsk,
mm * Types.unitCpu,
mm)
Nothing -> (0, 0, 0, 0)
(top_mem, top_dsk, top_cpu, top_spindles) =
case max_multiplier of
Just mm -> (mm * Types.unitMem,
mm * Types.unitDsk,
mm * Types.unitCpu,
mm)
Nothing -> (maxMem, maxDsk, maxCpu, maxSpindles)
name <- genFQDN
mem_t <- choose (base_mem, top_mem)
mem_f <- choose (base_mem, mem_t)
mem_n <- choose (0, mem_t mem_f)
dsk_t <- choose (base_dsk, top_dsk)
dsk_f <- choose (base_dsk, dsk_t)
cpu_t <- choose (base_cpu, top_cpu)
cpu_n <- choose (base_cpu, cpu_t)
offl <- arbitrary
spindles <- choose (base_spindles, top_spindles)
let n = Node.create name (fromIntegral mem_t) mem_n mem_f
(fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) cpu_n offl spindles
0 0 False
n' = Node.setPolicy nullIPolicy n
return $ Node.buildPeers n' Container.empty
genOnlineNode :: Gen Node.Node
genOnlineNode =
arbitrary `suchThat` (\n -> not (Node.offline n) &&
not (Node.failN1 n) &&
Node.availDisk n > 2 * Types.unitDsk &&
Node.availMem n > 2 * Types.unitMem &&
Node.availCpu n > 2 &&
Node.tSpindles n > 2)
genEmptyOnlineNode :: Gen Node.Node
genEmptyOnlineNode =
(do node <- arbitrary
let fmem = truncate (Node.tMem node) Node.nMem node
let node' = node { Node.offline = False
, Node.fMem = fmem
, Node.fMemForth = fmem
, Node.pMem = fromIntegral fmem / Node.tMem node
, Node.pMemForth = fromIntegral fmem / Node.tMem node
, Node.rMem = 0
, Node.rMemForth = 0
, Node.pRem = 0
, Node.pRemForth = 0
}
return node') `suchThat` (\ n -> not (Node.failN1 n) &&
Node.availDisk n > 0 &&
Node.availMem n > 0 &&
Node.availCpu n > 0 &&
Node.tSpindles n > 0)
genExclStorNode :: Gen Node.Node
genExclStorNode = do
n <- genOnlineNode
fs <- choose (Types.unitSpindle, Node.tSpindles n)
fsForth <- choose (Types.unitSpindle, fs)
let pd = fromIntegral fs / fromIntegral (Node.tSpindles n)::Double
let pdForth = fromIntegral fsForth / fromIntegral (Node.tSpindles n)::Double
return n { Node.exclStorage = True
, Node.fSpindles = fs
, Node.fSpindlesForth = fsForth
, Node.pDsk = pd
, Node.pDskForth = pdForth
}
genMaybeExclStorNode :: Gen Node.Node
genMaybeExclStorNode = oneof [genOnlineNode, genExclStorNode]
instance Arbitrary Node.Node where
arbitrary = genNode Nothing Nothing
genNodeList :: Gen Node.Node -> Gen Node.List
genNodeList ngen = fmap (snd . Loader.assignIndices) names_nodes
where names_nodes = (fmap . map) (\n -> (Node.name n, n)) nodes
nodes = listOf1 ngen `suchThat`
((\ns -> ns == nub ns) . map Node.name)
genUniqueNodeList :: Gen Node.Node -> Gen (Node.List, Types.NameAssoc)
genUniqueNodeList ngen = (do
nl <- genNodeList ngen
let na = (fst . Loader.assignIndices) $
map (\n -> (Node.name n, n)) (Container.elems nl)
return (nl, na)) `suchThat`
(\(nl, na) -> Container.size nl == Map.size na)
genNodeGraph :: Gen (Maybe Graph.Graph, Node.List, Instance.List)
genNodeGraph = do
nl <- genNodeList genOnlineNode `suchThat` ((2<=).Container.size)
il <- genInstanceList (genInstanceOnNodeList nl)
return (Node.mkNodeGraph nl il, nl, il)
prop_setAlias :: Node.Node -> String -> Bool
prop_setAlias node name =
Node.name newnode == Node.name node &&
Node.alias newnode == name
where newnode = Node.setAlias node name
prop_setOffline :: Node.Node -> Bool -> Property
prop_setOffline node status =
Node.offline newnode ==? status
where newnode = Node.setOffline node status
prop_setXmem :: Node.Node -> Int -> Property
prop_setXmem node xm =
Node.xMem newnode ==? xm
where newnode = Node.setXmem node xm
prop_setMcpu :: Node.Node -> Double -> Property
prop_setMcpu node mc =
Types.iPolicyVcpuRatio (Node.iPolicy newnode) ==? mc
where newnode = Node.setMcpu node mc
prop_addPri_NoN1Fail :: Property
prop_addPri_NoN1Fail =
forAll genMaybeExclStorNode $ \node ->
forAll (genInstanceSmallerThanNode node) $ \inst ->
let inst' = inst { Instance.mem = Node.fMem node Node.rMem node }
in (Node.addPri node inst' /=? Bad Types.FailN1)
prop_addPriFM :: Node.Node -> Instance.Instance -> Property
prop_addPriFM node inst =
Instance.mem inst >= Node.fMem node && not (Node.failN1 node) &&
Instance.usesMemory inst ==>
(Node.addPri node inst'' ==? Bad Types.FailMem)
where inst' = setInstanceSmallerThanNode node inst
inst'' = inst' { Instance.mem = Instance.mem inst }
prop_addPriFD :: Instance.Instance -> Property
prop_addPriFD inst =
forAll (genNode (Just 1) Nothing) $ \node ->
forAll (elements Instance.localStorageTemplates) $ \dt ->
Instance.dsk inst >= Node.fDsk node && not (Node.failN1 node) ==>
let inst' = setInstanceSmallerThanNode node inst
inst'' = inst' { Instance.dsk = Instance.dsk inst
, Instance.diskTemplate = dt }
in (Node.addPri node inst'' ==? Bad Types.FailDisk)
hasInstTooManySpindles :: Instance.Instance -> Int -> Bool
hasInstTooManySpindles inst sp_lim =
case Instance.getTotalSpindles inst of
Just s -> s > sp_lim
Nothing -> True
prop_addPriFS :: Instance.Instance -> Property
prop_addPriFS inst =
forAll genExclStorNode $ \node ->
forAll (elements Instance.localStorageTemplates) $ \dt ->
hasInstTooManySpindles inst (Node.fSpindles node) &&
not (Node.failN1 node) ==>
let inst' = setInstanceSmallerThanNode node inst
inst'' = inst' { Instance.disks = Instance.disks inst
, Instance.diskTemplate = dt }
in (Node.addPri node inst'' ==? Bad Types.FailSpindles)
prop_addPriFC :: Property
prop_addPriFC =
forAll (choose (1, maxCpu)) $ \extra ->
forAll genMaybeExclStorNode $ \node ->
forAll (arbitrary `suchThat` Instance.notOffline
`suchThat` (not . Instance.forthcoming)) $ \inst ->
let inst' = setInstanceSmallerThanNode node inst
inst'' = inst' { Instance.vcpus = Node.availCpu node + extra }
in case Node.addPri node inst'' of
Bad Types.FailCPU -> passTest
v -> failTest $ "Expected OpFail FailCPU, but got " ++ show v
prop_addSec :: Node.Node -> Instance.Instance -> Int -> Property
prop_addSec node inst pdx =
((Instance.mem inst >= (Node.fMem node Node.rMem node) &&
not (Instance.isOffline inst)) ||
Instance.dsk inst >= Node.fDsk node ||
(Node.exclStorage node &&
hasInstTooManySpindles inst (Node.fSpindles node))) &&
not (Node.failN1 node) ==>
isBad (Node.addSec node inst pdx)
prop_addOfflinePri :: NonNegative Int -> NonNegative Int -> Property
prop_addOfflinePri (NonNegative extra_mem) (NonNegative extra_cpu) =
forAll genMaybeExclStorNode $ \node ->
forAll (genInstanceSmallerThanNode node) $ \inst ->
let inst' = inst { Instance.runSt = Types.StatusOffline
, Instance.mem = Node.availMem node + extra_mem
, Instance.vcpus = Node.availCpu node + extra_cpu }
in case Node.addPriEx True node inst' of
Ok _ -> passTest
v -> failTest $ "Expected OpGood, but got: " ++ show v
prop_addOfflineSec :: NonNegative Int -> NonNegative Int
-> Types.Ndx -> Property
prop_addOfflineSec (NonNegative extra_mem) (NonNegative extra_cpu) pdx =
forAll genMaybeExclStorNode $ \node ->
forAll (genInstanceSmallerThanNode node) $ \inst ->
let inst' = inst { Instance.runSt = Types.StatusOffline
, Instance.mem = Node.availMem node + extra_mem
, Instance.vcpus = Node.availCpu node + extra_cpu
, Instance.diskTemplate = Types.DTDrbd8 }
in case Node.addSec node inst' pdx of
Ok _ -> passTest
v -> failTest $ "Expected OpGood/OpGood, but got: " ++ show v
prop_rMem :: Instance.Instance -> Property
prop_rMem inst =
not (Instance.isOffline inst) && not (Instance.forthcoming inst) ==>
forAll (genMaybeExclStorNode `suchThat` ((> Types.unitMem) . Node.fMem)) $
\node ->
let inst' = inst { Instance.pNode = 1, Instance.autoBalance = True
, Instance.diskTemplate = Types.DTDrbd8 }
inst_ab = setInstanceSmallerThanNode node inst'
inst_nb = inst_ab { Instance.autoBalance = False }
orig_rmem = Node.rMem node
inst_idx = Instance.idx inst_ab
node_add_ab = Node.addSec node inst_ab (1)
node_add_nb = Node.addSec node inst_nb (1)
node_del_ab = liftM (`Node.removeSec` inst_ab) node_add_ab
node_del_nb = liftM (`Node.removeSec` inst_nb) node_add_nb
in case (node_add_ab, node_add_nb, node_del_ab, node_del_nb) of
(Ok a_ab, Ok a_nb,
Ok d_ab, Ok d_nb) ->
counterexample "Consistency checks failed" $
Node.rMem a_ab > orig_rmem &&
Node.rMem a_ab orig_rmem == Instance.mem inst_ab &&
Node.rMem a_nb == orig_rmem &&
Node.rMem d_ab == orig_rmem &&
Node.rMem d_nb == orig_rmem &&
inst_idx `elem` Node.sList a_ab &&
inst_idx `notElem` Node.sList d_ab
x -> failTest $ "Failed to add/remove instances: " ++ show x
prop_setMdsk :: Node.Node -> SmallRatio -> Bool
prop_setMdsk node mx =
Node.loDsk node' >= 0 &&
fromIntegral (Node.loDsk node') <= Node.tDsk node &&
Node.availDisk node' >= 0 &&
Node.availDisk node' <= Node.fDsk node' &&
fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
Node.mDsk node' == mx'
where node' = Node.setMdsk node mx'
SmallRatio mx' = mx
prop_tagMaps_idempotent :: Property
prop_tagMaps_idempotent =
forAll genTags $ \tags ->
Node.delTags (Node.addTags m tags) tags ==? m
where m = Map.empty
prop_tagMaps_reject :: Property
prop_tagMaps_reject =
forAll (genTags `suchThat` (not . null)) $ \tags ->
let m = Node.addTags Map.empty tags
in all (\t -> Node.rejectAddTags m [t]) tags
prop_showField :: Node.Node -> Property
prop_showField node =
forAll (elements Node.defaultFields) $ \ field ->
fst (Node.showHeader field) /= Types.unknownField &&
Node.showField node field /= Types.unknownField
prop_computeGroups :: [Node.Node] -> Bool
prop_computeGroups nodes =
let ng = Node.computeGroups nodes
onlyuuid = map fst ng
in length nodes == sum (map (length . snd) ng) &&
all (\(guuid, ns) -> all ((== guuid) . Node.group) ns) ng &&
length (nub onlyuuid) == length onlyuuid &&
(null nodes || not (null ng))
prop_addPri_idempotent :: Property
prop_addPri_idempotent =
forAll genMaybeExclStorNode $ \node ->
forAll (genInstanceSmallerThanNode node) $ \inst ->
case Node.addPri node inst of
Ok node' -> Node.removePri node' inst ==? node
_ -> failTest "Can't add instance"
prop_addSec_idempotent :: Property
prop_addSec_idempotent =
forAll genMaybeExclStorNode $ \node ->
forAll (genInstanceSmallerThanNode node) $ \inst ->
let pdx = Node.idx node + 1
inst' = Instance.setPri inst pdx
inst'' = inst' { Instance.diskTemplate = Types.DTDrbd8 }
in case Node.addSec node inst'' pdx of
Ok node' -> Node.removeSec node' inst'' ==? node
_ -> failTest "Can't add instance"
case_emptyNodeList :: Assertion
case_emptyNodeList =
assertEqual "" Nothing $ Node.mkNodeGraph emptynodes emptyinstances
where emptynodes = Container.empty :: Node.List
emptyinstances = Container.empty :: Instance.List
prop_numVertices :: Property
prop_numVertices =
forAll genNodeGraph $ \(graph, nl, _) ->
(fmap numvertices graph ==? Just (Container.size nl))
where numvertices = length . Graph.vertices
prop_numEdges :: Property
prop_numEdges =
forAll genNodeGraph $ \(graph, _, il) ->
(fmap numedges graph ==? Just (numwithsec il * 2))
where numedges = length . Graph.edges
numwithsec = length . filter Instance.hasSecondary . Container.elems
prop_nodeGraphIsColorable :: Property
prop_nodeGraphIsColorable =
forAll genNodeGraph $ \(graph, _, _) ->
fmap HGraph.isColorable graph ==? Just True
prop_instanceIsEdge :: Property
prop_instanceIsEdge =
forAll genNodeGraph $ \(graph, _, il) ->
fmap (\g -> all (`isEdgeOn` g) (iwithsec il)) graph ==? Just True
where i `isEdgeOn` g = iEdges i `intersect` Graph.edges g == iEdges i
iEdges i = [ (Instance.pNode i, Instance.sNode i)
, (Instance.sNode i, Instance.pNode i)]
iwithsec = filter Instance.hasSecondary . Container.elems
prop_edgeIsInstance :: Property
prop_edgeIsInstance =
forAll genNodeGraph $ \(graph, _, il) ->
fmap (all (`isInstanceIn` il).Graph.edges) graph ==? Just True
where e `isInstanceIn` il = any (`hasNodes` e) (Container.elems il)
i `hasNodes` (v1,v2) =
Instance.allNodes i `elem` permutations [v1,v2]
testSuite "HTools/Node"
[ 'prop_setAlias
, 'prop_setOffline
, 'prop_setMcpu
, 'prop_setXmem
, 'prop_addPriFM
, 'prop_addPriFD
, 'prop_addPriFS
, 'prop_addPriFC
, 'prop_addPri_NoN1Fail
, 'prop_addSec
, 'prop_addOfflinePri
, 'prop_addOfflineSec
, 'prop_rMem
, 'prop_setMdsk
, 'prop_tagMaps_idempotent
, 'prop_tagMaps_reject
, 'prop_showField
, 'prop_computeGroups
, 'prop_addPri_idempotent
, 'prop_addSec_idempotent
, 'case_emptyNodeList
, 'prop_numVertices
, 'prop_numEdges
, 'prop_nodeGraphIsColorable
, 'prop_edgeIsInstance
, 'prop_instanceIsEdge
]