1 {-| Solver for N+1 cluster errors
5 module Main (main) where
12 import System.Console.GetOpt
13 import qualified System
15 import Text.Printf (printf)
17 import qualified Container
18 import qualified Cluster
22 -- | Command line options structure.
23 data Options = Options
24 { optShowNodes :: Bool
26 , optNodef :: FilePath
27 , optInstf :: FilePath
32 -- | Default values for the command line options.
33 defaultOptions :: Options
34 defaultOptions = Options
35 { optShowNodes = False
38 , optInstf = "instances"
43 {- | Start computing the solution at the given depth and recurse until
44 we find a valid solution or we exceed the maximum depth.
47 iterateDepth :: Cluster.Table
48 -> Int -- ^ Current round
49 -> Int -- ^ Max rounds
51 iterateDepth ini_tbl cur_round max_rounds =
52 let Cluster.Table _ ini_il ini_cv ini_plc = ini_tbl
53 all_inst = Container.elems ini_il
54 fin_tbl = Cluster.checkMove ini_tbl all_inst
55 (Cluster.Table _ _ fin_cv fin_plc) = fin_tbl
56 ini_plc_len = length ini_plc
57 fin_plc_len = length fin_plc
58 allowed_next = (max_rounds < 0 || cur_round < max_rounds)
61 printf " - round %d: " cur_round
64 if fin_cv < ini_cv then
65 if not allowed_next then
66 printf "%.8f, %d moves (stopping due to round limit)\n"
68 (fin_plc_len - ini_plc_len)
70 printf "%.8f, %d moves\n" fin_cv
71 (fin_plc_len - ini_plc_len)
73 "no improvement, stopping\n"
76 (if fin_cv < ini_cv then -- this round made success, try deeper
78 then iterateDepth fin_tbl (cur_round + 1) max_rounds
79 -- don't go deeper, but return the better solution
84 -- | Options list and functions
85 options :: [OptDescr (Options -> Options)]
87 [ Option ['p'] ["print-nodes"]
88 (NoArg (\ opts -> opts { optShowNodes = True }))
89 "print the final node list"
90 , Option ['C'] ["print-commands"]
91 (NoArg (\ opts -> opts { optShowCmds = True }))
92 "print the ganeti command list for reaching the solution"
93 , Option ['n'] ["nodes"]
94 (ReqArg (\ f opts -> opts { optNodef = f }) "FILE")
96 , Option ['i'] ["instances"]
97 (ReqArg (\ f opts -> opts { optInstf = f }) "FILE")
98 "the instance list FILE"
99 , Option ['m'] ["master"]
100 (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS")
101 "collect data via RAPI at the given ADDRESS"
102 , Option ['r'] ["max-rounds"]
103 (ReqArg (\ i opts -> opts { optMaxRounds = (read i)::Int }) "N")
104 "do not run for more than R rounds(useful for very unbalanced clusters)"
107 -- | Command line parser, using the 'options' structure.
108 parseOpts :: [String] -> IO (Options, [String])
110 case getOpt Permute options argv of
112 return (foldl (flip id) defaultOptions o, n)
114 ioError (userError (concat errs ++ usageInfo header options))
115 where header = "Usage: hbal [OPTION...]"
117 -- | Get a Right result or print the error and exit
118 readData :: (String -> IO (Either String String)) -> String -> IO String
119 readData fn host = do
123 putStrLn $ fromLeft nd
124 exitWith $ ExitFailure 1
125 return $ fromRight nd
130 cmd_args <- System.getArgs
131 (opts, _) <- parseOpts cmd_args
133 let (node_data, inst_data) =
134 case optMaster opts of
135 "" -> (readFile $ optNodef opts,
136 readFile $ optInstf opts)
137 host -> (readData getNodes host,
138 readData getInstances host)
140 (nl, il, ktn, kti) <- liftM2 Cluster.loadData node_data inst_data
143 printf "Loaded %d nodes, %d instances\n"
146 let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
147 printf "Initial check done: %d bad nodes, %d bad instances.\n"
148 (length bad_nodes) (length bad_instances)
150 when (length bad_nodes > 0) $ do
151 putStrLn "Cluster is not N+1 happy, please fix N+1 first. Exiting."
152 exitWith $ ExitFailure 1
154 when (optShowNodes opts) $
156 putStrLn "Initial cluster status:"
157 putStrLn $ Cluster.printNodes ktn nl
159 let ini_cv = Cluster.compCV nl
160 ini_tbl = Cluster.Table nl il ini_cv []
161 printf "Initial coefficients: overall %.8f, %s\n"
162 ini_cv (Cluster.printStats nl)
164 putStrLn "Trying to minimize the CV..."
165 fin_tbl <- iterateDepth ini_tbl 1 (optMaxRounds opts)
166 let (Cluster.Table fin_nl _ fin_cv fin_plc) = fin_tbl
167 ord_plc = reverse fin_plc
168 printf "Final coefficients: overall %.8f, %s\n"
170 (Cluster.printStats fin_nl)
172 printf "Solution length=%d\n" (length ord_plc)
174 let (sol_strs, cmd_strs) = Cluster.printSolution il ktn kti ord_plc
175 putStr $ unlines $ sol_strs
176 when (optShowCmds opts) $
179 putStrLn "Commands to run to reach the above solution:"
180 putStr $ unlines $ map (" echo gnt-instance " ++) $ concat cmd_strs
181 when (optShowNodes opts) $
183 let (orig_mem, orig_disk) = Cluster.totalResources nl
184 (final_mem, final_disk) = Cluster.totalResources fin_nl
186 putStrLn "Final cluster status:"
187 putStrLn $ Cluster.printNodes ktn fin_nl
188 printf "Original: mem=%d disk=%d\n" orig_mem orig_disk
189 printf "Final: mem=%d disk=%d\n" final_mem final_disk