Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / Cluster.hs @ 70db354e

History | View | Annotate | Download (29.4 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 for the individual binaries.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2009 Google Inc.
11

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Ganeti.HTools.Cluster
30
    (
31
     -- * Types
32
      Placement
33
    , Solution(..)
34
    , Table(..)
35
    , Removal
36
    , Score
37
    , IMove(..)
38
    -- * Generic functions
39
    , totalResources
40
    -- * First phase functions
41
    , computeBadItems
42
    -- * Second phase functions
43
    , computeSolution
44
    , applySolution
45
    , printSolution
46
    , printSolutionLine
47
    , formatCmds
48
    , printNodes
49
    -- * Balacing functions
50
    , applyMove
51
    , checkMove
52
    , compCV
53
    , printStats
54
    -- * IAllocator functions
55
    , allocateOnSingle
56
    , allocateOnPair
57
    , tryAlloc
58
    , tryReloc
59
    ) where
60

    
61
import Data.List
62
import Data.Maybe (isNothing, fromJust)
63
import Text.Printf (printf)
64
import Data.Function
65
import Control.Monad
66

    
67
import qualified Ganeti.HTools.Container as Container
68
import qualified Ganeti.HTools.Instance as Instance
69
import qualified Ganeti.HTools.Node as Node
70
import Ganeti.HTools.Types
71
import Ganeti.HTools.Utils
72

    
73
-- * Types
74

    
75
-- | A separate name for the cluster score type.
76
type Score = Double
77

    
78
-- | The description of an instance placement.
79
type Placement = (Idx, Ndx, Ndx, Score)
80

    
81
-- | A cluster solution described as the solution delta and the list
82
-- of placements.
83
data Solution = Solution Int [Placement]
84
                deriving (Eq, Ord, Show)
85

    
86
-- | A removal set.
87
data Removal = Removal Node.List [Instance.Instance]
88

    
89
-- | An instance move definition
90
data IMove = Failover                -- ^ Failover the instance (f)
91
           | ReplacePrimary Ndx      -- ^ Replace primary (f, r:np, f)
92
           | ReplaceSecondary Ndx    -- ^ Replace secondary (r:ns)
93
           | ReplaceAndFailover Ndx  -- ^ Replace secondary, failover (r:np, f)
94
           | FailoverAndReplace Ndx  -- ^ Failover, replace secondary (f, r:ns)
95
             deriving (Show)
96

    
97
-- | The complete state for the balancing solution
98
data Table = Table Node.List Instance.List Score [Placement]
99
             deriving (Show)
100

    
101
-- * Utility functions
102

    
103
-- | Returns the delta of a solution or -1 for Nothing.
104
solutionDelta :: Maybe Solution -> Int
105
solutionDelta sol = case sol of
106
                      Just (Solution d _) -> d
107
                      _ -> -1
108

    
109
-- | Cap the removal list if needed.
110
capRemovals :: [a] -> Int -> [a]
111
capRemovals removals max_removals =
112
    if max_removals > 0 then
113
        take max_removals removals
114
    else
115
        removals
116

    
117
-- | Check if the given node list fails the N+1 check.
118
verifyN1Check :: [Node.Node] -> Bool
119
verifyN1Check nl = any Node.failN1 nl
120

    
121
-- | Verifies the N+1 status and return the affected nodes.
122
verifyN1 :: [Node.Node] -> [Node.Node]
123
verifyN1 nl = filter Node.failN1 nl
124

    
125
{-| Computes the pair of bad nodes and instances.
126

    
127
The bad node list is computed via a simple 'verifyN1' check, and the
128
bad instance list is the list of primary and secondary instances of
129
those nodes.
130

    
131
-}
132
computeBadItems :: Node.List -> Instance.List ->
133
                   ([Node.Node], [Instance.Instance])
134
computeBadItems nl il =
135
  let bad_nodes = verifyN1 $ getOnline nl
136
      bad_instances = map (\idx -> Container.find idx il) $
137
                      sort $ nub $ concat $
138
                      map (\ n -> (Node.slist n) ++ (Node.plist n)) bad_nodes
139
  in
140
    (bad_nodes, bad_instances)
141

    
142
-- | Compute the total free disk and memory in the cluster.
143
totalResources :: Node.List -> (Int, Int)
144
totalResources nl =
145
    foldl'
146
    (\ (mem, dsk) node -> (mem + (Node.f_mem node),
147
                           dsk + (Node.f_dsk node)))
148
    (0, 0) (Container.elems nl)
149

    
150
-- | Compute the mem and disk covariance.
151
compDetailedCV :: Node.List -> (Double, Double, Double, Double, Double)
152
compDetailedCV nl =
153
    let
154
        all_nodes = Container.elems nl
155
        (offline, nodes) = partition Node.offline all_nodes
156
        mem_l = map Node.p_mem nodes
157
        dsk_l = map Node.p_dsk nodes
158
        mem_cv = varianceCoeff mem_l
159
        dsk_cv = varianceCoeff dsk_l
160
        n1_l = length $ filter Node.failN1 nodes
161
        n1_score = (fromIntegral n1_l) / (fromIntegral $ length nodes)
162
        res_l = map Node.p_rem nodes
163
        res_cv = varianceCoeff res_l
164
        offline_inst = sum . map (\n -> (length . Node.plist $ n) +
165
                                        (length . Node.slist $ n)) $ offline
166
        online_inst = sum . map (\n -> (length . Node.plist $ n) +
167
                                       (length . Node.slist $ n)) $ nodes
168
        off_score = (fromIntegral offline_inst) /
169
                    (fromIntegral $ online_inst + offline_inst)
170
    in (mem_cv, dsk_cv, n1_score, res_cv, off_score)
171

    
172
-- | Compute the /total/ variance.
173
compCV :: Node.List -> Double
174
compCV nl =
175
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
176
    in mem_cv + dsk_cv + n1_score + res_cv + off_score
177

    
178
-- | Compute online nodes from a Node.List
179
getOnline :: Node.List -> [Node.Node]
180
getOnline = filter (not . Node.offline) . Container.elems
181

    
182
-- * hn1 functions
183

    
184
-- | Add an instance and return the new node and instance maps.
185
addInstance :: Node.List -> Instance.Instance ->
186
               Node.Node -> Node.Node -> Maybe Node.List
187
addInstance nl idata pri sec =
188
  let pdx = Node.idx pri
189
      sdx = Node.idx sec
190
  in do
191
      pnode <- Node.addPri pri idata
192
      snode <- Node.addSec sec idata pdx
193
      new_nl <- return $ Container.addTwo sdx snode
194
                         pdx pnode nl
195
      return new_nl
196

    
197
-- | Remove an instance and return the new node and instance maps.
198
removeInstance :: Node.List -> Instance.Instance -> Node.List
199
removeInstance nl idata =
200
  let pnode = Instance.pnode idata
201
      snode = Instance.snode idata
202
      pn = Container.find pnode nl
203
      sn = Container.find snode nl
204
      new_nl = Container.addTwo
205
               pnode (Node.removePri pn idata)
206
               snode (Node.removeSec sn idata) nl in
207
  new_nl
208

    
209
-- | Remove an instance and return the new node map.
210
removeInstances :: Node.List -> [Instance.Instance] -> Node.List
211
removeInstances = foldl' removeInstance
212

    
213

    
214
{-| Compute a new version of a cluster given a solution.
215

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

    
219
It first removes the relocated instances after which it places them on
220
their new nodes.
221

    
222
 -}
223
applySolution :: Node.List -> Instance.List -> [Placement] -> Node.List
224
applySolution nl il sol =
225
    let odxes = map (\ (a, b, c, _) -> (Container.find a il,
226
                                        Node.idx (Container.find b nl),
227
                                        Node.idx (Container.find c nl))
228
                    ) sol
229
        idxes = (\ (x, _, _) -> x) (unzip3 odxes)
230
        nc = removeInstances nl idxes
231
    in
232
      foldl' (\ nz (a, b, c) ->
233
                 let new_p = Container.find b nz
234
                     new_s = Container.find c nz in
235
                 fromJust (addInstance nz a new_p new_s)
236
           ) nc odxes
