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, isAlphaNum)
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
71 -- | The allocation phase we're in (initial, after tiered allocs, or
72 -- after regular allocation).
77 statsData :: [(String, Cluster.CStats -> String)]
78 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
79 , ("INST_CNT", printf "%d" . Cluster.csNinst)
80 , ("MEM_FREE", printf "%d" . Cluster.csFmem)
81 , ("MEM_AVAIL", printf "%d" . Cluster.csAmem)
83 \cs -> printf "%d" (Cluster.csFmem cs - Cluster.csAmem cs))
84 , ("MEM_INST", printf "%d" . Cluster.csImem)
86 \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
88 \cs -> printf "%.8f" (fromIntegral (Cluster.csImem cs) /
90 , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
91 , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
93 \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
94 , ("DSK_INST", printf "%d" . Cluster.csIdsk)
96 \cs -> printf "%.8f" (fromIntegral (Cluster.csIdsk cs) /
98 , ("CPU_INST", printf "%d" . Cluster.csIcpu)
100 \cs -> printf "%.8f" (fromIntegral (Cluster.csIcpu cs) /
102 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
103 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
106 specData :: [(String, RSpec -> String)]
107 specData = [ ("MEM", printf "%d" . rspecMem)
108 , ("DSK", printf "%d" . rspecDsk)
109 , ("CPU", printf "%d" . rspecCpu)
112 clusterData :: [(String, Cluster.CStats -> String)]
113 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
114 , ("DSK", printf "%.0f" . Cluster.csTdsk)
115 , ("CPU", printf "%.0f" . Cluster.csTcpu)
118 -- | Recursively place instances on the cluster until we're out of space
119 iterateDepth :: Node.List
123 -> [Instance.Instance]
124 -> Result (FailStats, Node.List, [Instance.Instance])
125 iterateDepth nl il newinst nreq ixes =
126 let depth = length ixes
127 newname = printf "new-%d" depth::String
128 newidx = length (Container.elems il) + depth
129 newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
130 in case Cluster.tryAlloc nl il newi2 nreq of
132 Ok (errs, _, sols3) ->
134 Nothing -> Ok (Cluster.collapseFailures errs, nl, ixes)
135 Just (_, (xnl, xi, _)) ->
136 iterateDepth xnl il newinst nreq $! (xi:ixes)
138 tieredAlloc :: Node.List
142 -> [Instance.Instance]
143 -> Result (FailStats, Node.List, [Instance.Instance])
144 tieredAlloc nl il newinst nreq ixes =
145 case iterateDepth nl il newinst nreq ixes of
147 Ok (errs, nl', ixes') ->
148 case Instance.shrinkByType newinst . fst . last $
149 sortBy (compare `on` snd) errs of
150 Bad _ -> Ok (errs, nl', ixes')
152 tieredAlloc nl' il newinst' nreq ixes'
155 -- | Function to print stats for a given phase
156 printStats :: Phase -> Cluster.CStats -> [(String, String)]
158 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
159 where kind = case ph of
164 -- | Print final stats and related metrics
165 printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
166 printResults fin_nl num_instances allocs sreason = do
167 let fin_stats = Cluster.totalResources fin_nl
168 fin_instances = num_instances + allocs
170 when (num_instances + allocs /= Cluster.csNinst fin_stats) $
172 hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
173 \ != counted (%d)\n" (num_instances + allocs)
174 (Cluster.csNinst fin_stats)
175 exitWith $ ExitFailure 1
177 printKeys $ printStats PFinal fin_stats
178 printKeys [ ("ALLOC_USAGE", printf "%.8f"
179 ((fromIntegral num_instances::Double) /
180 fromIntegral fin_instances))
181 , ("ALLOC_INSTANCES", printf "%d" allocs)
182 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
184 printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
185 printf "%d" y)) sreason
186 -- this should be the final entry
187 printKeys [("OK", "1")]
189 -- | Ensure a value is quoted if needed
190 ensureQuoted :: String -> String
191 ensureQuoted v = if not (all (\c -> (isAlphaNum c || c == '.')) v)
195 -- | Format a list of key/values as a shell fragment
196 printKeys :: [(String, String)] -> IO ()
197 printKeys = mapM_ (\(k, v) ->
198 printf "HTS_%s=%s\n" (map toUpper k) (ensureQuoted v))
200 printInstance :: Node.List -> Instance.Instance -> [String]
201 printInstance nl i = [ Instance.name i
202 , (Container.nameOf nl $ Instance.pNode i)
203 , (let sdx = Instance.sNode i
204 in if sdx == Node.noSecondary then ""
205 else Container.nameOf nl sdx)
206 , show (Instance.mem i)
207 , show (Instance.dsk i)
208 , show (Instance.vcpus i)
214 cmd_args <- System.getArgs
215 (opts, args) <- parseOpts cmd_args "hspace" options
217 unless (null args) $ do
218 hPutStrLn stderr "Error: this program doesn't take any arguments."
219 exitWith $ ExitFailure 1
221 let verbose = optVerbose opts
222 ispec = optISpec opts
223 shownodes = optShowNodes opts
225 (fixed_nl, il, _, csf) <- loadExternalData opts
227 printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn ispec)) specData
228 printKeys [ ("SPEC_RQN", printf "%d" (optINodes opts)) ]
230 let num_instances = length $ Container.elems il
232 let offline_names = optOffline opts
233 all_nodes = Container.elems fixed_nl
234 all_names = map Node.name all_nodes
235 offline_wrong = filter (flip notElem all_names) offline_names
236 offline_indices = map Node.idx $
237 filter (\n -> elem (Node.name n) offline_names)
239 req_nodes = optINodes opts
243 when (length offline_wrong > 0) $ do
244 hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
245 (commaJoin offline_wrong)
246 exitWith $ ExitFailure 1
248 when (req_nodes /= 1 && req_nodes /= 2) $ do
249 hPrintf stderr "Error: Invalid required nodes (%d)\n" req_nodes
250 exitWith $ ExitFailure 1
252 let nm = Container.map (\n -> if elem (Node.idx n) offline_indices
253 then Node.setOffline n True
255 nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
258 when (length csf > 0 && verbose > 1) $
259 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
261 when (isJust shownodes) $
263 hPutStrLn stderr "Initial cluster status:"
264 hPutStrLn stderr $ Cluster.printNodes nl (fromJust shownodes)
266 let ini_cv = Cluster.compCV nl
267 ini_stats = Cluster.totalResources nl
270 hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
271 ini_cv (Cluster.printStats nl)
273 printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
274 printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
275 printKeys $ printStats PInitial ini_stats
277 let bad_nodes = fst $ Cluster.computeBadItems nl il
278 when (length bad_nodes > 0) $ do
279 -- This is failn1 case, so we print the same final stats and
281 printResults nl num_instances 0 [(FailN1, 1)]
285 let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
286 (rspecCpu spx) "ADMIN_down" [] (-1) (-1)
287 exitifbad val = (case val of
289 hPrintf stderr "Failure: %s\n" s
290 exitWith $ ExitFailure 1
294 let reqinst = iofspec ispec
296 -- Run the tiered allocation, if enabled
298 (case optTieredSpec opts of
301 let tresu = tieredAlloc nl il (iofspec tspec) req_nodes []
302 (_, trl_nl, trl_ixes) <- exitifbad tresu
303 let fin_trl_ixes = reverse trl_ixes
304 ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
305 spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
306 ix_byspec::[(RSpec, Int)]
307 spec_map' = map (\(spec, cnt) ->
308 printf "%d,%d,%d=%d" (rspecMem spec)
309 (rspecDsk spec) (rspecCpu spec) cnt)
312 when (verbose > 1) $ do
313 hPutStrLn stderr "Tiered allocation map"
314 hPutStr stderr . unlines . map ((:) ' ' . intercalate " ") $
315 formatTable (map (printInstance trl_nl) fin_trl_ixes)
316 [False, False, False, True, True, True]
318 when (isJust shownodes) $ do
320 hPutStrLn stderr "Tiered allocation status:"
321 hPutStrLn stderr $ Cluster.printNodes trl_nl (fromJust shownodes)
323 printKeys $ printStats PTiered (Cluster.totalResources trl_nl)
324 printKeys [("TSPEC", intercalate " " spec_map')])
326 -- Run the standard (avg-mode) allocation
328 let result = iterateDepth nl il reqinst req_nodes []
329 (ereason, fin_nl, ixes) <- exitifbad result
331 let allocs = length ixes
332 fin_ixes = reverse ixes
333 sreason = reverse $ sortBy (compare `on` 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 printResults fin_nl num_instances allocs sreason