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)
34 import qualified System
36 import Text.Printf (printf, hPrintf)
38 import qualified Ganeti.HTools.Container as Container
39 import qualified Ganeti.HTools.Cluster as Cluster
40 import qualified Ganeti.HTools.Node as Node
41 import qualified Ganeti.HTools.Instance as Instance
43 import Ganeti.HTools.Utils
44 import Ganeti.HTools.Types
45 import Ganeti.HTools.CLI
46 import Ganeti.HTools.ExtLoader
48 -- | Options list and functions
70 data Phase = PInitial | PFinal
72 statsData :: [(String, Cluster.CStats -> String)]
73 statsData = [ ("SCORE", printf "%.8f" . Cluster.cs_score)
74 , ("INST_CNT", printf "%d" . Cluster.cs_ninst)
75 , ("MEM_FREE", printf "%d" . Cluster.cs_fmem)
76 , ("MEM_AVAIL", printf "%d" . Cluster.cs_amem)
78 \cs -> printf "%d" (Cluster.cs_fmem cs - Cluster.cs_amem cs))
79 , ("MEM_INST", printf "%d" . Cluster.cs_imem)
81 \cs -> printf "%d" (Cluster.cs_xmem cs + Cluster.cs_nmem cs))
83 \cs -> printf "%.8f" (fromIntegral (Cluster.cs_imem cs) /
85 , ("DSK_FREE", printf "%d" . Cluster.cs_fdsk)
86 , ("DSK_AVAIL", printf "%d ". Cluster.cs_adsk)
88 \cs -> printf "%d" (Cluster.cs_fdsk cs - Cluster.cs_adsk cs))
89 , ("DSK_INST", printf "%d" . Cluster.cs_idsk)
91 \cs -> printf "%.8f" (fromIntegral (Cluster.cs_idsk cs) /
93 , ("CPU_INST", printf "%d" . Cluster.cs_icpu)
95 \cs -> printf "%.8f" (fromIntegral (Cluster.cs_icpu cs) /
97 , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.cs_mmem)
98 , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.cs_mdsk)
101 specData :: [(String, Options -> String)]
102 specData = [ ("MEM", printf "%d" . optIMem)
103 , ("DSK", printf "%d" . optIDsk)
104 , ("CPU", printf "%d" . optIVCPUs)
105 , ("RQN", printf "%d" . optINodes)
108 clusterData :: [(String, Cluster.CStats -> String)]
109 clusterData = [ ("MEM", printf "%.0f" . Cluster.cs_tmem)
110 , ("DSK", printf "%.0f" . Cluster.cs_tdsk)
111 , ("CPU", printf "%.0f" . Cluster.cs_tcpu)
114 -- | Recursively place instances on the cluster until we're out of space
115 iterateDepth :: Node.List
119 -> [Instance.Instance]
120 -> Result (FailStats, Node.List, [Instance.Instance])
121 iterateDepth nl il newinst nreq ixes =
122 let depth = length ixes
123 newname = printf "new-%d" depth::String
124 newidx = length (Container.elems il) + depth
125 newi2 = Instance.setIdx (Instance.setName newinst newname) newidx
126 in case Cluster.tryAlloc nl il newi2 nreq of
128 Ok (errs, _, sols3) ->
130 Nothing -> Ok (Cluster.collapseFailures errs, nl, ixes)
131 Just (_, (xnl, xi, _)) ->
132 iterateDepth xnl il newinst nreq $! (xi:ixes)
134 -- | Function to print stats for a given phase
135 printStats :: Phase -> Cluster.CStats -> [(String, String)]
137 map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
138 where kind = case ph of
142 -- | Print final stats and related metrics
143 printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
144 printResults fin_nl num_instances allocs sreason = do
145 let fin_stats = Cluster.totalResources fin_nl
146 fin_instances = num_instances + allocs
148 when (num_instances + allocs /= Cluster.cs_ninst fin_stats) $
150 hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
151 \ != counted (%d)\n" (num_instances + allocs)
152 (Cluster.cs_ninst fin_stats)
153 exitWith $ ExitFailure 1
155 printKeys $ printStats PFinal fin_stats
156 printKeys [ ("ALLOC_USAGE", printf "%.8f"
157 ((fromIntegral num_instances::Double) /
158 fromIntegral fin_instances))
159 , ("ALLOC_INSTANCES", printf "%d" allocs)
160 , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
162 printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
163 printf "%d" y)) sreason
164 -- this should be the final entry
165 printKeys [("OK", "1")]
167 -- | Format a list of key/values as a shell fragment
168 printKeys :: [(String, String)] -> IO ()
169 printKeys = mapM_ (\(k, v) -> printf "HTS_%s=%s\n" (map toUpper k) v)
174 cmd_args <- System.getArgs
175 (opts, args) <- parseOpts cmd_args "hspace" options
177 unless (null args) $ do
178 hPutStrLn stderr "Error: this program doesn't take any arguments."
179 exitWith $ ExitFailure 1
181 let verbose = optVerbose opts
183 (fixed_nl, il, csf) <- loadExternalData opts
185 printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn opts)) specData
187 let num_instances = length $ Container.elems il
189 let offline_names = optOffline opts
190 all_nodes = Container.elems fixed_nl
191 all_names = map Node.name all_nodes
192 offline_wrong = filter (flip notElem all_names) offline_names
193 offline_indices = map Node.idx $
194 filter (\n -> elem (Node.name n) offline_names)
196 req_nodes = optINodes opts
200 when (length offline_wrong > 0) $ do
201 hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
202 (commaJoin offline_wrong)
203 exitWith $ ExitFailure 1
205 when (req_nodes /= 1 && req_nodes /= 2) $ do
206 hPrintf stderr "Error: Invalid required nodes (%d)\n" req_nodes
207 exitWith $ ExitFailure 1
209 let nm = Container.map (\n -> if elem (Node.idx n) offline_indices
210 then Node.setOffline n True
212 nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
215 when (length csf > 0 && verbose > 1) $
216 hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
218 when (optShowNodes opts) $
220 hPutStrLn stderr "Initial cluster status:"
221 hPutStrLn stderr $ Cluster.printNodes nl
223 let ini_cv = Cluster.compCV nl
224 ini_stats = Cluster.totalResources nl
227 hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
228 ini_cv (Cluster.printStats nl)
230 printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
231 printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
232 printKeys $ printStats PInitial ini_stats
234 let bad_nodes = fst $ Cluster.computeBadItems nl il
235 when (length bad_nodes > 0) $ do
236 -- This is failn1 case, so we print the same final stats and
238 printResults nl num_instances 0 [(FailN1, 1)]
241 let nmlen = Container.maxNameLen nl
242 newinst = Instance.create "new" (optIMem opts) (optIDsk opts)
243 (optIVCPUs opts) "ADMIN_down" (-1) (-1)
245 let result = iterateDepth nl il newinst req_nodes []
246 (ereason, fin_nl, ixes) <- (case result of
248 hPrintf stderr "Failure: %s\n" s
249 exitWith $ ExitFailure 1
251 let allocs = length ixes
252 fin_ixes = reverse ixes
253 ix_namelen = maximum . map (length . Instance.name) $ fin_ixes
254 sreason = reverse $ sortBy (compare `on` snd) ereason
257 hPutStr stderr . unlines $
258 map (\i -> printf "Inst: %*s %-*s %-*s"
259 ix_namelen (Instance.name i)
260 nmlen (Container.nameOf fin_nl $ Instance.pnode i)
261 nmlen (let sdx = Instance.snode i
262 in if sdx == Node.noSecondary then ""
263 else Container.nameOf fin_nl sdx)
266 when (optShowNodes opts) $
269 hPutStrLn stderr "Final cluster status:"
270 hPutStrLn stderr $ Cluster.printNodes fin_nl
272 printResults fin_nl num_instances allocs sreason