237

    
238

    
239
-- ** First phase functions
240

    
241
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
242
    [3..n]), ...]
243

    
244
-}
245
genParts :: [a] -> Int -> [(a, [a])]
246
genParts l count =
247
    case l of
248
      [] -> []
249
      x:xs ->
250
          if length l < count then
251
              []
252
          else
253
              (x, xs) : (genParts xs count)
254

    
255
-- | Generates combinations of count items from the names list.
256
genNames :: Int -> [b] -> [[b]]
257
genNames count1 names1 =
258
  let aux_fn count names current =
259
          case count of
260
            0 -> [current]
261
            _ ->
262
                concatMap
263
                (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
264
                (genParts names count)
265
  in
266
    aux_fn count1 names1 []
267

    
268
{-| Checks if removal of instances results in N+1 pass.
269

    
270
Note: the check removal cannot optimize by scanning only the affected
271
nodes, since the cluster is known to be not healthy; only the check
272
placement can make this shortcut.
273

    
274
-}
275
checkRemoval :: Node.List -> [Instance.Instance] -> Maybe Removal
276
checkRemoval nl victims =
277
  let nx = removeInstances nl victims
278
      failN1 = verifyN1Check (Container.elems nx)
279
  in
280
    if failN1 then
281
      Nothing
282
    else
283
      Just $ Removal nx victims
284

    
285

    
286
-- | Computes the removals list for a given depth.
287
computeRemovals :: Node.List
288
                 -> [Instance.Instance]
289
                 -> Int
290
                 -> [Maybe Removal]
291
computeRemovals nl bad_instances depth =
292
    map (checkRemoval nl) $ genNames depth bad_instances
293

    
294
-- ** Second phase functions
295

    
296
-- | Single-node relocation cost.
297
nodeDelta :: Ndx -> Ndx -> Ndx -> Int
298
nodeDelta i p s =
299
    if i == p || i == s then
300
        0
301
    else
302
        1
303

    
304
-- | Compute best solution.
305
--
306
-- This function compares two solutions, choosing the minimum valid
307
-- solution.
308
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
309
compareSolutions a b = case (a, b) of
310
  (Nothing, x) -> x
311
  (x, Nothing) -> x
312
  (x, y) -> min x y
313

    
314
-- | Check if a given delta is worse then an existing solution.
315
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
316
tooHighDelta sol new_delta max_delta =
317
    if new_delta > max_delta && max_delta >=0 then
318
        True
319
    else
320
        case sol of
321
          Nothing -> False
322
          Just (Solution old_delta _) -> old_delta <= new_delta
323

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

    
326
    This is the workhorse of the allocation algorithm: given the
327
    current node and instance maps, the list of instances to be
328
    placed, and the current solution, this will return all possible
329
    solution by recursing until all target instances are placed.
330

    
331
-}
332
checkPlacement :: Node.List            -- ^ The current node list
333
               -> [Instance.Instance] -- ^ List of instances still to place
