1 {-# LANGUAGE TemplateHaskell #-}
2 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 {-| Unittests for ganeti-htools.
10 Copyright (C) 2009, 2010, 2011, 2012 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 Test.Ganeti.HTools.Text (testText) where
31 import Test.QuickCheck
33 import qualified Data.Map as Map
37 import Test.Ganeti.TestHelper
38 import Test.Ganeti.TestCommon
39 import Test.Ganeti.TestHTools
40 import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode)
41 import Test.Ganeti.HTools.Node (genNode, genOnlineNode)
43 import qualified Ganeti.HTools.Cluster as Cluster
44 import qualified Ganeti.HTools.Container as Container
45 import qualified Ganeti.HTools.Group as Group
46 import qualified Ganeti.HTools.Instance as Instance
47 import qualified Ganeti.HTools.Loader as Loader
48 import qualified Ganeti.HTools.Node as Node
49 import qualified Ganeti.HTools.Text as Text
50 import qualified Ganeti.HTools.Types as Types
51 import qualified Ganeti.HTools.Utils as Utils
53 -- * Instance text loader tests
55 prop_Text_Load_Instance :: String -> Int -> Int -> Int -> Types.InstanceStatus
56 -> NonEmptyList Char -> [Char]
57 -> NonNegative Int -> NonNegative Int -> Bool
58 -> Types.DiskTemplate -> Int -> Property
59 prop_Text_Load_Instance name mem dsk vcpus status
60 (NonEmpty pnode) snode
61 (NonNegative pdx) (NonNegative sdx) autobal dt su =
62 pnode /= snode && pdx /= sdx ==>
63 let vcpus_s = show vcpus
67 status_s = Types.instanceStatusToRaw status
70 else [(pnode, pdx), (snode, sdx)]
73 sbal = if autobal then "Y" else "N"
74 sdt = Types.diskTemplateToRaw dt
75 inst = Text.loadInst nl
76 [name, mem_s, dsk_s, vcpus_s, status_s,
77 sbal, pnode, snode, sdt, tags, su_s]
78 fail1 = Text.loadInst nl
79 [name, mem_s, dsk_s, vcpus_s, status_s,
80 sbal, pnode, pnode, tags]
82 Types.Bad msg -> failTest $ "Failed to load instance: " ++ msg
83 Types.Ok (_, i) -> printTestCase "Mismatch in some field while\
84 \ loading the instance" $
85 Instance.name i == name &&
86 Instance.vcpus i == vcpus &&
87 Instance.mem i == mem &&
88 Instance.pNode i == pdx &&
89 Instance.sNode i == (if null snode
92 Instance.autoBalance i == autobal &&
93 Instance.spindleUse i == su &&
96 prop_Text_Load_InstanceFail :: [(String, Int)] -> [String] -> Property
97 prop_Text_Load_InstanceFail ktn fields =
98 length fields /= 10 && length fields /= 11 ==>
99 case Text.loadInst nl fields of
100 Types.Ok _ -> failTest "Managed to load instance from invalid data"
101 Types.Bad msg -> printTestCase ("Unrecognised error message: " ++ msg) $
102 "Invalid/incomplete instance data: '" `isPrefixOf` msg
103 where nl = Map.fromList ktn
105 prop_Text_Load_Node :: String -> Int -> Int -> Int -> Int -> Int
106 -> Int -> Bool -> Bool
107 prop_Text_Load_Node name tm nm fm td fd tc fo =
108 let conv v = if v < 0
120 any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
121 gid = Group.uuid defGroup
122 in case Text.loadNode defGroupAssoc
123 [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s, gid] of
125 Just (name', node) ->
127 then Node.offline node
128 else Node.name node == name' && name' == name &&
129 Node.alias node == name &&
130 Node.tMem node == fromIntegral tm &&
131 Node.nMem node == nm &&
132 Node.fMem node == fm &&
133 Node.tDsk node == fromIntegral td &&
134 Node.fDsk node == fd &&
135 Node.tCpu node == fromIntegral tc
137 prop_Text_Load_NodeFail :: [String] -> Property
138 prop_Text_Load_NodeFail fields =
139 length fields /= 8 ==> isNothing $ Text.loadNode Map.empty fields
141 prop_Text_NodeLSIdempotent :: Property
142 prop_Text_NodeLSIdempotent =
143 forAll (genNode (Just 1) Nothing) $ \node ->
144 -- override failN1 to what loadNode returns by default
145 let n = Node.setPolicy Types.defIPolicy $
146 node { Node.failN1 = True, Node.offline = False }
148 (Text.loadNode defGroupAssoc.
149 Utils.sepSplit '|' . Text.serializeNode defGroupList) n ==?
150 Just (Node.name n, n)
152 prop_Text_ISpecIdempotent :: Types.ISpec -> Property
153 prop_Text_ISpecIdempotent ispec =
154 case Text.loadISpec "dummy" . Utils.sepSplit ',' .
155 Text.serializeISpec $ ispec of
156 Types.Bad msg -> failTest $ "Failed to load ispec: " ++ msg
157 Types.Ok ispec' -> ispec ==? ispec'
159 prop_Text_IPolicyIdempotent :: Types.IPolicy -> Property
160 prop_Text_IPolicyIdempotent ipol =
161 case Text.loadIPolicy . Utils.sepSplit '|' $
162 Text.serializeIPolicy owner ipol of
163 Types.Bad msg -> failTest $ "Failed to load ispec: " ++ msg
164 Types.Ok res -> (owner, ipol) ==? res
165 where owner = "dummy"
167 -- | This property, while being in the text tests, does more than just
168 -- test end-to-end the serialisation and loading back workflow; it
169 -- also tests the Loader.mergeData and the actuall
170 -- Cluster.iterateAlloc (for well-behaving w.r.t. instance
171 -- allocations, not for the business logic). As such, it's a quite
172 -- complex and slow test, and that's the reason we restrict it to
173 -- small cluster sizes.
174 prop_Text_CreateSerialise :: Property
175 prop_Text_CreateSerialise =
176 forAll genTags $ \ctags ->
177 forAll (choose (1, 20)) $ \maxiter ->
178 forAll (choose (2, 10)) $ \count ->
179 forAll genOnlineNode $ \node ->
180 forAll (genInstanceSmallerThanNode node) $ \inst ->
181 let nl = makeSmallCluster node count
182 reqnodes = Instance.requiredNodes $ Instance.diskTemplate inst
183 in case Cluster.genAllocNodes defGroupList nl reqnodes True >>= \allocn ->
184 Cluster.iterateAlloc nl Container.empty (Just maxiter) inst allocn [] []
186 Types.Bad msg -> failTest $ "Failed to allocate: " ++ msg
187 Types.Ok (_, _, _, [], _) -> printTestCase
188 "Failed to allocate: no allocations" False
189 Types.Ok (_, nl', il', _, _) ->
190 let cdata = Loader.ClusterData defGroupList nl' il' ctags
192 saved = Text.serializeCluster cdata
193 in case Text.parseData saved >>= Loader.mergeData [] [] [] [] of
194 Types.Bad msg -> failTest $ "Failed to load/merge: " ++ msg
195 Types.Ok (Loader.ClusterData gl2 nl2 il2 ctags2 cpol2) ->
196 ctags ==? ctags2 .&&.
197 Types.defIPolicy ==? cpol2 .&&.
199 defGroupList ==? gl2 .&&.
203 [ 'prop_Text_Load_Instance
204 , 'prop_Text_Load_InstanceFail
205 , 'prop_Text_Load_Node
206 , 'prop_Text_Load_NodeFail
207 , 'prop_Text_NodeLSIdempotent
208 , 'prop_Text_ISpecIdempotent
209 , 'prop_Text_IPolicyIdempotent
210 , 'prop_Text_CreateSerialise