Add a new node/instance field
[ganeti-local] / Ganeti / HTools / Node.hs
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     , setAlias
38     , setOffline
39     , setXmem
40     , setFmem
41     , setPri
42     , setSec
43     , setMdsk
44     , setMcpu
45     -- * Tag maps
46     , addTags
47     , delTags
48     , rejectAddTags
49     -- * Instance (re)location
50     , removePri
51     , removeSec
52     , addPri
53     , addSec
54     -- * Stats
55     , availDisk
56     , availMem
57     , availCpu
58     , conflictingPrimaries
59     -- * Formatting
60     , defaultFields
61     , showHeader
62     , showField
63     , list
64     -- * Misc stuff
65     , AssocList
66     , AllocElement
67     , noSecondary
68     ) where
69
70 import Data.List
71 import qualified Data.Map as Map
72 import qualified Data.Foldable as Foldable
73 import Text.Printf (printf)
74
75 import qualified Ganeti.HTools.Container as Container
76 import qualified Ganeti.HTools.Instance as Instance
77 import qualified Ganeti.HTools.PeerMap as P
78
79 import qualified Ganeti.HTools.Types as T
80
81 -- * Type declarations
82
83 -- | The tag map type
84 type TagMap = Map.Map String Int
85
86 -- | The node type.
87 data Node = Node
88     { name     :: String    -- ^ The node name
89     , alias    :: String    -- ^ The shortened name (for display purposes)
90     , tMem     :: Double    -- ^ Total memory (MiB)
91     , nMem     :: Int       -- ^ Node memory (MiB)
92     , fMem     :: Int       -- ^ Free memory (MiB)
93     , xMem     :: Int       -- ^ Unaccounted memory (MiB)
94     , tDsk     :: Double    -- ^ Total disk space (MiB)
95     , fDsk     :: Int       -- ^ Free disk space (MiB)
96     , tCpu     :: Double    -- ^ Total CPU count
97     , uCpu     :: Int       -- ^ Used VCPU count
98     , pList    :: [T.Idx]   -- ^ List of primary instance indices
99     , sList    :: [T.Idx]   -- ^ List of secondary instance indices
100     , idx      :: T.Ndx     -- ^ Internal index for book-keeping
101     , peers    :: P.PeerMap -- ^ Pnode to instance mapping
102     , failN1   :: Bool      -- ^ Whether the node has failed n1
103     , rMem     :: Int       -- ^ Maximum memory needed for failover by
104                             -- primaries of this node
105     , pMem     :: Double    -- ^ Percent of free memory
106     , pDsk     :: Double    -- ^ Percent of free disk
107     , pRem     :: Double    -- ^ Percent of reserved memory
108     , pCpu     :: Double    -- ^ Ratio of virtual to physical CPUs
109     , mDsk     :: Double    -- ^ Minimum free disk ratio
110     , mCpu     :: Double    -- ^ Max ratio of virt-to-phys CPUs
111     , loDsk    :: Int       -- ^ Autocomputed from mDsk low disk
112                             -- threshold
113     , hiCpu    :: Int       -- ^ Autocomputed from mCpu high cpu
114                             -- threshold
115     , offline  :: Bool      -- ^ Whether the node should not be used
116                             -- for allocations and skipped from score
117                             -- computations
118     , utilPool :: T.DynUtil -- ^ Total utilisation capacity
119     , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
120     , pTags    :: TagMap    -- ^ Map of primary instance tags and their count
121     } deriving (Show)
122
123 instance T.Element Node where
124     nameOf = name
125     idxOf = idx
126     setName = setName
127     setIdx = setIdx
128
129 -- | A simple name for the int, node association list.
130 type AssocList = [(T.Ndx, Node)]
131
132 -- | A simple name for a node map.
133 type List = Container.Container Node
134
135 -- | A simple name for an allocation element (here just for logistic
136 -- reasons)
137 type AllocElement = (List, Instance.Instance, [Node])
138
139 -- | Constant node index for a non-moveable instance.
140 noSecondary :: T.Ndx
141 noSecondary = -1
142
143 -- * Helper functions
144
145 -- | Add a tag to a tagmap
146 addTag :: TagMap -> String -> TagMap
147 addTag t s = Map.insertWith (+) s 1 t
148
149 -- | Add multiple tags
150 addTags :: TagMap -> [String] -> TagMap
151 addTags = foldl' addTag
152
153 -- | Adjust or delete a tag from a tagmap
154 delTag :: TagMap -> String -> TagMap
155 delTag t s = Map.update (\v -> if v > 1
156                                then Just (v-1)
157                                else Nothing)
158              s t
159
160 -- | Remove multiple tags
161 delTags :: TagMap -> [String] -> TagMap
162 delTags = foldl' delTag
163
164 -- | Check if we can add a list of tags to a tagmap
165 rejectAddTags :: TagMap -> [String] -> Bool
166 rejectAddTags t = any (`Map.member` t)
167
168 -- | Check how many primary instances have conflicting tags. The
169 -- algorithm to compute this is to sum the count of all tags, then
170 -- subtract the size of the tag map (since each tag has at least one,
171 -- non-conflicting instance); this is equivalent to summing the
172 -- values in the tag map minus one.
173 conflictingPrimaries :: Node -> Int
174 conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
175
176 -- * Initialization functions
177
178 -- | Create a new node.
179 --
180 -- The index and the peers maps are empty, and will be need to be
181 -- update later via the 'setIdx' and 'buildPeers' functions.
182 create :: String -> Double -> Int -> Int -> Double
183        -> Int -> Double -> Bool -> Node
184 create name_init mem_t_init mem_n_init mem_f_init
185        dsk_t_init dsk_f_init cpu_t_init offline_init =
186     Node { name = name_init
187          , alias = 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 = T.defReservedDiskRatio
208          , mCpu = T.defVcpuRatio
209          , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
210          , hiCpu = mCpuTohiCpu T.defVcpuRatio cpu_t_init
211          , utilPool = T.baseUtil
212          , utilLoad = T.zeroUtil
213          , pTags = Map.empty
214          }
215
216 -- | Conversion formula from mDsk\/tDsk to loDsk
217 mDskToloDsk :: Double -> Double -> Int
218 mDskToloDsk mval tdsk = floor (mval * tdsk)
219
220 -- | Conversion formula from mCpu\/tCpu to hiCpu
221 mCpuTohiCpu :: Double -> Double -> Int
222 mCpuTohiCpu mval tcpu = floor (mval * tcpu)
223
224 -- | Changes the index.
225 --
226 -- This is used only during the building of the data structures.
227 setIdx :: Node -> T.Ndx -> Node
228 setIdx t i = t {idx = i}
229
230 -- | Changes the name.
231 --
232 -- This is used only during the building of the data structures.
233 setName :: Node -> String -> Node
234 setName t s = t { name = s, alias = s }
235
236 -- | Changes the alias.
237 --
238 -- This is used only during the building of the data structures.
239 setAlias :: Node -> String -> Node
240 setAlias t s = t { alias = s }
241
242 -- | Sets the offline attribute.
243 setOffline :: Node -> Bool -> Node
244 setOffline t val = t { offline = val }
245
246 -- | Sets the unnaccounted memory.
247 setXmem :: Node -> Int -> Node
248 setXmem t val = t { xMem = val }
249
250 -- | Sets the max disk usage ratio
251 setMdsk :: Node -> Double -> Node
252 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
253
254 -- | Sets the max cpu usage ratio
255 setMcpu :: Node -> Double -> Node
256 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
257
258 -- | Computes the maximum reserved memory for peers from a peer map.
259 computeMaxRes :: P.PeerMap -> P.Elem
260 computeMaxRes = P.maxElem
261
262 -- | Builds the peer map for a given node.
263 buildPeers :: Node -> Instance.List -> Node
264 buildPeers t il =
265     let mdata = map
266                 (\i_idx -> let inst = Container.find i_idx il
267                            in (Instance.pNode inst, Instance.mem inst))
268                 (sList t)
269         pmap = P.accumArray (+) mdata
270         new_rmem = computeMaxRes pmap
271         new_failN1 = fMem t <= new_rmem
272         new_prem = fromIntegral new_rmem / tMem t
273     in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
274
275 -- | Assigns an instance to a node as primary and update the used VCPU
276 -- count, utilisation data and tags map.
277 setPri :: Node -> Instance.Instance -> Node
278 setPri t inst = t { pList = Instance.idx inst:pList t
279                   , uCpu = new_count
280                   , pCpu = fromIntegral new_count / tCpu t
281                   , utilLoad = utilLoad t `T.addUtil` Instance.util inst
282                   , pTags = addTags (pTags t) (Instance.tags inst)
283                   }
284     where new_count = uCpu t + Instance.vcpus inst
285
286 -- | Assigns an instance to a node as secondary without other updates.
287 setSec :: Node -> Instance.Instance -> Node
288 setSec t inst = t { sList = Instance.idx inst:sList t
289                   , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
290                                           T.dskWeight (Instance.util inst) }
291                   }
292     where old_load = utilLoad t
293
294 -- * Update functions
295
296 -- | Sets the free memory.
297 setFmem :: Node -> Int -> Node
298 setFmem t new_mem =
299     let new_n1 = new_mem <= rMem t
300         new_mp = fromIntegral new_mem / tMem t
301     in t { fMem = new_mem, failN1 = new_n1, pMem = new_mp }
302
303 -- | Removes a primary instance.
304 removePri :: Node -> Instance.Instance -> Node
305 removePri t inst =
306     let iname = Instance.idx inst
307         new_plist = delete iname (pList t)
308         new_mem = fMem t + Instance.mem inst
309         new_dsk = fDsk t + Instance.dsk inst
310         new_mp = fromIntegral new_mem / tMem t
311         new_dp = fromIntegral new_dsk / tDsk t
312         new_failn1 = new_mem <= rMem t
313         new_ucpu = uCpu t - Instance.vcpus inst
314         new_rcpu = fromIntegral new_ucpu / tCpu t
315         new_load = utilLoad t `T.subUtil` Instance.util inst
316     in t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
317          , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
318          , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
319          , pTags = delTags (pTags t) (Instance.tags inst) }
320
321 -- | Removes a secondary instance.
322 removeSec :: Node -> Instance.Instance -> Node
323 removeSec t inst =
324     let iname = Instance.idx inst
325         pnode = Instance.pNode inst
326         new_slist = delete iname (sList t)
327         new_dsk = fDsk t + Instance.dsk inst
328         old_peers = peers t
329         old_peem = P.find pnode old_peers
330         new_peem =  old_peem - Instance.mem inst
331         new_peers = P.add pnode new_peem old_peers
332         old_rmem = rMem t
333         new_rmem = if old_peem < old_rmem
334                    then old_rmem
335                    else computeMaxRes new_peers
336         new_prem = fromIntegral new_rmem / tMem t
337         new_failn1 = fMem t <= new_rmem
338         new_dp = fromIntegral new_dsk / tDsk t
339         old_load = utilLoad t
340         new_load = old_load { T.dskWeight = T.dskWeight old_load -
341                                             T.dskWeight (Instance.util inst) }
342     in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
343          , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
344          , pRem = new_prem, utilLoad = new_load }
345
346 -- | Adds a primary instance.
347 addPri :: Node -> Instance.Instance -> T.OpResult Node
348 addPri t inst =
349     let iname = Instance.idx inst
350         new_mem = fMem t - Instance.mem inst
351         new_dsk = fDsk t - Instance.dsk inst
352         new_failn1 = new_mem <= rMem t
353         new_ucpu = uCpu t + Instance.vcpus inst
354         new_pcpu = fromIntegral new_ucpu / tCpu t
355         new_dp = fromIntegral new_dsk / tDsk t
356         l_cpu = mCpu t
357         new_load = utilLoad t `T.addUtil` Instance.util inst
358         inst_tags = Instance.tags inst
359         old_tags = pTags t
360     in case () of
361          _ | new_mem <= 0 -> T.OpFail T.FailMem
362            | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
363            | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
364            | l_cpu >= 0 && l_cpu < new_pcpu -> T.OpFail T.FailCPU
365            | rejectAddTags old_tags inst_tags -> T.OpFail T.FailTags
366            | otherwise ->
367                let new_plist = iname:pList t
368                    new_mp = fromIntegral new_mem / tMem t
369                    r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
370                          , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
371                          , uCpu = new_ucpu, pCpu = new_pcpu
372                          , utilLoad = new_load
373                          , pTags = addTags old_tags inst_tags }
374                in T.OpGood r
375
376 -- | Adds a secondary instance.
377 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
378 addSec t inst pdx =
379     let iname = Instance.idx inst
380         old_peers = peers t
381         old_mem = fMem t
382         new_dsk = fDsk t - Instance.dsk inst
383         new_peem = P.find pdx old_peers + Instance.mem inst
384         new_peers = P.add pdx new_peem old_peers
385         new_rmem = max (rMem t) new_peem
386         new_prem = fromIntegral new_rmem / tMem t
387         new_failn1 = old_mem <= new_rmem
388         new_dp = fromIntegral new_dsk / tDsk t
389         old_load = utilLoad t
390         new_load = old_load { T.dskWeight = T.dskWeight old_load +
391                                             T.dskWeight (Instance.util inst) }
392     in case () of
393          _ | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
394            | Instance.mem inst >= old_mem -> T.OpFail T.FailMem
395            | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
396            | otherwise ->
397                let new_slist = iname:sList t
398                    r = t { sList = new_slist, fDsk = new_dsk
399                          , peers = new_peers, failN1 = new_failn1
400                          , rMem = new_rmem, pDsk = new_dp
401                          , pRem = new_prem, utilLoad = new_load }
402                in T.OpGood r
403
404 -- * Stats functions
405
406 -- | Computes the amount of available disk on a given node
407 availDisk :: Node -> Int
408 availDisk t =
409     let _f = fDsk t
410         _l = loDsk t
411     in if _f < _l
412        then 0
413        else _f - _l
414
415 -- | Computes the amount of available memory on a given node
416 availMem :: Node -> Int
417 availMem t =
418     let _f = fMem t
419         _l = rMem t
420     in if _f < _l
421        then 0
422        else _f - _l
423
424 -- | Computes the amount of available memory on a given node
425 availCpu :: Node -> Int
426 availCpu t =
427     let _u = uCpu t
428         _l = hiCpu t
429     in if _l >= _u
430        then _l - _u
431        else 0
432
433 -- * Display functions
434
435 showField :: Node -> String -> String
436 showField t field =
437     case field of
438       "name" -> name t
439       "status" -> if offline t then "-"
440                   else if failN1 t then "*" else " "
441       "tmem" -> printf "%5.0f" $ tMem t
442       "nmem" -> printf "%5d" $ nMem t
443       "xmem" -> printf "%5d" $ xMem t
444       "fmem" -> printf "%5d" $ fMem t
445       "imem" -> printf "%5d" imem
446       "rmem" -> printf "%5d" $ rMem t
447       "amem" -> printf "%5d" $ fMem t - rMem t
448       "tdsk" -> printf "%5.0f" $ tDsk t / 1024
449       "fdsk" -> printf "%5d" $ fDsk t `div` 1024
450       "tcpu" -> printf "%4.0f" $ tCpu t
451       "ucpu" -> printf "%4d" $ uCpu t
452       "plist" -> printf "%3d" $ length (pList t)
453       "slist" -> printf "%3d" $ length (sList t)
454       "pfmem" -> printf "%6.4f" $ pMem t
455       "pfdsk" -> printf "%6.4f" $ pDsk t
456       "rcpu"  -> printf "%5.2f" $ pCpu t
457       "cload" -> printf "%5.3f" uC
458       "mload" -> printf "%5.3f" uM
459       "dload" -> printf "%5.3f" uD
460       "nload" -> printf "%5.3f" uN
461       "ptags" -> intercalate "," . map (\(k, v) -> printf "%s=%d" k v) .
462                  Map.toList $ pTags t
463       _ -> printf "<unknown field>"
464     where
465       T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
466                   T.dskWeight = uD, T.netWeight = uN } = utilLoad t
467       imem = truncate (tMem t) - nMem t - xMem t - fMem t
468
469 -- | Returns the header and numeric propery of a field
470 showHeader :: String -> (String, Bool)
471 showHeader field =
472     case field of
473       "name" -> ("Name", False)
474       "status" -> ("F", False)
475       "tmem" -> ("t_mem", True)
476       "nmem" -> ("n_mem", True)
477       "xmem" -> ("x_mem", True)
478       "fmem" -> ("f_mem", True)
479       "imem" -> ("i_mem", True)
480       "rmem" -> ("r_mem", True)
481       "amem" -> ("a_mem", True)
482       "tdsk" -> ("t_dsk", True)
483       "fdsk" -> ("f_dsk", True)
484       "tcpu" -> ("pcpu", True)
485       "ucpu" -> ("vcpu", True)
486       "plist" -> ("pri", True)
487       "slist" -> ("sec", True)
488       "pfmem" -> ("p_fmem", True)
489       "pfdsk" -> ("p_fdsk", True)
490       "rcpu"  -> ("r_cpu", True)
491       "cload" -> ("lCpu", True)
492       "mload" -> ("lMem", True)
493       "dload" -> ("lDsk", True)
494       "nload" -> ("lNet", True)
495       "ptags" -> ("PrimaryTags", False)
496       _ -> ("<unknown field>", False)
497
498 -- | String converter for the node list functionality.
499 list :: [String] -> Node -> [String]
500 list fields t = map (showField t) fields
501
502
503 defaultFields :: [String]
504 defaultFields =
505     [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
506     , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "plist", "slist"
507     , "pfmem", "pfdsk", "rcpu"
508     , "cload", "mload", "dload", "nload" ]