334
               -> [Placement]         -- ^ Partial solution until now
335
               -> Int                 -- ^ The delta of the partial solution
336
               -> Maybe Solution      -- ^ The previous solution
337
               -> Int                 -- ^ Abort if the we go above this delta
338
               -> Maybe Solution      -- ^ The new solution
339
checkPlacement nl victims current current_delta prev_sol max_delta =
340
  let target = head victims
341
      opdx = Instance.pnode target
342
      osdx = Instance.snode target
343
      vtail = tail victims
344
      have_tail = (length vtail) > 0
345
      nodes = Container.elems nl
346
      iidx = Instance.idx target
347
  in
348
    foldl'
349
    (\ accu_p pri ->
350
         let
351
             pri_idx = Node.idx pri
352
             upri_delta = current_delta + nodeDelta pri_idx opdx osdx
353
             new_pri = Node.addPri pri target
354
             fail_delta1 = tooHighDelta accu_p upri_delta max_delta
355
         in
356
           if fail_delta1 || isNothing(new_pri) then accu_p
357
           else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
358
                foldl'
359
                (\ accu sec ->
360
                     let
361
                         sec_idx = Node.idx sec
362
                         upd_delta = upri_delta +
363
                                     nodeDelta sec_idx opdx osdx
364
                         fail_delta2 = tooHighDelta accu upd_delta max_delta
365
                         new_sec = Node.addSec sec target pri_idx
366
                     in
367
                       if sec_idx == pri_idx || fail_delta2 ||
368
                          isNothing new_sec then accu
369
                       else let
370
                           nx = Container.add sec_idx (fromJust new_sec) pri_nl
371
                           upd_cv = compCV nx
372
                           plc = (iidx, pri_idx, sec_idx, upd_cv)
373
                           c2 = plc:current
374
                           result =
375
                               if have_tail then
376
                                   checkPlacement nx vtail c2 upd_delta
377
                                                  accu max_delta
378
                               else
379
                                   Just (Solution upd_delta c2)
380
                      in compareSolutions accu result
381
                ) accu_p nodes
382
    ) prev_sol nodes
