Statistics
| Branch: | Tag: | Revision:

root / src / Cluster.hs @ 7dfaafb1

History | View | Annotate | Download (25 kB)

1
{-| Implementation of cluster-wide logic.
2

    
3
This module holds all pure cluster-logic; I\/O related functionality
4
goes into the "Main" module.
5

    
6
-}
7

    
8
module Cluster
9
    (
10
     -- * Types
11
     NodeList
12
    , InstanceList
13
    , Placement
14
    , Solution(..)
15
    , Table(..)
16
    , Removal
17
    -- * Generic functions
18
    , totalResources
19
    -- * First phase functions
20
    , computeBadItems
21
    -- * Second phase functions
22
    , computeSolution
23
    , applySolution
24
    , printSolution
25
    , printSolutionLine
26
    , printNodes
27
    -- * Balacing functions
28
    , checkMove
29
    , compCV
30
    , printStats
31
    -- * Loading functions
32
    , loadData
33
    ) where
34

    
35
import Data.List
36
import Data.Maybe (isNothing, fromJust)
37
import Text.Printf (printf)
38
import Data.Function
39

    
40
import qualified Container
41
import qualified Instance
42
import qualified Node
43
import Utils
44

    
45
type NodeList = Container.Container Node.Node
46
type InstanceList = Container.Container Instance.Instance
47
type Score = Double
48

    
49
-- | The description of an instance placement.
50
type Placement = (Int, Int, Int, Score)
51

    
52
{- | A cluster solution described as the solution delta and the list
53
of placements.
54

    
55
-}
56
data Solution = Solution Int [Placement]
57
                deriving (Eq, Ord, Show)
58

    
59
-- | Returns the delta of a solution or -1 for Nothing
60
solutionDelta :: Maybe Solution -> Int
61
solutionDelta sol = case sol of
62
                      Just (Solution d _) -> d
63
                      _ -> -1
64

    
65
-- | A removal set.
66
data Removal = Removal NodeList [Instance.Instance]
67

    
68
-- | An instance move definition
69
data IMove = Failover
70
           | ReplacePrimary Int
71
           | ReplaceSecondary Int
72
             deriving (Show)
73

    
74
-- | The complete state for the balancing solution
75
data Table = Table NodeList InstanceList Score [Placement]
76
             deriving (Show)
77

    
78
-- General functions
79

    
80
-- | Cap the removal list if needed.
81
capRemovals :: [a] -> Int -> [a]
82
capRemovals removals max_removals =
83
    if max_removals > 0 then
84
        take max_removals removals
85
    else
86
        removals
87

    
88
-- | Check if the given node list fails the N+1 check.
89
verifyN1Check :: [Node.Node] -> Bool
90
verifyN1Check nl = any Node.failN1 nl
91

    
92
-- | Verifies the N+1 status and return the affected nodes.
93
verifyN1 :: [Node.Node] -> [Node.Node]
94
verifyN1 nl = filter Node.failN1 nl
95

    
96
{-| Add an instance and return the new node and instance maps. -}
97
addInstance :: NodeList -> Instance.Instance ->
98
               Node.Node -> Node.Node -> Maybe NodeList
99
addInstance nl idata pri sec =
100
  let pdx = Node.idx pri
101
      sdx = Node.idx sec
102
  in do
103
      pnode <- Node.addPri pri idata
104
      snode <- Node.addSec sec idata pdx
105
      new_nl <- return $ Container.addTwo sdx snode
106
                         pdx pnode nl
107
      return new_nl
108

    
109
-- | Remove an instance and return the new node and instance maps.
110
removeInstance :: NodeList -> Instance.Instance -> NodeList
111
removeInstance nl idata =
112
  let pnode = Instance.pnode idata
113
      snode = Instance.snode idata
114
      pn = Container.find pnode nl
115
      sn = Container.find snode nl
116
      new_nl = Container.addTwo
117
               pnode (Node.removePri pn idata)
118
               snode (Node.removeSec sn idata) nl in
119
  new_nl
