Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / Node.hs @ 306cccd5

History | View | Annotate | Download (18.5 kB)

1
{-| Module describing a node.
2

    
3
    All updates are functional (copy-based) and return a new node with
4
    updated value.
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2009, 2010 Google Inc.
10

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

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

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

    
26
-}
27

    
28
module Ganeti.HTools.Node
29
    ( Node(..)
30
    , List
31
    -- * Constructor
32
    , create
33
    -- ** Finalization after data loading
34
    , buildPeers
35
    , setIdx
36
    , setAlias
37
    , setOffline
38
    , setXmem
39
    , setFmem
40
    , setPri
41
    , setSec
42
    , setMdsk
43
    , setMcpu
44
    -- * Tag maps
45
    , addTags
46
    , delTags
47
    , rejectAddTags
48
    -- * Instance (re)location
49
    , removePri
50
    , removeSec
51
    , addPri
52
    , addPriEx
53
    , addSec
54
    , addSecEx
55
    -- * Stats
56
    , availDisk
57
    , availMem
58
    , availCpu
59
    , conflictingPrimaries
60
    -- * Formatting
61
    , defaultFields
62
    , showHeader
63
    , showField
64
    , list
65
    -- * Misc stuff
66
    , AssocList
67
    , AllocElement
68
    , noSecondary
69
    ) where
70

    
71
import Data.List
72
import qualified Data.Map as Map
73
import qualified Data.Foldable as Foldable
74
import Text.Printf (printf)
75

    
76
import qualified Ganeti.HTools.Container as Container
77
import qualified Ganeti.HTools.Instance as Instance
78
import qualified Ganeti.HTools.PeerMap as P
79

    
80
import qualified Ganeti.HTools.Types as T
81

    
82
-- * Type declarations
83

    
84
-- | The tag map type
85
type TagMap = Map.Map String Int
86

    
87
-- | The node type.
88
data Node = Node
89
    { name     :: String    -- ^ The node name
90
    , alias    :: String    -- ^ The shortened name (for display purposes)
91
    , tMem     :: Double    -- ^ Total memory (MiB)
92
    , nMem     :: Int       -- ^ Node memory (MiB)
93
    , fMem     :: Int       -- ^ Free memory (MiB)
94
    , xMem     :: Int       -- ^ Unaccounted memory (MiB)
95
    , tDsk     :: Double    -- ^ Total disk space (MiB)
96
    , fDsk     :: Int       -- ^ Free disk space (MiB)
97
    , tCpu     :: Double    -- ^ Total CPU count
98
    , uCpu     :: Int       -- ^ Used VCPU count
99
    , pList    :: [T.Idx]   -- ^ List of primary instance indices
100
    , sList    :: [T.Idx]   -- ^ List of secondary instance indices
101
    , idx      :: T.Ndx     -- ^ Internal index for book-keeping
102
    , peers    :: P.PeerMap -- ^ Pnode to instance mapping
103
    , failN1   :: Bool      -- ^ Whether the node has failed n1
104
    , rMem     :: Int       -- ^ Maximum memory needed for failover by
105
                            -- primaries of this node
106
    , pMem     :: Double    -- ^ Percent of free memory
107
    , pDsk     :: Double    -- ^ Percent of free disk
108
    , pRem     :: Double    -- ^ Percent of reserved memory
109
    , pCpu     :: Double    -- ^ Ratio of virtual to physical CPUs
110
    , mDsk     :: Double    -- ^ Minimum free disk ratio
111
    , mCpu     :: Double    -- ^ Max ratio of virt-to-phys CPUs
112
    , loDsk    :: Int       -- ^ Autocomputed from mDsk low disk
113
                            -- threshold
114
    , hiCpu    :: Int       -- ^ Autocomputed from mCpu high cpu
115
                            -- threshold
116
    , offline  :: Bool      -- ^ Whether the node should not be used
117
                            -- for allocations and skipped from score
118
                            -- computations
119
    , utilPool :: T.DynUtil -- ^ Total utilisation capacity
120
    , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
121
    , pTags    :: TagMap    -- ^ Map of primary instance tags and their count
122
    } deriving (Show, Eq)
123

    
124
instance T.Element Node where
125
    nameOf = name
126
    idxOf = idx
127
    setAlias = setAlias
128
    setIdx = setIdx
129
    allNames n = [name n, alias n]