383

    
384
{-| Auxiliary function for solution computation.
385

    
386
We write this in an explicit recursive fashion in order to control
387
early-abort in case we have met the min delta. We can't use foldr
388
instead of explicit recursion since we need the accumulator for the
389
abort decision.
390

    
391
-}
392
advanceSolution :: [Maybe Removal] -- ^ The removal to process
393
                -> Int             -- ^ Minimum delta parameter
394
                -> Int             -- ^ Maximum delta parameter
395
                -> Maybe Solution  -- ^ Current best solution
396
                -> Maybe Solution  -- ^ New best solution
397
advanceSolution [] _ _ sol = sol
398
advanceSolution (Nothing:xs) m n sol = advanceSolution xs m n sol
399
advanceSolution ((Just (Removal nx removed)):xs) min_d max_d prev_sol =
400
    let new_sol = checkPlacement nx removed [] 0 prev_sol max_d
401
        new_delta = solutionDelta $! new_sol
402
    in
403
      if new_delta >= 0 && new_delta <= min_d then
404
          new_sol
405
      else
406
          advanceSolution xs min_d max_d new_sol
407

    
408
-- | Computes the placement solution.
409
solutionFromRemovals :: [Maybe Removal] -- ^ The list of (possible) removals
410
                     -> Int             -- ^ Minimum delta parameter
411
                     -> Int             -- ^ Maximum delta parameter
412
                     -> Maybe Solution  -- ^ The best solution found
413
solutionFromRemovals removals min_delta max_delta =
414
    advanceSolution removals min_delta max_delta Nothing
415

    
416
{-| Computes the solution at the given depth.
417

    
418
This is a wrapper over both computeRemovals and
419
solutionFromRemovals. In case we have no solution, we return Nothing.
420

    
421
-}
422
computeSolution :: Node.List        -- ^ The original node data
423
                -> [Instance.Instance] -- ^ The list of /bad/ instances
424
                -> Int             -- ^ The /depth/ of removals
425
                -> Int             -- ^ Maximum number of removals to process
426
                -> Int             -- ^ Minimum delta parameter
427
                -> Int             -- ^ Maximum delta parameter
428
                -> Maybe Solution  -- ^ The best solution found (or Nothing)
429
computeSolution nl bad_instances depth max_removals min_delta max_delta =
430
  let
431
      removals = computeRemovals nl bad_instances depth
432
      removals' = capRemovals removals max_removals
433
  in
434
    solutionFromRemovals removals' min_delta max_delta
435

    
436
-- * hbal functions
437

    
438
-- | Compute best table. Note that the ordering of the arguments is important.
439
compareTables :: Table -> Table -> Table
440
compareTables a@(Table _ _ a_cv _) b@(Table _ _ b_cv _ ) =
441
    if a_cv > b_cv then b else a
442

    
443
-- | Applies an instance move to a given node list and instance.
444
applyMove :: Node.List -> Instance.Instance
445
          -> IMove -> (Maybe Node.List, Instance.Instance, Ndx, Ndx)
446
-- Failover (f)
447
applyMove nl inst Failover =
448
    let old_pdx = Instance.pnode inst
449
        old_sdx = Instance.snode inst
450
        old_p = Container.find old_pdx nl
451
        old_s = Container.find old_sdx nl
452
        int_p = Node.removePri old_p inst
453
        int_s = Node.removeSec old_s inst
