Revision 266aea94 Ganeti/HTools/Cluster.hs

b/Ganeti/HTools/Cluster.hs
31 31
     -- * Types
32 32
      Placement
33 33
    , AllocSolution
34
    , Solution(..)
35 34
    , Table(..)
36
    , Removal
37 35
    , Score
38 36
    , IMove(..)
39 37
    , CStats(..)
......
42 40
    -- * First phase functions
43 41
    , computeBadItems
44 42
    -- * Second phase functions
45
    , computeSolution
46
    , applySolution
47 43
    , printSolution
48 44
    , printSolutionLine
49 45
    , formatCmds
......
83 79
-- | Allocation\/relocation solution.
84 80
type AllocSolution = [(Maybe Node.List, Instance.Instance, [Node.Node])]
85 81

  
86
-- | A cluster solution described as the solution delta and the list
87
-- of placements.
88
data Solution = Solution Int [Placement]
89
                deriving (Eq, Ord, Show)
90

  
91
-- | A removal set.
92
data Removal = Removal Node.List [Instance.Instance]
93

  
94 82
-- | An instance move definition
95 83
data IMove = Failover                -- ^ Failover the instance (f)
96 84
           | ReplacePrimary Ndx      -- ^ Replace primary (f, r:np, f)
......
115 103

  
116 104
-- * Utility functions
117 105

  
118
-- | Returns the delta of a solution or -1 for Nothing.
119
solutionDelta :: Maybe Solution -> Int
120
solutionDelta sol = case sol of
121
                      Just (Solution d _) -> d
122
                      _ -> -1
123

  
124
-- | Cap the removal list if needed.
125
capRemovals :: [a] -> Int -> [a]
126
capRemovals removals max_removals =
127
    if max_removals > 0 then
128
        take max_removals removals
129
    else
130
        removals
131

  
132
-- | Check if the given node list fails the N+1 check.
133
verifyN1Check :: [Node.Node] -> Bool
134
verifyN1Check nl = any Node.failN1 nl
135

  
136 106
-- | Verifies the N+1 status and return the affected nodes.
137 107
verifyN1 :: [Node.Node] -> [Node.Node]
138 108
verifyN1 nl = filter Node.failN1 nl
......
224 194
getOnline :: Node.List -> [Node.Node]
225 195
getOnline = filter (not . Node.offline) . Container.elems
226 196

  
227
-- * hn1 functions
228

  
229
-- | Add an instance and return the new node and instance maps.
230
addInstance :: Node.List -> Instance.Instance ->
231
               Node.Node -> Node.Node -> Maybe Node.List
232
addInstance nl idata pri sec =
233
  let pdx = Node.idx pri
234
      sdx = Node.idx sec
235
  in do
236
      pnode <- Node.addPri pri idata
237
      snode <- Node.addSec sec idata pdx
238
      new_nl <- return $ Container.addTwo sdx snode
239
                         pdx pnode nl
240
      return new_nl
241

  
242
-- | Remove an instance and return the new node and instance maps.
243
removeInstance :: Node.List -> Instance.Instance -> Node.List
244
removeInstance nl idata =
245
  let pnode = Instance.pnode idata
246
      snode = Instance.snode idata
247
      pn = Container.find pnode nl
248
      sn = Container.find snode nl
249
      new_nl = Container.addTwo
250
               pnode (Node.removePri pn idata)
251
               snode (Node.removeSec sn idata) nl in
252
  new_nl
253

  
254
-- | Remove an instance and return the new node map.
255
removeInstances :: Node.List -> [Instance.Instance] -> Node.List
256
removeInstances = foldl' removeInstance
257

  
258

  
259
{-| Compute a new version of a cluster given a solution.
260

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

  
264
It first removes the relocated instances after which it places them on
265
their new nodes.
266

  
267
 -}
268
applySolution :: Node.List -> Instance.List -> [Placement] -> Node.List
269
applySolution nl il sol =
270
    let odxes = map (\ (a, b, c, _) -> (Container.find a il,
271
                                        Node.idx (Container.find b nl),
272
                                        Node.idx (Container.find c nl))
273
                    ) sol
274
        idxes = (\ (x, _, _) -> x) (unzip3 odxes)
275
        nc = removeInstances nl idxes
276
    in
277
      foldl' (\ nz (a, b, c) ->
278
                 let new_p = Container.find b nz
279
                     new_s = Container.find c nz in
280
                 fromJust (addInstance nz a new_p new_s)
281
           ) nc odxes
282

  
283

  
284
-- ** First phase functions
285

  
286
{-| Given a list 1,2,3..n build a list of pairs [(1, [2..n]), (2,
287
    [3..n]), ...]
288

  
289
-}
290
genParts :: [a] -> Int -> [(a, [a])]
291
genParts l count =
292
    case l of
293
      [] -> []
294
      x:xs ->
295
          if length l < count then
296
              []
297
          else
298
              (x, xs) : (genParts xs count)
299

  
300
-- | Generates combinations of count items from the names list.
301
genNames :: Int -> [b] -> [[b]]
302
genNames count1 names1 =
303
  let aux_fn count names current =
