Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / Node.hs @ e87a419f

History | View | Annotate | Download (16.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 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
    , setName
37
    , setOffline
38
    , setXmem
39
    , setFmem
40
    , setPri
41
    , setSec
42
    , setMdsk
43
    , setMcpu
44
    -- * Instance (re)location
45
    , removePri
46
    , removeSec
47
    , addPri
48
    , addSec
49
    -- * Stats
50
    , availDisk
51
    , conflictingPrimaries
52
    -- * Formatting
53
    , defaultFields
54
    , showHeader
55
    , showField
56
    , list
57
    -- * Misc stuff
58
    , AssocList
59
    , AllocElement
60
    , noSecondary
61
    , noLimitInt
62
    ) where
63

    
64
import Data.List
65
import qualified Data.Map as Map
66
import qualified Data.Foldable as Foldable
67
import Text.Printf (printf)
68

    
69
import qualified Ganeti.HTools.Container as Container
70
import qualified Ganeti.HTools.Instance as Instance
71
import qualified Ganeti.HTools.PeerMap as P
72

    
73
import qualified Ganeti.HTools.Types as T
74

    
75
-- * Type declarations
76

    
77
-- | The tag map type
78
type TagMap = Map.Map String Int
79

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

    
116
instance T.Element Node where
117
    nameOf = name
118
    idxOf = idx
119
    setName = setName
120
    setIdx = setIdx
121

    
122
-- | A simple name for the int, node association list.
123
type AssocList = [(T.Ndx, Node)]
124

    
125
-- | A simple name for a node map.
126
type List = Container.Container Node
127

    
128
-- | A simple name for an allocation element (here just for logistic
129
-- reasons)
130
type AllocElement = (List, Instance.Instance, [Node])
131

    
132
-- | Constant node index for a non-moveable instance.
133
noSecondary :: T.Ndx
134
noSecondary = -1
135

    
136
-- | No limit value
137
noLimit :: Double
138
noLimit = -1
139

    
140
-- | No limit int value
141
noLimitInt :: Int
142
noLimitInt = -1
143

    
144
-- * Helper functions
145

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

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

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

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

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

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

    
177
-- * Initialization functions
178

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

    
216
-- | Changes the index.
217
--
218
-- This is used only during the building of the data structures.
219
setIdx :: Node -> T.Ndx -> Node
220
setIdx t i = t {idx = i}
221

    
222
-- | Changes the name.
223
--
224
-- This is used only during the building of the data structures.
225
setName :: Node -> String -> Node
226
setName t s = t {name = s}
227

    
228
-- | Sets the offline attribute.
229
setOffline :: Node -> Bool -> Node
230
setOffline t val = t { offline = val }
231

    
232
-- | Sets the unnaccounted memory.
233
setXmem :: Node -> Int -> Node
234
setXmem t val = t { xMem = val }
235

    
236
-- | Sets the max disk usage ratio
237
setMdsk :: Node -> Double -> Node
238
setMdsk t val = t { mDsk = val,
239
                    loDsk = if val == noLimit
240
                             then noLimitInt
241
                             else floor (val * tDsk t) }
242

    
243
-- | Sets the max cpu usage ratio
244
setMcpu :: Node -> Double -> Node
245
setMcpu t val = t { mCpu = val, hiCpu = hcpu }
246
    where new_hcpu = floor (val * tCpu t)::Int
247
          hcpu = if new_hcpu < 0
248
                 then noLimitInt
249
                 else new_hcpu
250

    
251
-- | Computes the maximum reserved memory for peers from a peer map.
252
computeMaxRes :: P.PeerMap -> P.Elem
253
computeMaxRes = P.maxElem
254

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

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

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

    
287
-- * Update functions
288

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

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

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

    
339
-- | Adds a primary instance.
340
addPri :: Node -> Instance.Instance -> T.OpResult Node
341
addPri t inst =
342
    let iname = Instance.idx inst
343
        new_mem = fMem t - Instance.mem inst
344
        new_dsk = fDsk t - Instance.dsk inst
345
        new_failn1 = new_mem <= rMem t
346
        new_ucpu = uCpu t + Instance.vcpus inst
347
        new_pcpu = fromIntegral new_ucpu / tCpu t
348
        new_dp = fromIntegral new_dsk / tDsk t
349
        l_cpu = mCpu t
350
        new_load = utilLoad t `T.addUtil` Instance.util inst
351
        inst_tags = Instance.tags inst
352
        old_tags = pTags t
353
    in case () of
354
         _ | new_mem <= 0 -> T.OpFail T.FailMem
355
           | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
356
           | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
357
           | l_cpu >= 0 && l_cpu < new_pcpu -> T.OpFail T.FailCPU
358
           | rejectAddTags old_tags inst_tags -> T.OpFail T.FailTags
359
           | otherwise ->
360
               let new_plist = iname:pList t
361
                   new_mp = fromIntegral new_mem / tMem t
362
                   r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
363
                         , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
364
                         , uCpu = new_ucpu, pCpu = new_pcpu
365
                         , utilLoad = new_load
366
                         , pTags = addTags old_tags inst_tags }
367
               in T.OpGood r