454
        new_nl = do -- Maybe monad
455
          new_p <- Node.addPri int_s inst
456
          new_s <- Node.addSec int_p inst old_sdx
457
          return $ Container.addTwo old_pdx new_s old_sdx new_p nl
458
    in (new_nl, Instance.setBoth inst old_sdx old_pdx, old_sdx, old_pdx)
459

    
460
-- Replace the primary (f:, r:np, f)
461
applyMove nl inst (ReplacePrimary new_pdx) =
462
    let old_pdx = Instance.pnode inst
463
        old_sdx = Instance.snode inst
464
        old_p = Container.find old_pdx nl
465
        old_s = Container.find old_sdx nl
466
        tgt_n = Container.find new_pdx nl
467
        int_p = Node.removePri old_p inst
468
        int_s = Node.removeSec old_s inst
469
        new_nl = do -- Maybe monad
470
          -- check that the current secondary can host the instance
471
          -- during the migration
472
          tmp_s <- Node.addPri int_s inst
473
          let tmp_s' = Node.removePri tmp_s inst
474
          new_p <- Node.addPri tgt_n inst
475
          new_s <- Node.addSec tmp_s' inst new_pdx
476
          return $ Container.add new_pdx new_p $
477
                 Container.addTwo old_pdx int_p old_sdx new_s nl
478
    in (new_nl, Instance.setPri inst new_pdx, new_pdx, old_sdx)
479

    
480
-- Replace the secondary (r:ns)
481
applyMove nl inst (ReplaceSecondary new_sdx) =
482
    let old_pdx = Instance.pnode inst
483
        old_sdx = Instance.snode inst
484
        old_s = Container.find old_sdx nl
485
        tgt_n = Container.find new_sdx nl
486
        int_s = Node.removeSec old_s inst
487
        new_nl = Node.addSec tgt_n inst old_pdx >>=
488
                 \new_s -> return $ Container.addTwo new_sdx
489
                           new_s old_sdx int_s nl
490
    in (new_nl, Instance.setSec inst new_sdx, old_pdx, new_sdx)
491

    
492
-- Replace the secondary and failover (r:np, f)
493
applyMove nl inst (ReplaceAndFailover new_pdx) =
494
    let old_pdx = Instance.pnode inst
495
        old_sdx = Instance.snode inst
496
        old_p = Container.find old_pdx nl
497
        old_s = Container.find old_sdx nl
498
        tgt_n = Container.find new_pdx nl
499
        int_p = Node.removePri old_p inst
500
        int_s = Node.removeSec old_s inst
501
        new_nl = do -- Maybe monad
502
          new_p <- Node.addPri tgt_n inst
503
          new_s <- Node.addSec int_p inst new_pdx
504
          return $ Container.add new_pdx new_p $
505
                 Container.addTwo old_pdx new_s old_sdx int_s nl
506
    in (new_nl, Instance.setBoth inst new_pdx old_pdx, new_pdx, old_pdx)
507

    
508
-- Failver and replace the secondary (f, r:ns)
509
applyMove nl inst (FailoverAndReplace new_sdx) =
510
    let old_pdx = Instance.pnode inst
511
        old_sdx = Instance.snode inst
512
        old_p = Container.find old_pdx nl
513
        old_s = Container.find old_sdx nl
514
        tgt_n = Container.find new_sdx nl
515
        int_p = Node.removePri old_p inst
516
        int_s = Node.removeSec old_s inst
517
        new_nl = do -- Maybe monad
518
          new_p <- Node.addPri int_s inst
519
          new_s <- Node.addSec tgt_n inst old_sdx
520
          return $ Container.add new_sdx new_s $
521
                 Container.addTwo old_sdx new_p old_pdx int_p nl
522
    in (new_nl, Instance.setBoth inst old_sdx new_sdx, old_sdx, new_sdx)
523

    
524
-- | Tries to allocate an instance on one given node.
525
allocateOnSingle :: Node.List -> Instance.Instance -> Node.Node
526
                 -> (Maybe Node.List, Instance.Instance)
527
allocateOnSingle nl inst p =
528
    let new_pdx = Node.idx p