130

    
131
-- | A simple name for the int, node association list.
132
type AssocList = [(T.Ndx, Node)]
133

    
134
-- | A simple name for a node map.
135
type List = Container.Container Node
136

    
137
-- | A simple name for an allocation element (here just for logistic
138
-- reasons)
139
type AllocElement = (List, Instance.Instance, [Node])
140

    
141
-- | Constant node index for a non-moveable instance.
142
noSecondary :: T.Ndx
143
noSecondary = -1
144

    
145
-- * Helper functions
146

    
147
-- | Add a tag to a tagmap
148
addTag :: TagMap -> String -> TagMap
149
addTag t s = Map.insertWith (+) s 1 t
150

    
151
-- | Add multiple tags
152
addTags :: TagMap -> [String] -> TagMap
153
addTags = foldl' addTag
154

    
155
-- | Adjust or delete a tag from a tagmap
156
delTag :: TagMap -> String -> TagMap
157
delTag t s = Map.update (\v -> if v > 1
158
                               then Just (v-1)
159
                               else Nothing)
160
             s t
161

    
162
-- | Remove multiple tags
163
delTags :: TagMap -> [String] -> TagMap
164
delTags = foldl' delTag
165

    
166
-- | Check if we can add a list of tags to a tagmap
167
rejectAddTags :: TagMap -> [String] -> Bool
168
rejectAddTags t = any (`Map.member` t)
169

    
170
-- | Check how many primary instances have conflicting tags. The
171
-- algorithm to compute this is to sum the count of all tags, then
172
-- subtract the size of the tag map (since each tag has at least one,
173
-- non-conflicting instance); this is equivalent to summing the
174
-- values in the tag map minus one.
175
conflictingPrimaries :: Node -> Int
176
conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
177

    
178
-- * Initialization functions
179

    
180
-- | Create a new node.
181
--
182
-- The index and the peers maps are empty, and will be need to be
183
-- update later via the 'setIdx' and 'buildPeers' functions.
184
create :: String -> Double -> Int -> Int -> Double
185
       -> Int -> Double -> Bool -> Node
186
create name_init mem_t_init mem_n_init mem_f_init
187
       dsk_t_init dsk_f_init cpu_t_init offline_init =
188
    Node { name = name_init
189
         , alias = name_init
190
         , tMem = mem_t_init
191
         , nMem = mem_n_init
192
         , fMem = mem_f_init
193
         , tDsk = dsk_t_init
194
         , fDsk = dsk_f_init
195
         , tCpu = cpu_t_init
196
         , uCpu = 0
197
         , pList = []
198
         , sList = []
199
         , failN1 = True
200
         , idx = -1
201
         , peers = P.empty
202
         , rMem = 0
203
         , pMem = fromIntegral mem_f_init / mem_t_init
204
         , pDsk = fromIntegral dsk_f_init / dsk_t_init
205
         , pRem = 0
206
         , pCpu = 0
207
         , offline = offline_init
208
         , xMem = 0
209
         , mDsk = T.defReservedDiskRatio
210
         , mCpu = T.defVcpuRatio
211
         , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
212
         , hiCpu = mCpuTohiCpu T.defVcpuRatio cpu_t_init
213
         , utilPool = T.baseUtil
214
         , utilLoad = T.zeroUtil
215
         , pTags = Map.empty
216
         }
217

    
218
-- | Conversion formula from mDsk\/tDsk to loDsk
219
mDskToloDsk :: Double -> Double -> Int
220
mDskToloDsk mval tdsk = floor (mval * tdsk)
221

    
222
-- | Conversion formula from mCpu\/tCpu to hiCpu
223
mCpuTohiCpu :: Double -> Double -> Int
224
mCpuTohiCpu mval tcpu = floor (mval * tcpu)
225

    
226
-- | Changes the index.
227
--
228
-- This is used only during the building of the data structures.
229
setIdx :: Node -> T.Ndx -> Node
230
setIdx t i = t {idx = i}
231

    
232
-- | Changes the alias.
233
--
234
-- This is used only during the building of the data structures.
235
setAlias :: Node -> String -> Node
236
setAlias t s = t { alias = s }
237

    
238
-- | Sets the offline attribute.
239
setOffline :: Node -> Bool -> Node
240
setOffline t val = t { offline = val }
241

    
242
-- | Sets the unnaccounted memory.
243
setXmem :: Node -> Int -> Node
244
setXmem t val = t { xMem = val }
245

    
246
-- | Sets the max disk usage ratio
247
setMdsk :: Node -> Double -> Node
248
setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
249

    
250
-- | Sets the max cpu usage ratio
251
setMcpu :: Node -> Double -> Node
252
setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
253

    
254
-- | Computes the maximum reserved memory for peers from a peer map.
255
computeMaxRes :: P.PeerMap -> P.Elem
256
computeMaxRes = P.maxElem
257

    
258
-- | Builds the peer map for a given node.
259
buildPeers :: Node -> Instance.List -> Node
260
buildPeers t il =
261
    let mdata = map
