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 0 0
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
180 n' = Node.buildPeers n Container.empty
184 instance Arbitrary OpCodes.ReplaceDisksMode where
185 arbitrary = elements [ OpCodes.ReplaceOnPrimary
186 , OpCodes.ReplaceOnSecondary
187 , OpCodes.ReplaceNewSecondary
188 , OpCodes.ReplaceAuto
191 instance Arbitrary OpCodes.OpCode where
193 op_id <- elements [ "OP_TEST_DELAY"
194 , "OP_INSTANCE_REPLACE_DISKS"
195 , "OP_INSTANCE_FAILOVER"
196 , "OP_INSTANCE_MIGRATE"
200 liftM3 OpCodes.OpTestDelay arbitrary arbitrary arbitrary
201 "OP_INSTANCE_REPLACE_DISKS" ->
202 liftM5 OpCodes.OpReplaceDisks arbitrary arbitrary
203 arbitrary arbitrary arbitrary
204 "OP_INSTANCE_FAILOVER" ->
205 liftM2 OpCodes.OpFailoverInstance arbitrary arbitrary
206 "OP_INSTANCE_MIGRATE" ->
207 liftM3 OpCodes.OpMigrateInstance arbitrary arbitrary arbitrary
208 _ -> fail "Wrong opcode")
210 instance Arbitrary Jobs.OpStatus where
211 arbitrary = elements [minBound..maxBound]
213 instance Arbitrary Jobs.JobStatus where
214 arbitrary = elements [minBound..maxBound]
218 -- If the list is not just an empty element, and if the elements do
219 -- not contain commas, then join+split should be idepotent
220 prop_Utils_commaJoinSplit lst = lst /= [""] &&
221 all (not . elem ',') lst ==>
222 Utils.sepSplit ',' (Utils.commaJoin lst) == lst
223 -- Split and join should always be idempotent
224 prop_Utils_commaSplitJoin s = Utils.commaJoin (Utils.sepSplit ',' s) == s
227 [ run prop_Utils_commaJoinSplit
228 , run prop_Utils_commaSplitJoin
231 -- | Make sure add is idempotent
232 prop_PeerMap_addIdempotent pmap key em =
233 fn puniq == fn (fn puniq)
234 where _types = (pmap::PeerMap.PeerMap,
235 key::PeerMap.Key, em::PeerMap.Elem)
236 fn = PeerMap.add key em
237 puniq = PeerMap.accumArray const pmap
239 -- | Make sure remove is idempotent
240 prop_PeerMap_removeIdempotent pmap key =
241 fn puniq == fn (fn puniq)
242 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
243 fn = PeerMap.remove key
244 puniq = PeerMap.accumArray const pmap
246 -- | Make sure a missing item returns 0
247 prop_PeerMap_findMissing pmap key =
248 PeerMap.find key (PeerMap.remove key puniq) == 0
249 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
250 puniq = PeerMap.accumArray const pmap
252 -- | Make sure an added item is found
253 prop_PeerMap_addFind pmap key em =
254 PeerMap.find key (PeerMap.add key em puniq) == em
255 where _types = (pmap::PeerMap.PeerMap,
256 key::PeerMap.Key, em::PeerMap.Elem)
257 puniq = PeerMap.accumArray const pmap
259 -- | Manual check that maxElem returns the maximum indeed, or 0 for null
260 prop_PeerMap_maxElem pmap =
261 PeerMap.maxElem puniq == if null puniq then 0
262 else (maximum . snd . unzip) puniq
263 where _types = pmap::PeerMap.PeerMap
264 puniq = PeerMap.accumArray const pmap
267 [ run prop_PeerMap_addIdempotent
268 , run prop_PeerMap_removeIdempotent
269 , run prop_PeerMap_maxElem
270 , run prop_PeerMap_addFind
271 , run prop_PeerMap_findMissing
276 prop_Container_addTwo cdata i1 i2 =
277 fn i1 i2 cont == fn i2 i1 cont &&
278 fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
279 where _types = (cdata::[Int],
281 cont = foldl (\c x -> Container.add x x c) Container.empty cdata
282 fn x1 x2 = Container.addTwo x1 x1 x2 x2
285 [ run prop_Container_addTwo ]
287 -- Simple instance tests, we only have setter/getters
289 prop_Instance_creat inst =
290 Instance.name inst == Instance.alias inst
292 prop_Instance_setIdx inst idx =
293 Instance.idx (Instance.setIdx inst idx) == idx
294 where _types = (inst::Instance.Instance, idx::Types.Idx)
296 prop_Instance_setName inst name =
297 Instance.name newinst == name &&
298 Instance.alias newinst == name
299 where _types = (inst::Instance.Instance, name::String)
300 newinst = Instance.setName inst name
302 prop_Instance_setAlias inst name =
303 Instance.name newinst == Instance.name inst &&
304 Instance.alias newinst == name
305 where _types = (inst::Instance.Instance, name::String)
306 newinst = Instance.setAlias inst name
308 prop_Instance_setPri inst pdx =
309 Instance.pNode (Instance.setPri inst pdx) == pdx
310 where _types = (inst::Instance.Instance, pdx::Types.Ndx)
312 prop_Instance_setSec inst sdx =
313 Instance.sNode (Instance.setSec inst sdx) == sdx
314 where _types = (inst::Instance.Instance, sdx::Types.Ndx)
316 prop_Instance_setBoth inst pdx sdx =
317 Instance.pNode si == pdx && Instance.sNode si == sdx
318 where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
319 si = Instance.setBoth inst pdx sdx
321 prop_Instance_runStatus_True inst =
322 let run_st = Instance.running inst
323 run_tx = Instance.runSt inst
325 run_tx `elem` Instance.runningStates ==> run_st
327 prop_Instance_runStatus_False inst =
328 let run_st = Instance.running inst
329 run_tx = Instance.runSt inst
331 run_tx `notElem` Instance.runningStates ==> not run_st
333 prop_Instance_shrinkMG inst =
334 Instance.mem inst >= 2 * Types.unitMem ==>
335 case Instance.shrinkByType inst Types.FailMem of
337 Instance.mem inst' == Instance.mem inst - Types.unitMem
340 prop_Instance_shrinkMF inst =
341 Instance.mem inst < 2 * Types.unitMem ==>
342 isBad $ Instance.shrinkByType inst Types.FailMem
344 prop_Instance_shrinkCG inst =
345 Instance.vcpus inst >= 2 * Types.unitCpu ==>
346 case Instance.shrinkByType inst Types.FailCPU of
348 Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
351 prop_Instance_shrinkCF inst =
352 Instance.vcpus inst < 2 * Types.unitCpu ==>
353 isBad $ Instance.shrinkByType inst Types.FailCPU
355 prop_Instance_shrinkDG inst =
356 Instance.dsk inst >= 2 * Types.unitDsk ==>
357 case Instance.shrinkByType inst Types.FailDisk of
359 Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
362 prop_Instance_shrinkDF inst =
363 Instance.dsk inst < 2 * Types.unitDsk ==>
364 isBad $ Instance.shrinkByType inst Types.FailDisk
366 prop_Instance_setMovable inst m =
367 Instance.movable inst' == m
368 where inst' = Instance.setMovable inst m
371 [ run prop_Instance_creat
372 , run prop_Instance_setIdx
373 , run prop_Instance_setName
374 , run prop_Instance_setAlias
375 , run prop_Instance_setPri
376 , run prop_Instance_setSec
377 , run prop_Instance_setBoth
378 , run prop_Instance_runStatus_True
379 , run prop_Instance_runStatus_False
380 , run prop_Instance_shrinkMG
381 , run prop_Instance_shrinkMF
382 , run prop_Instance_shrinkCG
383 , run prop_Instance_shrinkCF
384 , run prop_Instance_shrinkDG
385 , run prop_Instance_shrinkDF
386 , run prop_Instance_setMovable
389 -- Instance text loader tests
391 prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
392 not (null pnode) && pdx >= 0 && sdx >= 0 ==>
393 let vcpus_s = show vcpus
401 else [(pnode, pdx), (snode, rsdx)]
403 inst = Text.loadInst ndx
404 [name, mem_s, dsk_s, vcpus_s, status, pnode, snode, tags]::
405 Maybe (String, Instance.Instance)
406 fail1 = Text.loadInst ndx
407 [name, mem_s, dsk_s, vcpus_s, status, pnode, pnode, tags]::
408 Maybe (String, Instance.Instance)
409 _types = ( name::String, mem::Int, dsk::Int
410 , vcpus::Int, status::String
411 , pnode::String, snode::String
412 , pdx::Types.Ndx, sdx::Types.Ndx)
417 (Instance.name i == name &&
418 Instance.vcpus i == vcpus &&
419 Instance.mem i == mem &&
420 Instance.pNode i == pdx &&
421 Instance.sNode i == (if null snode
422 then Node.noSecondary
426 prop_Text_Load_InstanceFail ktn fields =
427 length fields /= 8 ==> isNothing $ Text.loadInst ktn fields
429 prop_Text_Load_Node name tm nm fm td fd tc fo =
430 let conv v = if v < 0
442 any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
443 in case Text.loadNode [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s] of
445 Just (name', node) ->
447 then Node.offline node
448 else Node.name node == name' && name' == name &&
449 Node.alias node == name &&
450 Node.tMem node == fromIntegral tm &&
451 Node.nMem node == nm &&
452 Node.fMem node == fm &&
453 Node.tDsk node == fromIntegral td &&
454 Node.fDsk node == fd &&
455 Node.tCpu node == fromIntegral tc
457 prop_Text_Load_NodeFail fields =
458 length fields /= 8 ==> isNothing $ Text.loadNode fields
460 prop_Text_NodeLSIdempotent node =
462 Utils.sepSplit '|' . Text.serializeNode) n ==
463 Just (Node.name n, n)
464 -- override failN1 to what loadNode returns by default
465 where n = node { Node.failN1 = True, Node.offline = False }
468 [ run prop_Text_Load_Instance
469 , run prop_Text_Load_InstanceFail
470 , run prop_Text_Load_Node
471 , run prop_Text_Load_NodeFail
472 , run prop_Text_NodeLSIdempotent
477 prop_Node_setAlias node name =
478 Node.name newnode == Node.name node &&
479 Node.alias newnode == name
480 where _types = (node::Node.Node, name::String)
481 newnode = Node.setAlias node name
483 prop_Node_setOffline node status =
484 Node.offline newnode == status
485 where newnode = Node.setOffline node status
487 prop_Node_setXmem node xm =
488 Node.xMem newnode == xm
489 where newnode = Node.setXmem node xm
491 prop_Node_setMcpu node mc =
492 Node.mCpu newnode == mc
493 where newnode = Node.setMcpu node mc
495 -- | Check that an instance add with too high memory or disk will be rejected
496 prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
497 not (Node.failN1 node)
499 case Node.addPri node inst'' of
500 Types.OpFail Types.FailMem -> True
502 where _types = (node::Node.Node, inst::Instance.Instance)
503 inst' = setInstanceSmallerThanNode node inst
504 inst'' = inst' { Instance.mem = Instance.mem inst }
506 prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
507 not (Node.failN1 node)
509 case Node.addPri node inst'' of
510 Types.OpFail Types.FailDisk -> True
512 where _types = (node::Node.Node, inst::Instance.Instance)
513 inst' = setInstanceSmallerThanNode node inst
514 inst'' = inst' { Instance.dsk = Instance.dsk inst }
516 prop_Node_addPriFC node inst = Instance.vcpus inst > Node.availCpu node &&
517 not (Node.failN1 node)
519 case Node.addPri node inst'' of
520 Types.OpFail Types.FailCPU -> True
522 where _types = (node::Node.Node, inst::Instance.Instance)
523 inst' = setInstanceSmallerThanNode node inst
524 inst'' = inst' { Instance.vcpus = Instance.vcpus inst }
526 -- | Check that an instance add with too high memory or disk will be rejected
527 prop_Node_addSec node inst pdx =
528 (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
529 Instance.dsk inst >= Node.fDsk node) &&
530 not (Node.failN1 node)
531 ==> isFailure (Node.addSec node inst pdx)
532 where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
534 newtype SmallRatio = SmallRatio Double deriving Show
535 instance Arbitrary SmallRatio where
538 return $ SmallRatio v
540 -- | Check mdsk setting
541 prop_Node_setMdsk node mx =
542 Node.loDsk node' >= 0 &&
543 fromIntegral (Node.loDsk node') <= Node.tDsk node &&
544 Node.availDisk node' >= 0 &&
545 Node.availDisk node' <= Node.fDsk node' &&
546 fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
547 Node.mDsk node' == mx'
548 where _types = (node::Node.Node, mx::SmallRatio)
549 node' = Node.setMdsk node mx'
553 prop_Node_tagMaps_idempotent tags =
554 Node.delTags (Node.addTags m tags) tags == m
555 where m = Data.Map.empty
557 prop_Node_tagMaps_reject tags =
559 any (\t -> Node.rejectAddTags m [t]) tags
560 where m = Node.addTags Data.Map.empty tags
562 prop_Node_showField node =
563 forAll (elements Node.defaultFields) $ \ field ->
564 fst (Node.showHeader field) /= Types.unknownField &&
565 Node.showField node field /= Types.unknownField
568 [ run prop_Node_setAlias
569 , run prop_Node_setOffline
570 , run prop_Node_setMcpu
571 , run prop_Node_setXmem
572 , run prop_Node_addPriFM
573 , run prop_Node_addPriFD
574 , run prop_Node_addPriFC
575 , run prop_Node_addSec
576 , run prop_Node_setMdsk
577 , run prop_Node_tagMaps_idempotent
578 , run prop_Node_tagMaps_reject
579 , run prop_Node_showField
585 -- | Check that the cluster score is close to zero for a homogeneous cluster
586 prop_Score_Zero node count =
587 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
588 (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
589 let fn = Node.buildPeers node Container.empty
590 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
591 nl = Container.fromAssocList nlst
592 score = Cluster.compCV nl
593 -- we can't say == 0 here as the floating point errors accumulate;
594 -- this should be much lower than the default score in CLI.hs
597 -- | Check that cluster stats are sane
598 prop_CStats_sane node count =
599 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
600 (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
601 let fn = Node.buildPeers node Container.empty
602 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
603 nl = Container.fromAssocList nlst
604 cstats = Cluster.totalResources nl
605 in Cluster.csAdsk cstats >= 0 &&
606 Cluster.csAdsk cstats <= Cluster.csFdsk cstats
608 -- | Check that one instance is allocated correctly, without
610 prop_ClusterAlloc_sane node inst =
611 forAll (choose (5, 20)) $ \count ->
612 not (Node.offline node)
613 && not (Node.failN1 node)
614 && Node.availDisk node > 0
615 && Node.availMem node > 0
617 let nl = makeSmallCluster node count
620 inst' = setInstanceSmallerThanNode node inst
621 in case Cluster.tryAlloc nl il inst' rqnodes of
623 Types.Ok (_, _, sols3) ->
626 (_, (xnl, xi, _)):[] ->
627 let cv = Cluster.compCV xnl
628 il' = Container.add (Instance.idx xi) xi il
629 tbl = Cluster.Table xnl il' cv []
630 in not (canBalance tbl True False)
633 -- | Checks that on a 2-5 node cluster, we can allocate a random
634 -- instance spec via tiered allocation (whatever the original instance
635 -- spec), on either one or two nodes
636 prop_ClusterCanTieredAlloc node inst =
637 forAll (choose (2, 5)) $ \count ->
638 forAll (choose (1, 2)) $ \rqnodes ->
639 not (Node.offline node)
640 && not (Node.failN1 node)
643 let nl = makeSmallCluster node count
645 in case Cluster.tieredAlloc nl il inst rqnodes [] of
647 Types.Ok (_, _, il', ixes) -> not (null ixes) &&
648 IntMap.size il' == length ixes
650 -- | Checks that on a 4-8 node cluster, once we allocate an instance,
651 -- we can also evacuate it
652 prop_ClusterAllocEvac node inst =
653 forAll (choose (4, 8)) $ \count ->
654 not (Node.offline node)
655 && not (Node.failN1 node)
658 let nl = makeSmallCluster node count
661 inst' = setInstanceSmallerThanNode node inst
662 in case Cluster.tryAlloc nl il inst' rqnodes of
664 Types.Ok (_, _, sols3) ->
667 (_, (xnl, xi, _)):[] ->
668 let sdx = Instance.sNode xi
669 il' = Container.add (Instance.idx xi) xi il
670 in case Cluster.tryEvac xnl il' [sdx] of
675 -- | Check that allocating multiple instances on a cluster, then
676 -- adding an empty node, results in a valid rebalance
677 prop_ClusterAllocBalance node =
678 forAll (choose (3, 5)) $ \count ->
679 not (Node.offline node)
680 && not (Node.failN1 node)
682 && not (isNodeBig node 8)
684 let nl = makeSmallCluster node count
685 (hnode, nl') = IntMap.deleteFindMax nl
688 i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
689 in case Cluster.iterateAlloc nl' il i_templ rqnodes [] of
691 Types.Ok (_, xnl, il', _) ->
692 let ynl = Container.add (Node.idx hnode) hnode xnl
693 cv = Cluster.compCV ynl
694 tbl = Cluster.Table ynl il' cv []
695 in canBalance tbl True False
698 [ run prop_Score_Zero
699 , run prop_CStats_sane
700 , run prop_ClusterAlloc_sane
701 , run prop_ClusterCanTieredAlloc
702 , run prop_ClusterAllocEvac
703 , run prop_ClusterAllocBalance
706 -- | Check that opcode serialization is idempotent
708 prop_OpCodes_serialization op =
709 case J.readJSON (J.showJSON op) of
711 J.Ok op' -> op == op'
712 where _types = op::OpCodes.OpCode
715 [ run prop_OpCodes_serialization
718 -- | Check that (queued) job\/opcode status serialization is idempotent
719 prop_OpStatus_serialization os =
720 case J.readJSON (J.showJSON os) of
722 J.Ok os' -> os == os'
723 where _types = os::Jobs.OpStatus
725 prop_JobStatus_serialization js =
726 case J.readJSON (J.showJSON js) of
728 J.Ok js' -> js == js'
729 where _types = js::Jobs.JobStatus
732 [ run prop_OpStatus_serialization
733 , run prop_JobStatus_serialization
738 prop_Loader_lookupNode ktn inst node =
739 isJust (Loader.lookupNode ktn inst node) == (node `elem` names)
740 where names = map fst ktn
742 prop_Loader_lookupInstance kti inst =
743 isJust (Loader.lookupInstance kti inst) == (inst `elem` names)
744 where names = map fst kti
746 prop_Loader_lookupInstanceIdx kti inst =
747 case (Loader.lookupInstance kti inst,
748 findIndex (\p -> fst p == inst) kti) of
749 (Nothing, Nothing) -> True
750 (Just idx, Just ex) -> idx == snd (kti !! ex)
753 prop_Loader_assignIndices enames =
754 length nassoc == length enames &&
755 length kt == length enames &&
756 (if not (null enames)
757 then maximum (map fst kt) == length enames - 1
759 where (nassoc, kt) = Loader.assignIndices enames
760 _types = enames::[(String, Node.Node)]
763 -- | Checks that the number of primary instances recorded on the nodes
765 prop_Loader_mergeData ns =
766 let na = map (\n -> (Node.idx n, n)) ns
767 in case Loader.mergeData [] [] [] (na, [], []) of
769 Types.Ok (nl, il, _) ->
770 let nodes = Container.elems nl
771 instances = Container.elems il
772 in (sum . map (length . Node.pList)) nodes == 0 &&
776 [ run prop_Loader_lookupNode
777 , run prop_Loader_lookupInstance
778 , run prop_Loader_lookupInstanceIdx
779 , run prop_Loader_assignIndices
780 , run prop_Loader_mergeData