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
44 -- * Instance (re)location
51 , conflictingPrimaries
65 import qualified Data.Map as Map
66 import qualified Data.Foldable as Foldable
67 import Text.Printf (printf)
69 import qualified Ganeti.HTools.Container as Container
70 import qualified Ganeti.HTools.Instance as Instance
71 import qualified Ganeti.HTools.PeerMap as P
73 import qualified Ganeti.HTools.Types as T
75 -- * Type declarations
78 type TagMap = Map.Map String Int
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
106 , hiCpu :: Int -- ^ Autocomputed from mCpu high cpu
108 , offline :: Bool -- ^ Whether the node should not be used
109 -- for allocations and skipped from score
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
116 instance T.Element Node where
122 -- | A simple name for the int, node association list.
123 type AssocList = [(T.Ndx, Node)]
125 -- | A simple name for a node map.
126 type List = Container.Container Node
128 -- | A simple name for an allocation element (here just for logistic
130 type AllocElement = (List, Instance.Instance, [Node])
132 -- | Constant node index for a non-moveable instance.
140 -- | No limit int value
144 -- * Helper functions
146 -- | Add a tag to a tagmap
147 addTag :: TagMap -> String -> TagMap
148 addTag t s = Map.insertWith (+) s 1 t
150 -- | Add multiple tags
151 addTags :: TagMap -> [String] -> TagMap
152 addTags = foldl' addTag
154 -- | Adjust or delete a tag from a tagmap
155 delTag :: TagMap -> String -> TagMap
156 delTag t s = Map.update (\v -> if v > 1
161 -- | Remove multiple tags
162 delTags :: TagMap -> [String] -> TagMap
163 delTags = foldl' delTag
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)
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
177 -- * Initialization functions
179 -- | Create a new node.
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
201 , pMem = fromIntegral mem_f_init / mem_t_init
202 , pDsk = fromIntegral dsk_f_init / dsk_t_init
205 , offline = offline_init
211 , utilPool = T.baseUtil
212 , utilLoad = T.zeroUtil
216 -- | Changes the index.
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}
222 -- | Changes the name.
224 -- This is used only during the building of the data structures.
225 setName :: Node -> String -> Node
226 setName t s = t {name = s}
228 -- | Sets the offline attribute.
229 setOffline :: Node -> Bool -> Node
230 setOffline t val = t { offline = val }
232 -- | Sets the unnaccounted memory.
233 setXmem :: Node -> Int -> Node
234 setXmem t val = t { xMem = val }
236 -- | Sets the max disk usage ratio
237 setMdsk :: Node -> Double -> Node
238 setMdsk t val = t { mDsk = val,
239 loDsk = if val == noLimit
241 else floor (val * tDsk t) }
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
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
411 -- * Display functions
413 showField :: Node -> String -> String
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) .
441 _ -> printf "<unknown field>"
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
447 -- | Returns the header and numeric propery of a field
448 showHeader :: String -> (String, Bool)
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)
476 -- | String converter for the node list functionality.
477 list :: [String] -> Node -> [String]
478 list fields t = map (showField t) fields
481 defaultFields :: [String]
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" ]