262
                (\i_idx -> let inst = Container.find i_idx il
263
                           in (Instance.pNode inst, Instance.mem inst))
264
                (sList t)
265
        pmap = P.accumArray (+) mdata
266
        new_rmem = computeMaxRes pmap
267
        new_failN1 = fMem t <= new_rmem
268
        new_prem = fromIntegral new_rmem / tMem t
269
    in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
270

    
271
-- | Assigns an instance to a node as primary and update the used VCPU
272
-- count, utilisation data and tags map.
273
setPri :: Node -> Instance.Instance -> Node
274
setPri t inst = t { pList = Instance.idx inst:pList t
275
                  , uCpu = new_count
276
                  , pCpu = fromIntegral new_count / tCpu t
277
                  , utilLoad = utilLoad t `T.addUtil` Instance.util inst
278
                  , pTags = addTags (pTags t) (Instance.tags inst)
279
                  }
280
    where new_count = uCpu t + Instance.vcpus inst
281

    
282
-- | Assigns an instance to a node as secondary without other updates.
283
setSec :: Node -> Instance.Instance -> Node
284
setSec t inst = t { sList = Instance.idx inst:sList t
285
                  , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
286
                                          T.dskWeight (Instance.util inst) }
287
                  }
288
    where old_load = utilLoad t
289

    
290
-- * Update functions
291

    
292
-- | Sets the free memory.
293
setFmem :: Node -> Int -> Node
294
setFmem t new_mem =
295
    let new_n1 = new_mem <= rMem t
296
        new_mp = fromIntegral new_mem / tMem t
297
    in t { fMem = new_mem, failN1 = new_n1, pMem = new_mp }
298

    
299
-- | Removes a primary instance.
300
removePri :: Node -> Instance.Instance -> Node
301
removePri t inst =
302
    let iname = Instance.idx inst
303
        new_plist = delete iname (pList t)
304
        new_mem = fMem t + Instance.mem inst
305
        new_dsk = fDsk t + Instance.dsk inst
306
        new_mp = fromIntegral new_mem / tMem t
307
        new_dp = fromIntegral new_dsk / tDsk t
308
        new_failn1 = new_mem <= rMem t
309
        new_ucpu = uCpu t - Instance.vcpus inst
310
        new_rcpu = fromIntegral new_ucpu / tCpu t
311
        new_load = utilLoad t `T.subUtil` Instance.util inst
312
    in t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
313
         , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
314
         , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
315
         , pTags = delTags (pTags t) (Instance.tags inst) }
316

    
317
-- | Removes a secondary instance.
318
removeSec :: Node -> Instance.Instance -> Node
319
removeSec t inst =
320
    let iname = Instance.idx inst
321
        pnode = Instance.pNode inst
322
        new_slist = delete iname (sList t)
323
        new_dsk = fDsk t + Instance.dsk inst
324
        old_peers = peers t
325
        old_peem = P.find pnode old_peers
326
        new_peem =  old_peem - Instance.mem inst
327
        new_peers = if new_peem > 0
328
                    then P.add pnode new_peem old_peers
329
                    else P.remove pnode old_peers
330
        old_rmem = rMem t
331
        new_rmem = if old_peem < old_rmem
332
                   then old_rmem
333
                   else computeMaxRes new_peers
334
        new_prem = fromIntegral new_rmem / tMem t
335
        new_failn1 = fMem t <= new_rmem
336
        new_dp = fromIntegral new_dsk / tDsk t
337
        old_load = utilLoad t
338
        new_load = old_load { T.dskWeight = T.dskWeight old_load -
339
                                            T.dskWeight (Instance.util inst) }
340
    in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
341
         , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
342
         , pRem = new_prem, utilLoad = new_load }
