Revision 9188aeef Ganeti/HTools/Cluster.hs

b/Ganeti/HTools/Cluster.hs
47 47
import Ganeti.HTools.Types
48 48
import Ganeti.HTools.Utils
49 49

  
50
-- | A separate name for the cluster score type
50
-- * Types
51

  
52
-- | A separate name for the cluster score type.
51 53
type Score = Double
52 54

  
53 55
-- | The description of an instance placement.
54 56
type Placement = (Idx, Ndx, Ndx, Score)
55 57

  
56
{- | A cluster solution described as the solution delta and the list
57
of placements.
58

  
59
-}
58
-- | A cluster solution described as the solution delta and the list
59
-- of placements.
60 60
data Solution = Solution Int [Placement]
61 61
                deriving (Eq, Ord, Show)
62 62

  
63
-- | Returns the delta of a solution or -1 for Nothing
64
solutionDelta :: Maybe Solution -> Int
65
solutionDelta sol = case sol of
66
                      Just (Solution d _) -> d
67
                      _ -> -1
68

  
69 63
-- | A removal set.
70 64
data Removal = Removal Node.List [Instance.Instance]
71 65

  
......
81 75
data Table = Table Node.List Instance.List Score [Placement]
82 76
             deriving (Show)
83 77

  
84
-- General functions
78
-- * Utility functions
79

  
80
-- | Returns the delta of a solution or -1 for Nothing.
81
solutionDelta :: Maybe Solution -> Int
82
solutionDelta sol = case sol of
83
                      Just (Solution d _) -> d
84
                      _ -> -1
85 85

  
86 86
-- | Cap the removal list if needed.
87 87
capRemovals :: [a] -> Int -> [a]
......
99 99
verifyN1 :: [Node.Node] -> [Node.Node]
100 100
verifyN1 nl = filter Node.failN1 nl
101 101

  
102
{-| Add an instance and return the new node and instance maps. -}
102
{-| Computes the pair of bad nodes and instances.
103

  
104
The bad node list is computed via a simple 'verifyN1' check, and the
105
bad instance list is the list of primary and secondary instances of
106
those nodes.
107

  
108
-}
109
computeBadItems :: Node.List -> Instance.List ->
110
                   ([Node.Node], [Instance.Instance])
111
computeBadItems nl il =
112
  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
113
      bad_instances = map (\idx -> Container.find idx il) $
114
                      sort $ nub $ concat $
115
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
116
  in
117
    (bad_nodes, bad_instances)
118

  
119
-- | Compute the total free disk and memory in the cluster.
120
totalResources :: Container.Container Node.Node -> (Int, Int)
121
totalResources nl =
122
    foldl'
123
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
124
                           dsk + (Node.f_dsk node)))
125
    (0, 0) (Container.elems nl)
126

  
127
-- | Compute the mem and disk covariance.
128
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
129
compDetailedCV nl =
130
    let
131
        all_nodes = Container.elems nl
132
        (offline, nodes) = partition Node.offline all_nodes
133
        mem_l = map Node.p_mem nodes
134
        dsk_l = map Node.p_dsk nodes
135
        mem_cv = varianceCoeff mem_l
136
        dsk_cv = varianceCoeff dsk_l
137
        n1_l = length $ filter Node.failN1 nodes
138
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
139
        res_l = map Node.p_rem nodes
140
        res_cv = varianceCoeff res_l
141
        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
142
                                        (length . Node.slist $ n)) $ offline
143
        online_inst = sum . map (\n -> (length . Node.plist $ n) +
144
                                       (length . Node.slist $ n)) $ nodes
145
        off_score = (fromIntegral offline_inst) /
146
                    (fromIntegral $ online_inst + offline_inst)
147
    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
148

  
149
-- | Compute the /total/ variance.
150
compCV :: Node.List -> Double
151
compCV nl =
152
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
153
    in mem_cv + dsk_cv + n1_score + res_cv + off_score
154

  
155
-- * hn1 functions
156

  
157
-- | Add an instance and return the new node and instance maps.
103 158
addInstance :: Node.List -> Instance.Instance ->
104 159
               Node.Node -> Node.Node -> Maybe Node.List