120

    
121
-- | Remove an instance and return the new node map.
122
removeInstances :: NodeList -> [Instance.Instance] -> NodeList
123
removeInstances = foldl' removeInstance
124

    
125
-- | Compute the total free disk and memory in the cluster.
126
totalResources :: Container.Container Node.Node -> (Int, Int)
127
totalResources nl =
128
    foldl'
129
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
130
                           dsk + (Node.f_dsk node)))
131
    (0, 0) (Container.elems nl)
132

    
133
{- | Compute a new version of a cluster given a solution.
134

    
135
This is not used for computing the solutions, but for applying a
136
(known-good) solution to the original cluster for final display.
137

    
138
It first removes the relocated instances after which it places them on
139
their new nodes.
140

    
141
 -}
142
applySolution :: NodeList -> InstanceList -> [Placement] -> NodeList
143
applySolution nl il sol =
144
    let odxes = map (\ (a, b, c, _) -> (Container.find a il,
145
                                        Node.idx (Container.find b nl),
146
                                        Node.idx (Container.find c nl))
147
                    ) sol
148
        idxes = (\ (x, _, _) -> x) (unzip3 odxes)
149
        nc = removeInstances nl idxes
150
    in
151
      foldl' (\ nz (a, b, c) ->
152
                 let new_p = Container.find b nz
153
                     new_s = Container.find c nz in
154
                 fromJust (addInstance nz a new_p new_s)
155
           ) nc odxes
156

    
157

    
158
-- First phase functions
159

    
160
{- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
161
    [3..n]), ...]
162

    
163
-}
164
genParts :: [a] -> Int -> [(a, [a])]
165
genParts l count =
166
    case l of
167
      [] -> []
168
      x:xs ->
169
          if length l < count then
170
              []
171
          else
172
              (x, xs) : (genParts xs count)
173

    
174
-- | Generates combinations of count items from the names list.
175
genNames :: Int -> [b] -> [[b]]
176
genNames count1 names1 =
177
  let aux_fn count names current =
178
          case count of
179
            0 -> [current]
180
            _ ->
181
                concatMap
