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