Fix hspace's KM metrics
[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     , 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" ]