1 {-| Cluster space sizing
7 Copyright (C) 2009, 2010 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 module Main (main) where
28 import Data.Char (toUpper, isAlphaNum)
31 import Data.Maybe (isJust, fromJust)
32 import Data.Ord (comparing)
34 import System (exitWith, ExitCode(..))
35 import System.FilePath
37 import qualified System
39 import Text.Printf (printf, hPrintf)
41 import qualified Ganeti.HTools.Container as Container
42 import qualified Ganeti.HTools.Cluster as Cluster
43 import qualified Ganeti.HTools.Node as Node
44 import qualified Ganeti.HTools.Instance as Instance
46 import Ganeti.HTools.Utils
47 import Ganeti.HTools.Types
48 import Ganeti.HTools.CLI
49 import Ganeti.HTools.ExtLoader
50 import Ganeti.HTools.Text (serializeCluster)
52 -- | Options list and functions
75 -- | The allocation phase we're in (initial, after tiered allocs, or
76 -- after regular allocation).
81 statsData :: [(String, Cluster.CStats -> String)]
82 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
83 , ("INST_CNT", printf "%d" . Cluster.csNinst)
84 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
85 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
87 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
88 , ("MEM_INST", printf "%d" . Cluster.csImem)
90 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
92 \cs -> printf "%.8f" (fromIntegral (Cluster.csImem cs) /
94 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
95 , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
97 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
98 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
100 \cs -> printf "%.8f" (fromIntegral (Cluster.csIdsk cs) /
102 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
104 \cs -> printf "%.8f" (fromIntegral (Cluster.csIcpu cs) /
106 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
107 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
110 specData :: [(String, RSpec -> String)]
111 specData = [ ("MEM", printf "%d" . rspecMem)
112 , ("DSK", printf "%d" . rspecDsk)
113 , ("CPU", printf "%d" . rspecCpu)
116 clusterData :: [(String, Cluster.CStats -> String)]
117 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
118 , ("DSK", printf "%.0f" . Cluster.csTdsk)
119 , ("CPU", printf "%.0f" . Cluster.csTcpu)
120 , ("VCPU", printf "%d" . Cluster.csVcpu)
123 -- | Function to print stats for a given phase
124 printStats :: Phase -> Cluster.CStats -> [(String, String)]
126 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
127 where kind = case ph of
132 -- | Print final stats and related metrics
133 printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
134 printResults fin_nl num_instances allocs sreason = do
135 let fin_stats = Cluster.totalResources fin_nl
136 fin_instances = num_instances + allocs
138 when (num_instances + allocs /= Cluster.csNinst fin_stats) $
140 hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
141 \ != counted (%d)\n" (num_instances + allocs)
142 (Cluster.csNinst fin_stats) :: IO ()
143 exitWith $ ExitFailure 1
145 printKeys $ printStats PFinal fin_stats
146 printKeys [ ("ALLOC_USAGE", printf "%.8f"
147 ((fromIntegral num_instances::Double) /
148 fromIntegral fin_instances))
149 , ("ALLOC_INSTANCES", printf "%d" allocs)
150 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
152 printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
153 printf "%d" y)) sreason
154 -- this should be the final entry
155 printKeys [("OK", "1")]
157 formatRSpec :: Double -> String -> RSpec -> [(String, String)]
158 formatRSpec m_cpu s r =
159 [ ("KM_" ++ s ++ "_CPU", show $ rspecCpu r)
160 , ("KM_" ++ s ++ "_NPU", show $ fromIntegral (rspecCpu r) / m_cpu)
161 , ("KM_" ++ s ++ "_MEM", show $ rspecMem r)
162 , ("KM_" ++ s ++ "_DSK", show $ rspecDsk r)
165 printAllocationStats :: Double -> Node.List -> Node.List -> IO ()
166 printAllocationStats m_cpu ini_nl fin_nl = do
167 let ini_stats = Cluster.totalResources ini_nl
168 fin_stats = Cluster.totalResources fin_nl
169 (rini, ralo, runa) = Cluster.computeAllocationDelta ini_stats fin_stats
170 printKeys $ formatRSpec m_cpu "USED" rini
171 printKeys $ formatRSpec m_cpu "POOL"ralo
172 printKeys $ formatRSpec m_cpu "UNAV" runa
174 -- | Ensure a value is quoted if needed
175 ensureQuoted :: String -> String
176 ensureQuoted v = if not (all (\c -> (isAlphaNum c || c == '.')) v)
180 -- | Format a list of key\/values as a shell fragment
181 printKeys :: [(String, String)] -> IO ()
182 printKeys = mapM_ (\(k, v) ->
183 printf "HTS_%s=%s\n" (map toUpper k) (ensureQuoted v))
185 printInstance :: Node.List -> Instance.Instance -> [String]
186 printInstance nl i = [ Instance.name i
187 , Container.nameOf nl $ Instance.pNode i
188 , let sdx = Instance.sNode i
189 in if sdx == Node.noSecondary then ""
190 else Container.nameOf nl sdx
191 , show (Instance.mem i)
192 , show (Instance.dsk i)
193 , show (Instance.vcpus i)
199 cmd_args <- System.getArgs
200 (opts, args) <- parseOpts cmd_args "hspace" options
202 unless (null args) $ do
203 hPutStrLn stderr "Error: this program doesn't take any arguments."
204 exitWith $ ExitFailure 1
206 let verbose = optVerbose opts
207 ispec = optISpec opts
208 shownodes = optShowNodes opts
210 (fixed_nl, il, _) <- loadExternalData opts
212 printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn ispec)) specData
213 printKeys [ ("SPEC_RQN", printf "%d" (optINodes opts)) ]
215 let num_instances = length $ Container.elems il
217 let offline_names = optOffline opts
218 all_nodes = Container.elems fixed_nl
219 all_names = map Node.name all_nodes
220 offline_wrong = filter (`notElem` all_names) offline_names
221 offline_indices = map Node.idx $
223 Node.name n `elem` offline_names ||
224 Node.alias n `elem` offline_names)
226 req_nodes = optINodes opts
230 when (length offline_wrong > 0) $ do
231 hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
232 (commaJoin offline_wrong) :: IO ()
233 exitWith $ ExitFailure 1
235 when (req_nodes /= 1 && req_nodes /= 2) $ do
236 hPrintf stderr "Error: Invalid required nodes (%d)\n"
238 exitWith $ ExitFailure 1
240 let nm = Container.map (\n -> if Node.idx n `elem` offline_indices
241 then Node.setOffline n True
243 nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
245 csf = commonSuffix fixed_nl il
247 when (length csf > 0 && verbose > 1) $
248 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
250 when (isJust shownodes) $
252 hPutStrLn stderr "Initial cluster status:"
253 hPutStrLn stderr $ Cluster.printNodes nl (fromJust shownodes)
255 let ini_cv = Cluster.compCV nl
256 ini_stats = Cluster.totalResources nl
259 hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
260 ini_cv (Cluster.printStats nl)
262 printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
263 printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
264 printKeys $ printStats PInitial ini_stats
266 let bad_nodes = fst $ Cluster.computeBadItems nl il
267 stop_allocation = length bad_nodes > 0
268 result_noalloc = ([(FailN1, 1)]::FailStats, nl, il, [])
271 let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
272 (rspecCpu spx) "running" [] (-1) (-1)
273 exitifbad val = (case val of
275 hPrintf stderr "Failure: %s\n" s :: IO ()
276 exitWith $ ExitFailure 1
280 let reqinst = iofspec ispec
282 -- Run the tiered allocation, if enabled
284 (case optTieredSpec opts of
287 (_, trl_nl, trl_il, trl_ixes) <-
289 then return result_noalloc
290 else exitifbad (Cluster.tieredAlloc nl il (iofspec tspec)
292 let fin_trl_ixes = reverse trl_ixes
293 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
294 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
295 ix_byspec::[(RSpec, Int)]
296 spec_map' = map (\(spec, cnt) ->
297 printf "%d,%d,%d=%d" (rspecMem spec)
298 (rspecDsk spec) (rspecCpu spec) cnt)
301 when (verbose > 1) $ do
302 hPutStrLn stderr "Tiered allocation map"
303 hPutStr stderr . unlines . map ((:) ' ' . intercalate " ") $
304 formatTable (map (printInstance trl_nl) fin_trl_ixes)
305 [False, False, False, True, True, True]
307 when (isJust shownodes) $ do
309 hPutStrLn stderr "Tiered allocation status:"
310 hPutStrLn stderr $ Cluster.printNodes trl_nl (fromJust shownodes)
312 when (isJust $ optSaveCluster opts) $
314 let out_path = (fromJust $ optSaveCluster opts) <.> "tiered"
315 adata = serializeCluster trl_nl trl_il
316 writeFile out_path adata
317 hPrintf stderr "The cluster state after tiered allocation\
318 \ has been written to file '%s'\n"
320 printKeys $ printStats PTiered (Cluster.totalResources trl_nl)
321 printKeys [("TSPEC", intercalate " " spec_map')]
322 printAllocationStats m_cpu nl trl_nl)
324 -- Run the standard (avg-mode) allocation
326 (ereason, fin_nl, fin_il, ixes) <-
328 then return result_noalloc
329 else exitifbad (Cluster.iterateAlloc nl il reqinst req_nodes [])
331 let allocs = length ixes
332 fin_ixes = reverse ixes
333 sreason = reverse $ sortBy (comparing snd) ereason
335 when (verbose > 1) $ do
336 hPutStrLn stderr "Instance map"
337 hPutStr stderr . unlines . map ((:) ' ' . intercalate " ") $
338 formatTable (map (printInstance fin_nl) fin_ixes)
339 [False, False, False, True, True, True]
340 when (isJust shownodes) $
343 hPutStrLn stderr "Final cluster status:"
344 hPutStrLn stderr $ Cluster.printNodes fin_nl (fromJust shownodes)
346 when (isJust $ optSaveCluster opts) $
348 let out_path = (fromJust $ optSaveCluster opts) <.> "alloc"
349 adata = serializeCluster fin_nl fin_il
350 writeFile out_path adata
351 hPrintf stderr "The cluster state after standard allocation\
352 \ has been written to file '%s'\n"
355 printResults fin_nl num_instances allocs sreason