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
49 -- * Instance (re)location
58 , conflictingPrimaries
71 import qualified Data.Map as Map
72 import qualified Data.Foldable as Foldable
73 import Text.Printf (printf)
75 import qualified Ganeti.HTools.Container as Container
76 import qualified Ganeti.HTools.Instance as Instance
77 import qualified Ganeti.HTools.PeerMap as P
79 import qualified Ganeti.HTools.Types as T
81 -- * Type declarations
84 type TagMap = Map.Map String Int
88 { name :: String -- ^ The node name
89 , alias :: String -- ^ The shortened name (for display purposes)
90 , tMem :: Double -- ^ Total memory (MiB)
91 , nMem :: Int -- ^ Node memory (MiB)
92 , fMem :: Int -- ^ Free memory (MiB)
93 , xMem :: Int -- ^ Unaccounted memory (MiB)
94 , tDsk :: Double -- ^ Total disk space (MiB)
95 , fDsk :: Int -- ^ Free disk space (MiB)
96 , tCpu :: Double -- ^ Total CPU count
97 , uCpu :: Int -- ^ Used VCPU count
98 , pList :: [T.Idx] -- ^ List of primary instance indices
99 , sList :: [T.Idx] -- ^ List of secondary instance indices
100 , idx :: T.Ndx -- ^ Internal index for book-keeping
101 , peers :: P.PeerMap -- ^ Pnode to instance mapping
102 , failN1 :: Bool -- ^ Whether the node has failed n1
103 , rMem :: Int -- ^ Maximum memory needed for failover by
104 -- primaries of this node
105 , pMem :: Double -- ^ Percent of free memory
106 , pDsk :: Double -- ^ Percent of free disk
107 , pRem :: Double -- ^ Percent of reserved memory
108 , pCpu :: Double -- ^ Ratio of virtual to physical CPUs
109 , mDsk :: Double -- ^ Minimum free disk ratio
110 , mCpu :: Double -- ^ Max ratio of virt-to-phys CPUs
111 , loDsk :: Int -- ^ Autocomputed from mDsk low disk
113 , hiCpu :: Int -- ^ Autocomputed from mCpu high cpu
115 , offline :: Bool -- ^ Whether the node should not be used
116 -- for allocations and skipped from score
118 , utilPool :: T.DynUtil -- ^ Total utilisation capacity
119 , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
120 , pTags :: TagMap -- ^ Map of primary instance tags and their count
123 instance T.Element Node where
129 -- | A simple name for the int, node association list.
130 type AssocList = [(T.Ndx, Node)]
132 -- | A simple name for a node map.
133 type List = Container.Container Node
135 -- | A simple name for an allocation element (here just for logistic
137 type AllocElement = (List, Instance.Instance, [Node])
139 -- | Constant node index for a non-moveable instance.
143 -- * Helper functions
145 -- | Add a tag to a tagmap
146 addTag :: TagMap -> String -> TagMap
147 addTag t s = Map.insertWith (+) s 1 t
149 -- | Add multiple tags
150 addTags :: TagMap -> [String] -> TagMap
151 addTags = foldl' addTag
153 -- | Adjust or delete a tag from a tagmap
154 delTag :: TagMap -> String -> TagMap
155 delTag t s = Map.update (\v -> if v > 1
160 -- | Remove multiple tags
161 delTags :: TagMap -> [String] -> TagMap
162 delTags = foldl' delTag
164 -- | Check if we can add a list of tags to a tagmap
165 rejectAddTags :: TagMap -> [String] -> Bool
166 rejectAddTags t = any (`Map.member` t)
168 -- | Check how many primary instances have conflicting tags. The
169 -- algorithm to compute this is to sum the count of all tags, then
170 -- subtract the size of the tag map (since each tag has at least one,
171 -- non-conflicting instance); this is equivalent to summing the
172 -- values in the tag map minus one.
173 conflictingPrimaries :: Node -> Int
174 conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
176 -- * Initialization functions
178 -- | Create a new node.
180 -- The index and the peers maps are empty, and will be need to be
181 -- update later via the 'setIdx' and 'buildPeers' functions.
182 create :: String -> Double -> Int -> Int -> Double
183 -> Int -> Double -> Bool -> Node
184 create name_init mem_t_init mem_n_init mem_f_init
185 dsk_t_init dsk_f_init cpu_t_init offline_init =
186 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
207 , mDsk = T.defReservedDiskRatio
208 , mCpu = T.defVcpuRatio
209 , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
210 , hiCpu = mCpuTohiCpu T.defVcpuRatio cpu_t_init
211 , utilPool = T.baseUtil
212 , utilLoad = T.zeroUtil
216 -- | Conversion formula from mDsk\/tDsk to loDsk
217 mDskToloDsk :: Double -> Double -> Int
218 mDskToloDsk mval tdsk = floor (mval * tdsk)
220 -- | Conversion formula from mCpu\/tCpu to hiCpu
221 mCpuTohiCpu :: Double -> Double -> Int
222 mCpuTohiCpu mval tcpu = floor (mval * tcpu)
224 -- | Changes the index.
226 -- This is used only during the building of the data structures.
227 setIdx :: Node -> T.Ndx -> Node
228 setIdx t i = t {idx = i}
230 -- | Changes the name.
232 -- This is used only during the building of the data structures.
233 setName :: Node -> String -> Node
234 setName t s = t { name = s, alias = s }
236 -- | Changes the alias.
238 -- This is used only during the building of the data structures.
239 setAlias :: Node -> String -> Node
240 setAlias t s = t { alias = s }
242 -- | Sets the offline attribute.
243 setOffline :: Node -> Bool -> Node
244 setOffline t val = t { offline = val }
246 -- | Sets the unnaccounted memory.
247 setXmem :: Node -> Int -> Node
248 setXmem t val = t { xMem = val }
250 -- | Sets the max disk usage ratio
251 setMdsk :: Node -> Double -> Node
252 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
254 -- | Sets the max cpu usage ratio
255 setMcpu :: Node -> Double -> Node
256 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
258 -- | Computes the maximum reserved memory for peers from a peer map.
259 computeMaxRes :: P.PeerMap -> P.Elem
260 computeMaxRes = P.maxElem
262 -- | Builds the peer map for a given node.
263 buildPeers :: Node -> Instance.List -> Node
266 (\i_idx -> let inst = Container.find i_idx il
267 in (Instance.pNode inst, Instance.mem inst))
269 pmap = P.accumArray (+) mdata
270 new_rmem = computeMaxRes pmap
271 new_failN1 = fMem t <= new_rmem
272 new_prem = fromIntegral new_rmem / tMem t
273 in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
275 -- | Assigns an instance to a node as primary and update the used VCPU
276 -- count, utilisation data and tags map.
277 setPri :: Node -> Instance.Instance -> Node
278 setPri t inst = t { pList = Instance.idx inst:pList t
280 , pCpu = fromIntegral new_count / tCpu t
281 , utilLoad = utilLoad t `T.addUtil` Instance.util inst
282 , pTags = addTags (pTags t) (Instance.tags inst)
284 where new_count = uCpu t + Instance.vcpus inst
286 -- | Assigns an instance to a node as secondary without other updates.
287 setSec :: Node -> Instance.Instance -> Node
288 setSec t inst = t { sList = Instance.idx inst:sList t
289 , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
290 T.dskWeight (Instance.util inst) }
292 where old_load = utilLoad t
294 -- * Update functions
296 -- | Sets the free memory.
297 setFmem :: Node -> Int -> Node
299 let new_n1 = new_mem <= rMem t
300 new_mp = fromIntegral new_mem / tMem t
301 in t { fMem = new_mem, failN1 = new_n1, pMem = new_mp }
303 -- | Removes a primary instance.
304 removePri :: Node -> Instance.Instance -> Node
306 let iname = Instance.idx inst
307 new_plist = delete iname (pList t)
308 new_mem = fMem t + Instance.mem inst
309 new_dsk = fDsk t + Instance.dsk inst
310 new_mp = fromIntegral new_mem / tMem t
311 new_dp = fromIntegral new_dsk / tDsk t
312 new_failn1 = new_mem <= rMem t
313 new_ucpu = uCpu t - Instance.vcpus inst
314 new_rcpu = fromIntegral new_ucpu / tCpu t
315 new_load = utilLoad t `T.subUtil` Instance.util inst
316 in t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
317 , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
318 , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
319 , pTags = delTags (pTags t) (Instance.tags inst) }
321 -- | Removes a secondary instance.
322 removeSec :: Node -> Instance.Instance -> Node
324 let iname = Instance.idx inst
325 pnode = Instance.pNode inst
326 new_slist = delete iname (sList t)
327 new_dsk = fDsk t + Instance.dsk inst
329 old_peem = P.find pnode old_peers
330 new_peem = old_peem - Instance.mem inst
331 new_peers = P.add pnode new_peem old_peers
333 new_rmem = if old_peem < old_rmem
335 else computeMaxRes new_peers
336 new_prem = fromIntegral new_rmem / tMem t
337 new_failn1 = fMem t <= new_rmem
338 new_dp = fromIntegral new_dsk / tDsk t
339 old_load = utilLoad t
340 new_load = old_load { T.dskWeight = T.dskWeight old_load -
341 T.dskWeight (Instance.util inst) }
342 in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
343 , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
344 , pRem = new_prem, utilLoad = new_load }
346 -- | Adds a primary instance.
347 addPri :: Node -> Instance.Instance -> T.OpResult Node
349 let iname = Instance.idx inst
350 new_mem = fMem t - Instance.mem inst
351 new_dsk = fDsk t - Instance.dsk inst
352 new_failn1 = new_mem <= rMem t
353 new_ucpu = uCpu t + Instance.vcpus inst
354 new_pcpu = fromIntegral new_ucpu / tCpu t
355 new_dp = fromIntegral new_dsk / tDsk t
357 new_load = utilLoad t `T.addUtil` Instance.util inst
358 inst_tags = Instance.tags inst
361 _ | new_mem <= 0 -> T.OpFail T.FailMem
362 | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
363 | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
364 | l_cpu >= 0 && l_cpu < new_pcpu -> T.OpFail T.FailCPU
365 | rejectAddTags old_tags inst_tags -> T.OpFail T.FailTags
367 let new_plist = iname:pList t
368 new_mp = fromIntegral new_mem / tMem t
369 r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
370 , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
371 , uCpu = new_ucpu, pCpu = new_pcpu
372 , utilLoad = new_load
373 , pTags = addTags old_tags inst_tags }
376 -- | Adds a secondary instance.
377 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
379 let iname = Instance.idx inst
382 new_dsk = fDsk t - Instance.dsk inst
383 new_peem = P.find pdx old_peers + Instance.mem inst
384 new_peers = P.add pdx new_peem old_peers
385 new_rmem = max (rMem t) new_peem
386 new_prem = fromIntegral new_rmem / tMem t
387 new_failn1 = old_mem <= new_rmem
388 new_dp = fromIntegral new_dsk / tDsk t
389 old_load = utilLoad t
390 new_load = old_load { T.dskWeight = T.dskWeight old_load +
391 T.dskWeight (Instance.util inst) }
393 _ | new_dsk <= 0 || mDsk t > new_dp -> T.OpFail T.FailDisk
394 | Instance.mem inst >= old_mem -> T.OpFail T.FailMem
395 | new_failn1 && not (failN1 t) -> T.OpFail T.FailMem
397 let new_slist = iname:sList t
398 r = t { sList = new_slist, fDsk = new_dsk
399 , peers = new_peers, failN1 = new_failn1
400 , rMem = new_rmem, pDsk = new_dp
401 , pRem = new_prem, utilLoad = new_load }
406 -- | Computes the amount of available disk on a given node
407 availDisk :: Node -> Int
415 -- | Computes the amount of available memory on a given node
416 availMem :: Node -> Int
424 -- | Computes the amount of available memory on a given node
425 availCpu :: Node -> Int
433 -- * Display functions
435 showField :: Node -> String -> String
439 "status" -> if offline t then "-"
440 else if failN1 t then "*" else " "
441 "tmem" -> printf "%5.0f" $ tMem t
442 "nmem" -> printf "%5d" $ nMem t
443 "xmem" -> printf "%5d" $ xMem t
444 "fmem" -> printf "%5d" $ fMem t
445 "imem" -> printf "%5d" imem
446 "rmem" -> printf "%5d" $ rMem t
447 "amem" -> printf "%5d" $ fMem t - rMem t
448 "tdsk" -> printf "%5.0f" $ tDsk t / 1024
449 "fdsk" -> printf "%5d" $ fDsk t `div` 1024
450 "tcpu" -> printf "%4.0f" $ tCpu t
451 "ucpu" -> printf "%4d" $ uCpu t
452 "plist" -> printf "%3d" $ length (pList t)
453 "slist" -> printf "%3d" $ length (sList t)
454 "pfmem" -> printf "%6.4f" $ pMem t
455 "pfdsk" -> printf "%6.4f" $ pDsk t
456 "rcpu" -> printf "%5.2f" $ pCpu t
457 "cload" -> printf "%5.3f" uC
458 "mload" -> printf "%5.3f" uM
459 "dload" -> printf "%5.3f" uD
460 "nload" -> printf "%5.3f" uN
461 "ptags" -> intercalate "," . map (\(k, v) -> printf "%s=%d" k v) .
463 _ -> printf "<unknown field>"
465 T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
466 T.dskWeight = uD, T.netWeight = uN } = utilLoad t
467 imem = truncate (tMem t) - nMem t - xMem t - fMem t
469 -- | Returns the header and numeric propery of a field
470 showHeader :: String -> (String, Bool)
473 "name" -> ("Name", False)
474 "status" -> ("F", False)
475 "tmem" -> ("t_mem", True)
476 "nmem" -> ("n_mem", True)
477 "xmem" -> ("x_mem", True)
478 "fmem" -> ("f_mem", True)
479 "imem" -> ("i_mem", True)
480 "rmem" -> ("r_mem", True)
481 "amem" -> ("a_mem", True)
482 "tdsk" -> ("t_dsk", True)
483 "fdsk" -> ("f_dsk", True)
484 "tcpu" -> ("pcpu", True)
485 "ucpu" -> ("vcpu", True)
486 "plist" -> ("pri", True)
487 "slist" -> ("sec", True)
488 "pfmem" -> ("p_fmem", True)
489 "pfdsk" -> ("p_fdsk", True)
490 "rcpu" -> ("r_cpu", True)
491 "cload" -> ("lCpu", True)
492 "mload" -> ("lMem", True)
493 "dload" -> ("lDsk", True)
494 "nload" -> ("lNet", True)
495 "ptags" -> ("PrimaryTags", False)
496 _ -> ("<unknown field>", False)
498 -- | String converter for the node list functionality.
499 list :: [String] -> Node -> [String]
500 list fields t = map (showField t) fields
503 defaultFields :: [String]
505 [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
506 , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "plist", "slist"
507 , "pfmem", "pfdsk", "rcpu"
508 , "cload", "mload", "dload", "nload" ]