1 {-| Module describing a node.
3 All updates are functional (copy-based) and return a new node with
9 Copyright (C) 2009, 2010 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
59 , conflictingPrimaries
72 import qualified Data.Map as Map
73 import qualified Data.Foldable as Foldable
74 import Text.Printf (printf)
76 import qualified Ganeti.HTools.Container as Container
77 import qualified Ganeti.HTools.Instance as Instance
78 import qualified Ganeti.HTools.PeerMap as P
80 import qualified Ganeti.HTools.Types as T
82 -- * Type declarations
85 type TagMap = Map.Map String Int
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
114 , hiCpu :: Int -- ^ Autocomputed from mCpu high cpu
116 , offline :: Bool -- ^ Whether the node should not be used
117 -- for allocations and skipped from score
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)
124 instance T.Element Node where
129 allNames n = [name n, alias n]
131 -- | A simple name for the int, node association list.
132 type AssocList = [(T.Ndx, Node)]
134 -- | A simple name for a node map.
135 type List = Container.Container Node
137 -- | A simple name for an allocation element (here just for logistic
139 type AllocElement = (List, Instance.Instance, [Node])
141 -- | Constant node index for a non-moveable instance.
145 -- * Helper functions
147 -- | Add a tag to a tagmap
148 addTag :: TagMap -> String -> TagMap
149 addTag t s = Map.insertWith (+) s 1 t
151 -- | Add multiple tags
152 addTags :: TagMap -> [String] -> TagMap
153 addTags = foldl' addTag
155 -- | Adjust or delete a tag from a tagmap
156 delTag :: TagMap -> String -> TagMap
157 delTag t s = Map.update (\v -> if v > 1
162 -- | Remove multiple tags
163 delTags :: TagMap -> [String] -> TagMap
164 delTags = foldl' delTag
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)
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
178 -- * Initialization functions
180 -- | Create a new node.
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
203 , pMem = fromIntegral mem_f_init / mem_t_init
204 , pDsk = fromIntegral dsk_f_init / dsk_t_init
207 , offline = offline_init
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
218 -- | Conversion formula from mDsk\/tDsk to loDsk
219 mDskToloDsk :: Double -> Double -> Int
220 mDskToloDsk mval tdsk = floor (mval * tdsk)
222 -- | Conversion formula from mCpu\/tCpu to hiCpu
223 mCpuTohiCpu :: Double -> Double -> Int
224 mCpuTohiCpu mval tcpu = floor (mval * tcpu)
226 -- | Changes the index.
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}
232 -- | Changes the alias.
234 -- This is used only during the building of the data structures.
235 setAlias :: Node -> String -> Node
236 setAlias t s = t { alias = s }
238 -- | Sets the offline attribute.
239 setOffline :: Node -> Bool -> Node
240 setOffline t val = t { offline = val }
242 -- | Sets the unnaccounted memory.
243 setXmem :: Node -> Int -> Node
244 setXmem t val = t { xMem = val }
246 -- | Sets the max disk usage ratio
247 setMdsk :: Node -> Double -> Node
248 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
250 -- | Sets the max cpu usage ratio
251 setMcpu :: Node -> Double -> Node
252 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
254 -- | Computes the maximum reserved memory for peers from a peer map.
255 computeMaxRes :: P.PeerMap -> P.Elem
256 computeMaxRes = P.maxElem
258 -- | Builds the peer map for a given node.
259 buildPeers :: Node -> Instance.List -> Node
262 (\i_idx -> let inst = Container.find i_idx il
263 in (Instance.pNode inst, Instance.mem inst))
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}
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
276 , pCpu = fromIntegral new_count / tCpu t
277 , utilLoad = utilLoad t `T.addUtil` Instance.util inst
278 , pTags = addTags (pTags t) (Instance.tags inst)
280 where new_count = uCpu t + Instance.vcpus inst
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) }
288 where old_load = utilLoad t
290 -- * Update functions
292 -- | Sets the free memory.
293 setFmem :: Node -> Int -> Node
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 }
299 -- | Removes a primary instance.
300 removePri :: Node -> Instance.Instance -> Node
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) }
317 -- | Removes a secondary instance.
318 removeSec :: Node -> Instance.Instance -> Node
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
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
331 new_rmem = if old_peem < 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 }
344 -- | Adds a primary instance (basic version).
345 addPri :: Node -> Instance.Instance -> T.OpResult Node
346 addPri = addPriEx False
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
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
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
367 new_load = utilLoad t `T.addUtil` Instance.util inst
368 inst_tags = Instance.tags inst
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
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 }
388 -- | Adds a secondary instance (basic version).
389 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
390 addSec = addSecEx False
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
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) }
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
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 }
424 -- | Computes the amount of available disk on a given node
425 availDisk :: Node -> Int
433 -- | Computes the amount of available memory on a given node
434 availMem :: Node -> Int
442 -- | Computes the amount of available memory on a given node
443 availCpu :: Node -> Int
451 -- * Display functions
453 showField :: Node -> String -> String
456 "idx" -> printf "%4d" $ idx 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) .
485 "peermap" -> show $ peers t
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
492 -- | Returns the header and numeric propery of a field
493 showHeader :: String -> (String, Bool)
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)
526 -- | String converter for the node list functionality.
527 list :: [String] -> Node -> [String]
528 list fields t = map (showField t) fields
531 defaultFields :: [String]
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" ]