343

    
344
-- | Adds a primary instance (basic version).
345
addPri :: Node -> Instance.Instance -> T.OpResult Node
346
addPri = addPriEx False
347

    
348
-- | Adds a primary instance (extended version).
349
addPriEx :: Bool               -- ^ Whether to override the N+1 and
350
                               -- other /soft/ checks, useful if we
351
                               -- come from a worse status
352
                               -- (e.g. offline)
353
         -> Node               -- ^ The target node
354
         -> Instance.Instance  -- ^ The instance to add
355
         -> T.OpResult Node    -- ^ The result of the operation,
356
                               -- either the new version of the node
357
                               -- or a failure mode
358
addPriEx force t inst =
359
    let iname = Instance.idx inst
360
        new_mem = fMem t - Instance.mem inst
361
        new_dsk = fDsk t - Instance.dsk inst
362
        new_failn1 = new_mem <= rMem t
363
        new_ucpu = uCpu t + Instance.vcpus inst
364
        new_pcpu = fromIntegral new_ucpu / tCpu t
365
        new_dp = fromIntegral new_dsk / tDsk t
366
        l_cpu = mCpu t
367
        new_load = utilLoad t `T.addUtil` Instance.util inst
368
        inst_tags = Instance.tags inst
369
        old_tags = pTags t
370
        strict = not force
371
    in case () of
372
         _ | new_mem <= 0 -> T.OpFail T.FailMem
373
           | new_dsk <= 0 -> T.OpFail T.FailDisk
374
           | mDsk t > new_dp && strict -> T.OpFail T.FailDisk
375
           | new_failn1 && not (failN1 t) && strict -> T.OpFail T.FailMem
376
           | l_cpu >= 0 && l_cpu < new_pcpu && strict -> T.OpFail T.FailCPU
377
           | rejectAddTags old_tags inst_tags -> T.OpFail T.FailTags
378
           | otherwise ->
379
               let new_plist = iname:pList t
380
                   new_mp = fromIntegral new_mem / tMem t
381
                   r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
382
                         , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
383
                         , uCpu = new_ucpu, pCpu = new_pcpu
384
                         , utilLoad = new_load
385
                         , pTags = addTags old_tags inst_tags }
386
               in T.OpGood r
387

    
388
-- | Adds a secondary instance (basic version).
389
addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
390
addSec = addSecEx False
391

    
392
-- | Adds a secondary instance (extended version).
393
addSecEx :: Bool -> Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
394
addSecEx force t inst pdx =
395
    let iname = Instance.idx inst
396
        old_peers = peers t
397
        old_mem = fMem t
398
        new_dsk = fDsk t - Instance.dsk inst
399
        new_peem = P.find pdx old_peers + Instance.mem inst
400
        new_peers = P.add pdx new_peem old_peers
401
        new_rmem = max (rMem t) new_peem
402
        new_prem = fromIntegral new_rmem / tMem t
403
        new_failn1 = old_mem <= new_rmem
404
        new_dp = fromIntegral new_dsk / tDsk t
405
        old_load = utilLoad t
406
        new_load = old_load { T.dskWeight = T.dskWeight old_load +
407
                                            T.dskWeight (Instance.util inst) }
408
        strict = not force
409
    in case () of
410
         _ | new_dsk <= 0 -> T.OpFail T.FailDisk
411
           | mDsk t > new_dp && strict -> T.OpFail T.FailDisk
412
           | Instance.mem inst >= old_mem && strict -> T.OpFail T.FailMem
413
           | new_failn1 && not (failN1 t) && strict -> T.OpFail T.FailMem
414
           | otherwise ->
415
               let new_slist = iname:sList t
416
                   r = t { sList = new_slist, fDsk = new_dsk
417
                         , peers = new_peers, failN1 = new_failn1
418
                         , rMem = new_rmem, pDsk = new_dp
419
                         , pRem = new_prem, utilLoad = new_load }
420
               in T.OpGood r
421

    
422
-- * Stats functions
423

    
424
-- | Computes the amount of available disk on a given node
425
availDisk :: Node -> Int
426
availDisk t =
427
    let _f = fDsk t
428
        _l = loDsk t
429
    in if _f < _l
430
       then 0
431
       else _f - _l
432

    
433
-- | Computes the amount of available memory on a given node
434
availMem :: Node -> Int
435
availMem t =
436
    let _f = fMem t
437
        _l = rMem t
438
    in if _f < _l