529
        new_nl = Node.addPri p inst >>= \new_p ->
530
                 return $ Container.add new_pdx new_p nl
531
    in (new_nl, Instance.setBoth inst new_pdx Node.noSecondary)
532

    
533
-- | Tries to allocate an instance on a given pair of nodes.
534
allocateOnPair :: Node.List -> Instance.Instance -> Node.Node -> Node.Node
535
               -> (Maybe Node.List, Instance.Instance)
536
allocateOnPair nl inst tgt_p tgt_s =
537
    let new_pdx = Node.idx tgt_p
538
        new_sdx = Node.idx tgt_s
539
        new_nl = do -- Maybe monad
540
          new_p <- Node.addPri tgt_p inst
541
          new_s <- Node.addSec tgt_s inst new_pdx
542
          return $ Container.addTwo new_pdx new_p new_sdx new_s nl
543
    in (new_nl, Instance.setBoth inst new_pdx new_sdx)
544

    
545
-- | Tries to perform an instance move and returns the best table
546
-- between the original one and the new one.
547
checkSingleStep :: Table -- ^ The original table
548
                -> Instance.Instance -- ^ The instance to move
549
                -> Table -- ^ The current best table
550
                -> IMove -- ^ The move to apply
551
                -> Table -- ^ The final best table
552
checkSingleStep ini_tbl target cur_tbl move =
553
    let
554
        Table ini_nl ini_il _ ini_plc = ini_tbl
555
        (tmp_nl, new_inst, pri_idx, sec_idx) = applyMove ini_nl target move
556
    in
557
      if isNothing tmp_nl then cur_tbl
558
      else
559
          let tgt_idx = Instance.idx target
560
              upd_nl = fromJust tmp_nl
561
              upd_cvar = compCV upd_nl
562
              upd_il = Container.add tgt_idx new_inst ini_il
563
              upd_plc = (tgt_idx, pri_idx, sec_idx, upd_cvar):ini_plc
564
              upd_tbl = Table upd_nl upd_il upd_cvar upd_plc
565
          in
566
            compareTables cur_tbl upd_tbl
567

    
568
-- | Given the status of the current secondary as a valid new node
569
-- and the current candidate target node,
570
-- generate the possible moves for a instance.
571
possibleMoves :: Bool -> Ndx -> [IMove]
572
possibleMoves True tdx =
573
    [ReplaceSecondary tdx,
574
     ReplaceAndFailover tdx,
575
     ReplacePrimary tdx,
576
     FailoverAndReplace tdx]
577

    
578
possibleMoves False tdx =
579
    [ReplaceSecondary tdx,
580
     ReplaceAndFailover tdx]
581

    
582
-- | Compute the best move for a given instance.
583
checkInstanceMove :: [Ndx]             -- Allowed target node indices
584
                  -> Table             -- Original table
585
                  -> Instance.Instance -- Instance to move
586
                  -> Table             -- Best new table for this instance
587
checkInstanceMove nodes_idx ini_tbl target =
588
    let
589
        opdx = Instance.pnode target
590
        osdx = Instance.snode target
591
        nodes = filter (\idx -> idx /= opdx && idx /= osdx) nodes_idx
592
        use_secondary = elem osdx nodes_idx
593
        aft_failover = if use_secondary -- if allowed to failover
594
                       then checkSingleStep ini_tbl target ini_tbl Failover
595
                       else ini_tbl
596
        all_moves = concatMap (possibleMoves use_secondary) nodes
597
    in
598
      -- iterate over the possible nodes for this instance
599
      foldl' (checkSingleStep ini_tbl target) aft_failover all_moves
600

    
601
-- | Compute the best next move.
602
checkMove :: [Ndx]               -- ^ Allowed target node indices
603
          -> Table               -- ^ The current solution
604
          -> [Instance.Instance] -- ^ List of instances still to move
605
          -> Table               -- ^ The new solution
606
checkMove nodes_idx ini_tbl victims =
607
    let Table _ _ _ ini_plc = ini_tbl
608
        -- iterate over all instances, computing the best move
609
        best_tbl =
610
            foldl'
