1 {-| Unittests for ganeti-htools
7 Copyright (C) 2009 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
38 import Test.QuickCheck
39 import Test.QuickCheck.Batch
40 import Data.List (findIndex)
43 import qualified Text.JSON as J
44 import qualified Data.Map
45 import qualified Data.IntMap as IntMap
46 import qualified Ganeti.OpCodes as OpCodes
47 import qualified Ganeti.Jobs as Jobs
48 import qualified Ganeti.HTools.CLI as CLI
49 import qualified Ganeti.HTools.Cluster as Cluster
50 import qualified Ganeti.HTools.Container as Container
51 import qualified Ganeti.HTools.IAlloc as IAlloc
52 import qualified Ganeti.HTools.Instance as Instance
53 import qualified Ganeti.HTools.Loader as Loader
54 import qualified Ganeti.HTools.Node as Node
55 import qualified Ganeti.HTools.PeerMap as PeerMap
56 import qualified Ganeti.HTools.Text as Text
57 import qualified Ganeti.HTools.Types as Types
58 import qualified Ganeti.HTools.Utils as Utils
62 -- | Maximum memory (1TiB, somewhat random value)
66 -- | Maximum disk (8TiB, somewhat random value)
68 maxDsk = 1024 * 1024 * 8
70 -- | Max CPUs (1024, somewhat random value)
76 -- | Simple checker for whether OpResult is fail or pass
77 isFailure :: Types.OpResult a -> Bool
78 isFailure (Types.OpFail _) = True
81 -- | Simple checker for whether Result is fail or pass
82 isOk :: Types.Result a -> Bool
83 isOk (Types.Ok _ ) = True
86 isBad :: Types.Result a -> Bool
89 -- | Update an instance to be smaller than a node
90 setInstanceSmallerThanNode node inst =
91 inst { Instance.mem = Node.availMem node `div` 2
92 , Instance.dsk = Node.availDisk node `div` 2
93 , Instance.vcpus = Node.availCpu node `div` 2
96 -- | Create an instance given its spec
97 createInstance mem dsk vcpus =
98 Instance.create "inst-unnamed" mem dsk vcpus "running" [] (-1) (-1)
100 -- | Create a small cluster by repeating a node spec
101 makeSmallCluster :: Node.Node -> Int -> Node.List
102 makeSmallCluster node count =
103 let fn = Node.buildPeers node Container.empty
104 namelst = map (\n -> (Node.name n, n)) (replicate count fn)
105 (_, nlst) = Loader.assignIndices namelst
106 in Container.fromAssocList nlst
108 -- | Checks if a node is "big" enough
109 isNodeBig :: Node.Node -> Int -> Bool
110 isNodeBig node size = Node.availDisk node > size * Types.unitDsk
111 && Node.availMem node > size * Types.unitMem
112 && Node.availCpu node > size * Types.unitCpu
114 canBalance :: Cluster.Table -> Bool -> Bool -> Bool
115 canBalance tbl dm evac = isJust $ Cluster.tryBalance tbl dm evac
117 -- * Arbitrary instances
119 -- copied from the introduction to quickcheck
120 instance Arbitrary Char where
121 arbitrary = choose ('\32', '\128')
123 -- let's generate a random instance
124 instance Arbitrary Instance.Instance where
127 mem <- choose (0, maxMem)
128 dsk <- choose (0, maxDsk)
129 run_st <- elements ["ERROR_up", "ERROR_down", "ADMIN_down"
130 , "ERROR_nodedown", "ERROR_nodeoffline"
132 , "no_such_status1", "no_such_status2"]
135 vcpus <- choose (0, maxCpu)
136 return $ Instance.create name mem dsk vcpus run_st [] pn sn
139 instance Arbitrary Node.Node where
142 mem_t <- choose (0, maxMem)
143 mem_f <- choose (0, mem_t)
144 mem_n <- choose (0, mem_t - mem_f)
145 dsk_t <- choose (0, maxDsk)
146 dsk_f <- choose (0, dsk_t)
147 cpu_t <- choose (0, maxCpu)
149 let n = Node.create name (fromIntegral mem_t) mem_n mem_f
150 (fromIntegral dsk_t) dsk_f (fromIntegral cpu_t) offl
151 n' = Node.buildPeers n Container.empty
155 instance Arbitrary OpCodes.ReplaceDisksMode where
156 arbitrary = elements [ OpCodes.ReplaceOnPrimary
157 , OpCodes.ReplaceOnSecondary
158 , OpCodes.ReplaceNewSecondary
159 , OpCodes.ReplaceAuto
162 instance Arbitrary OpCodes.OpCode where
164 op_id <- elements [ "OP_TEST_DELAY"
165 , "OP_INSTANCE_REPLACE_DISKS"
166 , "OP_INSTANCE_FAILOVER"
167 , "OP_INSTANCE_MIGRATE"
171 liftM3 OpCodes.OpTestDelay arbitrary arbitrary arbitrary
172 "OP_INSTANCE_REPLACE_DISKS" ->
173 liftM5 OpCodes.OpReplaceDisks arbitrary arbitrary
174 arbitrary arbitrary arbitrary
175 "OP_INSTANCE_FAILOVER" ->
176 liftM2 OpCodes.OpFailoverInstance arbitrary arbitrary
177 "OP_INSTANCE_MIGRATE" ->
178 liftM3 OpCodes.OpMigrateInstance arbitrary arbitrary arbitrary
179 _ -> fail "Wrong opcode")
181 instance Arbitrary Jobs.OpStatus where
182 arbitrary = elements [minBound..maxBound]
184 instance Arbitrary Jobs.JobStatus where
185 arbitrary = elements [minBound..maxBound]
189 -- | Make sure add is idempotent
190 prop_PeerMap_addIdempotent pmap key em =
191 fn puniq == fn (fn puniq)
192 where _types = (pmap::PeerMap.PeerMap,
193 key::PeerMap.Key, em::PeerMap.Elem)
194 fn = PeerMap.add key em
195 puniq = PeerMap.accumArray const pmap
197 -- | Make sure remove is idempotent
198 prop_PeerMap_removeIdempotent pmap key =
199 fn puniq == fn (fn puniq)
200 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
201 fn = PeerMap.remove key
202 puniq = PeerMap.accumArray const pmap
204 -- | Make sure a missing item returns 0
205 prop_PeerMap_findMissing pmap key =
206 PeerMap.find key (PeerMap.remove key puniq) == 0
207 where _types = (pmap::PeerMap.PeerMap, key::PeerMap.Key)
208 puniq = PeerMap.accumArray const pmap
210 -- | Make sure an added item is found
211 prop_PeerMap_addFind pmap key em =
212 PeerMap.find key (PeerMap.add key em puniq) == em
213 where _types = (pmap::PeerMap.PeerMap,
214 key::PeerMap.Key, em::PeerMap.Elem)
215 puniq = PeerMap.accumArray const pmap
217 -- | Manual check that maxElem returns the maximum indeed, or 0 for null
218 prop_PeerMap_maxElem pmap =
219 PeerMap.maxElem puniq == if null puniq then 0
220 else (maximum . snd . unzip) puniq
221 where _types = pmap::PeerMap.PeerMap
222 puniq = PeerMap.accumArray const pmap
225 [ run prop_PeerMap_addIdempotent
226 , run prop_PeerMap_removeIdempotent
227 , run prop_PeerMap_maxElem
228 , run prop_PeerMap_addFind
229 , run prop_PeerMap_findMissing
234 prop_Container_addTwo cdata i1 i2 =
235 fn i1 i2 cont == fn i2 i1 cont &&
236 fn i1 i2 cont == fn i1 i2 (fn i1 i2 cont)
237 where _types = (cdata::[Int],
239 cont = foldl (\c x -> Container.add x x c) Container.empty cdata
240 fn x1 x2 = Container.addTwo x1 x1 x2 x2
243 [ run prop_Container_addTwo ]
245 -- Simple instance tests, we only have setter/getters
247 prop_Instance_creat inst =
248 Instance.name inst == Instance.alias inst
250 prop_Instance_setIdx inst idx =
251 Instance.idx (Instance.setIdx inst idx) == idx
252 where _types = (inst::Instance.Instance, idx::Types.Idx)
254 prop_Instance_setName inst name =
255 Instance.name newinst == name &&
256 Instance.alias newinst == name
257 where _types = (inst::Instance.Instance, name::String)
258 newinst = Instance.setName inst name
260 prop_Instance_setAlias inst name =
261 Instance.name newinst == Instance.name inst &&
262 Instance.alias newinst == name
263 where _types = (inst::Instance.Instance, name::String)
264 newinst = Instance.setAlias inst name
266 prop_Instance_setPri inst pdx =
267 Instance.pNode (Instance.setPri inst pdx) == pdx
268 where _types = (inst::Instance.Instance, pdx::Types.Ndx)
270 prop_Instance_setSec inst sdx =
271 Instance.sNode (Instance.setSec inst sdx) == sdx
272 where _types = (inst::Instance.Instance, sdx::Types.Ndx)
274 prop_Instance_setBoth inst pdx sdx =
275 Instance.pNode si == pdx && Instance.sNode si == sdx
276 where _types = (inst::Instance.Instance, pdx::Types.Ndx, sdx::Types.Ndx)
277 si = Instance.setBoth inst pdx sdx
279 prop_Instance_runStatus_True inst =
280 let run_st = Instance.running inst
281 run_tx = Instance.runSt inst
283 run_tx `elem` Instance.runningStates ==> run_st
285 prop_Instance_runStatus_False inst =
286 let run_st = Instance.running inst
287 run_tx = Instance.runSt inst
289 run_tx `notElem` Instance.runningStates ==> not run_st
291 prop_Instance_shrinkMG inst =
292 Instance.mem inst >= 2 * Types.unitMem ==>
293 case Instance.shrinkByType inst Types.FailMem of
295 Instance.mem inst' == Instance.mem inst - Types.unitMem
298 prop_Instance_shrinkMF inst =
299 Instance.mem inst < 2 * Types.unitMem ==>
300 isBad $ Instance.shrinkByType inst Types.FailMem
302 prop_Instance_shrinkCG inst =
303 Instance.vcpus inst >= 2 * Types.unitCpu ==>
304 case Instance.shrinkByType inst Types.FailCPU of
306 Instance.vcpus inst' == Instance.vcpus inst - Types.unitCpu
309 prop_Instance_shrinkCF inst =
310 Instance.vcpus inst < 2 * Types.unitCpu ==>
311 isBad $ Instance.shrinkByType inst Types.FailCPU
313 prop_Instance_shrinkDG inst =
314 Instance.dsk inst >= 2 * Types.unitDsk ==>
315 case Instance.shrinkByType inst Types.FailDisk of
317 Instance.dsk inst' == Instance.dsk inst - Types.unitDsk
320 prop_Instance_shrinkDF inst =
321 Instance.dsk inst < 2 * Types.unitDsk ==>
322 isBad $ Instance.shrinkByType inst Types.FailDisk
324 prop_Instance_setMovable inst m =
325 Instance.movable inst' == m
326 where inst' = Instance.setMovable inst m
329 [ run prop_Instance_creat
330 , run prop_Instance_setIdx
331 , run prop_Instance_setName
332 , run prop_Instance_setAlias
333 , run prop_Instance_setPri
334 , run prop_Instance_setSec
335 , run prop_Instance_setBoth
336 , run prop_Instance_runStatus_True
337 , run prop_Instance_runStatus_False
338 , run prop_Instance_shrinkMG
339 , run prop_Instance_shrinkMF
340 , run prop_Instance_shrinkCG
341 , run prop_Instance_shrinkCF
342 , run prop_Instance_shrinkDG
343 , run prop_Instance_shrinkDF
344 , run prop_Instance_setMovable
347 -- Instance text loader tests
349 prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
350 not (null pnode) && pdx >= 0 && sdx >= 0 ==>
351 let vcpus_s = show vcpus
359 else [(pnode, pdx), (snode, rsdx)]
361 inst = Text.loadInst ndx
362 [name, mem_s, dsk_s, vcpus_s, status, pnode, snode, tags]::
363 Maybe (String, Instance.Instance)
364 fail1 = Text.loadInst ndx
365 [name, mem_s, dsk_s, vcpus_s, status, pnode, pnode, tags]::
366 Maybe (String, Instance.Instance)
367 _types = ( name::String, mem::Int, dsk::Int
368 , vcpus::Int, status::String
369 , pnode::String, snode::String
370 , pdx::Types.Ndx, sdx::Types.Ndx)
375 (Instance.name i == name &&
376 Instance.vcpus i == vcpus &&
377 Instance.mem i == mem &&
378 Instance.pNode i == pdx &&
379 Instance.sNode i == (if null snode
380 then Node.noSecondary
384 prop_Text_Load_InstanceFail ktn fields =
385 length fields /= 8 ==> isNothing $ Text.loadInst ktn fields
387 prop_Text_Load_Node name tm nm fm td fd tc fo =
388 let conv v = if v < 0
400 any_broken = any (< 0) [tm, nm, fm, td, fd, tc]
401 in case Text.loadNode [name, tm_s, nm_s, fm_s, td_s, fd_s, tc_s, fo_s] of
403 Just (name', node) ->
405 then Node.offline node
406 else Node.name node == name' && name' == name &&
407 Node.alias node == name &&
408 Node.tMem node == fromIntegral tm &&
409 Node.nMem node == nm &&
410 Node.fMem node == fm &&
411 Node.tDsk node == fromIntegral td &&
412 Node.fDsk node == fd &&
413 Node.tCpu node == fromIntegral tc
415 prop_Text_Load_NodeFail fields =
416 length fields /= 8 ==> isNothing $ Text.loadNode fields
419 [ run prop_Text_Load_Instance
420 , run prop_Text_Load_InstanceFail
421 , run prop_Text_Load_Node
422 , run prop_Text_Load_NodeFail
427 prop_Node_setAlias node name =
428 Node.name newnode == Node.name node &&
429 Node.alias newnode == name
430 where _types = (node::Node.Node, name::String)
431 newnode = Node.setAlias node name
433 prop_Node_setOffline node status =
434 Node.offline newnode == status
435 where newnode = Node.setOffline node status
437 prop_Node_setXmem node xm =
438 Node.xMem newnode == xm
439 where newnode = Node.setXmem node xm
441 prop_Node_setMcpu node mc =
442 Node.mCpu newnode == mc
443 where newnode = Node.setMcpu node mc
445 -- | Check that an instance add with too high memory or disk will be rejected
446 prop_Node_addPriFM node inst = Instance.mem inst >= Node.fMem node &&
447 not (Node.failN1 node)
449 case Node.addPri node inst'' of
450 Types.OpFail Types.FailMem -> True
452 where _types = (node::Node.Node, inst::Instance.Instance)
453 inst' = setInstanceSmallerThanNode node inst
454 inst'' = inst' { Instance.mem = Instance.mem inst }
456 prop_Node_addPriFD node inst = Instance.dsk inst >= Node.fDsk node &&
457 not (Node.failN1 node)
459 case Node.addPri node inst'' of
460 Types.OpFail Types.FailDisk -> True
462 where _types = (node::Node.Node, inst::Instance.Instance)
463 inst' = setInstanceSmallerThanNode node inst
464 inst'' = inst' { Instance.dsk = Instance.dsk inst }
466 prop_Node_addPriFC node inst = Instance.vcpus inst > Node.availCpu node &&
467 not (Node.failN1 node)
469 case Node.addPri node inst'' of
470 Types.OpFail Types.FailCPU -> True
472 where _types = (node::Node.Node, inst::Instance.Instance)
473 inst' = setInstanceSmallerThanNode node inst
474 inst'' = inst' { Instance.vcpus = Instance.vcpus inst }
476 -- | Check that an instance add with too high memory or disk will be rejected
477 prop_Node_addSec node inst pdx =
478 (Instance.mem inst >= (Node.fMem node - Node.rMem node) ||
479 Instance.dsk inst >= Node.fDsk node) &&
480 not (Node.failN1 node)
481 ==> isFailure (Node.addSec node inst pdx)
482 where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
484 newtype SmallRatio = SmallRatio Double deriving Show
485 instance Arbitrary SmallRatio where
488 return $ SmallRatio v
490 -- | Check mdsk setting
491 prop_Node_setMdsk node mx =
492 Node.loDsk node' >= 0 &&
493 fromIntegral (Node.loDsk node') <= Node.tDsk node &&
494 Node.availDisk node' >= 0 &&
495 Node.availDisk node' <= Node.fDsk node' &&
496 fromIntegral (Node.availDisk node') <= Node.tDsk node' &&
497 Node.mDsk node' == mx'
498 where _types = (node::Node.Node, mx::SmallRatio)
499 node' = Node.setMdsk node mx'
503 prop_Node_tagMaps_idempotent tags =
504 Node.delTags (Node.addTags m tags) tags == m
505 where m = Data.Map.empty
507 prop_Node_tagMaps_reject tags =
509 any (\t -> Node.rejectAddTags m [t]) tags
510 where m = Node.addTags Data.Map.empty tags
512 prop_Node_showField node =
513 forAll (elements Node.defaultFields) $ \ field ->
514 fst (Node.showHeader field) /= Types.unknownField &&
515 Node.showField node field /= Types.unknownField
518 [ run prop_Node_setAlias
519 , run prop_Node_setOffline
520 , run prop_Node_setMcpu
521 , run prop_Node_setXmem
522 , run prop_Node_addPriFM
523 , run prop_Node_addPriFD
524 , run prop_Node_addPriFC
525 , run prop_Node_addSec
526 , run prop_Node_setMdsk
527 , run prop_Node_tagMaps_idempotent
528 , run prop_Node_tagMaps_reject
529 , run prop_Node_showField
535 -- | Check that the cluster score is close to zero for a homogeneous cluster
536 prop_Score_Zero node count =
537 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
538 (Node.tDsk node > 0) && (Node.tMem node > 0)) ==>
539 let fn = Node.buildPeers node Container.empty
540 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
541 nl = Container.fromAssocList nlst
542 score = Cluster.compCV nl
543 -- we can't say == 0 here as the floating point errors accumulate;
544 -- this should be much lower than the default score in CLI.hs
547 -- | Check that cluster stats are sane
548 prop_CStats_sane node count =
549 (not (Node.offline node) && not (Node.failN1 node) && (count > 0) &&
550 (Node.availDisk node > 0) && (Node.availMem node > 0)) ==>
551 let fn = Node.buildPeers node Container.empty
552 nlst = zip [1..] $ replicate count fn::[(Types.Ndx, Node.Node)]
553 nl = Container.fromAssocList nlst
554 cstats = Cluster.totalResources nl
555 in Cluster.csAdsk cstats >= 0 &&
556 Cluster.csAdsk cstats <= Cluster.csFdsk cstats
558 -- | Check that one instance is allocated correctly, without
560 prop_ClusterAlloc_sane node inst =
561 forAll (choose (5, 20)) $ \count ->
562 not (Node.offline node)
563 && not (Node.failN1 node)
564 && Node.availDisk node > 0
565 && Node.availMem node > 0
567 let nl = makeSmallCluster node count
570 inst' = setInstanceSmallerThanNode node inst
571 in case Cluster.tryAlloc nl il inst' rqnodes of
573 Types.Ok (_, _, sols3) ->
576 (_, (xnl, xi, _)):[] ->
577 let cv = Cluster.compCV xnl
578 il' = Container.add (Instance.idx xi) xi il
579 tbl = Cluster.Table xnl il' cv []
580 in not (canBalance tbl True False)
583 -- | Checks that on a 2-5 node cluster, we can allocate a random
584 -- instance spec via tiered allocation (whatever the original instance
585 -- spec), on either one or two nodes
586 prop_ClusterCanTieredAlloc node inst =
587 forAll (choose (2, 5)) $ \count ->
588 forAll (choose (1, 2)) $ \rqnodes ->
589 not (Node.offline node)
590 && not (Node.failN1 node)
593 let nl = makeSmallCluster node count
595 in case Cluster.tieredAlloc nl il inst rqnodes [] of
597 Types.Ok (_, _, ixes) -> not (null ixes)
599 -- | Checks that on a 4-8 node cluster, once we allocate an instance,
600 -- we can also evacuate it
601 prop_ClusterAllocEvac node inst =
602 forAll (choose (4, 8)) $ \count ->
603 not (Node.offline node)
604 && not (Node.failN1 node)
607 let nl = makeSmallCluster node count
610 inst' = setInstanceSmallerThanNode node inst
611 in case Cluster.tryAlloc nl il inst' rqnodes of
613 Types.Ok (_, _, sols3) ->
616 (_, (xnl, xi, _)):[] ->
617 let sdx = Instance.sNode xi
618 il' = Container.add (Instance.idx xi) xi il
619 in case Cluster.tryEvac xnl il' [sdx] of
624 -- | Check that allocating multiple instances on a cluster, then
625 -- adding an empty node, results in a valid rebalance
626 prop_ClusterAllocBalance node =
627 forAll (choose (3, 5)) $ \count ->
628 not (Node.offline node)
629 && not (Node.failN1 node)
631 && not (isNodeBig node 8)
633 let nl = makeSmallCluster node count
634 (hnode, nl') = IntMap.deleteFindMax nl
637 i_templ = createInstance Types.unitMem Types.unitDsk Types.unitCpu
638 in case Cluster.iterateAlloc nl' il i_templ rqnodes [] of
640 Types.Ok (_, xnl, insts) ->
641 let ynl = Container.add (Node.idx hnode) hnode xnl
642 cv = Cluster.compCV ynl
644 Container.add (Instance.idx i) i l)
646 tbl = Cluster.Table ynl il' cv []
647 in canBalance tbl True False
650 [ run prop_Score_Zero
651 , run prop_CStats_sane
652 , run prop_ClusterAlloc_sane
653 , run prop_ClusterCanTieredAlloc
654 , run prop_ClusterAllocEvac
655 , run prop_ClusterAllocBalance
658 -- | Check that opcode serialization is idempotent
660 prop_OpCodes_serialization op =
661 case J.readJSON (J.showJSON op) of
663 J.Ok op' -> op == op'
664 where _types = op::OpCodes.OpCode
667 [ run prop_OpCodes_serialization
670 -- | Check that (queued) job\/opcode status serialization is idempotent
671 prop_OpStatus_serialization os =
672 case J.readJSON (J.showJSON os) of
674 J.Ok os' -> os == os'
675 where _types = os::Jobs.OpStatus
677 prop_JobStatus_serialization js =
678 case J.readJSON (J.showJSON js) of
680 J.Ok js' -> js == js'
681 where _types = js::Jobs.JobStatus
684 [ run prop_OpStatus_serialization
685 , run prop_JobStatus_serialization
690 prop_Loader_lookupNode ktn inst node =
691 isJust (Loader.lookupNode ktn inst node) == (node `elem` names)
692 where names = map fst ktn
694 prop_Loader_lookupInstance kti inst =
695 isJust (Loader.lookupInstance kti inst) == (inst `elem` names)
696 where names = map fst kti
698 prop_Loader_lookupInstanceIdx kti inst =
699 case (Loader.lookupInstance kti inst,
700 findIndex (\p -> fst p == inst) kti) of
701 (Nothing, Nothing) -> True
702 (Just idx, Just ex) -> idx == snd (kti !! ex)
705 prop_Loader_assignIndices enames =
706 length nassoc == length enames &&
707 length kt == length enames &&
708 (if not (null enames)
709 then maximum (map fst kt) == length enames - 1
711 where (nassoc, kt) = Loader.assignIndices enames
712 _types = enames::[(String, Node.Node)]
715 -- | Checks that the number of primary instances recorded on the nodes
717 prop_Loader_mergeData ns =
718 let na = map (\n -> (Node.idx n, n)) ns
719 in case Loader.mergeData [] [] [] (na, [], []) of
721 Types.Ok (nl, il, _) ->
722 let nodes = Container.elems nl
723 instances = Container.elems il
724 in (sum . map (length . Node.pList)) nodes == 0 &&
728 [ run prop_Loader_lookupNode
729 , run prop_Loader_lookupInstance
730 , run prop_Loader_lookupInstanceIdx
731 , run prop_Loader_assignIndices
732 , run prop_Loader_mergeData