1 {-| Implementation of cluster-wide logic.
3 This module holds all pure cluster-logic; I\/O related functionality
4 goes into the "Main" module.
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 Container
42 import qualified Instance
46 type NodeList = Container.Container Node.Node
47 type InstanceList = Container.Container Instance.Instance
50 -- | The description of an instance placement.
51 type Placement = (Int, Int, Int, Score)
53 {- | A cluster solution described as the solution delta and the list
57 data Solution = Solution Int [Placement]
58 deriving (Eq, Ord, Show)
60 -- | Returns the delta of a solution or -1 for Nothing
61 solutionDelta :: Maybe Solution -> Int
62 solutionDelta sol = case sol of
63 Just (Solution d _) -> d
67 data Removal = Removal NodeList [Instance.Instance]
69 -- | An instance move definition
70 data IMove = Failover -- ^ Failover the instance (f)
71 | ReplacePrimary Int -- ^ Replace primary (f, r:np, f)
72 | ReplaceSecondary Int -- ^ Replace secondary (r:ns)
73 | ReplaceAndFailover Int -- ^ Replace secondary, failover (r:np, f)
74 | FailoverAndReplace Int -- ^ Failover, replace secondary (f, r:ns)
77 -- | The complete state for the balancing solution
78 data Table = Table NodeList InstanceList Score [Placement]
83 -- | Cap the removal list if needed.
84 capRemovals :: [a] -> Int -> [a]
85 capRemovals removals max_removals =
86 if max_removals > 0 then
87 take max_removals removals
91 -- | Check if the given node list fails the N+1 check.
92 verifyN1Check :: [Node.Node] -> Bool
93 verifyN1Check nl = any Node.failN1 nl
95 -- | Verifies the N+1 status and return the affected nodes.
96 verifyN1 :: [Node.Node] -> [Node.Node]
97 verifyN1 nl = filter Node.failN1 nl
99 {-| Add an instance and return the new node and instance maps. -}
100 addInstance :: NodeList -> Instance.Instance ->
101 Node.Node -> Node.Node -> Maybe NodeList
102 addInstance nl idata pri sec =
103 let pdx = Node.idx pri
106 pnode <- Node.addPri pri idata
107 snode <- Node.addSec sec idata pdx
108 new_nl <- return $ Container.addTwo sdx snode
112 -- | Remove an instance and return the new node and instance maps.
113 removeInstance :: NodeList -> Instance.Instance -> NodeList
114 removeInstance nl idata =
115 let pnode = Instance.pnode idata
116 snode = Instance.snode idata
117 pn = Container.find pnode nl
118 sn = Container.find snode nl
119 new_nl = Container.addTwo
120 pnode (Node.removePri pn idata)
121 snode (Node.removeSec sn idata) nl in
124 -- | Remove an instance and return the new node map.
125 removeInstances :: NodeList -> [Instance.Instance] -> NodeList
126 removeInstances = foldl' removeInstance
128 -- | Compute the total free disk and memory in the cluster.
129 totalResources :: Container.Container Node.Node -> (Int, Int)
132 (\ (mem, dsk) node -> (mem + (Node.f_mem node),
133 dsk + (Node.f_dsk node)))
134 (0, 0) (Container.elems nl)
136 {- | Compute a new version of a cluster given a solution.
138 This is not used for computing the solutions, but for applying a
139 (known-good) solution to the original cluster for final display.
141 It first removes the relocated instances after which it places them on
145 applySolution :: NodeList -> InstanceList -> [Placement] -> NodeList
146 applySolution nl il sol =
147 let odxes = map (\ (a, b, c, _) -> (Container.find a il,
148 Node.idx (Container.find b nl),
149 Node.idx (Container.find c nl))
151 idxes = (\ (x, _, _) -> x) (unzip3 odxes)
152 nc = removeInstances nl idxes
154 foldl' (\ nz (a, b, c) ->
155 let new_p = Container.find b nz
156 new_s = Container.find c nz in
157 fromJust (addInstance nz a new_p new_s)
161 -- First phase functions
163 {- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
167 genParts :: [a] -> Int -> [(a, [a])]
172 if length l < count then
175 (x, xs) : (genParts xs count)
177 -- | Generates combinations of count items from the names list.
178 genNames :: Int -> [b] -> [[b]]
179 genNames count1 names1 =
180 let aux_fn count names current =
185 (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
186 (genParts names count)
188 aux_fn count1 names1 []
190 {- | Computes the pair of bad nodes and instances.
192 The bad node list is computed via a simple 'verifyN1' check, and the
193 bad instance list is the list of primary and secondary instances of
197 computeBadItems :: NodeList -> InstanceList ->
198 ([Node.Node], [Instance.Instance])
199 computeBadItems nl il =
200 let bad_nodes = verifyN1 $ Container.elems nl
201 bad_instances = map (\idx -> Container.find idx il) $
202 sort $ nub $ concat $
203 map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
205 (bad_nodes, bad_instances)
208 {- | Checks if removal of instances results in N+1 pass.
210 Note: the check removal cannot optimize by scanning only the affected
211 nodes, since the cluster is known to be not healthy; only the check
212 placement can make this shortcut.
215 checkRemoval :: NodeList -> [Instance.Instance] -> Maybe Removal
216 checkRemoval nl victims =
217 let nx = removeInstances nl victims
218 failN1 = verifyN1Check (Container.elems nx)
223 Just $ Removal nx victims
226 -- | Computes the removals list for a given depth
227 computeRemovals :: Cluster.NodeList
228 -> [Instance.Instance]
230 -> [Maybe Cluster.Removal]
231 computeRemovals nl bad_instances depth =
232 map (checkRemoval nl) $ genNames depth bad_instances
234 -- Second phase functions
236 -- | Single-node relocation cost
237 nodeDelta :: Int -> Int -> Int -> Int
239 if i == p || i == s then
244 {-| Compute best solution.
246 This function compares two solutions, choosing the minimum valid
249 compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
250 compareSolutions a b = case (a, b) of
255 -- | Compute best table. Note that the ordering of the arguments is important.
256 compareTables :: Table -> Table -> Table
257 compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
258 if a_cv > b_cv then b else a
260 -- | Check if a given delta is worse then an existing solution.
261 tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
262 tooHighDelta sol new_delta max_delta =
263 if new_delta > max_delta && max_delta >=0 then
268 Just (Solution old_delta _) -> old_delta <= new_delta
270 {-| Check if placement of instances still keeps the cluster N+1 compliant.
272 This is the workhorse of the allocation algorithm: given the
273 current node and instance maps, the list of instances to be
274 placed, and the current solution, this will return all possible
275 solution by recursing until all target instances are placed.
278 checkPlacement :: NodeList -- ^ The current node list
279 -> [Instance.Instance] -- ^ List of instances still to place
280 -> [Placement] -- ^ Partial solution until now
281 -> Int -- ^ The delta of the partial solution
282 -> Maybe Solution -- ^ The previous solution
283 -> Int -- ^ Abort if the we go above this delta
284 -> Maybe Solution -- ^ The new solution
285 checkPlacement nl victims current current_delta prev_sol max_delta =
286 let target = head victims
287 opdx = Instance.pnode target
288 osdx = Instance.snode target
290 have_tail = (length vtail) > 0
291 nodes = Container.elems nl
292 iidx = Instance.idx target
297 pri_idx = Node.idx pri
298 upri_delta = current_delta + nodeDelta pri_idx opdx osdx
299 new_pri = Node.addPri pri target
300 fail_delta1 = tooHighDelta accu_p upri_delta max_delta
302 if fail_delta1 || isNothing(new_pri) then accu_p
303 else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
307 sec_idx = Node.idx sec
308 upd_delta = upri_delta +
309 nodeDelta sec_idx opdx osdx
310 fail_delta2 = tooHighDelta accu upd_delta max_delta
311 new_sec = Node.addSec sec target pri_idx
313 if sec_idx == pri_idx || fail_delta2 ||
314 isNothing new_sec then accu
316 nx = Container.add sec_idx (fromJust new_sec) pri_nl
318 plc = (iidx, pri_idx, sec_idx, upd_cv)
322 checkPlacement nx vtail c2 upd_delta
325 Just (Solution upd_delta c2)
326 in compareSolutions accu result
331 applyMove :: NodeList -> Instance.Instance
332 -> IMove -> (Maybe NodeList, Instance.Instance, Int, Int)
334 applyMove nl inst Failover =
335 let old_pdx = Instance.pnode inst
336 old_sdx = Instance.snode inst
337 old_p = Container.find old_pdx nl
338 old_s = Container.find old_sdx nl
339 int_p = Node.removePri old_p inst
340 int_s = Node.removeSec old_s inst
341 new_p = Node.addPri int_s inst
342 new_s = Node.addSec int_p inst old_sdx
343 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
344 else Just $ Container.addTwo old_pdx (fromJust new_s)
345 old_sdx (fromJust new_p) nl
346 in (new_nl, Instance.setBoth inst old_sdx old_pdx, old_sdx, old_pdx)
348 -- Replace the primary (f:, r:np, f)
349 applyMove nl inst (ReplacePrimary new_pdx) =
350 let old_pdx = Instance.pnode inst
351 old_sdx = Instance.snode inst
352 old_p = Container.find old_pdx nl
353 old_s = Container.find old_sdx nl
354 tgt_n = Container.find new_pdx nl
355 int_p = Node.removePri old_p inst
356 int_s = Node.removeSec old_s inst
357 new_p = Node.addPri tgt_n inst
358 new_s = Node.addSec int_s inst new_pdx
359 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
360 else Just $ Container.add new_pdx (fromJust new_p) $
361 Container.addTwo old_pdx int_p
362 old_sdx (fromJust new_s) nl
363 in (new_nl, Instance.setPri inst new_pdx, new_pdx, old_sdx)
365 -- Replace the secondary (r:ns)
366 applyMove nl inst (ReplaceSecondary new_sdx) =
367 let old_pdx = Instance.pnode inst
368 old_sdx = Instance.snode inst
369 old_s = Container.find old_sdx nl
370 tgt_n = Container.find new_sdx nl
371 int_s = Node.removeSec old_s inst
372 new_s = Node.addSec tgt_n inst old_pdx
373 new_nl = if isNothing(new_s) then Nothing
374 else Just $ Container.addTwo new_sdx (fromJust new_s)
376 in (new_nl, Instance.setSec inst new_sdx, old_pdx, new_sdx)
378 -- Replace the secondary and failover (r:np, f)
379 applyMove nl inst (ReplaceAndFailover new_pdx) =
380 let old_pdx = Instance.pnode inst
381 old_sdx = Instance.snode inst
382 old_p = Container.find old_pdx nl
383 old_s = Container.find old_sdx nl
384 tgt_n = Container.find new_pdx nl
385 int_p = Node.removePri old_p inst
386 int_s = Node.removeSec old_s inst
387 new_p = Node.addPri tgt_n inst
388 new_s = Node.addSec int_p inst new_pdx
389 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
390 else Just $ Container.add new_pdx (fromJust new_p) $
391 Container.addTwo old_pdx (fromJust new_s)
393 in (new_nl, Instance.setBoth inst new_pdx old_pdx, new_pdx, old_pdx)
395 -- Failver and replace the secondary (f, r:ns)
396 applyMove nl inst (FailoverAndReplace new_sdx) =
397 let old_pdx = Instance.pnode inst
398 old_sdx = Instance.snode inst
399 old_p = Container.find old_pdx nl
400 old_s = Container.find old_sdx nl
401 tgt_n = Container.find new_sdx nl
402 int_p = Node.removePri old_p inst
403 int_s = Node.removeSec old_s inst
404 new_p = Node.addPri int_s inst
405 new_s = Node.addSec tgt_n inst old_sdx
406 new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
407 else Just $ Container.add new_sdx (fromJust new_s) $
408 Container.addTwo old_sdx (fromJust new_p)
410 in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
412 checkSingleStep :: Table -- ^ The original table
413 -> Instance.Instance -- ^ The instance to move
414 -> Table -- ^ The current best table
415 -> IMove -- ^ The move to apply
416 -> Table -- ^ The final best table
417 checkSingleStep ini_tbl target cur_tbl move =
419 Table ini_nl ini_il _ ini_plc = ini_tbl
420 (tmp_nl, new_inst, pri_idx, sec_idx) = applyMove ini_nl target move
422 if isNothing tmp_nl then cur_tbl
424 let tgt_idx = Instance.idx target
425 upd_nl = fromJust tmp_nl
426 upd_cvar = compCV upd_nl
427 upd_il = Container.add tgt_idx new_inst ini_il
428 upd_plc = (tgt_idx, pri_idx, sec_idx, upd_cvar):ini_plc
429 upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
431 compareTables cur_tbl upd_tbl
433 checkInstanceMove :: [Int] -- Allowed target node indices
434 -> Table -- Original table
435 -> Instance.Instance -- Instance to move
436 -> Table -- Best new table for this instance
437 checkInstanceMove nodes_idx ini_tbl target =
439 opdx = Instance.pnode target
440 osdx = Instance.snode target
441 nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
442 aft_failover = checkSingleStep ini_tbl target ini_tbl Failover
443 all_moves = concatMap (\idx -> [ReplacePrimary idx,
444 ReplaceSecondary idx,
445 ReplaceAndFailover idx,
446 FailoverAndReplace idx]) nodes
448 -- iterate over the possible nodes for this instance
449 foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
451 -- | Compute the best next move.
452 checkMove :: [Int] -- ^ Allowed target node indices
453 -> Table -- ^ The current solution
454 -> [Instance.Instance] -- ^ List of instances still to move
455 -> Table -- ^ The new solution
456 checkMove nodes_idx ini_tbl victims =
457 let Table _ _ _ ini_plc = ini_tbl
458 -- iterate over all instances, computing the best move
461 (\ step_tbl elem -> compareTables step_tbl $
462 checkInstanceMove nodes_idx ini_tbl elem)
464 Table _ _ _ best_plc = best_tbl
466 if length best_plc == length ini_plc then -- no advancement
471 {- | Auxiliary function for solution computation.
473 We write this in an explicit recursive fashion in order to control
474 early-abort in case we have met the min delta. We can't use foldr
475 instead of explicit recursion since we need the accumulator for the
479 advanceSolution :: [Maybe Removal] -- ^ The removal to process
480 -> Int -- ^ Minimum delta parameter
481 -> Int -- ^ Maximum delta parameter
482 -> Maybe Solution -- ^ Current best solution
483 -> Maybe Solution -- ^ New best solution
484 advanceSolution [] _ _ sol = sol
485 advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
486 advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
487 let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
488 new_delta = solutionDelta $! new_sol
490 if new_delta >= 0 && new_delta <= min_d then
493 advanceSolution xs min_d max_d new_sol
495 -- | Computes the placement solution.
496 solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
497 -> Int -- ^ Minimum delta parameter
498 -> Int -- ^ Maximum delta parameter
499 -> Maybe Solution -- ^ The best solution found
500 solutionFromRemovals removals min_delta max_delta =
501 advanceSolution removals min_delta max_delta Nothing
503 {- | Computes the solution at the given depth.
505 This is a wrapper over both computeRemovals and
506 solutionFromRemovals. In case we have no solution, we return Nothing.
509 computeSolution :: NodeList -- ^ The original node data
510 -> [Instance.Instance] -- ^ The list of /bad/ instances
511 -> Int -- ^ The /depth/ of removals
512 -> Int -- ^ Maximum number of removals to process
513 -> Int -- ^ Minimum delta parameter
514 -> Int -- ^ Maximum delta parameter
515 -> Maybe Solution -- ^ The best solution found (or Nothing)
516 computeSolution nl bad_instances depth max_removals min_delta max_delta =
518 removals = computeRemovals nl bad_instances depth
519 removals' = capRemovals removals max_removals
521 solutionFromRemovals removals' min_delta max_delta
523 -- Solution display functions (pure)
525 -- | Given the original and final nodes, computes the relocation description.
526 computeMoves :: String -- ^ The instance name
527 -> String -- ^ Original primary
528 -> String -- ^ Original secondary
529 -> String -- ^ New primary
530 -> String -- ^ New secondary
531 -> (String, [String])
532 -- ^ Tuple of moves and commands list; moves is containing
533 -- either @/f/@ for failover or @/r:name/@ for replace
534 -- secondary, while the command list holds gnt-instance
535 -- commands (without that prefix), e.g \"@failover instance1@\"
536 computeMoves i a b c d =
537 if c == a then {- Same primary -}
538 if d == b then {- Same sec??! -}
540 else {- Change of secondary -}
542 [printf "replace-disks -n %s %s" d i])
544 if c == b then {- Failover and ... -}
545 if d == a then {- that's all -}
546 ("f", [printf "failover %s" i])
549 [printf "failover %s" i,
550 printf "replace-disks -n %s %s" d i])
552 if d == a then {- ... and keep primary as secondary -}
554 [printf "replace-disks -n %s %s" c i,
555 printf "failover %s" i])
557 if d == b then {- ... keep same secondary -}
558 (printf "f r:%s f" c,
559 [printf "failover %s" i,
560 printf "replace-disks -n %s %s" c i,
561 printf "failover %s" i])
563 else {- Nothing in common -}
564 (printf "r:%s f r:%s" c d,
565 [printf "replace-disks -n %s %s" c i,
566 printf "failover %s" i,
567 printf "replace-disks -n %s %s" d i])
569 {-| Converts a placement to string format -}
570 printSolutionLine :: InstanceList
576 -> (String, [String])
577 printSolutionLine il ktn kti nmlen imlen plc =
579 pmlen = (2*nmlen + 1)
581 inst = Container.find i il
582 inam = fromJust $ lookup (Instance.idx inst) kti
583 npri = fromJust $ lookup p ktn
584 nsec = fromJust $ lookup s ktn
585 opri = fromJust $ lookup (Instance.pnode inst) ktn
586 osec = fromJust $ lookup (Instance.snode inst) ktn
587 (moves, cmds) = computeMoves inam opri osec npri nsec
588 ostr = (printf "%s:%s" opri osec)::String
589 nstr = (printf "%s:%s" npri nsec)::String
591 (printf " %-*s %-*s => %-*s %.8f a=%s"
592 imlen inam pmlen ostr
596 formatCmds :: [[String]] -> String
597 formatCmds cmd_strs =
598 unlines $ map (" echo " ++) $
599 concat $ map (\(a, b) ->
600 (printf "step %d" (a::Int)):(map ("gnt-instance" ++) b)) $
603 {-| Converts a solution to string format -}
604 printSolution :: InstanceList
608 -> ([String], [[String]])
609 printSolution il ktn kti sol =
611 mlen_fn = maximum . (map length) . snd . unzip
615 unzip $ map (printSolutionLine il ktn kti nmlen imlen) sol
617 -- | Print the node list.
618 printNodes :: [(Int, String)] -> NodeList -> String
620 let snl = sortBy (compare `on` Node.idx) (Container.elems nl)
621 snl' = map (\ n -> ((fromJust $ lookup (Node.idx n) ktn), n)) snl
622 m_name = maximum . (map length) . fst . unzip $ snl'
623 helper = Node.list m_name
624 header = printf "%2s %-*s %5s %5s %5s %5s %5s %3s %3s %7s %7s"
625 "N1" m_name "Name" "t_mem" "f_mem" "r_mem"
627 "pri" "sec" "p_fmem" "p_fdsk"
628 in unlines $ (header:map (uncurry helper) snl')
630 -- | Compute the mem and disk covariance.
631 compDetailedCV :: NodeList -> (Double, Double, Double, Double)
634 nodes = Container.elems nl
635 mem_l = map Node.p_mem nodes
636 dsk_l = map Node.p_dsk nodes
637 mem_cv = varianceCoeff mem_l
638 dsk_cv = varianceCoeff dsk_l
639 n1_l = length $ filter Node.failN1 nodes
640 n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
641 res_l = map Node.p_rem nodes
642 res_cv = varianceCoeff res_l
643 in (mem_cv, dsk_cv, n1_score, res_cv)
645 -- | Compute the 'total' variance.
646 compCV :: NodeList -> Double
648 let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
649 in mem_cv + dsk_cv + n1_score + res_cv
651 printStats :: NodeList -> String
653 let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
654 in printf "f_mem=%.8f, r_mem=%.8f, f_dsk=%.8f, n1=%.3f"
655 mem_cv res_cv dsk_cv n1_score
657 -- Balancing functions
661 {- | Convert newline and delimiter-separated text.
663 This function converts a text in tabular format as generated by
664 @gnt-instance list@ and @gnt-node list@ to a list of objects using a
665 supplied conversion function.
668 loadTabular :: String -> ([String] -> (String, a))
669 -> (a -> Int -> a) -> ([(String, Int)], [(Int, a)])
670 loadTabular text_data convert_fn set_fn =
671 let lines_data = lines text_data
672 rows = map (sepSplit '|') lines_data
673 kerows = (map convert_fn rows)
674 idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
678 -- | For each instance, add its index to its primary and secondary nodes
679 fixNodes :: [(Int, Node.Node)]
680 -> [(Int, Instance.Instance)]
681 -> [(Int, Node.Node)]
683 foldl' (\accu (idx, inst) ->
685 assocEqual = (\ (i, _) (j, _) -> i == j)
686 pdx = Instance.pnode inst
687 sdx = Instance.snode inst
688 pold = fromJust $ lookup pdx accu
689 sold = fromJust $ lookup sdx accu
690 pnew = Node.setPri pold idx
691 snew = Node.setSec sold idx
692 ac1 = deleteBy assocEqual (pdx, pold) accu
693 ac2 = deleteBy assocEqual (sdx, sold) ac1
694 ac3 = (pdx, pnew):(sdx, snew):ac2
697 -- | Compute the longest common suffix of a [(Int, String)] list that
698 -- | starts with a dot
699 longestDomain :: [(Int, String)] -> String
700 longestDomain [] = ""
701 longestDomain ((_,x):xs) =
703 onlyStrings = snd $ unzip xs
705 foldr (\ suffix accu -> if all (isSuffixOf suffix) onlyStrings
708 "" $ filter (isPrefixOf ".") (tails x)
710 -- | Remove tails from the (Int, String) lists
711 stripSuffix :: String -> [(Int, String)] -> [(Int, String)]
712 stripSuffix suffix lst =
713 let sflen = length suffix in
714 map (\ (key, name) -> (key, take ((length name) - sflen) name)) lst
716 {-| Initializer function that loads the data from a node and list file
717 and massages it into the correct format. -}
718 loadData :: String -- ^ Node data in text format
719 -> String -- ^ Instance data in text format
720 -> (Container.Container Node.Node,
721 Container.Container Instance.Instance,
722 String, [(Int, String)], [(Int, String)])
723 loadData ndata idata =
725 {- node file: name mem disk -}
726 (ktn, nl) = loadTabular ndata
727 (\ (i:jt:jf:kt:kf:[]) -> (i, Node.create jt jf kt kf))
729 {- instance file: name mem disk -}
730 (kti, il) = loadTabular idata
731 (\ (i:j:k:l:m:[]) -> (i,
733 (fromJust $ lookup l ktn)
734 (fromJust $ lookup m ktn)))
737 il3 = Container.fromAssocList il
738 nl3 = Container.fromAssocList
739 (map (\ (k, v) -> (k, Node.buildPeers v il3 (length nl2))) nl2)
742 common_suffix = longestDomain (xti ++ xtn)
743 stn = stripSuffix common_suffix xtn
744 sti = stripSuffix common_suffix xti
746 (nl3, il3, common_suffix, stn, sti)