Stop modifying names for internal computations
[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     , 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     , addSec
53     -- * Stats
54     , availDisk
55     , availMem
56     , availCpu
57     , conflictingPrimaries
58     -- * Formatting
59     , defaultFields
60     , showHeader
61     , showField
62     , list
63     -- * Misc stuff
64     , AssocList
65     , AllocElement
66     , noSecondary
67     ) where
68
69 import Data.List
70 import qualified Data.Map as Map
71 import qualified Data.Foldable as Foldable
72 import Text.Printf (printf)
73
74 import qualified Ganeti.HTools.Container as Container
75 import qualified Ganeti.HTools.Instance as Instance
76 import qualified Ganeti.HTools.PeerMap as P
77
78 import qualified Ganeti.HTools.Types as T
79
80 -- * Type declarations
81
82 -- | The tag map type
83 type TagMap = Map.Map String Int
84
85 -- | The node type.
86 data Node = Node
87     { name     :: String    -- ^ The node name
88     , alias    :: String    -- ^ The shortened name (for display purposes)
89     , tMem     :: Double    -- ^ Total memory (MiB)
90     , nMem     :: Int       -- ^ Node memory (MiB)
91     , fMem     :: Int       -- ^ Free memory (MiB)
92     , xMem     :: Int       -- ^ Unaccounted memory (MiB)
93     , tDsk     :: Double    -- ^ Total disk space (MiB)
94     , fDsk     :: Int       -- ^ Free disk space (MiB)
95     , tCpu     :: Double    -- ^ Total CPU count
96     , uCpu     :: Int       -- ^ Used VCPU count
97     , pList    :: [T.Idx]   -- ^ List of primary instance indices
98     , sList    :: [T.Idx]   -- ^ List of secondary instance indices
99     , idx      :: T.Ndx     -- ^ Internal index for book-keeping
100     , peers    :: P.PeerMap -- ^ Pnode to instance mapping
101     , failN1   :: Bool      -- ^ Whether the node has failed n1
102     , rMem     :: Int       -- ^ Maximum memory needed for failover by
103                             -- primaries of this node
104     , pMem     :: Double    -- ^ Percent of free memory
105     , pDsk     :: Double    -- ^ Percent of free disk
106     , pRem     :: Double    -- ^ Percent of reserved memory
107     , pCpu     :: Double    -- ^ Ratio of virtual to physical CPUs
108     , mDsk     :: Double    -- ^ Minimum free disk ratio
109     , mCpu     :: Double    -- ^ Max ratio of virt-to-phys CPUs
110     , loDsk    :: Int       -- ^ Autocomputed from mDsk low disk
111                             -- threshold
112     , hiCpu    :: Int       -- ^ Autocomputed from mCpu high cpu
113                             -- threshold
114     , offline  :: Bool      -- ^ Whether the node should not be used
115                             -- for allocations and skipped from score
116                             -- computations
117     , utilPool :: T.DynUtil -- ^ Total utilisation capacity
118     , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
119     , pTags    :: TagMap    -- ^ Map of primary instance tags and their count
120     } deriving (Show)
121
122 instance T.Element Node where
123     nameOf = name
124     idxOf = idx
125     setAlias = setAlias
126     setIdx = setIdx
127
128 -- | A simple name for the int, node association list.
129 type AssocList = [(T.Ndx, Node)]
130
131 -- | A simple name for a node map.
132 type List = Container.Container Node
133
134 -- | A simple name for an allocation element (here just for logistic
135 -- reasons)
136 type AllocElement = (List, Instance.Instance, [Node])
137
138 -- | Constant node index for a non-moveable instance.
139 noSecondary :: T.Ndx
140 noSecondary = -1
141
142 -- * Helper functions
143
144 -- | Add a tag to a tagmap
145 addTag :: TagMap -> String -> TagMap
146 addTag t s = Map.insertWith (+) s 1 t
147
148 -- | Add multiple tags
149 addTags :: TagMap -> [String] -> TagMap
150 addTags = foldl' addTag
151
152 -- | Adjust or delete a tag from a tagmap
153 delTag :: TagMap -> String -> TagMap
154 delTag t s = Map.update (\v -> if v > 1
155                                then Just (v-1)
156                                else Nothing)
157              s t
158
159 -- | Remove multiple tags
160 delTags :: TagMap -> [String] -> TagMap
161 delTags = foldl' delTag
162
163 -- | Check if we can add a list of tags to a tagmap
164 rejectAddTags :: TagMap -> [String] -> Bool
165 rejectAddTags t = any (`Map.member` t)
166
167 -- | Check how many primary instances have conflicting tags. The
168 -- algorithm to compute this is to sum the count of all tags, then
169 -- subtract the size of the tag map (since each tag has at least one,
170 -- non-conflicting instance); this is equivalent to summing the
171 -- values in the tag map minus one.
172 conflictingPrimaries :: Node -> Int
173 conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
174
175 -- * Initialization functions
176
177 -- | Create a new node.
178 --
179 -- The index and the peers maps are empty, and will be need to be
180 -- update later via the 'setIdx' and 'buildPeers' functions.
181 create :: String -> Double -> Int -> Int -> Double
182        -> Int -> Double -> Bool -> Node
183 create name_init mem_t_init mem_n_init mem_f_init
184        dsk_t_init dsk_f_init cpu_t_init offline_init =
185     Node { name = name_init
186          , alias = name_init
187          , tMem = mem_t_init
188          , nMem = mem_n_init
189          , fMem = mem_f_init
190          , tDsk = dsk_t_init
191          , fDsk = dsk_f_init
192          , tCpu = cpu_t_init
193          , uCpu = 0
194          , pList = []
195          , sList = []
196          , failN1 = True
197          , idx = -1
198          , peers = P.empty
199          , rMem = 0
200          , pMem = fromIntegral mem_f_init / mem_t_init
201          , pDsk = fromIntegral dsk_f_init / dsk_t_init
202          , pRem = 0
203          , pCpu = 0
204          , offline = offline_init
205          , xMem = 0
206          , mDsk = T.defReservedDiskRatio
207          , mCpu = T.defVcpuRatio
208          , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
209          , hiCpu = mCpuTohiCpu T.defVcpuRatio cpu_t_init
210          , utilPool = T.baseUtil
211          , utilLoad = T.zeroUtil
212          , pTags = Map.empty
213          }
214
215 -- | Conversion formula from mDsk\/tDsk to loDsk
216 mDskToloDsk :: Double -> Double -> Int
217 mDskToloDsk mval tdsk = floor (mval * tdsk)
218
219 -- | Conversion formula from mCpu\/tCpu to hiCpu
220 mCpuTohiCpu :: Double -> Double -> Int
221 mCpuTohiCpu mval tcpu = floor (mval * tcpu)
222
223 -- | Changes the index.
224 --
225 -- This is used only during the building of the data structures.
226 setIdx :: Node -> T.Ndx -> Node
227 setIdx t i = t {idx = i}
228
229 -- | Changes the alias.
230 --
231 -- This is used only during the building of the data structures.
232 setAlias :: Node -> String -> Node
233 setAlias t s = t { alias = s }
234
235 -- | Sets the offline attribute.
236 setOffline :: Node -> Bool -> Node
237 setOffline t val = t { offline = val }
238
239 -- | Sets the unnaccounted memory.
240 setXmem :: Node -> Int -> Node
241 setXmem t val = t { xMem = val }
242
243 -- | Sets the max disk usage ratio
244 setMdsk :: Node -> Double -> Node
245 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
246
247 -- | Sets the max cpu usage ratio
248 setMcpu :: Node -> Double -> Node
249 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
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 if _f < _l
405        then 0
406        else _f - _l
407
408 -- | Computes the amount of available memory on a given node
409 availMem :: Node -> Int
410 availMem t =
411     let _f = fMem t
412         _l = rMem t
413     in if _f < _l
414        then 0
415        else _f - _l
416
417 -- | Computes the amount of available memory on a given node
418 availCpu :: Node -> Int
419 availCpu t =
420     let _u = uCpu t
421         _l = hiCpu t
422     in if _l >= _u
423        then _l - _u
424        else 0
425
426 -- * Display functions
427
428 showField :: Node -> String -> String
429 showField t field =
430     case field of
431       "name" -> alias t
432       "fqdn" -> name t
433       "status" -> if offline t then "-"
434                   else if failN1 t then "*" else " "
435       "tmem" -> printf "%5.0f" $ tMem t
436       "nmem" -> printf "%5d" $ nMem t
437       "xmem" -> printf "%5d" $ xMem t
438       "fmem" -> printf "%5d" $ fMem t
439       "imem" -> printf "%5d" imem
440       "rmem" -> printf "%5d" $ rMem t
441       "amem" -> printf "%5d" $ fMem t - rMem t
442       "tdsk" -> printf "%5.0f" $ tDsk t / 1024
443       "fdsk" -> printf "%5d" $ fDsk t `div` 1024
444       "tcpu" -> printf "%4.0f" $ tCpu t
445       "ucpu" -> printf "%4d" $ uCpu t
446       "plist" -> printf "%3d" $ length (pList t)
447       "slist" -> printf "%3d" $ length (sList t)
448       "pfmem" -> printf "%6.4f" $ pMem t
449       "pfdsk" -> printf "%6.4f" $ pDsk t
450       "rcpu"  -> printf "%5.2f" $ pCpu t
451       "cload" -> printf "%5.3f" uC
452       "mload" -> printf "%5.3f" uM
453       "dload" -> printf "%5.3f" uD
454       "nload" -> printf "%5.3f" uN
455       "ptags" -> intercalate "," . map (\(k, v) -> printf "%s=%d" k v) .
456                  Map.toList $ pTags t
457       _ -> printf "<unknown field>"
458     where
459       T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
460                   T.dskWeight = uD, T.netWeight = uN } = utilLoad t
461       imem = truncate (tMem t) - nMem t - xMem t - fMem t
462
463 -- | Returns the header and numeric propery of a field
464 showHeader :: String -> (String, Bool)
465 showHeader field =
466     case field of
467       "name" -> ("Name", False)
468       "fqdn" -> ("Name", False)
469       "status" -> ("F", False)
470       "tmem" -> ("t_mem", True)
471       "nmem" -> ("n_mem", True)
472       "xmem" -> ("x_mem", True)
473       "fmem" -> ("f_mem", True)
474       "imem" -> ("i_mem", True)
475       "rmem" -> ("r_mem", True)
476       "amem" -> ("a_mem", True)
477       "tdsk" -> ("t_dsk", True)
478       "fdsk" -> ("f_dsk", True)
479       "tcpu" -> ("pcpu", True)
480       "ucpu" -> ("vcpu", True)
481       "plist" -> ("pri", True)
482       "slist" -> ("sec", True)
483       "pfmem" -> ("p_fmem", True)
484       "pfdsk" -> ("p_fdsk", True)
485       "rcpu"  -> ("r_cpu", True)
486       "cload" -> ("lCpu", True)
487       "mload" -> ("lMem", True)
488       "dload" -> ("lDsk", True)
489       "nload" -> ("lNet", True)
490       "ptags" -> ("PrimaryTags", False)
491       _ -> ("<unknown field>", False)
492
493 -- | String converter for the node list functionality.
494 list :: [String] -> Node -> [String]
495 list fields t = map (showField t) fields
496
497
498 defaultFields :: [String]
499 defaultFields =
500     [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
501     , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "plist", "slist"
502     , "pfmem", "pfdsk", "rcpu"
503     , "cload", "mload", "dload", "nload" ]