1 {-| Module describing a node.
3 All updates are functional (copy-based) and return a new node with
9 Copyright (C) 2009 Google Inc.
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.
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.
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
28 module Ganeti.HTools.Node
33 -- ** Finalization after data loading
48 -- * Instance (re)location
57 , conflictingPrimaries
70 import qualified Data.Map as Map
71 import qualified Data.Foldable as Foldable
72 import Text.Printf (printf)
74 import qualified Ganeti.HTools.Container as Container
75 import qualified Ganeti.HTools.Instance as Instance
76 import qualified Ganeti.HTools.PeerMap as P
78 import qualified Ganeti.HTools.Types as T
80 -- * Type declarations
83 type TagMap = Map.Map String Int
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
112 , hiCpu :: Int -- ^ Autocomputed from mCpu high cpu
114 , offline :: Bool -- ^ Whether the node should not be used
115 -- for allocations and skipped from score
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
122 instance T.Element Node where
128 -- | A simple name for the int, node association list.
129 type AssocList = [(T.Ndx, Node)]
131 -- | A simple name for a node map.
132 type List = Container.Container Node
134 -- | A simple name for an allocation element (here just for logistic
136 type AllocElement = (List, Instance.Instance, [Node])
138 -- | Constant node index for a non-moveable instance.
142 -- * Helper functions
144 -- | Add a tag to a tagmap
145 addTag :: TagMap -> String -> TagMap
146 addTag t s = Map.insertWith (+) s 1 t
148 -- | Add multiple tags
149 addTags :: TagMap -> [String] -> TagMap
150 addTags = foldl' addTag
152 -- | Adjust or delete a tag from a tagmap
153 delTag :: TagMap -> String -> TagMap
154 delTag t s = Map.update (\v -> if v > 1
159 -- | Remove multiple tags
160 delTags :: TagMap -> [String] -> TagMap
161 delTags = foldl' delTag
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)
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
175 -- * Initialization functions
177 -- | Create a new node.
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
200 , pMem = fromIntegral mem_f_init / mem_t_init
201 , pDsk = fromIntegral dsk_f_init / dsk_t_init
204 , offline = offline_init
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
215 -- | Conversion formula from mDsk\/tDsk to loDsk
216 mDskToloDsk :: Double -> Double -> Int
217 mDskToloDsk mval tdsk = floor (mval * tdsk)
219 -- | Conversion formula from mCpu\/tCpu to hiCpu
220 mCpuTohiCpu :: Double -> Double -> Int
221 mCpuTohiCpu mval tcpu = floor (mval * tcpu)
223 -- | Changes the index.
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}
229 -- | Changes the alias.
231 -- This is used only during the building of the data structures.
232 setAlias :: Node -> String -> Node
233 setAlias t s = t { alias = s }
235 -- | Sets the offline attribute.
236 setOffline :: Node -> Bool -> Node
237 setOffline t val = t { offline = val }
239 -- | Sets the unnaccounted memory.
240 setXmem :: Node -> Int -> Node
241 setXmem t val = t { xMem = val }
243 -- | Sets the max disk usage ratio
244 setMdsk :: Node -> Double -> Node
245 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
247 -- | Sets the max cpu usage ratio
248 setMcpu :: Node -> Double -> Node
249 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
251 -- | Computes the maximum reserved memory for peers from a peer map.
252 computeMaxRes :: P.PeerMap -> P.Elem
253 computeMaxRes = P.maxElem
255 -- | Builds the peer map for a given node.
256 buildPeers :: Node -> Instance.List -> Node
259 (\i_idx -> let inst = Container.find i_idx il
260 in (Instance.pNode inst, Instance.mem inst))
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}
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
273 , pCpu = fromIntegral new_count / tCpu t
274 , utilLoad = utilLoad t `T.addUtil` Instance.util inst
275 , pTags = addTags (pTags t) (Instance.tags inst)
277 where new_count = uCpu t + Instance.vcpus inst
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) }
285 where old_load = utilLoad t
287 -- * Update functions
289 -- | Sets the free memory.
290 setFmem :: Node -> Int -> Node
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 }
296 -- | Removes a primary instance.
297 removePri :: Node -> Instance.Instance -> Node
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) }
314 -- | Removes a secondary instance.
315 removeSec :: Node -> Instance.Instance -> Node
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
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
326 new_rmem = if old_peem < 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 }
339 -- | Adds a primary instance.
340 addPri :: Node -> Instance.Instance -> T.OpResult Node
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
350 new_load = utilLoad t `T.addUtil` Instance.util inst
351 inst_tags = Instance.tags inst
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
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 }
369 -- | Adds a secondary instance.
370 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
372 let iname = Instance.idx inst
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) }
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
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 }
399 -- | Computes the amount of available disk on a given node
400 availDisk :: Node -> Int
408 -- | Computes the amount of available memory on a given node
409 availMem :: Node -> Int
417 -- | Computes the amount of available memory on a given node
418 availCpu :: Node -> Int
426 -- * Display functions
428 showField :: Node -> String -> String
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) .
457 _ -> printf "<unknown field>"
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
463 -- | Returns the header and numeric propery of a field
464 showHeader :: String -> (String, Bool)
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)
493 -- | String converter for the node list functionality.
494 list :: [String] -> Node -> [String]
495 list fields t = map (showField t) fields
498 defaultFields :: [String]
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" ]