105 160
addInstance nl idata pri sec =
......
128 183
removeInstances :: Node.List -> [Instance.Instance] -> Node.List
129 184
removeInstances = foldl' removeInstance
130 185

  
131
-- | Compute the total free disk and memory in the cluster.
132
totalResources :: Container.Container Node.Node -> (Int, Int)
133
totalResources nl =
134
    foldl'
135
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
136
                           dsk + (Node.f_dsk node)))
137
    (0, 0) (Container.elems nl)
138 186

  
139
{- | Compute a new version of a cluster given a solution.
187
{-| Compute a new version of a cluster given a solution.
140 188

  
141 189
This is not used for computing the solutions, but for applying a
142 190
(known-good) solution to the original cluster for final display.
......
161 209
           ) nc odxes
162 210

  
163 211

  
164
-- First phase functions
212
-- ** First phase functions
165 213

  
166
{- | Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
214
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
167 215
    [3..n]), ...]
168 216

  
169 217
-}
......
190 238
  in
191 239
    aux_fn count1 names1 []
192 240

  
193
{- | Computes the pair of bad nodes and instances.
194

  
195
The bad node list is computed via a simple 'verifyN1' check, and the
196
bad instance list is the list of primary and secondary instances of
197
those nodes.
198

  
199
-}
200
computeBadItems :: Node.List -> Instance.List ->
201
                   ([Node.Node], [Instance.Instance])
202
computeBadItems nl il =
203
  let bad_nodes = verifyN1 $ filter (not . Node.offline) $ Container.elems nl
204
      bad_instances = map (\idx -> Container.find idx il) $
205
                      sort $ nub $ concat $
206
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
207
  in
208
    (bad_nodes, bad_instances)
209

  
210

  
211
{- | Checks if removal of instances results in N+1 pass.
241
{-| Checks if removal of instances results in N+1 pass.
212 242

  
213 243
Note: the check removal cannot optimize by scanning only the affected
214 244
nodes, since the cluster is known to be not healthy; only the check
......
226 256
      Just $ Removal nx victims
227 257

  
228 258

  
229
-- | Computes the removals list for a given depth
259
-- | Computes the removals list for a given depth.
230 260
computeRemovals :: Node.List
231 261
                 -> [Instance.Instance]
232 262
                 -> Int
......
234 264
computeRemovals nl bad_instances depth =
235 265
    map (checkRemoval nl) $ genNames depth bad_instances
236 266

  
237
-- Second phase functions
267
-- ** Second phase functions
238 268

  
239
-- | Single-node relocation cost
269
-- | Single-node relocation cost.
240 270
nodeDelta :: Ndx -> Ndx -> Ndx -> Int
241 271
nodeDelta i p s =
242 272
    if i == p || i == s then
......
244 274
    else
245 275
        1
246 276

  
247
{-| Compute best solution.
248

  
249
    This function compares two solutions, choosing the minimum valid
250
    solution.
251
-}
277
-- | Compute best solution.
278
--
279
-- This function compares two solutions, choosing the minimum valid
280
-- solution.
252 281
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
253 282
compareSolutions a b = case (a, b) of
254 283
  (Nothing, x) -> x
255 284
  (x, Nothing) -> x
256 285
  (x, y) -> min x y
257 286

  
258
-- | Compute best table. Note that the ordering of the arguments is important.
259
compareTables :: Table -> Table -> Table
260
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
261
    if a_cv > b_cv then b else a
262

  
263 287
-- | Check if a given delta is worse then an existing solution.
264 288
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
265 289
tooHighDelta sol new_delta max_delta =
......
330 354
                ) accu_p nodes
331 355
    ) prev_sol nodes
332 356

  
333
-- | Apply a move
357
{-| Auxiliary function for solution computation.
358

  
359
We write this in an explicit recursive fashion in order to control
360
early-abort in case we have met the min delta. We can't use foldr
361
instead of explicit recursion since we need the accumulator for the
362
abort decision.
363

  
364
-}
365
advanceSolution :: [Maybe Removal] -- ^ The removal to process
366
                -> Int             -- ^ Minimum delta parameter
367
                -> Int             -- ^ Maximum delta parameter
368
                -> Maybe Solution  -- ^ Current best solution