368

    
369
-- | Adds a secondary instance.
370
addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
371
addSec t inst pdx =
372
    let iname = Instance.idx inst
373
        old_peers = peers t
374
        old_mem = fMem t
375
        new_dsk = fDsk t - Instance.dsk inst
376
        new_peem = P.find pdx old_peers + Instance.mem inst
377
        new_peers = P.add pdx new_peem old_peers
378
        new_rmem = max (rMem t) new_peem
379
        new_prem = fromIntegral new_rmem / tMem t
380
        new_failn1 = old_mem <= new_rmem
381
        new_dp = fromIntegral new_dsk / tDsk t
382
        old_load = utilLoad t
383
        new_load = old_load { T.dskWeight = T.dskWeight old_load +
384
                                            T.dskWeight (Instance.util inst) }
385
    in case () of
386
         _ | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
387
           | Instance.mem inst >= old_mem -> T.OpFail T.FailMem
388
           | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
389
           | otherwise ->
390
               let new_slist = iname:sList t
391
                   r = t { sList = new_slist, fDsk = new_dsk
392
                         , peers = new_peers, failN1 = new_failn1
393
                         , rMem = new_rmem, pDsk = new_dp
394
                         , pRem = new_prem, utilLoad = new_load }
395
               in T.OpGood r
396

    
397
-- * Stats functions
398

    
399
-- | Computes the amount of available disk on a given node
400
availDisk :: Node -> Int
401
availDisk t =
402
    let _f = fDsk t
403
        _l = loDsk t
404
    in
405
      if _l == noLimitInt
406
      then _f
407
      else if _f < _l
408
           then 0
409
           else _f - _l
410

    
411
-- * Display functions
412

    
413
showField :: Node -> String -> String
414
showField t field =
415
    case field of
416
      "name" -> name t
417
      "status" -> if offline t then "-"
418
                  else if failN1 t then "*" else " "
419
      "tmem" -> printf "%5.0f" $ tMem t
420
      "nmem" -> printf "%5d" $ nMem t
421
      "xmem" -> printf "%5d" $ xMem t
422
      "fmem" -> printf "%5d" $ fMem t
423
      "imem" -> printf "%5d" imem
424
      "rmem" -> printf "%5d" $ rMem t
425
      "amem" -> printf "%5d" $ fMem t - rMem t
426
      "tdsk" -> printf "%5.0f" $ tDsk t / 1024
427
      "fdsk" -> printf "%5d" $ fDsk t `div` 1024
428
      "tcpu" -> printf "%4.0f" $ tCpu t
429
      "ucpu" -> printf "%4d" $ uCpu t
430
      "plist" -> printf "%3d" $ length (pList t)
431
      "slist" -> printf "%3d" $ length (sList t)
432
      "pfmem" -> printf "%6.4f" $ pMem t
433
      "pfdsk" -> printf "%6.4f" $ pDsk t
434
      "rcpu"  -> printf "%5.2f" $ pCpu t
435
      "cload" -> printf "%5.3f" uC
436
      "mload" -> printf "%5.3f" uM
437
      "dload" -> printf "%5.3f" uD
438
      "nload" -> printf "%5.3f" uN
439
      "ptags" -> intercalate "," . map (\(k, v) -> printf "%s=%d" k v) .
440
                 Map.toList $ pTags t
441
      _ -> printf "<unknown field>"
442
    where
443
      T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
444
                  T.dskWeight = uD, T.netWeight = uN } = utilLoad t
445
      imem = truncate (tMem t) - nMem t - xMem t - fMem t
446

    
447
-- | Returns the header and numeric propery of a field
448
showHeader :: String -> (String, Bool)
449
showHeader field =
450
    case field of
451
      "name" -> ("Name", False)
452
      "status" -> ("F", False)
453
      "tmem" -> ("t_mem", True)
454
      "nmem" -> ("n_mem", True)
455
      "xmem" -> ("x_mem", True)
456
      "fmem" -> ("f_mem", True)
457
      "imem" -> ("i_mem", True)
458
      "rmem" -> ("r_mem", True)
459
      "amem" -> ("a_mem", True)
460
      "tdsk" -> ("t_dsk", True)
461
      "fdsk" -> ("f_dsk", True)
462
      "tcpu" -> ("pcpu", True)
463
      "ucpu" -> ("vcpu", True)
464
      "plist" -> ("pri", True)
465
      "slist" -> ("sec", True)
466
      "pfmem" -> ("p_fmem", True)
467
      "pfdsk" -> ("p_fdsk", True)
468
      "rcpu"  -> ("r_cpu", True)
469
      "cload" -> ("lCpu", True)
470
      "mload" -> ("lMem", True)
471
      "dload" -> ("lDsk", True)
472
      "nload" -> ("lNet", True)
473
      "ptags" -> ("PrimaryTags", False)
474
      _ -> ("<unknown field>", False)
475

    
476
-- | String converter for the node list functionality.
477
list :: [String] -> Node -> [String]
478
list fields t = map (showField t) fields
479

    
480

    
481
defaultFields :: [String]
482
defaultFields =
483
    [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
484
    , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "plist", "slist"
485
    , "pfmem", "pfdsk", "rcpu"
486
    , "cload", "mload", "dload", "nload" ]