1 {-| Module describing a node.
3 All updates are functional (copy-based) and return a new node with
9 Copyright (C) 2009, 2010, 2011, 2012 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
62 , conflictingPrimaries
75 import Data.List hiding (group)
76 import qualified Data.Map as Map
77 import qualified Data.Foldable as Foldable
78 import Data.Ord (comparing)
79 import Text.Printf (printf)
81 import qualified Ganeti.HTools.Container as Container
82 import qualified Ganeti.HTools.Instance as Instance
83 import qualified Ganeti.HTools.PeerMap as P
85 import qualified Ganeti.HTools.Types as T
87 -- * Type declarations
89 -- | The tag map type.
90 type TagMap = Map.Map String Int
94 { name :: String -- ^ The node name
95 , alias :: String -- ^ The shortened name (for display purposes)
96 , tMem :: Double -- ^ Total memory (MiB)
97 , nMem :: Int -- ^ Node memory (MiB)
98 , fMem :: Int -- ^ Free memory (MiB)
99 , xMem :: Int -- ^ Unaccounted memory (MiB)
100 , tDsk :: Double -- ^ Total disk space (MiB)
101 , fDsk :: Int -- ^ Free disk space (MiB)
102 , tCpu :: Double -- ^ Total CPU count
103 , uCpu :: Int -- ^ Used VCPU count
104 , pList :: [T.Idx] -- ^ List of primary instance indices
105 , sList :: [T.Idx] -- ^ List of secondary instance indices
106 , idx :: T.Ndx -- ^ Internal index for book-keeping
107 , peers :: P.PeerMap -- ^ Pnode to instance mapping
108 , failN1 :: Bool -- ^ Whether the node has failed n1
109 , rMem :: Int -- ^ Maximum memory needed for failover by
110 -- primaries of this node
111 , pMem :: Double -- ^ Percent of free memory
112 , pDsk :: Double -- ^ Percent of free disk
113 , pRem :: Double -- ^ Percent of reserved memory
114 , pCpu :: Double -- ^ Ratio of virtual to physical CPUs
115 , mDsk :: Double -- ^ Minimum free disk ratio
116 , mCpu :: Double -- ^ Max ratio of virt-to-phys CPUs
117 , loDsk :: Int -- ^ Autocomputed from mDsk low disk
119 , hiCpu :: Int -- ^ Autocomputed from mCpu high cpu
121 , offline :: Bool -- ^ Whether the node should not be used for
122 -- allocations and skipped from score
124 , utilPool :: T.DynUtil -- ^ Total utilisation capacity
125 , utilLoad :: T.DynUtil -- ^ Sum of instance utilisation
126 , pTags :: TagMap -- ^ Map of primary instance tags and their count
127 , group :: T.Gdx -- ^ The node's group (index)
128 , iPolicy :: T.IPolicy -- ^ The instance policy (of the node's group)
129 } deriving (Show, Read, Eq)
131 instance T.Element Node where
136 allNames n = [name n, alias n]
138 -- | A simple name for the int, node association list.
139 type AssocList = [(T.Ndx, Node)]
141 -- | A simple name for a node map.
142 type List = Container.Container Node
144 -- | A simple name for an allocation element (here just for logistic
146 type AllocElement = (List, Instance.Instance, [Node], T.Score)
148 -- | Constant node index for a non-moveable instance.
152 -- * Helper functions
154 -- | Add a tag to a tagmap.
155 addTag :: TagMap -> String -> TagMap
156 addTag t s = Map.insertWith (+) s 1 t
158 -- | Add multiple tags.
159 addTags :: TagMap -> [String] -> TagMap
160 addTags = foldl' addTag
162 -- | Adjust or delete a tag from a tagmap.
163 delTag :: TagMap -> String -> TagMap
164 delTag t s = Map.update (\v -> if v > 1
169 -- | Remove multiple tags.
170 delTags :: TagMap -> [String] -> TagMap
171 delTags = foldl' delTag
173 -- | Check if we can add a list of tags to a tagmap.
174 rejectAddTags :: TagMap -> [String] -> Bool
175 rejectAddTags t = any (`Map.member` t)
177 -- | Check how many primary instances have conflicting tags. The
178 -- algorithm to compute this is to sum the count of all tags, then
179 -- subtract the size of the tag map (since each tag has at least one,
180 -- non-conflicting instance); this is equivalent to summing the
181 -- values in the tag map minus one.
182 conflictingPrimaries :: Node -> Int
183 conflictingPrimaries (Node { pTags = t }) = Foldable.sum t - Map.size t
185 -- * Initialization functions
187 -- | Create a new node.
189 -- The index and the peers maps are empty, and will be need to be
190 -- update later via the 'setIdx' and 'buildPeers' functions.
191 create :: String -> Double -> Int -> Int -> Double
192 -> Int -> Double -> Bool -> T.Gdx -> Node
193 create name_init mem_t_init mem_n_init mem_f_init
194 dsk_t_init dsk_f_init cpu_t_init offline_init group_init =
195 Node { name = name_init
210 , pMem = fromIntegral mem_f_init / mem_t_init
211 , pDsk = fromIntegral dsk_f_init / dsk_t_init
214 , offline = offline_init
216 , mDsk = T.defReservedDiskRatio
217 , mCpu = T.defVcpuRatio
218 , loDsk = mDskToloDsk T.defReservedDiskRatio dsk_t_init
219 , hiCpu = mCpuTohiCpu T.defVcpuRatio cpu_t_init
220 , utilPool = T.baseUtil
221 , utilLoad = T.zeroUtil
224 , iPolicy = T.defIPolicy
227 -- | Conversion formula from mDsk\/tDsk to loDsk.
228 mDskToloDsk :: Double -> Double -> Int
229 mDskToloDsk mval = floor . (mval *)
231 -- | Conversion formula from mCpu\/tCpu to hiCpu.
232 mCpuTohiCpu :: Double -> Double -> Int
233 mCpuTohiCpu mval = floor . (mval *)
235 -- | Changes the index.
237 -- This is used only during the building of the data structures.
238 setIdx :: Node -> T.Ndx -> Node
239 setIdx t i = t {idx = i}
241 -- | Changes the alias.
243 -- This is used only during the building of the data structures.
244 setAlias :: Node -> String -> Node
245 setAlias t s = t { alias = s }
247 -- | Sets the offline attribute.
248 setOffline :: Node -> Bool -> Node
249 setOffline t val = t { offline = val }
251 -- | Sets the unnaccounted memory.
252 setXmem :: Node -> Int -> Node
253 setXmem t val = t { xMem = val }
255 -- | Sets the max disk usage ratio.
256 setMdsk :: Node -> Double -> Node
257 setMdsk t val = t { mDsk = val, loDsk = mDskToloDsk val (tDsk t) }
259 -- | Sets the max cpu usage ratio.
260 setMcpu :: Node -> Double -> Node
261 setMcpu t val = t { mCpu = val, hiCpu = mCpuTohiCpu val (tCpu t) }
263 -- | Sets the policy.
264 setPolicy :: T.IPolicy -> Node -> Node
265 setPolicy pol node = node { iPolicy = pol }
267 -- | Computes the maximum reserved memory for peers from a peer map.
268 computeMaxRes :: P.PeerMap -> P.Elem
269 computeMaxRes = P.maxElem
271 -- | Builds the peer map for a given node.
272 buildPeers :: Node -> Instance.List -> Node
275 (\i_idx -> let inst = Container.find i_idx il
276 mem = if Instance.usesSecMem inst
277 then Instance.mem inst
279 in (Instance.pNode inst, mem))
281 pmap = P.accumArray (+) mdata
282 new_rmem = computeMaxRes pmap
283 new_failN1 = fMem t <= new_rmem
284 new_prem = fromIntegral new_rmem / tMem t
285 in t {peers=pmap, failN1 = new_failN1, rMem = new_rmem, pRem = new_prem}
287 -- | Assigns an instance to a node as primary and update the used VCPU
288 -- count, utilisation data and tags map.
289 setPri :: Node -> Instance.Instance -> Node
290 setPri t inst = t { pList = Instance.idx inst:pList t
292 , pCpu = fromIntegral new_count / tCpu t
293 , utilLoad = utilLoad t `T.addUtil` Instance.util inst
294 , pTags = addTags (pTags t) (Instance.tags inst)
296 where new_count = Instance.applyIfOnline inst (+ Instance.vcpus inst)
299 -- | Assigns an instance to a node as secondary without other updates.
300 setSec :: Node -> Instance.Instance -> Node
301 setSec t inst = t { sList = Instance.idx inst:sList t
302 , utilLoad = old_load { T.dskWeight = T.dskWeight old_load +
303 T.dskWeight (Instance.util inst) }
305 where old_load = utilLoad t
307 -- * Update functions
309 -- | Sets the free memory.
310 setFmem :: Node -> Int -> Node
312 let new_n1 = new_mem <= rMem t
313 new_mp = fromIntegral new_mem / tMem t
314 in t { fMem = new_mem, failN1 = new_n1, pMem = new_mp }
316 -- | Removes a primary instance.
317 removePri :: Node -> Instance.Instance -> Node
319 let iname = Instance.idx inst
320 new_plist = delete iname (pList t)
321 new_mem = Instance.applyIfOnline inst (+ Instance.mem inst) (fMem t)
322 new_dsk = fDsk t + Instance.dsk inst
323 new_mp = fromIntegral new_mem / tMem t
324 new_dp = fromIntegral new_dsk / tDsk t
325 new_failn1 = new_mem <= rMem t
326 new_ucpu = Instance.applyIfOnline inst
327 (\x -> x - Instance.vcpus inst) (uCpu t)
328 new_rcpu = fromIntegral new_ucpu / tCpu t
329 new_load = utilLoad t `T.subUtil` Instance.util inst
330 in t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
331 , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
332 , uCpu = new_ucpu, pCpu = new_rcpu, utilLoad = new_load
333 , pTags = delTags (pTags t) (Instance.tags inst) }
335 -- | Removes a secondary instance.
336 removeSec :: Node -> Instance.Instance -> Node
338 let iname = Instance.idx inst
340 pnode = Instance.pNode inst
341 new_slist = delete iname (sList t)
342 new_dsk = if Instance.usesLocalStorage inst
343 then cur_dsk + Instance.dsk inst
346 old_peem = P.find pnode old_peers
347 new_peem = if Instance.usesSecMem inst
348 then old_peem - Instance.mem inst
350 new_peers = if new_peem > 0
351 then P.add pnode new_peem old_peers
352 else P.remove pnode old_peers
354 new_rmem = if old_peem < old_rmem
356 else computeMaxRes new_peers
357 new_prem = fromIntegral new_rmem / tMem t
358 new_failn1 = fMem t <= new_rmem
359 new_dp = fromIntegral new_dsk / tDsk t
360 old_load = utilLoad t
361 new_load = old_load { T.dskWeight = T.dskWeight old_load -
362 T.dskWeight (Instance.util inst) }
363 in t { sList = new_slist, fDsk = new_dsk, peers = new_peers
364 , failN1 = new_failn1, rMem = new_rmem, pDsk = new_dp
365 , pRem = new_prem, utilLoad = new_load }
367 -- | Adds a primary instance (basic version).
368 addPri :: Node -> Instance.Instance -> T.OpResult Node
369 addPri = addPriEx False
371 -- | Adds a primary instance (extended version).
372 addPriEx :: Bool -- ^ Whether to override the N+1 and
373 -- other /soft/ checks, useful if we
374 -- come from a worse status
376 -> Node -- ^ The target node
377 -> Instance.Instance -- ^ The instance to add
378 -> T.OpResult Node -- ^ The result of the operation,
379 -- either the new version of the node
381 addPriEx force t inst =
382 let iname = Instance.idx inst
383 uses_disk = Instance.usesLocalStorage inst
385 new_mem = Instance.applyIfOnline inst
386 (\x -> x - Instance.mem inst) (fMem t)
387 new_dsk = if uses_disk
388 then cur_dsk - Instance.dsk inst
390 new_failn1 = new_mem <= rMem t
391 new_ucpu = Instance.applyIfOnline inst (+ Instance.vcpus inst) (uCpu t)
392 new_pcpu = fromIntegral new_ucpu / tCpu t
393 new_dp = fromIntegral new_dsk / tDsk t
395 new_load = utilLoad t `T.addUtil` Instance.util inst
396 inst_tags = Instance.tags inst
400 _ | new_mem <= 0 -> T.OpFail T.FailMem
401 | uses_disk && new_dsk <= 0 -> T.OpFail T.FailDisk
402 | uses_disk && mDsk t > new_dp && strict -> T.OpFail T.FailDisk
403 | new_failn1 && not (failN1 t) && strict -> T.OpFail T.FailMem
404 | l_cpu >= 0 && l_cpu < new_pcpu && strict -> T.OpFail T.FailCPU
405 | rejectAddTags old_tags inst_tags -> T.OpFail T.FailTags
407 let new_plist = iname:pList t
408 new_mp = fromIntegral new_mem / tMem t
409 r = t { pList = new_plist, fMem = new_mem, fDsk = new_dsk
410 , failN1 = new_failn1, pMem = new_mp, pDsk = new_dp
411 , uCpu = new_ucpu, pCpu = new_pcpu
412 , utilLoad = new_load
413 , pTags = addTags old_tags inst_tags }
416 -- | Adds a secondary instance (basic version).
417 addSec :: Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
418 addSec = addSecEx False
420 -- | Adds a secondary instance (extended version).
421 addSecEx :: Bool -> Node -> Instance.Instance -> T.Ndx -> T.OpResult Node
422 addSecEx force t inst pdx =
423 let iname = Instance.idx inst
426 new_dsk = fDsk t - Instance.dsk inst
427 secondary_needed_mem = if Instance.usesSecMem inst
428 then Instance.mem inst
430 new_peem = P.find pdx old_peers + secondary_needed_mem
431 new_peers = P.add pdx new_peem old_peers
432 new_rmem = max (rMem t) new_peem
433 new_prem = fromIntegral new_rmem / tMem t
434 new_failn1 = old_mem <= new_rmem
435 new_dp = fromIntegral new_dsk / tDsk t
436 old_load = utilLoad t
437 new_load = old_load { T.dskWeight = T.dskWeight old_load +
438 T.dskWeight (Instance.util inst) }
441 _ | not (Instance.hasSecondary inst) -> T.OpFail T.FailDisk
442 | new_dsk <= 0 -> T.OpFail T.FailDisk
443 | mDsk t > new_dp && strict -> T.OpFail T.FailDisk
444 | secondary_needed_mem >= old_mem && strict -> T.OpFail T.FailMem
445 | new_failn1 && not (failN1 t) && strict -> T.OpFail T.FailMem
447 let new_slist = iname:sList t
448 r = t { sList = new_slist, fDsk = new_dsk
449 , peers = new_peers, failN1 = new_failn1
450 , rMem = new_rmem, pDsk = new_dp
451 , pRem = new_prem, utilLoad = new_load }
456 -- | Computes the amount of available disk on a given node.
457 availDisk :: Node -> Int
465 -- | Computes the amount of used disk on a given node.
467 iDsk t = truncate (tDsk t) - fDsk t
469 -- | Computes the amount of available memory on a given node.
470 availMem :: Node -> Int
478 -- | Computes the amount of available memory on a given node.
479 availCpu :: Node -> Int
487 -- | The memory used by instances on a given node.
489 iMem t = truncate (tMem t) - nMem t - xMem t - fMem t
491 -- * Display functions
493 -- | Return a field for a given node.
494 showField :: Node -- ^ Node which we're querying
495 -> String -- ^ Field name
496 -> String -- ^ Field value as string
499 "idx" -> printf "%4d" $ idx t
502 "status" -> case () of
506 "tmem" -> printf "%5.0f" $ tMem t
507 "nmem" -> printf "%5d" $ nMem t
508 "xmem" -> printf "%5d" $ xMem t
509 "fmem" -> printf "%5d" $ fMem t
510 "imem" -> printf "%5d" $ iMem t
511 "rmem" -> printf "%5d" $ rMem t
512 "amem" -> printf "%5d" $ fMem t - rMem t
513 "tdsk" -> printf "%5.0f" $ tDsk t / 1024
514 "fdsk" -> printf "%5d" $ fDsk t `div` 1024
515 "tcpu" -> printf "%4.0f" $ tCpu t
516 "ucpu" -> printf "%4d" $ uCpu t
517 "pcnt" -> printf "%3d" $ length (pList t)
518 "scnt" -> printf "%3d" $ length (sList t)
519 "plist" -> show $ pList t
520 "slist" -> show $ sList t
521 "pfmem" -> printf "%6.4f" $ pMem t
522 "pfdsk" -> printf "%6.4f" $ pDsk t
523 "rcpu" -> printf "%5.2f" $ pCpu t
524 "cload" -> printf "%5.3f" uC
525 "mload" -> printf "%5.3f" uM
526 "dload" -> printf "%5.3f" uD
527 "nload" -> printf "%5.3f" uN
528 "ptags" -> intercalate "," . map (uncurry (printf "%s=%d")) .
530 "peermap" -> show $ peers t
533 T.DynUtil { T.cpuWeight = uC, T.memWeight = uM,
534 T.dskWeight = uD, T.netWeight = uN } = utilLoad t
536 -- | Returns the header and numeric propery of a field.
537 showHeader :: String -> (String, Bool)
540 "idx" -> ("Index", True)
541 "name" -> ("Name", False)
542 "fqdn" -> ("Name", False)
543 "status" -> ("F", False)
544 "tmem" -> ("t_mem", True)
545 "nmem" -> ("n_mem", True)
546 "xmem" -> ("x_mem", True)
547 "fmem" -> ("f_mem", True)
548 "imem" -> ("i_mem", True)
549 "rmem" -> ("r_mem", True)
550 "amem" -> ("a_mem", True)
551 "tdsk" -> ("t_dsk", True)
552 "fdsk" -> ("f_dsk", True)
553 "tcpu" -> ("pcpu", True)
554 "ucpu" -> ("vcpu", True)
555 "pcnt" -> ("pcnt", True)
556 "scnt" -> ("scnt", True)
557 "plist" -> ("primaries", True)
558 "slist" -> ("secondaries", True)
559 "pfmem" -> ("p_fmem", True)
560 "pfdsk" -> ("p_fdsk", True)
561 "rcpu" -> ("r_cpu", True)
562 "cload" -> ("lCpu", True)
563 "mload" -> ("lMem", True)
564 "dload" -> ("lDsk", True)
565 "nload" -> ("lNet", True)
566 "ptags" -> ("PrimaryTags", False)
567 "peermap" -> ("PeerMap", False)
568 -- TODO: add node fields (group.uuid, group)
569 _ -> (T.unknownField, False)
571 -- | String converter for the node list functionality.
572 list :: [String] -> Node -> [String]
573 list fields t = map (showField t) fields
575 -- | Constant holding the fields we're displaying by default.
576 defaultFields :: [String]
578 [ "status", "name", "tmem", "nmem", "imem", "xmem", "fmem"
579 , "rmem", "tdsk", "fdsk", "tcpu", "ucpu", "pcnt", "scnt"
580 , "pfmem", "pfdsk", "rcpu"
581 , "cload", "mload", "dload", "nload" ]
583 -- | Split a list of nodes into a list of (node group UUID, list of
584 -- associated nodes).
585 computeGroups :: [Node] -> [(T.Gdx, [Node])]
586 computeGroups nodes =
587 let nodes' = sortBy (comparing group) nodes
588 nodes'' = groupBy (\a b -> group a == group b) nodes'
589 in map (\nl -> (group (head nl), nl)) nodes''