304
          case count of
305
            0 -> [current]
306
            _ ->
307
                concatMap
308
                (\ (x, xs) -> aux_fn (count - 1) xs (x:current))
309
                (genParts names count)
310
  in
311
    aux_fn count1 names1 []
312

  
313
{-| Checks if removal of instances results in N+1 pass.
314

  
315
Note: the check removal cannot optimize by scanning only the affected
316
nodes, since the cluster is known to be not healthy; only the check
317
placement can make this shortcut.
318

  
319
-}
320
checkRemoval :: Node.List -> [Instance.Instance] -> Maybe Removal
321
checkRemoval nl victims =
322
  let nx = removeInstances nl victims
323
      failN1 = verifyN1Check (Container.elems nx)
324
  in
325
    if failN1 then
326
      Nothing
327
    else
328
      Just $ Removal nx victims
329

  
330

  
331
-- | Computes the removals list for a given depth.
332
computeRemovals :: Node.List
333
                 -> [Instance.Instance]
334
                 -> Int
335
                 -> [Maybe Removal]
336
computeRemovals nl bad_instances depth =
337
    map (checkRemoval nl) $ genNames depth bad_instances
338

  
339
-- ** Second phase functions
340

  
341
-- | Single-node relocation cost.
342
nodeDelta :: Ndx -> Ndx -> Ndx -> Int
343
nodeDelta i p s =
344
    if i == p || i == s then
345
        0
346
    else
347
        1
348

  
349
-- | Compute best solution.
350
--
351
-- This function compares two solutions, choosing the minimum valid
352
-- solution.
353
compareSolutions :: Maybe Solution -> Maybe Solution -> Maybe Solution
354
compareSolutions a b = case (a, b) of
355
  (Nothing, x) -> x
356
  (x, Nothing) -> x
357
  (x, y) -> min x y
358

  
359
-- | Check if a given delta is worse then an existing solution.
360
tooHighDelta :: Maybe Solution -> Int -> Int -> Bool
361
tooHighDelta sol new_delta max_delta =
362
    if new_delta > max_delta && max_delta >=0 then
363
        True
364
    else
365
        case sol of
366
          Nothing -> False
367
          Just (Solution old_delta _) -> old_delta <= new_delta
368

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

  
371
    This is the workhorse of the allocation algorithm: given the
372
    current node and instance maps, the list of instances to be
373
    placed, and the current solution, this will return all possible
374
    solution by recursing until all target instances are placed.
375

  
376
-}
377
checkPlacement :: Node.List            -- ^ The current node list
378
               -> [Instance.Instance] -- ^ List of instances still to place
379
               -> [Placement]         -- ^ Partial solution until now
380
               -> Int                 -- ^ The delta of the partial solution
381
               -> Maybe Solution      -- ^ The previous solution
382
               -> Int                 -- ^ Abort if the we go above this delta
383
               -> Maybe Solution      -- ^ The new solution
384
checkPlacement nl victims current current_delta prev_sol max_delta =
385
  let target = head victims
386
      opdx = Instance.pnode target
387
      osdx = Instance.snode target
388
      vtail = tail victims
389
      have_tail = (length vtail) > 0
390
      nodes = Container.elems nl
391
      iidx = Instance.idx target
392
  in
393
    foldl'
394
    (\ accu_p pri ->
395
         let
396
             pri_idx = Node.idx pri
397
             upri_delta = current_delta + nodeDelta pri_idx opdx osdx
398
             new_pri = Node.addPri pri target
399
             fail_delta1 = tooHighDelta accu_p upri_delta max_delta
400
         in
401
           if fail_delta1 || isNothing(new_pri) then accu_p
402
           else let pri_nl = Container.add pri_idx (fromJust new_pri) nl in
403
                foldl'
404
                (\ accu sec ->
405
                     let
406
                         sec_idx = Node.idx sec
407
                         upd_delta = upri_delta +
408
                                     nodeDelta sec_idx opdx osdx
409
                         fail_delta2 = tooHighDelta accu upd_delta max_delta
410
                         new_sec = Node.addSec sec target pri_idx
411
                     in
412
                       if sec_idx == pri_idx || fail_delta2 ||
413
                          isNothing new_sec then accu
414
                       else let
415
                           nx = Container.add sec_idx (fromJust new_sec) pri_nl
416
                           upd_cv = compCV nx
417
                           plc = (iidx, pri_idx, sec_idx, upd_cv)
418
                           c2 = plc:current
419
                           result =
420
                               if have_tail then
421
                                   checkPlacement nx vtail c2 upd_delta
422
                                                  accu max_delta
423
                               else
424
                                   Just (Solution upd_delta c2)
425
                      in compareSolutions accu result
426
                ) accu_p nodes
427
    ) prev_sol nodes
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 :: Node.List        -- ^ 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 197
-- * hbal functions
482 198

  
483 199
-- | Compute best table. Note that the ordering of the arguments is important.

Also available in: Unified diff