182
                (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
183
                (genParts names count)
184
  in
185
    aux_fn count1 names1 []
186

    
187
{- | Computes the pair of bad nodes and instances.
188

    
189
The bad node list is computed via a simple 'verifyN1' check, and the
190
bad instance list is the list of primary and secondary instances of
191
those nodes.
192

    
193
-}
194
computeBadItems :: NodeList -> InstanceList ->
195
                   ([Node.Node], [Instance.Instance])
196
computeBadItems nl il =
197
  let bad_nodes = verifyN1 $ Container.elems nl
198
      bad_instances = map (\idx -> Container.find idx il) $
199
                      sort $ nub $ concat $
200
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
201
  in
202
    (bad_nodes, bad_instances)
203

    
204

    
205
{- | Checks if removal of instances results in N+1 pass.
206

    
207
Note: the check removal cannot optimize by scanning only the affected
208
nodes, since the cluster is known to be not healthy; only the check
209
placement can make this shortcut.
210

    
211
-}
212
checkRemoval :: NodeList -> [Instance.Instance] -> Maybe Removal
213
checkRemoval nl victims =
214
  let nx = removeInstances nl victims
215
      failN1 = verifyN1Check (Container.elems nx)
216
  in
217
    if failN1 then
218
      Nothing
219
    else
220
      Just $ Removal nx victims
221

    
222

    
223
-- | Computes the removals list for a given depth
224
computeRemovals :: Cluster.NodeList
225
                 -> [Instance.Instance]
226
                 -> Int
227
                 -> [Maybe Cluster.Removal]
228
computeRemovals nl bad_instances depth =
229
    map (checkRemoval nl) $ genNames depth bad_instances
230

    
231
-- Second phase functions
232

    
233
-- | Single-node relocation cost
234
nodeDelta :: Int -> Int -> Int -> Int
235
nodeDelta i p s =
236
    if i == p || i == s then
237
        0
238
    else
239
        1
240

    
241
{-| Compute best solution.
242

    
243
    This function compares two solutions, choosing the minimum valid
244
    solution.
245
-}
246
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
247
compareSolutions a b = case (a, b) of
248
  (Nothing, x) -> x
249
  (x, Nothing) -> x
250
  (x, y) -> min x y
251

    
252
-- | Compute best table. Note that the ordering of the arguments is important.
253
compareTables :: Table -> Table -> Table
254
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
255
    if a_cv > b_cv then b else a
256

    
257
-- | Check if a given delta is worse then an existing solution.
258
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
259
tooHighDelta sol new_delta max_delta =
260
    if new_delta > max_delta && max_delta >=0 then
261
        True
262
    else
263
        case sol of
264
          Nothing -> False
265
          Just (Solution old_delta _) -> old_delta <= new_delta
266

    
267
{-| Check if placement of instances still keeps the cluster N+1 compliant.
268

    
269
    This is the workhorse of the allocation algorithm: given the
270
    current node and instance maps, the list of instances to be
271
    placed, and the current solution, this will return all possible
272
    solution by recursing until all target instances are placed.
273

    
274
-}
275
checkPlacement :: NodeList            -- ^ The current node list
276
               -> [Instance.Instance] -- ^ List of instances still to place
277
               -> [Placement]         -- ^ Partial solution until now
278
               -> Int                 -- ^ The delta of the partial solution
279
               -> Maybe Solution      -- ^ The previous solution
280
               -> Int                 -- ^ Abort if the we go above this delta
281
               -> Maybe Solution      -- ^ The new solution
282
checkPlacement nl victims current current_delta prev_sol max_delta =
283
  let target = head victims
284
      opdx = Instance.pnode target
285
      osdx = Instance.snode target
286
      vtail = tail victims
287
      have_tail = (length vtail) > 0
288
      nodes = Container.elems nl
289
      iidx = Instance.idx target
290
  in
291
    foldl'
292
    (\ accu_p pri ->
293
         let
294
             pri_idx = Node.idx pri
295
             upri_delta = current_delta + nodeDelta pri_idx opdx osdx
296
             new_pri = Node.addPri pri target
297
             fail_delta1 = tooHighDelta accu_p upri_delta max_delta
298
         in
299
           if fail_delta1 || isNothing(new_pri) then accu_p
300
           else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
301
                foldl'
302
                (\ accu sec ->
303
                     let
304
                         sec_idx = Node.idx sec
305
                         upd_delta = upri_delta +
306
                                     nodeDelta sec_idx opdx osdx
307
                         fail_delta2 = tooHighDelta accu upd_delta max_delta
308
                         new_sec = Node.addSec sec target pri_idx
309
                     in
310
                       if sec_idx == pri_idx || fail_delta2 ||
311
                          isNothing new_sec then accu
312
                       else let
313
                           nx = Container.add sec_idx (fromJust new_sec) pri_nl
314
                           upd_cv = compCV nx
315
                           plc = (iidx, pri_idx, sec_idx, upd_cv)
316
                           c2 = plc:current
317
                           result =
318
                               if have_tail then
319
                                   checkPlacement nx vtail c2 upd_delta
320
                                                  accu max_delta
321
                               else
322
                                   Just (Solution upd_delta c2)
323
                      in compareSolutions accu result
324
                ) accu_p nodes
325
    ) prev_sol nodes
326

    
327
-- | Apply a move
328
applyMove :: NodeList -> Instance.Instance
329
          -> IMove -> (Maybe NodeList, Instance.Instance, Int, Int)
330
applyMove nl inst Failover =
331
    let old_pdx = Instance.pnode inst
332
        old_sdx = Instance.snode inst
333
        old_p = Container.find old_pdx nl
334
        old_s = Container.find old_sdx nl
335
        int_p = Node.removePri old_p inst
336
        int_s = Node.removeSec old_s inst
337
        new_p = Node.addPri int_s inst
338
        new_s = Node.addSec int_p inst old_sdx
339
        new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
340
                 else Just $ Container.addTwo old_pdx (fromJust new_s)
341
                      old_sdx (fromJust new_p) nl
342
    in (new_nl, Instance.setBoth inst old_sdx old_pdx, old_sdx, old_pdx)
343

    
344
applyMove nl inst (ReplacePrimary new_pdx) =
345
    let old_pdx = Instance.pnode inst
346
        old_sdx = Instance.snode inst
347
        old_p = Container.find old_pdx nl
348
        old_s = Container.find old_sdx nl
349
        tgt_n = Container.find new_pdx nl
350
        int_p = Node.removePri old_p inst
351
        int_s = Node.removeSec old_s inst
352
        new_p = Node.addPri tgt_n inst
353
        new_s = Node.addSec int_s inst new_pdx
354
        new_nl = if isNothing(new_p) || isNothing(new_s) then Nothing
355
                 else Just $ Container.add new_pdx (fromJust new_p) $
356
                      Container.addTwo old_pdx int_p
357
                               old_sdx (fromJust new_s) nl
358
    in (new_nl, Instance.setPri inst new_pdx, new_pdx, old_sdx)
359

    
360
applyMove nl inst (ReplaceSecondary new_sdx) =
361
    let old_pdx = Instance.pnode inst
362
        old_sdx = Instance.snode inst
363
        old_s = Container.find old_sdx nl
364
        tgt_n = Container.find new_sdx nl
365
        int_s = Node.removeSec old_s inst
366
        new_s = Node.addSec tgt_n inst old_pdx
367
        new_nl = if isNothing(new_s) then Nothing
368
                 else Just $ Container.addTwo new_sdx (fromJust new_s)
369
                      old_sdx int_s nl
370
    in (new_nl, Instance.setSec inst new_sdx, old_pdx, new_sdx)
371

    
372
checkSingleStep :: Table -- ^ The original table
373
                -> Instance.Instance -- ^ The instance to move
374
                -> Table -- ^ The current best table
375
                -> IMove -- ^ The move to apply
376
                -> Table -- ^ The final best table
377
checkSingleStep ini_tbl target cur_tbl move =
378
    let
379
        Table ini_nl ini_il _ ini_plc = ini_tbl
380
        (tmp_nl, new_inst, pri_idx, sec_idx) = applyMove ini_nl target move
381
    in
382
      if isNothing tmp_nl then cur_tbl
383
      else
384
          let tgt_idx = Instance.idx target
385
              upd_nl = fromJust tmp_nl
386
              upd_cvar = compCV upd_nl
387
              upd_il = Container.add tgt_idx new_inst ini_il
388
              upd_plc = (tgt_idx, pri_idx, sec_idx, upd_cvar):ini_plc
389
              upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
390
          in
391
            compareTables cur_tbl upd_tbl
392

    
393
checkInstanceMove :: [Int]             -- Allowed target node indices
394
                  -> Table             -- Original table
395
                  -> Instance.Instance -- Instance to move
396
                  -> Table             -- Best new table for this instance
397
checkInstanceMove nodes_idx ini_tbl target =
398
    let
399
        opdx = Instance.pnode target
400
        osdx = Instance.snode target
401
        nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
402
        aft_failover = checkSingleStep ini_tbl target ini_tbl Failover
403
        all_moves = concatMap (\idx -> [ReplacePrimary idx,
404
                                        ReplaceSecondary idx]) nodes
405
    in
406
      -- iterate over the possible nodes for this instance
407
      foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
408

    
409
-- | Compute the best next move.
410
checkMove :: [Int]               -- ^ Allowed target node indices
411
          -> Table               -- ^ The current solution
412
          -> [Instance.Instance] -- ^ List of instances still to move
413
          -> Table               -- ^ The new solution
414
checkMove nodes_idx ini_tbl victims =
415
    let Table _ _ _ ini_plc = ini_tbl
416
        -- iterate over all instances, computing the best move
417
        best_tbl =
418
            foldl'
419
            (\ step_tbl elem -> compareTables step_tbl $
420
                                checkInstanceMove nodes_idx ini_tbl elem)
421
            ini_tbl victims
422
        Table _ _ _ best_plc = best_tbl
423
    in
424
      if length best_plc == length ini_plc then -- no advancement
425
          ini_tbl
426
      else
427
          best_tbl
428

    
429
{- | Auxiliary function for solution computation.
430

    
431
We write this in an explicit recursive fashion in order to control
432
early-abort in case we have met the min delta. We can't use foldr
433
instead of explicit recursion since we need the accumulator for the
434
abort decision.
435

    
436
-}
437
advanceSolution :: [Maybe Removal] -- ^ The removal to process
438
                -> Int             -- ^ Minimum delta parameter
439
                -> Int             -- ^ Maximum delta parameter
440
                -> Maybe Solution  -- ^ Current best solution
441
                -> Maybe Solution  -- ^ New best solution
442
advanceSolution [] _ _ sol = sol
443
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
444
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
445
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
446
        new_delta = solutionDelta $! new_sol
447
    in
448
      if new_delta >= 0 && new_delta <= min_d then
449
          new_sol
450
      else
451
          advanceSolution xs min_d max_d new_sol
452

    
453
-- | Computes the placement solution.
454
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
455
                     -> Int             -- ^ Minimum delta parameter
456
                     -> Int             -- ^ Maximum delta parameter
457
                     -> Maybe Solution  -- ^ The best solution found
458
solutionFromRemovals removals min_delta max_delta =
459
    advanceSolution removals min_delta max_delta Nothing
460

    
461
{- | Computes the solution at the given depth.
462

    
463
This is a wrapper over both computeRemovals and
464
solutionFromRemovals. In case we have no solution, we return Nothing.
465

    
466
-}
467
computeSolution :: NodeList        -- ^ The original node data
468
                -> [Instance.Instance] -- ^ The list of /bad/ instances
469
                -> Int             -- ^ The /depth/ of removals
470
                -> Int             -- ^ Maximum number of removals to process
471
                -> Int             -- ^ Minimum delta parameter
472
                -> Int             -- ^ Maximum delta parameter
473
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
474
computeSolution nl bad_instances depth max_removals min_delta max_delta =
475
  let
476
      removals = computeRemovals nl bad_instances depth
477
      removals' = capRemovals removals max_removals
478
  in
479
    solutionFromRemovals removals' min_delta max_delta
480

    
481
-- Solution display functions (pure)
482

    
483
-- | Given the original and final nodes, computes the relocation description.
484
computeMoves :: String -- ^ The instance name
485
             -> String -- ^ Original primary
486
             -> String -- ^ Original secondary
487
             -> String -- ^ New primary
488
             -> String -- ^ New secondary
489
             -> (String, [String])
490
                -- ^ Tuple of moves and commands list; moves is containing
491
                -- either @/f/@ for failover or @/r:name/@ for replace
492
                -- secondary, while the command list holds gnt-instance
493
                -- commands (without that prefix), e.g \"@failover instance1@\"
494
computeMoves i a b c d =
495
    if c == a then {- Same primary -}
496
        if d == b then {- Same sec??! -}
497
            ("-", [])
498
        else {- Change of secondary -}
499
            (printf "r:%s" d,
500
             [printf "replace-disks -n %s %s" d i])
501
    else
502
        if c == b then {- Failover and ... -}
503
            if d == a then {- that's all -}
504
                ("f", [printf "failover %s" i])
505
            else
506
                (printf "f r:%s" d,
507
                 [printf "failover %s" i,
508
                  printf "replace-disks -n %s %s" d i])
509
        else
510
            if d == a then {- ... and keep primary as secondary -}
511
                (printf "r:%s f" c,
512
                 [printf "replace-disks -n %s %s" c i,
513
                  printf "failover %s" i])
514
            else
515
                if d == b then {- ... keep same secondary -}
516
                    (printf "f r:%s f" c,
517
                     [printf "failover %s" i,
518
                      printf "replace-disks -n %s %s" c i,
519
                      printf "failover %s" i])
520

    
521
                else {- Nothing in common -}
522
                    (printf "r:%s f r:%s" c d,
523
                     [printf "replace-disks -n %s %s" c i,
524
                      printf "failover %s" i,
525
                      printf "replace-disks -n %s %s" d i])
526

    
527
{-| Converts a placement to string format -}
528
printSolutionLine :: InstanceList
529
              -> [(Int, String)]
530
              -> [(Int, String)]
531
              -> Int
532
              -> Int
533
              -> Placement
534
              -> (String, [String])
535
printSolutionLine il ktn kti nmlen imlen plc =
536
    let
537
        pmlen = (2*nmlen + 1)
538
        (i, p, s, c) = plc
539
        inst = Container.find i il
540
        inam = fromJust $ lookup (Instance.idx inst) kti
541
        npri = fromJust $ lookup p ktn
542
        nsec = fromJust $ lookup s ktn
543
        opri = fromJust $ lookup (Instance.pnode inst) ktn
544
        osec = fromJust $ lookup (Instance.snode inst) ktn
545
        (moves, cmds) =  computeMoves inam opri osec npri nsec
546
        ostr = (printf "%s:%s" opri osec)::String
547
        nstr = (printf "%s:%s" npri nsec)::String
548
    in
549
      (printf "  %-*s %-*s => %-*s %.8f a=%s"
550
       imlen inam pmlen ostr
551
       pmlen nstr c moves,
552
       cmds)
553

    
554
{-| Converts a solution to string format -}
555
printSolution :: InstanceList
556
              -> [(Int, String)]
557
              -> [(Int, String)]
558
              -> [Placement]
559
              -> ([String], [[String]])
560
printSolution il ktn kti sol =
561
    let
562
        mlen_fn = maximum . (map length) . snd . unzip
563
        imlen = mlen_fn kti
564
        nmlen = mlen_fn ktn
565
    in
566
      unzip $ map (printSolutionLine il ktn kti nmlen imlen) sol
567

    
568
-- | Print the node list.
569
printNodes :: [(Int, String)] -> NodeList -> String
570
printNodes ktn nl =
571
    let snl = sortBy (compare `on` Node.idx) (Container.elems nl)
572
        snl' = map (\ n -> ((fromJust $ lookup (Node.idx n) ktn), n)) snl
573
        m_name = maximum . (map length) . fst . unzip $ snl'
574
        helper = Node.list m_name
575
        header = printf "%2s %-*s %5s %5s %5s %5s %5s %3s %3s %7s %7s"
576
                 "N1" m_name "Name" "t_mem" "f_mem" "r_mem"
577
                 "t_dsk" "f_dsk"
578
                 "pri" "sec" "p_fmem" "p_fdsk"
579
    in unlines $ (header:map (uncurry helper) snl')
580

    
581
-- | Compute the mem and disk covariance.
582
compDetailedCV :: NodeList -> (Double, Double, Double, Double)
583
compDetailedCV nl =
584
    let
585
        nodes = Container.elems nl
586
        mem_l = map Node.p_mem nodes
587
        dsk_l = map Node.p_dsk nodes
588
        mem_cv = varianceCoeff mem_l
589
        dsk_cv = varianceCoeff dsk_l
590
        n1_l = length $ filter Node.failN1 nodes
591
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
592
        res_l = map Node.p_rem nodes
593
        res_cv = varianceCoeff res_l
594
    in (mem_cv, dsk_cv, n1_score, res_cv)
595

    
596
-- | Compute the 'total' variance.
597
compCV :: NodeList -> Double
598
compCV nl =
599
    let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
600
    in mem_cv + dsk_cv + n1_score + res_cv
601

    
602
printStats :: NodeList -> String
603
printStats nl =
604
    let (mem_cv, dsk_cv, n1_score, res_cv) = compDetailedCV nl
605
    in printf "f_mem=%.8f, r_mem=%.8f, f_dsk=%.8f, n1=%.3f"
606
       mem_cv res_cv dsk_cv n1_score
607

    
608
-- Balancing functions
609

    
610
-- Loading functions
611

    
612
{- | Convert newline and delimiter-separated text.
613

    
614
This function converts a text in tabular format as generated by
615
@gnt-instance list@ and @gnt-node list@ to a list of objects using a
616
supplied conversion function.
617

    
618
-}
619
loadTabular :: String -> ([String] -> (String, a))
620
            -> (a -> Int -> a) -> ([(String, Int)], [(Int, a)])
621
loadTabular text_data convert_fn set_fn =
622
    let lines_data = lines text_data
623
        rows = map (sepSplit '|') lines_data
624
        kerows = (map convert_fn rows)
625
        idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
626
                  (zip [0..] kerows)
627
    in unzip idxrows
628

    
629
-- | For each instance, add its index to its primary and secondary nodes
630
fixNodes :: [(Int, Node.Node)]
631
         -> [(Int, Instance.Instance)]
632
         -> [(Int, Node.Node)]
633
fixNodes nl il =
634
    foldl' (\accu (idx, inst) ->
635
                let
636
                    assocEqual = (\ (i, _) (j, _) -> i == j)
637
                    pdx = Instance.pnode inst
638
                    sdx = Instance.snode inst
639
                    pold = fromJust $ lookup pdx accu
640
                    sold = fromJust $ lookup sdx accu
641
                    pnew = Node.setPri pold idx
642
                    snew = Node.setSec sold idx
643
                    ac1 = deleteBy assocEqual (pdx, pold) accu
644
                    ac2 = deleteBy assocEqual (sdx, sold) ac1
645
                    ac3 = (pdx, pnew):(sdx, snew):ac2
646
                in ac3) nl il
647

    
648
-- | Compute the longest common suffix of a [(Int, String)] list that
649
-- | starts with a dot
650
longestDomain :: [(Int, String)] -> String
651
longestDomain [] = ""
652
longestDomain ((_,x):xs) =
653
    let
654
        onlyStrings = snd $ unzip xs
655
    in
656
      foldr (\ suffix accu -> if all (isSuffixOf suffix) onlyStrings
657
                              then suffix
658
                              else accu)
659
      "" $ filter (isPrefixOf ".") (tails x)
660

    
661
-- | Remove tails from the (Int, String) lists
662
stripSuffix :: String -> [(Int, String)] -> [(Int, String)]
663
stripSuffix suffix lst =
664
    let sflen = length suffix in
665
    map (\ (key, name) -> (key, take ((length name) - sflen) name)) lst
666

    
667
{-| Initializer function that loads the data from a node and list file
668
    and massages it into the correct format. -}
669
loadData :: String -- ^ Node data in text format
670
         -> String -- ^ Instance data in text format
671
         -> (Container.Container Node.Node,
672
             Container.Container Instance.Instance,
673
             String, [(Int, String)], [(Int, String)])
674
loadData ndata idata =
675
    let
676
    {- node file: name mem disk -}
677
        (ktn, nl) = loadTabular ndata
678
                    (\ (i:jt:jf:kt:kf:[]) -> (i, Node.create jt jf kt kf))
679
                    Node.setIdx
680
    {- instance file: name mem disk -}
681
        (kti, il) = loadTabular idata
682
                    (\ (i:j:k:l:m:[]) -> (i,
683
                                           Instance.create j k
684
                                               (fromJust $ lookup l ktn)
685
                                               (fromJust $ lookup m ktn)))
686
                    Instance.setIdx
687
        nl2 = fixNodes nl il
688
        il3 = Container.fromAssocList il
689
        nl3 = Container.fromAssocList
690
             (map (\ (k, v) -> (k, Node.buildPeers v il3 (length nl2))) nl2)
691
        xtn = swapPairs ktn
692
        xti = swapPairs kti
693
        common_suffix = longestDomain (xti ++ xtn)
694
        stn = stripSuffix common_suffix xtn
695
        sti = stripSuffix common_suffix xti
696
    in
697
      (nl3, il3, common_suffix, stn, sti)