369
                -> Maybe Solution  -- ^ New best solution
370
advanceSolution [] _ _ sol = sol
371
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
372
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
373
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
374
        new_delta = solutionDelta $! new_sol
375
    in
376
      if new_delta >= 0 && new_delta <= min_d then
377
          new_sol
378
      else
379
          advanceSolution xs min_d max_d new_sol
380

  
381
-- | Computes the placement solution.
382
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
383
                     -> Int             -- ^ Minimum delta parameter
384
                     -> Int             -- ^ Maximum delta parameter
385
                     -> Maybe Solution  -- ^ The best solution found
386
solutionFromRemovals removals min_delta max_delta =
387
    advanceSolution removals min_delta max_delta Nothing
388

  
389
{-| Computes the solution at the given depth.
390

  
391
This is a wrapper over both computeRemovals and
392
solutionFromRemovals. In case we have no solution, we return Nothing.
393

  
394
-}
395
computeSolution :: Node.List        -- ^ The original node data
396
                -> [Instance.Instance] -- ^ The list of /bad/ instances
397
                -> Int             -- ^ The /depth/ of removals
398
                -> Int             -- ^ Maximum number of removals to process
399
                -> Int             -- ^ Minimum delta parameter
400
                -> Int             -- ^ Maximum delta parameter
401
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
402
computeSolution nl bad_instances depth max_removals min_delta max_delta =
403
  let
404
      removals = computeRemovals nl bad_instances depth
405
      removals' = capRemovals removals max_removals
406
  in
407
    solutionFromRemovals removals' min_delta max_delta
408

  
409
-- * hbal functions
410

  
411
-- | Compute best table. Note that the ordering of the arguments is important.
412
compareTables :: Table -> Table -> Table
413
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
414
    if a_cv > b_cv then b else a
415

  
416
-- | Applies an instance move to a given node list and instance.
334 417
applyMove :: Node.List -> Instance.Instance
335 418
          -> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx)
336 419
-- Failover (f)
......
407 490
                 Container.addTwo old_sdx new_p old_pdx int_p nl
408 491
    in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
409 492

  
493
-- | Tries to allocate an instance on one given node.
410 494
allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
411 495
                 -> (Maybe Node.List, Instance.Instance)
412 496
allocateOnSingle nl inst p =
......
415 499
                 return $ Container.add new_pdx new_p nl
416 500
    in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary)
417 501

  
502
-- | Tries to allocate an instance on a given pair of nodes.
418 503
allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
419 504
               -> (Maybe Node.List, Instance.Instance)
420 505
allocateOnPair nl inst tgt_p tgt_s =
......
426 511
          return $ Container.addTwo new_pdx new_p new_sdx new_s nl
427 512
    in (new_nl, Instance.setBoth inst new_pdx new_sdx)
428 513

  
514
-- | Tries to perform an instance move and returns the best table
515
-- between the original one and the new one.
429 516
checkSingleStep :: Table -- ^ The original table
430 517
                -> Instance.Instance -- ^ The instance to move
431 518
                -> Table -- ^ The current best table
......
502 589
      else
503 590
          best_tbl
504 591

  
505
{- | Auxiliary function for solution computation.
506

  
507
We write this in an explicit recursive fashion in order to control
508
early-abort in case we have met the min delta. We can't use foldr
509
instead of explicit recursion since we need the accumulator for the
510
abort decision.
511

  
512
-}
513
advanceSolution :: [Maybe Removal] -- ^ The removal to process
514
                -> Int             -- ^ Minimum delta parameter
515
                -> Int             -- ^ Maximum delta parameter
516
                -> Maybe Solution  -- ^ Current best solution
517
                -> Maybe Solution  -- ^ New best solution
518
advanceSolution [] _ _ sol = sol
519
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
520
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
521
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
522
        new_delta = solutionDelta $! new_sol
523
    in
524
      if new_delta >= 0 && new_delta <= min_d then
525
          new_sol
526
      else
527
          advanceSolution xs min_d max_d new_sol
528

  
529
-- | Computes the placement solution.
530
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
531
                     -> Int             -- ^ Minimum delta parameter
532
                     -> Int             -- ^ Maximum delta parameter
