1 {-| Cluster space sizing
7 Copyright (C) 2009 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)
31 import Data.Maybe (isJust, fromJust)
35 import qualified System
37 import Text.Printf (printf, hPrintf)
39 import qualified Ganeti.HTools.Container as Container
40 import qualified Ganeti.HTools.Cluster as Cluster
41 import qualified Ganeti.HTools.Node as Node
42 import qualified Ganeti.HTools.Instance as Instance
44 import Ganeti.HTools.Utils
45 import Ganeti.HTools.Types
46 import Ganeti.HTools.CLI
47 import Ganeti.HTools.ExtLoader
49 -- | Options list and functions
72 -- | The allocation phase we're in (initial, after tiered allocs, or
73 -- after regular allocation).
78 statsData :: [(String, Cluster.CStats -> String)]
79 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
80 , ("INST_CNT", printf "%d" . Cluster.csNinst)
81 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
82 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
84 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
85 , ("MEM_INST", printf "%d" . Cluster.csImem)
87 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
89 \cs -> printf "%.8f" (fromIntegral (Cluster.csImem cs) /
91 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
92 , ("DSK_AVAIL", printf "%d ". Cluster.csAdsk)
94 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
95 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
97 \cs -> printf "%.8f" (fromIntegral (Cluster.csIdsk cs) /
99 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
101 \cs -> printf "%.8f" (fromIntegral (Cluster.csIcpu cs) /
103 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
104 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
107 specData :: [(String, RSpec -> String)]
108 specData = [ ("MEM", printf "%d" . rspecMem)
109 , ("DSK", printf "%d" . rspecDsk)
110 , ("CPU", printf "%d" . rspecCpu)
113 clusterData :: [(String, Cluster.CStats -> String)]
114 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
115 , ("DSK", printf "%.0f" . Cluster.csTdsk)
116 , ("CPU", printf "%.0f" . Cluster.csTcpu)
119 -- | Recursively place instances on the cluster until we're out of space
120 iterateDepth :: Node.List
124 -> [Instance.Instance]
125 -> Result (FailStats, Node.List, [Instance.Instance])
126 iterateDepth nl il newinst nreq ixes =
127 let depth = length ixes
128 newname = printf "new-%d" depth::String
129 newidx = length (Container.elems il) + depth
130 newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
131 in case Cluster.tryAlloc nl il newi2 nreq of
133 Ok (errs, _, sols3) ->
135 Nothing -> Ok (Cluster.collapseFailures errs, nl, ixes)
136 Just (_, (xnl, xi, _)) ->
137 iterateDepth xnl il newinst nreq $! (xi:ixes)
139 tieredAlloc :: Node.List
143 -> [Instance.Instance]
144 -> Result (FailStats, Node.List, [Instance.Instance])
145 tieredAlloc nl il newinst nreq ixes =
146 case iterateDepth nl il newinst nreq ixes of
148 Ok (errs, nl', ixes') ->
149 case Instance.shrinkByType newinst . fst . last $
150 sortBy (compare `on` snd) errs of
151 Bad _ -> Ok (errs, nl', ixes')
153 tieredAlloc nl' il newinst' nreq ixes'
156 -- | Function to print stats for a given phase
157 printStats :: Phase -> Cluster.CStats -> [(String, String)]
159 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
160 where kind = case ph of
165 -- | Print final stats and related metrics
166 printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
167 printResults fin_nl num_instances allocs sreason = do
168 let fin_stats = Cluster.totalResources fin_nl
169 fin_instances = num_instances + allocs
171 when (num_instances + allocs /= Cluster.csNinst fin_stats) $
173 hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
174 \ != counted (%d)\n" (num_instances + allocs)
175 (Cluster.csNinst fin_stats)
176 exitWith $ ExitFailure 1
178 printKeys $ printStats PFinal fin_stats
179 printKeys [ ("ALLOC_USAGE", printf "%.8f"
180 ((fromIntegral num_instances::Double) /
181 fromIntegral fin_instances))
182 , ("ALLOC_INSTANCES", printf "%d" allocs)
183 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
185 printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
186 printf "%d" y)) sreason
187 -- this should be the final entry
188 printKeys [("OK", "1")]
190 -- | Format a list of key/values as a shell fragment
191 printKeys :: [(String, String)] -> IO ()
192 printKeys = mapM_ (\(k, v) -> printf "HTS_%s=%s\n" (map toUpper k) v)
194 printInstance :: Node.List -> Instance.Instance -> [String]
195 printInstance nl i = [ Instance.name i
196 , (Container.nameOf nl $ Instance.pNode i)
197 , (let sdx = Instance.sNode i
198 in if sdx == Node.noSecondary then ""
199 else Container.nameOf nl sdx)
200 , show (Instance.mem i)
201 , show (Instance.dsk i)
202 , show (Instance.vcpus i)
208 cmd_args <- System.getArgs
209 (opts, args) <- parseOpts cmd_args "hspace" options
211 unless (null args) $ do
212 hPutStrLn stderr "Error: this program doesn't take any arguments."
213 exitWith $ ExitFailure 1
215 let verbose = optVerbose opts
216 ispec = optISpec opts
217 shownodes = optShowNodes opts
219 (fixed_nl, il, csf) <- loadExternalData opts
221 printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn ispec)) specData
222 printKeys [ ("SPEC_RQN", printf "%d" (optINodes opts)) ]
224 let num_instances = length $ Container.elems il
226 let offline_names = optOffline opts
227 all_nodes = Container.elems fixed_nl
228 all_names = map Node.name all_nodes
229 offline_wrong = filter (flip notElem all_names) offline_names
230 offline_indices = map Node.idx $
231 filter (\n -> elem (Node.name n) offline_names)
233 req_nodes = optINodes opts
237 when (length offline_wrong > 0) $ do
238 hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
239 (commaJoin offline_wrong)
240 exitWith $ ExitFailure 1
242 when (req_nodes /= 1 && req_nodes /= 2) $ do
243 hPrintf stderr "Error: Invalid required nodes (%d)\n" req_nodes
244 exitWith $ ExitFailure 1
246 let nm = Container.map (\n -> if elem (Node.idx n) offline_indices
247 then Node.setOffline n True
249 nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
252 when (length csf > 0 && verbose > 1) $
253 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
255 when (isJust shownodes) $
257 hPutStrLn stderr "Initial cluster status:"
258 hPutStrLn stderr $ Cluster.printNodes nl (fromJust shownodes)
260 let ini_cv = Cluster.compCV nl
261 ini_stats = Cluster.totalResources nl
264 hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
265 ini_cv (Cluster.printStats nl)
267 printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
268 printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
269 printKeys $ printStats PInitial ini_stats
271 let bad_nodes = fst $ Cluster.computeBadItems nl il
272 when (length bad_nodes > 0) $ do
273 -- This is failn1 case, so we print the same final stats and
275 printResults nl num_instances 0 [(FailN1, 1)]
279 let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
280 (rspecCpu spx) "ADMIN_down" [] (-1) (-1)
281 exitifbad val = (case val of
283 hPrintf stderr "Failure: %s\n" s
284 exitWith $ ExitFailure 1
288 let reqinst = iofspec ispec
290 -- Run the tiered allocation, if enabled
292 (case optTieredSpec opts of
295 let tresu = tieredAlloc nl il (iofspec tspec) req_nodes []
296 (_, trl_nl, trl_ixes) <- exitifbad tresu
297 let fin_trl_ixes = reverse trl_ixes
298 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
299 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
300 ix_byspec::[(RSpec, Int)]
301 spec_map' = map (\(spec, cnt) ->
302 printf "%d,%d,%d=%d" (rspecMem spec)
303 (rspecDsk spec) (rspecCpu spec) cnt)
306 when (verbose > 1) $ do
307 hPutStrLn stderr "Tiered allocation map"
308 hPutStr stderr . unlines . map ((:) ' ' . intercalate " ") $
309 formatTable (map (printInstance trl_nl) fin_trl_ixes)
310 [False, False, False, True, True, True]
312 when (isJust shownodes) $ do
314 hPutStrLn stderr "Tiered allocation status:"
315 hPutStrLn stderr $ Cluster.printNodes trl_nl (fromJust shownodes)
317 printKeys $ printStats PTiered (Cluster.totalResources trl_nl)
318 printKeys [("TSPEC", intercalate " " spec_map')])
320 -- Run the standard (avg-mode) allocation
322 let result = iterateDepth nl il reqinst req_nodes []
323 (ereason, fin_nl, ixes) <- exitifbad result
325 let allocs = length ixes
326 fin_ixes = reverse ixes
327 sreason = reverse $ sortBy (compare `on` snd) ereason
329 when (verbose > 1) $ do
330 hPutStrLn stderr "Instance map"
331 hPutStr stderr . unlines . map ((:) ' ' . intercalate " ") $
332 formatTable (map (printInstance fin_nl) fin_ixes)
333 [False, False, False, True, True, True]
334 when (isJust shownodes) $
337 hPutStrLn stderr "Final cluster status:"
338 hPutStrLn stderr $ Cluster.printNodes fin_nl (fromJust shownodes)
340 printResults fin_nl num_instances allocs sreason