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