533
                     -> Maybe Solution  -- ^ The best solution found
534
solutionFromRemovals removals min_delta max_delta =
535
    advanceSolution removals min_delta max_delta Nothing
536

  
537
{- | Computes the solution at the given depth.
538

  
539
This is a wrapper over both computeRemovals and
540
solutionFromRemovals. In case we have no solution, we return Nothing.
541 592

  
542
-}
543
computeSolution :: Node.List        -- ^ The original node data
544
                -> [Instance.Instance] -- ^ The list of /bad/ instances
545
                -> Int             -- ^ The /depth/ of removals
546
                -> Int             -- ^ Maximum number of removals to process
547
                -> Int             -- ^ Minimum delta parameter
548
                -> Int             -- ^ Maximum delta parameter
549
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
550
computeSolution nl bad_instances depth max_removals min_delta max_delta =
551
  let
552
      removals = computeRemovals nl bad_instances depth
553
      removals' = capRemovals removals max_removals
554
  in
555
    solutionFromRemovals removals' min_delta max_delta
556

  
557
-- Solution display functions (pure)
593
-- * Formatting functions
558 594

  
559 595
-- | Given the original and final nodes, computes the relocation description.
560 596
computeMoves :: String -- ^ The instance name
......
600 636
                      printf "migrate -f %s" i,
601 637
                      printf "replace-disks -n %s %s" d i])
602 638

  
603
{-| Converts a placement to string format -}
604
printSolutionLine :: Node.List
605
                  -> Instance.List
606
                  -> Int
607
                  -> Int
608
                  -> Placement
609
                  -> Int
639
-- | Converts a placement to string format.
640
printSolutionLine :: Node.List     -- ^ The node list
641
                  -> Instance.List -- ^ The instance list
642
                  -> Int           -- ^ Maximum node name length
643
                  -> Int           -- ^ Maximum instance name length
644
                  -> Placement     -- ^ The current placement
645
                  -> Int           -- ^ The index of the placement in
646
                                   -- the solution
610 647
                  -> (String, [String])
611 648
printSolutionLine nl il nmlen imlen plc pos =
612 649
    let
......
627 664
       pmlen nstr c moves,
628 665
       cmds)
629 666

  
667
-- | Given a list of commands, prefix them with @gnt-instance@ and
668
-- also beautify the display a little.
630 669
formatCmds :: [[String]] -> String
631 670
formatCmds cmd_strs =
632 671
    unlines $
......
636 675
        (map ("gnt-instance " ++) b)) $
637 676
        zip [1..] cmd_strs
638 677

  
639
{-| Converts a solution to string format -}
678
-- | Converts a solution to string format.
640 679
printSolution :: Node.List
641 680
              -> Instance.List
642 681
              -> [Placement]
......
663 702
                 "pri" "sec" "p_fmem" "p_fdsk"
664 703
    in unlines $ (header:map helper snl)
665 704

  
666
-- | Compute the mem and disk covariance.
667
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
668
compDetailedCV nl =
669
    let
670
        all_nodes = Container.elems nl
671
        (offline, nodes) = partition Node.offline all_nodes
672
        mem_l = map Node.p_mem nodes
673
        dsk_l = map Node.p_dsk nodes
674
        mem_cv = varianceCoeff mem_l
675
        dsk_cv = varianceCoeff dsk_l
676
        n1_l = length $ filter Node.failN1 nodes
677
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
678
        res_l = map Node.p_rem nodes
679
        res_cv = varianceCoeff res_l
680
        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
681
                                        (length . Node.slist $ n)) $ offline
682
        online_inst = sum . map (\n -> (length . Node.plist $ n) +
683
                                       (length . Node.slist $ n)) $ nodes
684
        off_score = (fromIntegral offline_inst) /
685
                    (fromIntegral $ online_inst + offline_inst)
686
    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
687

  
688
-- | Compute the 'total' variance.
689
compCV :: Node.List -> Double
690
compCV nl =
691
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
692
    in mem_cv + dsk_cv + n1_score + res_cv + off_score
693

  
705
-- | Shows statistics for a given node list.
694 706
printStats :: Node.List -> String
695 707
printStats nl =
696 708
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl

Also available in: Unified diff