439
       then 0
440
       else _f - _l
441

    
442
-- | Computes the amount of available memory on a given node
443
availCpu :: Node -> Int
444
availCpu t =
445
    let _u = uCpu t
446
        _l = hiCpu t
447
    in if _l >= _u
448
       then _l - _u
449
       else 0
450

    
451
-- * Display functions
452

    
453
showField :: Node -> String -> String
454
showField t field =
455
    case field of
456
      "idx"  -> printf "%4d" $ idx t
457
      "name" -> alias t
458
      "fqdn" -> name t
459
      "status" -> if offline t then "-"
460
                  else if failN1 t then "*" else " "
461
      "tmem" -> printf "%5.0f" $ tMem t
462
      "nmem" -> printf "%5d" $ nMem t
463
      "xmem" -> printf "%5d" $ xMem t
464
      "fmem" -> printf "%5d" $ fMem t
465
      "imem" -> printf "%5d" imem
466
      "rmem" -> printf "%5d" $ rMem t
467
      "amem" -> printf "%5d" $ fMem t - rMem t
468
      "tdsk" -> printf "%5.0f" $ tDsk t / 1024
469
      "fdsk" -> printf "%5d" $ fDsk t `div` 1024
470
      "tcpu" -> printf "%4.0f" $ tCpu t
471
      "ucpu" -> printf "%4d" $ uCpu t
472
      "pcnt" -> printf "%3d" $ length (pList t)
473
      "scnt" -> printf "%3d" $ length (sList t)
474
      "plist" -> show $ pList t
475
      "slist" -> show $ sList t
476
      "pfmem" -> printf "%6.4f" $ pMem t
477
      "pfdsk" -> printf "%6.4f" $ pDsk t
478
      "rcpu"  -> printf "%5.2f" $ pCpu t
479
      "cload" -> printf "%5.3f" uC
480
      "mload" -> printf "%5.3f" uM
481
      "dload" -> printf "%5.3f" uD
482
      "nload" -> printf "%5.3f" uN
483
      "ptags" -> intercalate "," . map (\(k, v) -> printf "%s=%d" k v) .
484
                 Map.toList $ pTags t
485
      "peermap" -> show $ peers t
486
      _ -> T.unknownField
487
    where
488
      T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
489
                  T.dskWeight = uD, T.netWeight = uN } = utilLoad t
490
      imem = truncate (tMem t) - nMem t - xMem t - fMem t
491

    
492
-- | Returns the header and numeric propery of a field
493
showHeader :: String -> (String, Bool)
494
showHeader field =
495
    case field of
496
      "idx" -> ("Index", True)
497
      "name" -> ("Name", False)
498
      "fqdn" -> ("Name", False)
499
      "status" -> ("F", False)
500
      "tmem" -> ("t_mem", True)
501
      "nmem" -> ("n_mem", True)
502
      "xmem" -> ("x_mem", True)
503
      "fmem" -> ("f_mem", True)
504
      "imem" -> ("i_mem", True)
505
      "rmem" -> ("r_mem", True)
506
      "amem" -> ("a_mem", True)
507
      "tdsk" -> ("t_dsk", True)
508
      "fdsk" -> ("f_dsk", True)
509
      "tcpu" -> ("pcpu", True)
510
      "ucpu" -> ("vcpu", True)
511
      "pcnt" -> ("pcnt", True)
512
      "scnt" -> ("scnt", True)
513
      "plist" -> ("primaries", True)
514
      "slist" -> ("secondaries", True)
515
      "pfmem" -> ("p_fmem", True)
516
      "pfdsk" -> ("p_fdsk", True)
517
      "rcpu"  -> ("r_cpu", True)
518
      "cload" -> ("lCpu", True)
519
      "mload" -> ("lMem", True)
520
      "dload" -> ("lDsk", True)
521
      "nload" -> ("lNet", True)
522
      "ptags" -> ("PrimaryTags", False)
523
      "peermap" -> ("PeerMap", False)
524
      _ -> (T.unknownField, False)
525

    
526
-- | String converter for the node list functionality.
527
list :: [String] -> Node -> [String]
528
list fields t = map (showField t) fields
529

    
530

    
531
defaultFields :: [String]
532
defaultFields =
533
    [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
534
    , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "pcnt", "scnt"
535
    , "pfmem", "pfdsk", "rcpu"
536
    , "cload", "mload", "dload", "nload" ]