1 {-| Unittests for ganeti-htools
7 Copyright (C) 2009, 2010 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 module Ganeti.HTools.QC
39 import Test.QuickCheck
40 import Test.QuickCheck.Batch
41 import Data.List (findIndex, intercalate)
44 import qualified Text.JSON as J
45 import qualified Data.Map
46 import qualified Data.IntMap as IntMap
47 import qualified Ganeti.OpCodes as OpCodes
48 import qualified Ganeti.Jobs as Jobs
49 import qualified Ganeti.Luxi
50 import qualified Ganeti.HTools.CLI as CLI
51 import qualified Ganeti.HTools.Cluster as Cluster
52 import qualified Ganeti.HTools.Container as Container
53 import qualified Ganeti.HTools.ExtLoader
54 import qualified Ganeti.HTools.IAlloc as IAlloc
55 import qualified Ganeti.HTools.Instance as Instance
56 import qualified Ganeti.HTools.Loader as Loader
57 import qualified Ganeti.HTools.Luxi
58 import qualified Ganeti.HTools.Node as Node
59 import qualified Ganeti.HTools.PeerMap as PeerMap
60 import qualified Ganeti.HTools.Rapi
61 import qualified Ganeti.HTools.Simu
62 import qualified Ganeti.HTools.Text as Text
63 import qualified Ganeti.HTools.Types as Types
64 import qualified Ganeti.HTools.Utils as Utils
65 import qualified Ganeti.HTools.Version
69 -- | Maximum memory (1TiB, somewhat random value)
73 -- | Maximum disk (8TiB, somewhat random value)
75 maxDsk = 1024 * 1024 * 8
77 -- | Max CPUs (1024, somewhat random value)
83 -- | Simple checker for whether OpResult is fail or pass
84 isFailure :: Types.OpResult a -> Bool
85 isFailure (Types.OpFail _) = True
88 -- | Simple checker for whether Result is fail or pass
89 isOk :: Types.Result a -> Bool
90 isOk (Types.Ok _ ) = True
93 isBad :: Types.Result a -> Bool
96 -- | Update an instance to be smaller than a node
97 setInstanceSmallerThanNode node inst =
98 inst { Instance.mem = Node.availMem node `div` 2
99 , Instance.dsk = Node.availDisk node `div` 2
100 , Instance.vcpus = Node.availCpu node `div` 2
103 -- | Create an instance given its spec
104 createInstance mem dsk vcpus =
105 Instance.create "inst-unnamed" mem dsk vcpus "running" [] (-1) (-1)
107 -- | Create a small cluster by repeating a node spec
108 makeSmallCluster :: Node.Node -> Int -> Node.List
109 makeSmallCluster node count =
110 let fn = Node.buildPeers node Container.empty
111 namelst = map (\n -> (Node.name n, n)) (replicate count fn)
112 (_, nlst) = Loader.assignIndices namelst
113 in Container.fromAssocList nlst
115 -- | Checks if a node is "big" enough
116 isNodeBig :: Node.Node -> Int -> Bool
117 isNodeBig node size = Node.availDisk node > size * Types.unitDsk
118 && Node.availMem node > size * Types.unitMem
119 && Node.availCpu node > size * Types.unitCpu
121 canBalance :: Cluster.Table -> Bool -> Bool -> Bool
122 canBalance tbl dm evac = isJust $ Cluster.tryBalance tbl dm evac
124 -- * Arbitrary instances
126 -- copied from the introduction to quickcheck
127 instance Arbitrary Char where
128 arbitrary = choose ('\32', '\128')
130 newtype DNSChar = DNSChar { dnsGetChar::Char }
131 instance Arbitrary DNSChar where
133 x <- elements (['a'..'z'] ++ ['0'..'9'] ++ "_-")
136 getName :: Gen String
139 dn <- vector n::Gen [DNSChar]
140 return (map dnsGetChar dn)
143 getFQDN :: Gen String
146 ncomps <- choose (1, 4)
147 frest <- vector ncomps::Gen [[DNSChar]]
148 let frest' = map (map dnsGetChar) frest
149 return (felem ++ "." ++ intercalate "." frest')
151 -- let's generate a random instance
152 instance Arbitrary Instance.Instance where
155 mem <- choose (0, maxMem)
156 dsk <- choose (0, maxDsk)
157 run_st <- elements ["ERROR_up", "ERROR_down", "ADMIN_down"
158 , "ERROR_nodedown", "ERROR_nodeoffline"
160 , "no_such_status1", "no_such_status2"]
163 vcpus <- choose (0, maxCpu)
164 return $ Instance.create name mem dsk vcpus run_st [] pn sn
167 instance Arbitrary Node.Node where
170 mem_t <- choose (0, maxMem)
171 mem_f <- choose (0, mem_t)
172 mem_n <- choose (0, mem_t - mem_f)
173 dsk_t <- choose (0, maxDsk)
174 dsk_f <- choose (0, dsk_t)
175 cpu_t <- choose (0, maxCpu)
177 let n = Node.create name (fromIntegral mem_t) mem_n mem_f
178 (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl
179 n' = Node.buildPeers n Container.empty
183 instance Arbitrary OpCodes.ReplaceDisksMode where
184 arbitrary = elements [ OpCodes.ReplaceOnPrimary
185 , OpCodes.ReplaceOnSecondary
186 , OpCodes.ReplaceNewSecondary
187 , OpCodes.ReplaceAuto
190 instance Arbitrary OpCodes.OpCode where
192 op_id <- elements [ "OP_TEST_DELAY"
193 , "OP_INSTANCE_REPLACE_DISKS"
194 , "OP_INSTANCE_FAILOVER"
195 , "OP_INSTANCE_MIGRATE"
199 liftM3 OpCodes.OpTestDelay arbitrary arbitrary arbitrary
200 "OP_INSTANCE_REPLACE_DISKS" ->
201 liftM5 OpCodes.OpReplaceDisks arbitrary arbitrary
202 arbitrary arbitrary arbitrary
203 "OP_INSTANCE_FAILOVER" ->
204 liftM2 OpCodes.OpFailoverInstance arbitrary arbitrary
205 "OP_INSTANCE_MIGRATE" ->
206 liftM3 OpCodes.OpMigrateInstance arbitrary arbitrary arbitrary
207 _ -> fail "Wrong opcode")
209 instance Arbitrary Jobs.OpStatus where
210 arbitrary = elements [minBound..maxBound]
212 instance Arbitrary Jobs.JobStatus where
213 arbitrary = elements [minBound..maxBound]
217 -- If the list is not just an empty element, and if the elements do
218 -- not contain commas, then join+split should be idepotent
219 prop_Utils_commaJoinSplit lst = lst /= [""] &&
220 all (not . elem ',') lst ==>
221 Utils.sepSplit ',' (Utils.commaJoin lst) == lst
222 -- Split and join should always be idempotent
223 prop_Utils_commaSplitJoin s = Utils.commaJoin (Utils.sepSplit ',' s) == s
226 [ run prop_Utils_commaJoinSplit
227 , run prop_Utils_commaSplitJoin
230 -- | Make sure add is idempotent
231 prop_PeerMap_addIdempotent pmap key em =
232 fn puniq == fn (fn puniq)
233 where _types = (pmap::PeerMap.PeerMap,
234 key::PeerMap.Key, em::PeerMap.Elem)
235 fn = PeerMap.add key em
236 puniq = PeerMap.accumArray const pmap
238 -- | Make sure remove is idempotent
239 prop_PeerMap_removeIdempotent pmap key =
240 fn puniq == fn (fn puniq)
241 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
242 fn = PeerMap.remove key
243 puniq = PeerMap.accumArray const pmap
245 -- | Make sure a missing item returns 0
246 prop_PeerMap_findMissing pmap key =
247 PeerMap.find key (PeerMap.remove key puniq) == 0
248 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
249 puniq = PeerMap.accumArray const pmap
251 -- | Make sure an added item is found
252 prop_PeerMap_addFind pmap key em =
253 PeerMap.find key (PeerMap.add key em puniq) == em
254 where _types = (pmap::PeerMap.PeerMap,
255 key::PeerMap.Key, em::PeerMap.Elem)
256 puniq = PeerMap.accumArray const pmap
258 -- | Manual check that maxElem returns the maximum indeed, or 0 for null
259 prop_PeerMap_maxElem pmap =
260 PeerMap.maxElem puniq == if null puniq then 0
261 else (maximum . snd . unzip) puniq
262 where _types = pmap::PeerMap.PeerMap
263 puniq = PeerMap.accumArray const pmap
266 [ run prop_PeerMap_addIdempotent
267 , run prop_PeerMap_removeIdempotent
268 , run prop_PeerMap_maxElem
269 , run prop_PeerMap_addFind
270 , run prop_PeerMap_findMissing
275 prop_Container_addTwo cdata i1 i2 =
276 fn i1 i2 cont == fn i2 i1 cont &&
277 fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
278 where _types = (cdata::[Int],
280 cont = foldl (\c x -> Container.add x x c) Container.empty cdata
281 fn x1 x2 = Container.addTwo x1 x1 x2 x2
284 [ run prop_Container_addTwo ]
286 -- Simple instance tests, we only have setter/getters
288 prop_Instance_creat inst =
289 Instance.name inst == Instance.alias inst
291 prop_Instance_setIdx inst idx =
292 Instance.idx (Instance.setIdx inst idx) == idx
293 where _types = (inst::Instance.Instance, idx::Types.Idx)
295 prop_Instance_setName inst name =
296 Instance.name newinst == name &&
297 Instance.alias newinst == name
298 where _types = (inst::Instance.Instance, name::String)
299 newinst = Instance.setName inst name
301 prop_Instance_setAlias inst name =
302 Instance.name newinst == Instance.name inst &&
303 Instance.alias newinst == name
304 where _types = (inst::Instance.Instance, name::String)
305 newinst = Instance.setAlias inst name
307 prop_Instance_setPri inst pdx =
308 Instance.pNode (Instance.setPri inst pdx) == pdx
309 where _types = (inst::Instance.Instance, pdx::Types.Ndx)
311 prop_Instance_setSec inst sdx =
312 Instance.sNode (Instance.setSec inst sdx) == sdx
313 where _types = (inst::Instance.Instance, sdx::Types.Ndx)
315 prop_Instance_setBoth inst pdx sdx =
316 Instance.pNode si == pdx && Instance.sNode si == sdx
317 where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
318 si = Instance.setBoth inst pdx sdx
320 prop_Instance_runStatus_True inst =
321 let run_st = Instance.running inst
322 run_tx = Instance.runSt inst
324 run_tx `elem` Instance.runningStates ==> run_st
326 prop_Instance_runStatus_False inst =
327 let run_st = Instance.running inst
328 run_tx = Instance.runSt inst
330 run_tx `notElem` Instance.runningStates ==> not run_st
332 prop_Instance_shrinkMG inst =
333 Instance.mem inst >= 2 * Types.unitMem ==>
334 case Instance.shrinkByType inst Types.FailMem of
336 Instance.mem inst' == Instance.mem inst - Types.unitMem
339 prop_Instance_shrinkMF inst =
340 Instance.mem inst < 2 * Types.unitMem ==>
341 isBad $ Instance.shrinkByType inst Types.FailMem
343 prop_Instance_shrinkCG inst =
344 Instance.vcpus inst >= 2 * Types.unitCpu ==>
345 case Instance.shrinkByType inst Types.FailCPU of
347 Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
350 prop_Instance_shrinkCF inst =
351 Instance.vcpus inst < 2 * Types.unitCpu ==>
352 isBad $ Instance.shrinkByType inst Types.FailCPU
354 prop_Instance_shrinkDG inst =
355 Instance.dsk inst >= 2 * Types.unitDsk ==>
356 case Instance.shrinkByType inst Types.FailDisk of
358 Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
361 prop_Instance_shrinkDF inst =
362 Instance.dsk inst < 2 * Types.unitDsk ==>
363 isBad $ Instance.shrinkByType inst Types.FailDisk
365 prop_Instance_setMovable inst m =
366 Instance.movable inst' == m
367 where inst' = Instance.setMovable inst m
370 [ run prop_Instance_creat
371 , run prop_Instance_setIdx
372 , run prop_Instance_setName
373 , run prop_Instance_setAlias
374 , run prop_Instance_setPri
375 , run prop_Instance_setSec
376 , run prop_Instance_setBoth
377 , run prop_Instance_runStatus_True
378 , run prop_Instance_runStatus_False
379 , run prop_Instance_shrinkMG
380 , run prop_Instance_shrinkMF
381 , run prop_Instance_shrinkCG
382 , run prop_Instance_shrinkCF
383 , run prop_Instance_shrinkDG
384 , run prop_Instance_shrinkDF
385 , run prop_Instance_setMovable
388 -- Instance text loader tests
390 prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
391 not (null pnode) && pdx >= 0 && sdx >= 0 ==>
392 let vcpus_s = show vcpus
400 else [(pnode, pdx), (snode, rsdx)]
402 inst = Text.loadInst ndx
403 [name, mem_s, dsk_s, vcpus_s, status, pnode, snode, tags]::
404 Maybe (String, Instance.Instance)
405 fail1 = Text.loadInst ndx
406 [name, mem_s, dsk_s, vcpus_s, status, pnode, pnode, tags]::
407 Maybe (String, Instance.Instance)
408 _types = ( name::String, mem::Int, dsk::Int
409 , vcpus::Int, status::String
410 , pnode::String, snode::String
411 , pdx::Types.Ndx, sdx::Types.Ndx)
416 (Instance.name i == name &&
417 Instance.vcpus i == vcpus &&
418 Instance.mem i == mem &&
419 Instance.pNode i == pdx &&
420 Instance.sNode i == (if null snode
421 then Node.noSecondary
425 prop_Text_Load_InstanceFail ktn fields =
426 length fields /= 8 ==> isNothing $ Text.loadInst ktn fields
428 prop_Text_Load_Node name tm nm fm td fd tc fo =
429 let conv v = if v < 0
441 any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
442 in case Text.loadNode [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s] of
444 Just (name', node) ->
446 then Node.offline node
447 else Node.name node == name' && name' == name &&
448 Node.alias node == name &&
449 Node.tMem node == fromIntegral tm &&
450 Node.nMem node == nm &&
451 Node.fMem node == fm &&
452 Node.tDsk node == fromIntegral td &&
453 Node.fDsk node == fd &&
454 Node.tCpu node == fromIntegral tc
456 prop_Text_Load_NodeFail fields =
457 length fields /= 8 ==> isNothing $ Text.loadNode fields
459 prop_Text_NodeLSIdempotent node =
461 Utils.sepSplit '|' . Text.serializeNode) n ==
462 Just (Node.name n, n)
463 -- override failN1 to what loadNode returns by default
464 where n = node { Node.failN1 = True, Node.offline = False }
467 [ run prop_Text_Load_Instance
468 , run prop_Text_Load_InstanceFail
469 , run prop_Text_Load_Node
470 , run prop_Text_Load_NodeFail
471 , run prop_Text_NodeLSIdempotent
476 prop_Node_setAlias node name =
477 Node.name newnode == Node.name node &&
478 Node.alias newnode == name
479 where _types = (node::Node.Node, name::String)
480 newnode = Node.setAlias node name
482 prop_Node_setOffline node status =
483 Node.offline newnode == status
484 where newnode = Node.setOffline node status
486 prop_Node_setXmem node xm =
487 Node.xMem newnode == xm
488 where newnode = Node.setXmem node xm
490 prop_Node_setMcpu node mc =
491 Node.mCpu newnode == mc
492 where newnode = Node.setMcpu node mc
494 -- | Check that an instance add with too high memory or disk will be rejected
495 prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
496 not (Node.failN1 node)
498 case Node.addPri node inst'' of
499 Types.OpFail Types.FailMem -> True
501 where _types = (node::Node.Node, inst::Instance.Instance)
502 inst' = setInstanceSmallerThanNode node inst
503 inst'' = inst' { Instance.mem = Instance.mem inst }
505 prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
506 not (Node.failN1 node)
508 case Node.addPri node inst'' of
509 Types.OpFail Types.FailDisk -> True
511 where _types = (node::Node.Node, inst::Instance.Instance)
512 inst' = setInstanceSmallerThanNode node inst
513 inst'' = inst' { Instance.dsk = Instance.dsk inst }
515 prop_Node_addPriFC node inst = Instance.vcpus inst > Node.availCpu node &&
516 not (Node.failN1 node)
518 case Node.addPri node inst'' of
519 Types.OpFail Types.FailCPU -> True
521 where _types = (node::Node.Node, inst::Instance.Instance)
522 inst' = setInstanceSmallerThanNode node inst
523 inst'' = inst' { Instance.vcpus = Instance.vcpus inst }
525 -- | Check that an instance add with too high memory or disk will be rejected
526 prop_Node_addSec node inst pdx =
527 (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
528 Instance.dsk inst >= Node.fDsk node) &&
529 not (Node.failN1 node)
530 ==> isFailure (Node.addSec node inst pdx)
531 where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
533 newtype SmallRatio = SmallRatio Double deriving Show
534 instance Arbitrary SmallRatio where
537 return $ SmallRatio v
539 -- | Check mdsk setting
540 prop_Node_setMdsk node mx =
541 Node.loDsk node' >= 0 &&
542 fromIntegral (Node.loDsk node') <= Node.tDsk node &&
543 Node.availDisk node' >= 0 &&
544 Node.availDisk node' <= Node.fDsk node' &&
545 fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
546 Node.mDsk node' == mx'
547 where _types = (node::Node.Node, mx::SmallRatio)
548 node' = Node.setMdsk node mx'
552 prop_Node_tagMaps_idempotent tags =
553 Node.delTags (Node.addTags m tags) tags == m
554 where m = Data.Map.empty
556 prop_Node_tagMaps_reject tags =
558 any (\t -> Node.rejectAddTags m [t]) tags
559 where m = Node.addTags Data.Map.empty tags
561 prop_Node_showField node =
562 forAll (elements Node.defaultFields) $ \ field ->
563 fst (Node.showHeader field) /= Types.unknownField &&
564 Node.showField node field /= Types.unknownField
567 [ run prop_Node_setAlias
568 , run prop_Node_setOffline
569 , run prop_Node_setMcpu
570 , run prop_Node_setXmem
571 , run prop_Node_addPriFM
572 , run prop_Node_addPriFD
573 , run prop_Node_addPriFC
574 , run prop_Node_addSec
575 , run prop_Node_setMdsk
576 , run prop_Node_tagMaps_idempotent
577 , run prop_Node_tagMaps_reject
578 , run prop_Node_showField
584 -- | Check that the cluster score is close to zero for a homogeneous cluster
585 prop_Score_Zero node count =
586 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
587 (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
588 let fn = Node.buildPeers node Container.empty
589 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
590 nl = Container.fromAssocList nlst
591 score = Cluster.compCV nl
592 -- we can't say == 0 here as the floating point errors accumulate;
593 -- this should be much lower than the default score in CLI.hs
596 -- | Check that cluster stats are sane
597 prop_CStats_sane node count =
598 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
599 (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
600 let fn = Node.buildPeers node Container.empty
601 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
602 nl = Container.fromAssocList nlst
603 cstats = Cluster.totalResources nl
604 in Cluster.csAdsk cstats >= 0 &&
605 Cluster.csAdsk cstats <= Cluster.csFdsk cstats
607 -- | Check that one instance is allocated correctly, without
609 prop_ClusterAlloc_sane node inst =
610 forAll (choose (5, 20)) $ \count ->
611 not (Node.offline node)
612 && not (Node.failN1 node)
613 && Node.availDisk node > 0
614 && Node.availMem node > 0
616 let nl = makeSmallCluster node count
619 inst' = setInstanceSmallerThanNode node inst
620 in case Cluster.tryAlloc nl il inst' rqnodes of
622 Types.Ok (_, _, sols3) ->
625 (_, (xnl, xi, _)):[] ->
626 let cv = Cluster.compCV xnl
627 il' = Container.add (Instance.idx xi) xi il
628 tbl = Cluster.Table xnl il' cv []
629 in not (canBalance tbl True False)
632 -- | Checks that on a 2-5 node cluster, we can allocate a random
633 -- instance spec via tiered allocation (whatever the original instance
634 -- spec), on either one or two nodes
635 prop_ClusterCanTieredAlloc node inst =
636 forAll (choose (2, 5)) $ \count ->
637 forAll (choose (1, 2)) $ \rqnodes ->
638 not (Node.offline node)
639 && not (Node.failN1 node)
642 let nl = makeSmallCluster node count
644 in case Cluster.tieredAlloc nl il inst rqnodes [] of
646 Types.Ok (_, _, ixes) -> not (null ixes)
648 -- | Checks that on a 4-8 node cluster, once we allocate an instance,
649 -- we can also evacuate it
650 prop_ClusterAllocEvac node inst =
651 forAll (choose (4, 8)) $ \count ->
652 not (Node.offline node)
653 && not (Node.failN1 node)
656 let nl = makeSmallCluster node count
659 inst' = setInstanceSmallerThanNode node inst
660 in case Cluster.tryAlloc nl il inst' rqnodes of
662 Types.Ok (_, _, sols3) ->
665 (_, (xnl, xi, _)):[] ->
666 let sdx = Instance.sNode xi
667 il' = Container.add (Instance.idx xi) xi il
668 in case Cluster.tryEvac xnl il' [sdx] of
673 -- | Check that allocating multiple instances on a cluster, then
674 -- adding an empty node, results in a valid rebalance
675 prop_ClusterAllocBalance node =
676 forAll (choose (3, 5)) $ \count ->
677 not (Node.offline node)
678 && not (Node.failN1 node)
680 && not (isNodeBig node 8)
682 let nl = makeSmallCluster node count
683 (hnode, nl') = IntMap.deleteFindMax nl
686 i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
687 in case Cluster.iterateAlloc nl' il i_templ rqnodes [] of
689 Types.Ok (_, xnl, insts) ->
690 let ynl = Container.add (Node.idx hnode) hnode xnl
691 cv = Cluster.compCV ynl
693 Container.add (Instance.idx i) i l)
695 tbl = Cluster.Table ynl il' cv []
696 in canBalance tbl True False
699 [ run prop_Score_Zero
700 , run prop_CStats_sane
701 , run prop_ClusterAlloc_sane
702 , run prop_ClusterCanTieredAlloc
703 , run prop_ClusterAllocEvac
704 , run prop_ClusterAllocBalance
707 -- | Check that opcode serialization is idempotent
709 prop_OpCodes_serialization op =
710 case J.readJSON (J.showJSON op) of
712 J.Ok op' -> op == op'
713 where _types = op::OpCodes.OpCode
716 [ run prop_OpCodes_serialization
719 -- | Check that (queued) job\/opcode status serialization is idempotent
720 prop_OpStatus_serialization os =
721 case J.readJSON (J.showJSON os) of
723 J.Ok os' -> os == os'
724 where _types = os::Jobs.OpStatus
726 prop_JobStatus_serialization js =
727 case J.readJSON (J.showJSON js) of
729 J.Ok js' -> js == js'
730 where _types = js::Jobs.JobStatus
733 [ run prop_OpStatus_serialization
734 , run prop_JobStatus_serialization
739 prop_Loader_lookupNode ktn inst node =
740 isJust (Loader.lookupNode ktn inst node) == (node `elem` names)
741 where names = map fst ktn
743 prop_Loader_lookupInstance kti inst =
744 isJust (Loader.lookupInstance kti inst) == (inst `elem` names)
745 where names = map fst kti
747 prop_Loader_lookupInstanceIdx kti inst =
748 case (Loader.lookupInstance kti inst,
749 findIndex (\p -> fst p == inst) kti) of
750 (Nothing, Nothing) -> True
751 (Just idx, Just ex) -> idx == snd (kti !! ex)
754 prop_Loader_assignIndices enames =
755 length nassoc == length enames &&
756 length kt == length enames &&
757 (if not (null enames)
758 then maximum (map fst kt) == length enames - 1
760 where (nassoc, kt) = Loader.assignIndices enames
761 _types = enames::[(String, Node.Node)]
764 -- | Checks that the number of primary instances recorded on the nodes
766 prop_Loader_mergeData ns =
767 let na = map (\n -> (Node.idx n, n)) ns
768 in case Loader.mergeData [] [] [] (na, [], []) of
770 Types.Ok (nl, il, _) ->
771 let nodes = Container.elems nl
772 instances = Container.elems il
773 in (sum . map (length . Node.pList)) nodes == 0 &&
777 [ run prop_Loader_lookupNode
778 , run prop_Loader_lookupInstance
779 , run prop_Loader_lookupInstanceIdx
780 , run prop_Loader_assignIndices
781 , run prop_Loader_mergeData