1 {-| Implementation of cluster-wide logic.
3 This module holds all pure cluster-logic; I\/O related functionality
4 goes into the "Main" module for the individual binaries.
8 module Ganeti.HTools.Cluster
17 -- * Generic functions
19 -- * First phase functions
21 -- * Second phase functions
28 -- * Balacing functions
32 -- * Loading functions
37 import Data.Maybe (isNothing, fromJust)
38 import Text.Printf (printf)
41 import qualified Ganeti.HTools.Container as Container
42 import qualified Ganeti.HTools.Instance as Instance
43 import qualified Ganeti.HTools.Node as Node
44 import Ganeti.HTools.Utils
46 type NodeList = Container.Container Node.Node
47 type InstanceList = Container.Container Instance.Instance
48 -- | A separate name for the cluster score type
51 -- | The description of an instance placement.
52 type Placement = (Int, Int, Int, Score)
54 {- | A cluster solution described as the solution delta and the list
58 data Solution = Solution Int [Placement]
59 deriving (Eq, Ord, Show)
61 -- | Returns the delta of a solution or -1 for Nothing
62 solutionDelta :: Maybe Solution -> Int
63 solutionDelta sol = case sol of
64 Just (Solution d _) -> d
68 data Removal = Removal NodeList [Instance.Instance]
70 -- | An instance move definition
71 data IMove = Failover -- ^ Failover the instance (f)
72 | ReplacePrimary Int -- ^ Replace primary (f, r:np, f)
73 | ReplaceSecondary Int -- ^ Replace secondary (r:ns)
74 | ReplaceAndFailover Int -- ^ Replace secondary, failover (r:np, f)
75 | FailoverAndReplace Int -- ^ Failover, replace secondary (f, r:ns)
78 -- | The complete state for the balancing solution
79 data Table = Table NodeList InstanceList Score [Placement]
84 -- | Cap the removal list if needed.
85 capRemovals :: [a] -> Int -> [a]
86 capRemovals removals max_removals =
87 if max_removals > 0 then
88 take max_removals removals
92 -- | Check if the given node list fails the N+1 check.
93 verifyN1Check :: [Node.Node] -> Bool
94 verifyN1Check nl = any Node.failN1 nl
96 -- | Verifies the N+1 status and return the affected nodes.
97 verifyN1 :: [Node.Node] -> [Node.Node]
98 verifyN1 nl = filter Node.failN1 nl
100 {-| Add an instance and return the new node and instance maps. -}
101 addInstance :: NodeList -> Instance.Instance ->
102 Node.Node -> Node.Node -> Maybe NodeList
103 addInstance nl idata pri sec =
104 let pdx = Node.idx pri
107 pnode <- Node.addPri pri idata
108 snode <- Node.addSec sec idata pdx
109 new_nl <- return $ Container.addTwo sdx snode
113 -- | Remove an instance and return the new node and instance maps.
114 removeInstance :: NodeList -> Instance.Instance -> NodeList
115 removeInstance nl idata =
116 let pnode = Instance.pnode idata
117 snode = Instance.snode idata
118 pn = Container.find pnode nl
119 sn = Container.find snode nl
120 new_nl = Container.addTwo
121 pnode (Node.removePri pn idata)
122 snode (Node.removeSec sn idata) nl in
125 -- | Remove an instance and return the new node map.
126 removeInstances :: NodeList -> [Instance.Instance] -> NodeList
127 removeInstances = foldl' removeInstance
129 -- | Compute the total free disk and memory in the cluster.
130 totalResources :: Container.Container Node.Node -> (Int, Int)
133 (\ (mem, dsk) node -> (mem + (Node.f_mem node),
134 dsk + (Node.f_dsk node)))
135 (0, 0) (Container.elems nl)
137 {- | Compute a new version of a cluster given a solution.
139 This is not used for computing the solutions, but for applying a
140 (known-good) solution to the original cluster for final display.
142 It first removes the relocated instances after which it places them on
146 applySolution :: NodeList -> InstanceList -> [Placement] -> NodeList
147 applySolution nl il sol =
148 let odxes = map (\ (a, b, c, _) -> (Container.find a il,
149 Node.idx (Container.find b nl),
150 Node.idx (Container.find c nl))
152 idxes = (\ (x, _, _) -> x) (unzip3 odxes)
153 nc = removeInstances nl idxes
155 foldl' (\ nz (a, b, c) ->
156 let new_p = Container.find b nz
157 new_s = Container.find c nz in
158 fromJust (addInstance nz a new_p new_s)
162 -- First phase functions
164 {- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
168 genParts :: [a] -> Int -> [(a, [a])]
173 if length l < count then
176 (x, xs) : (genParts xs count)
178 -- | Generates combinations of count items from the names list.
179 genNames :: Int -> [b] -> [[b]]
180 genNames count1 names1 =
181 let aux_fn count names current =
186 (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
187 (genParts names count)
189 aux_fn count1 names1 []
191 {- | Computes the pair of bad nodes and instances.
193 The bad node list is computed via a simple 'verifyN1' check, and the
194 bad instance list is the list of primary and secondary instances of
198 computeBadItems :: NodeList -> InstanceList ->
199 ([Node.Node], [Instance.Instance])
200 computeBadItems nl il =
201 let bad_nodes = verifyN1 $ Container.elems nl
202 bad_instances = map (\idx -> Container.find idx il) $
203 sort $ nub $ concat $
204 map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
206 (bad_nodes, bad_instances)
209 {- | Checks if removal of instances results in N+1 pass.
211 Note: the check removal cannot optimize by scanning only the affected
212 nodes, since the cluster is known to be not healthy; only the check
213 placement can make this shortcut.
216 checkRemoval :: NodeList -> [Instance.Instance] -> Maybe Removal
217 checkRemoval nl victims =
218 let nx = removeInstances nl victims
219 failN1 = verifyN1Check (Container.elems nx)
224 Just $ Removal nx victims
227 -- | Computes the removals list for a given depth
228 computeRemovals :: NodeList
229 -> [Instance.Instance]
232 computeRemovals nl bad_instances depth =
233 map (checkRemoval nl) $ genNames depth bad_instances
235 -- Second phase functions
237 -- | Single-node relocation cost
238 nodeDelta :: Int -> Int -> Int -> Int
240 if i == p || i == s then
245 {-| Compute best solution.
247 This function compares two solutions, choosing the minimum valid
250 compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
251 compareSolutions a b = case (a, b) of
256 -- | Compute best table. Note that the ordering of the arguments is important.
257 compareTables :: Table -> Table -> Table
258 compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
259 if a_cv > b_cv then b else a
261 -- | Check if a given delta is worse then an existing solution.
262 tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
263 tooHighDelta sol new_delta max_delta =
264 if new_delta > max_delta && max_delta >=0 then
269 Just (Solution old_delta _) -> old_delta <= new_delta
271 {-| Check if placement of instances still keeps the cluster N+1 compliant.
273 This is the workhorse of the allocation algorithm: given the
274 current node and instance maps, the list of instances to be
275 placed, and the current solution, this will return all possible
276 solution by recursing until all target instances are placed.
279 checkPlacement :: NodeList -- ^ The current node list
280 -> [Instance.Instance] -- ^ List of instances still to place
281 -> [Placement] -- ^ Partial solution until now
282 -> Int -- ^ The delta of the partial solution
283 -> Maybe Solution -- ^ The previous solution
284 -> Int -- ^ Abort if the we go above this delta
285 -> Maybe Solution -- ^ The new solution
286 checkPlacement nl victims current current_delta prev_sol max_delta =
287 let target = head victims
288 opdx = Instance.pnode target
289 osdx = Instance.snode target
291 have_tail = (length vtail) > 0
292 nodes = Container.elems nl
293 iidx = Instance.idx target
298 pri_idx = Node.idx pri
299 upri_delta = current_delta + nodeDelta pri_idx opdx osdx
300 new_pri = Node.addPri pri target
301 fail_delta1 = tooHighDelta accu_p upri_delta max_delta
303 if fail_delta1 || isNothing(new_pri) then accu_p
304 else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
308 sec_idx = Node.idx sec
309 upd_delta = upri_delta +
310 nodeDelta sec_idx opdx osdx
311 fail_delta2 = tooHighDelta accu upd_delta max_delta
312 new_sec = Node.addSec sec target pri_idx
314 if sec_idx == pri_idx || fail_delta2 ||
315 isNothing new_sec then accu
317 nx = Container.add sec_idx (fromJust new_sec) pri_nl
319 plc = (iidx, pri_idx, sec_idx, upd_cv)
323 checkPlacement nx vtail c2 upd_delta
326 Just (Solution upd_delta c2)
327 in compareSolutions accu result
332 applyMove :: NodeList -> Instance.Instance
333 -> IMove -> (Maybe NodeList, Instance.Instance, Int, Int)
335 applyMove nl inst Failover =
336 let old_pdx = Instance.pnode inst
337 old_sdx = Instance.snode inst
338 old_p = Container.find old_pdx nl
339 old_s = Container.find old_sdx nl
340 int_p = Node.removePri old_p inst
341 int_s = Node.removeSec old_s inst
342 new_p = Node.addPri int_s inst
343 new_s = Node.addSec int_p inst old_sdx
344 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
345 else Just $ Container.addTwo old_pdx (fromJust new_s)
346 old_sdx (fromJust new_p) nl
347 in (new_nl, Instance.setBoth inst old_sdx old_pdx, old_sdx, old_pdx)
349 -- Replace the primary (f:, r:np, f)
350 applyMove nl inst (ReplacePrimary new_pdx) =
351 let old_pdx = Instance.pnode inst
352 old_sdx = Instance.snode inst
353 old_p = Container.find old_pdx nl
354 old_s = Container.find old_sdx nl
355 tgt_n = Container.find new_pdx nl
356 int_p = Node.removePri old_p inst
357 int_s = Node.removeSec old_s inst
358 new_p = Node.addPri tgt_n inst
359 new_s = Node.addSec int_s inst new_pdx
360 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
361 else Just $ Container.add new_pdx (fromJust new_p) $
362 Container.addTwo old_pdx int_p
363 old_sdx (fromJust new_s) nl
364 in (new_nl, Instance.setPri inst new_pdx, new_pdx, old_sdx)
366 -- Replace the secondary (r:ns)
367 applyMove nl inst (ReplaceSecondary new_sdx) =
368 let old_pdx = Instance.pnode inst
369 old_sdx = Instance.snode inst
370 old_s = Container.find old_sdx nl
371 tgt_n = Container.find new_sdx nl
372 int_s = Node.removeSec old_s inst
373 new_s = Node.addSec tgt_n inst old_pdx
374 new_nl = if isNothing(new_s) then Nothing
375 else Just $ Container.addTwo new_sdx (fromJust new_s)
377 in (new_nl, Instance.setSec inst new_sdx, old_pdx, new_sdx)
379 -- Replace the secondary and failover (r:np, f)
380 applyMove nl inst (ReplaceAndFailover new_pdx) =
381 let old_pdx = Instance.pnode inst
382 old_sdx = Instance.snode inst
383 old_p = Container.find old_pdx nl
384 old_s = Container.find old_sdx nl
385 tgt_n = Container.find new_pdx nl
386 int_p = Node.removePri old_p inst
387 int_s = Node.removeSec old_s inst
388 new_p = Node.addPri tgt_n inst
389 new_s = Node.addSec int_p inst new_pdx
390 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
391 else Just $ Container.add new_pdx (fromJust new_p) $
392 Container.addTwo old_pdx (fromJust new_s)
394 in (new_nl, Instance.setBoth inst new_pdx old_pdx, new_pdx, old_pdx)
396 -- Failver and replace the secondary (f, r:ns)
397 applyMove nl inst (FailoverAndReplace new_sdx) =
398 let old_pdx = Instance.pnode inst
399 old_sdx = Instance.snode inst
400 old_p = Container.find old_pdx nl
401 old_s = Container.find old_sdx nl
402 tgt_n = Container.find new_sdx nl
403 int_p = Node.removePri old_p inst
404 int_s = Node.removeSec old_s inst
405 new_p = Node.addPri int_s inst
406 new_s = Node.addSec tgt_n inst old_sdx
407 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
408 else Just $ Container.add new_sdx (fromJust new_s) $
409 Container.addTwo old_sdx (fromJust new_p)
411 in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
413 checkSingleStep :: Table -- ^ The original table
414 -> Instance.Instance -- ^ The instance to move
415 -> Table -- ^ The current best table
416 -> IMove -- ^ The move to apply
417 -> Table -- ^ The final best table
418 checkSingleStep ini_tbl target cur_tbl move =
420 Table ini_nl ini_il _ ini_plc = ini_tbl
421 (tmp_nl, new_inst, pri_idx, sec_idx) = applyMove ini_nl target move
423 if isNothing tmp_nl then cur_tbl
425 let tgt_idx = Instance.idx target
426 upd_nl = fromJust tmp_nl
427 upd_cvar = compCV upd_nl
428 upd_il = Container.add tgt_idx new_inst ini_il
429 upd_plc = (tgt_idx, pri_idx, sec_idx, upd_cvar):ini_plc
430 upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
432 compareTables cur_tbl upd_tbl
434 checkInstanceMove :: [Int] -- Allowed target node indices
435 -> Table -- Original table
436 -> Instance.Instance -- Instance to move
437 -> Table -- Best new table for this instance
438 checkInstanceMove nodes_idx ini_tbl target =
440 opdx = Instance.pnode target
441 osdx = Instance.snode target
442 nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
443 aft_failover = checkSingleStep ini_tbl target ini_tbl Failover
444 all_moves = concatMap (\idx -> [ReplacePrimary idx,
445 ReplaceSecondary idx,
446 ReplaceAndFailover idx,
447 FailoverAndReplace idx]) nodes
449 -- iterate over the possible nodes for this instance
450 foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
452 -- | Compute the best next move.
453 checkMove :: [Int] -- ^ Allowed target node indices
454 -> Table -- ^ The current solution
455 -> [Instance.Instance] -- ^ List of instances still to move
456 -> Table -- ^ The new solution
457 checkMove nodes_idx ini_tbl victims =
458 let Table _ _ _ ini_plc = ini_tbl
459 -- iterate over all instances, computing the best move
462 (\ step_tbl elem -> compareTables step_tbl $
463 checkInstanceMove nodes_idx ini_tbl elem)
465 Table _ _ _ best_plc = best_tbl
467 if length best_plc == length ini_plc then -- no advancement
472 {- | Auxiliary function for solution computation.
474 We write this in an explicit recursive fashion in order to control
475 early-abort in case we have met the min delta. We can't use foldr
476 instead of explicit recursion since we need the accumulator for the
480 advanceSolution :: [Maybe Removal] -- ^ The removal to process
481 -> Int -- ^ Minimum delta parameter
482 -> Int -- ^ Maximum delta parameter
483 -> Maybe Solution -- ^ Current best solution
484 -> Maybe Solution -- ^ New best solution
485 advanceSolution [] _ _ sol = sol
486 advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
487 advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
488 let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
489 new_delta = solutionDelta $! new_sol
491 if new_delta >= 0 && new_delta <= min_d then
494 advanceSolution xs min_d max_d new_sol
496 -- | Computes the placement solution.
497 solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
498 -> Int -- ^ Minimum delta parameter
499 -> Int -- ^ Maximum delta parameter
500 -> Maybe Solution -- ^ The best solution found
501 solutionFromRemovals removals min_delta max_delta =
502 advanceSolution removals min_delta max_delta Nothing
504 {- | Computes the solution at the given depth.
506 This is a wrapper over both computeRemovals and
507 solutionFromRemovals. In case we have no solution, we return Nothing.
510 computeSolution :: NodeList -- ^ The original node data
511 -> [Instance.Instance] -- ^ The list of /bad/ instances
512 -> Int -- ^ The /depth/ of removals
513 -> Int -- ^ Maximum number of removals to process
514 -> Int -- ^ Minimum delta parameter
515 -> Int -- ^ Maximum delta parameter
516 -> Maybe Solution -- ^ The best solution found (or Nothing)
517 computeSolution nl bad_instances depth max_removals min_delta max_delta =
519 removals = computeRemovals nl bad_instances depth
520 removals' = capRemovals removals max_removals
522 solutionFromRemovals removals' min_delta max_delta
524 -- Solution display functions (pure)
526 -- | Given the original and final nodes, computes the relocation description.
527 computeMoves :: String -- ^ The instance name
528 -> String -- ^ Original primary
529 -> String -- ^ Original secondary
530 -> String -- ^ New primary
531 -> String -- ^ New secondary
532 -> (String, [String])
533 -- ^ Tuple of moves and commands list; moves is containing
534 -- either @/f/@ for failover or @/r:name/@ for replace
535 -- secondary, while the command list holds gnt-instance
536 -- commands (without that prefix), e.g \"@failover instance1@\"
537 computeMoves i a b c d =
538 if c == a then {- Same primary -}
539 if d == b then {- Same sec??! -}
541 else {- Change of secondary -}
543 [printf "replace-disks -n %s %s" d i])
545 if c == b then {- Failover and ... -}
546 if d == a then {- that's all -}
547 ("f", [printf "migrate %s" i])
550 [printf "migrate %s" i,
551 printf "replace-disks -n %s %s" d i])
553 if d == a then {- ... and keep primary as secondary -}
555 [printf "replace-disks -n %s %s" c i,
556 printf "migrate %s" i])
558 if d == b then {- ... keep same secondary -}
559 (printf "f r:%s f" c,
560 [printf "migrate %s" i,
561 printf "replace-disks -n %s %s" c i,
562 printf "migrate %s" i])
564 else {- Nothing in common -}
565 (printf "r:%s f r:%s" c d,
566 [printf "replace-disks -n %s %s" c i,
567 printf "migrate %s" i,
568 printf "replace-disks -n %s %s" d i])
570 {-| Converts a placement to string format -}
571 printSolutionLine :: InstanceList
578 -> (String, [String])
579 printSolutionLine il ktn kti nmlen imlen plc pos =
581 pmlen = (2*nmlen + 1)
583 inst = Container.find i il
584 inam = fromJust $ lookup (Instance.idx inst) kti
585 npri = fromJust $ lookup p ktn
586 nsec = fromJust $ lookup s ktn
587 opri = fromJust $ lookup (Instance.pnode inst) ktn
588 osec = fromJust $ lookup (Instance.snode inst) ktn
589 (moves, cmds) = computeMoves inam opri osec npri nsec
590 ostr = (printf "%s:%s" opri osec)::String
591 nstr = (printf "%s:%s" npri nsec)::String
593 (printf " %3d. %-*s %-*s => %-*s %.8f a=%s"
594 pos imlen inam pmlen ostr
598 formatCmds :: [[String]] -> String
599 formatCmds cmd_strs =
600 unlines $ map (" echo " ++) $
601 concat $ map (\(a, b) ->
602 (printf "step %d" (a::Int)):(map ("gnt-instance " ++) b)) $
605 {-| Converts a solution to string format -}
606 printSolution :: InstanceList
610 -> ([String], [[String]])
611 printSolution il ktn kti sol =
613 mlen_fn = maximum . (map length) . snd . unzip
617 unzip $ map (uncurry $ printSolutionLine il ktn kti nmlen imlen) $
620 -- | Print the node list.
621 printNodes :: [(Int, String)] -> NodeList -> String
623 let snl = sortBy (compare `on` Node.idx) (Container.elems nl)
624 snl' = map (\ n -> ((fromJust $ lookup (Node.idx n) ktn), n)) snl
625 m_name = maximum . (map length) . fst . unzip $ snl'
626 helper = Node.list m_name
627 header = printf "%2s %-*s %5s %5s %5s %5s %5s %3s %3s %7s %7s"
628 "N1" m_name "Name" "t_mem" "f_mem" "r_mem"
630 "pri" "sec" "p_fmem" "p_fdsk"
631 in unlines $ (header:map (uncurry helper) snl')
633 -- | Compute the mem and disk covariance.
634 compDetailedCV :: NodeList -> (Double, Double, Double, Double)
637 nodes = Container.elems nl
638 mem_l = map Node.p_mem nodes
639 dsk_l = map Node.p_dsk nodes
640 mem_cv = varianceCoeff mem_l
641 dsk_cv = varianceCoeff dsk_l
642 n1_l = length $ filter Node.failN1 nodes
643 n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
644 res_l = map Node.p_rem nodes
645 res_cv = varianceCoeff res_l
646 in (mem_cv, dsk_cv, n1_score, res_cv)
648 -- | Compute the 'total' variance.
649 compCV :: NodeList -> Double
651 let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
652 in mem_cv + dsk_cv + n1_score + res_cv
654 printStats :: NodeList -> String
656 let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
657 in printf "f_mem=%.8f, r_mem=%.8f, f_dsk=%.8f, n1=%.3f"
658 mem_cv res_cv dsk_cv n1_score
660 -- Balancing functions
664 {- | Convert newline and delimiter-separated text.
666 This function converts a text in tabular format as generated by
667 @gnt-instance list@ and @gnt-node list@ to a list of objects using a
668 supplied conversion function.
671 loadTabular :: String -> ([String] -> (String, a))
672 -> (a -> Int -> a) -> ([(String, Int)], [(Int, a)])
673 loadTabular text_data convert_fn set_fn =
674 let lines_data = lines text_data
675 rows = map (sepSplit '|') lines_data
676 kerows = (map convert_fn rows)
677 idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
681 -- | For each instance, add its index to its primary and secondary nodes
682 fixNodes :: [(Int, Node.Node)]
683 -> [(Int, Instance.Instance)]
684 -> [(Int, Node.Node)]
686 foldl' (\accu (idx, inst) ->
688 assocEqual = (\ (i, _) (j, _) -> i == j)
689 pdx = Instance.pnode inst
690 sdx = Instance.snode inst
691 pold = fromJust $ lookup pdx accu
692 sold = fromJust $ lookup sdx accu
693 pnew = Node.setPri pold idx
694 snew = Node.setSec sold idx
695 ac1 = deleteBy assocEqual (pdx, pold) accu
696 ac2 = deleteBy assocEqual (sdx, sold) ac1
697 ac3 = (pdx, pnew):(sdx, snew):ac2
700 -- | Compute the longest common suffix of a [(Int, String)] list that
701 -- | starts with a dot
702 longestDomain :: [(Int, String)] -> String
703 longestDomain [] = ""
704 longestDomain ((_,x):xs) =
706 onlyStrings = snd $ unzip xs
708 foldr (\ suffix accu -> if all (isSuffixOf suffix) onlyStrings
711 "" $ filter (isPrefixOf ".") (tails x)
713 -- | Remove tails from the (Int, String) lists
714 stripSuffix :: String -> [(Int, String)] -> [(Int, String)]
715 stripSuffix suffix lst =
716 let sflen = length suffix in
717 map (\ (key, name) -> (key, take ((length name) - sflen) name)) lst
719 {-| Initializer function that loads the data from a node and list file
720 and massages it into the correct format. -}
721 loadData :: String -- ^ Node data in text format
722 -> String -- ^ Instance data in text format
723 -> (Container.Container Node.Node,
724 Container.Container Instance.Instance,
725 String, [(Int, String)], [(Int, String)])
726 loadData ndata idata =
728 {- node file: name mem disk -}
729 (ktn, nl) = loadTabular ndata
730 (\ (i:jt:jf:kt:kf:[]) -> (i, Node.create jt jf kt kf))
732 {- instance file: name mem disk -}
733 (kti, il) = loadTabular idata
734 (\ (i:j:k:l:m:[]) -> (i,
736 (fromJust $ lookup l ktn)
737 (fromJust $ lookup m ktn)))
740 il3 = Container.fromAssocList il
741 nl3 = Container.fromAssocList
742 (map (\ (k, v) -> (k, Node.buildPeers v il3 (length nl2))) nl2)
745 common_suffix = longestDomain (xti ++ xtn)
746 stn = stripSuffix common_suffix xtn
747 sti = stripSuffix common_suffix xti
749 (nl3, il3, common_suffix, stn, sti)