611
            (\ step_tbl elem ->
612
                 if Instance.snode elem == Node.noSecondary then step_tbl
613
                    else compareTables step_tbl $
614
                         checkInstanceMove nodes_idx ini_tbl elem)
615
            ini_tbl victims
616
        Table _ _ _ best_plc = best_tbl
617
    in
618
      if length best_plc == length ini_plc then -- no advancement
619
          ini_tbl
620
      else
621
          best_tbl
622

    
623
-- * Alocation functions
624

    
625
-- | Try to allocate an instance on the cluster.
626
tryAlloc :: (Monad m) =>
627
            Node.List         -- ^ The node list
628
         -> Instance.List     -- ^ The instance list
629
         -> Instance.Instance -- ^ The instance to allocate
630
         -> Int               -- ^ Required number of nodes
631
         -> m [(Maybe Node.List, Instance.Instance, [Node.Node])]
632
                              -- ^ Possible solution list
633
tryAlloc nl _ inst 2 =
634
    let all_nodes = getOnline nl
635
        all_pairs = liftM2 (,) all_nodes all_nodes
636
        ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs
637
        sols = map (\(p, s) -> let (mnl, i) = allocateOnPair nl inst p s
638
                               in (mnl, i, [p, s]))
639
               ok_pairs
640
    in return sols
641

    
642
tryAlloc nl _ inst 1 =
643
    let all_nodes = getOnline nl
644
        sols = map (\p -> let (mnl, i) = allocateOnSingle nl inst p
645
                          in (mnl, i, [p]))
646
               all_nodes
647
    in return sols
648

    
649
tryAlloc _ _ _ reqn = fail $ "Unsupported number of alllocation \
650
                             \destinations required (" ++ (show reqn) ++
651
                                               "), only two supported"
652

    
653
-- | Try to allocate an instance on the cluster.
654
tryReloc :: (Monad m) =>
655
            Node.List     -- ^ The node list
656
         -> Instance.List -- ^ The instance list
657
         -> Idx           -- ^ The index of the instance to move
658
         -> Int           -- ^ The numver of nodes required
659
         -> [Ndx]         -- ^ Nodes which should not be used
660
         -> m [(Maybe Node.List, Instance.Instance, [Node.Node])]
661
                          -- ^ Solution list
662
tryReloc nl il xid 1 ex_idx =
663
    let all_nodes = getOnline nl
664
        inst = Container.find xid il
665
        ex_idx' = (Instance.pnode inst):ex_idx
666
        valid_nodes = filter (not . flip elem ex_idx' . Node.idx) all_nodes
667
        valid_idxes = map Node.idx valid_nodes
668
        sols1 = map (\x -> let (mnl, i, _, _) =
669
                                   applyMove nl inst (ReplaceSecondary x)
670
                           in (mnl, i, [Container.find x nl])
671
                     ) valid_idxes
672
    in return sols1
673

    
674
tryReloc _ _ _ reqn _  = fail $ "Unsupported number of relocation \
675
                                \destinations required (" ++ (show reqn) ++
676
                                                  "), only one supported"
677

    
678
-- * Formatting functions
679

    
680
-- | Given the original and final nodes, computes the relocation description.
681
computeMoves :: String -- ^ The instance name
682
             -> String -- ^ Original primary
683
             -> String -- ^ Original secondary
684
             -> String -- ^ New primary
685
             -> String -- ^ New secondary
686
             -> (String, [String])
687
                -- ^ Tuple of moves and commands list; moves is containing
688
                -- either @/f/@ for failover or @/r:name/@ for replace
689
                -- secondary, while the command list holds gnt-instance
690
                -- commands (without that prefix), e.g \"@failover instance1@\"
691
computeMoves i a b c d =
692
    if c == a then {- Same primary -}
693
        if d == b then {- Same sec??! -}
694
            ("-", [])
695
        else {- Change of secondary -}
696
            (printf "r:%s" d,
697
             [printf "replace-disks -n %s %s" d i])
698
    else
699
        if c == b then {- Failover and ... -}
700
            if d == a then {- that's all -}
701
                ("f", [printf "migrate -f %s" i])
702
            else
703
                (printf "f r:%s" d,
704
                 [printf "migrate -f %s" i,
705
                  printf "replace-disks -n %s %s" d i])
706
        else
707
            if d == a then {- ... and keep primary as secondary -}
708
                (printf "r:%s f" c,
709
                 [printf "replace-disks -n %s %s" c i,
710
                  printf "migrate -f %s" i])
711
            else
712
                if d == b then {- ... keep same secondary -}
713
                    (printf "f r:%s f" c,
714
                     [printf "migrate -f %s" i,
715
                      printf "replace-disks -n %s %s" c i,
716
                      printf "migrate -f %s" i])
717

    
718
                else {- Nothing in common -}
719
                    (printf "r:%s f r:%s" c d,
720
                     [printf "replace-disks -n %s %s" c i,
721
                      printf "migrate -f %s" i,
722
                      printf "replace-disks -n %s %s" d i])
723

    
724
-- | Converts a placement to string format.
725
printSolutionLine :: Node.List     -- ^ The node list
726
                  -> Instance.List -- ^ The instance list
727
                  -> Int           -- ^ Maximum node name length
728
                  -> Int           -- ^ Maximum instance name length
729
                  -> Placement     -- ^ The current placement
730
                  -> Int           -- ^ The index of the placement in
731
                                   -- the solution
732
                  -> (String, [String])
733
printSolutionLine nl il nmlen imlen plc pos =
734
    let
735
        pmlen = (2*nmlen + 1)
736
        (i, p, s, c) = plc
737
        inst = Container.find i il
738
        inam = Instance.name inst
739
        npri = Container.nameOf nl p
740
        nsec = Container.nameOf nl s
741
        opri = Container.nameOf nl $ Instance.pnode inst
742
        osec = Container.nameOf nl $ Instance.snode inst
743
        (moves, cmds) =  computeMoves inam opri osec npri nsec
744
        ostr = (printf "%s:%s" opri osec)::String
745
        nstr = (printf "%s:%s" npri nsec)::String
746
    in
747
      (printf "  %3d. %-*s %-*s => %-*s %.8f a=%s"
748
       pos imlen inam pmlen ostr
749
       pmlen nstr c moves,
750
       cmds)
751

    
752
-- | Given a list of commands, prefix them with @gnt-instance@ and
753
-- also beautify the display a little.
754
formatCmds :: [[String]] -> String
755
formatCmds cmd_strs =
756
    unlines $
757
    concat $ map (\(a, b) ->
758
        (printf "echo step %d" (a::Int)):
759
        (printf "check"):
760
        (map ("gnt-instance " ++) b)) $
761
        zip [1..] cmd_strs
762

    
763
-- | Converts a solution to string format.
764
printSolution :: Node.List
765
              -> Instance.List
766
              -> [Placement]
767
              -> ([String], [[String]])
768
printSolution nl il sol =
769
    let
770
        nmlen = Container.maxNameLen nl
771
        imlen = Container.maxNameLen il
772
    in
773
      unzip $ map (uncurry $ printSolutionLine nl il nmlen imlen) $
774
            zip sol [1..]
775

    
776
-- | Print the node list.
777
printNodes :: Node.List -> String
778
printNodes nl =
779
    let snl = sortBy (compare `on` Node.idx) (Container.elems nl)
780
        m_name = maximum . map (length . Node.name) $ snl
781
        helper = Node.list m_name
782
        header = printf
783
                 "%2s %-*s %5s %5s %5s %5s %5s %5s %5s %5s %3s %3s %7s %7s"
784
                 " F" m_name "Name"
785
                 "t_mem" "n_mem" "i_mem" "x_mem" "f_mem" "r_mem"
786
                 "t_dsk" "f_dsk"
787
                 "pri" "sec" "p_fmem" "p_fdsk"
788
    in unlines $ (header:map helper snl)
789

    
790
-- | Shows statistics for a given node list.
791
printStats :: Node.List -> String
792
printStats nl =
793
    let (mem_cv, dsk_cv, n1_score, res_cv, off_score) = compDetailedCV nl
794
    in printf "f_mem=%.8f, r_mem=%.8f, f_dsk=%.8f, n1=%.3f, uf=%.3f"
795
       mem_cv res_cv dsk_cv n1_score off_score