{-| Solver for N+1 cluster errors -} module Main (main) where import Data.List import Data.Function import Monad import System import System.IO import System.Console.GetOpt import qualified System import Text.Printf (printf) import qualified Ganeti.HTools.Container as Container import qualified Ganeti.HTools.Instance as Instance import qualified Ganeti.HTools.Cluster as Cluster import qualified Ganeti.HTools.CLI as CLI import Ganeti.HTools.Types -- | Command line options structure. data Options = Options { optShowNodes :: Bool , optShowCmds :: Bool , optNodef :: FilePath , optNodeSet :: Bool -- ^ The nodes have been set by options , optInstf :: FilePath -- ^ Path to the instances file , optInstSet :: Bool -- ^ The insts have been set by options , optMinDepth :: Int , optMaxRemovals :: Int , optMinDelta :: Int , optMaxDelta :: Int , optMaster :: String , optShowVer :: Bool -- ^ Just show the program version , optShowHelp :: Bool -- ^ Just show the help } deriving Show instance CLI.CLIOptions Options where showVersion = optShowVer showHelp = optShowHelp instance CLI.EToolOptions Options where nodeFile = optNodef nodeSet = optNodeSet instFile = optInstf instSet = optInstSet masterName = optMaster silent _ = False -- | Default values for the command line options. defaultOptions :: Options defaultOptions = Options { optShowNodes = False , optShowCmds = False , optNodef = "nodes" , optNodeSet = False , optInstf = "instances" , optInstSet = False , optMinDepth = 1 , optMaxRemovals = -1 , optMinDelta = 0 , optMaxDelta = -1 , optMaster = "" , optShowVer = False , optShowHelp = False } {- | Start computing the solution at the given depth and recurse until we find a valid solution or we exceed the maximum depth. -} iterateDepth :: NodeList -> [Instance.Instance] -> Int -> Int -> Int -> Int -> IO (Maybe Cluster.Solution) iterateDepth nl bad_instances depth max_removals min_delta max_delta = let max_depth = length bad_instances sol = Cluster.computeSolution nl bad_instances depth max_removals min_delta max_delta in do printf "%d " depth hFlush stdout case sol `seq` sol of Nothing -> if depth > max_depth then return Nothing else iterateDepth nl bad_instances (depth + 1) max_removals min_delta max_delta _ -> return sol -- | Options list and functions options :: [OptDescr (Options -> Options)] options = [ Option ['p'] ["print-nodes"] (NoArg (\ opts -> opts { optShowNodes = True })) "print the final node list" , Option ['C'] ["print-commands"] (NoArg (\ opts -> opts { optShowCmds = True })) "print the ganeti command list for reaching the solution" , Option ['n'] ["nodes"] (ReqArg (\ f opts -> opts { optNodef = f, optNodeSet = True }) "FILE") "the node list FILE" , Option ['i'] ["instances"] (ReqArg (\ f opts -> opts { optInstf = f, optInstSet = True }) "FILE") "the instance list FILE" , Option ['d'] ["depth"] (ReqArg (\ i opts -> opts { optMinDepth = (read i)::Int }) "D") "start computing the solution at depth D" , Option ['r'] ["max-removals"] (ReqArg (\ i opts -> opts { optMaxRemovals = (read i)::Int }) "R") "do not process more than R removal sets (useful for high depths)" , Option ['L'] ["max-delta"] (ReqArg (\ i opts -> opts { optMaxDelta = (read i)::Int }) "L") "refuse solutions with delta higher than L" , Option ['l'] ["min-delta"] (ReqArg (\ i opts -> opts { optMinDelta = (read i)::Int }) "L") "return once a solution with delta L or lower has been found" , Option ['m'] ["master"] (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS") "collect data via RAPI at the given ADDRESS" , Option ['V'] ["version"] (NoArg (\ opts -> opts { optShowVer = True})) "show the version of the program" , Option ['h'] ["help"] (NoArg (\ opts -> opts { optShowHelp = True})) "show help" ] -- | Main function. main :: IO () main = do cmd_args <- System.getArgs (opts, args) <- CLI.parseOpts cmd_args "hn1" options defaultOptions unless (null args) $ do hPutStrLn stderr "Error: this program doesn't take any arguments." exitWith $ ExitFailure 1 (nl, il, csf) <- CLI.loadExternalData opts printf "Loaded %d nodes, %d instances\n" (Container.size nl) (Container.size il) when (length csf > 0) $ do printf "Note: Stripping common suffix of '%s' from names\n" csf let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il min_depth = optMinDepth opts printf "Initial check done: %d bad nodes, %d bad instances.\n" (length bad_nodes) (length bad_instances) when (null bad_instances) $ do putStrLn "Happy time! Cluster is fine, no need to burn CPU." exitWith ExitSuccess when (length bad_instances < min_depth) $ do printf "Error: depth %d is higher than the number of bad instances.\n" min_depth exitWith $ ExitFailure 2 let ini_cv = Cluster.compCV nl printf "Initial coefficients: overall %.8f, %s\n" ini_cv (Cluster.printStats nl) putStr "Computing solution: depth " hFlush stdout result <- iterateDepth nl bad_instances min_depth (optMaxRemovals opts) (optMinDelta opts) (optMaxDelta opts) let (min_d, solution) = case result of Just (Cluster.Solution a b) -> (a, reverse b) Nothing -> (-1, []) when (min_d == -1) $ do putStrLn "failed. Try to run with higher depth." exitWith $ ExitFailure 1 printf "found.\n" let ns = Cluster.applySolution nl il solution fin_cv = Cluster.compCV ns printf "Final coefficients: overall %.8f, %s\n" fin_cv (Cluster.printStats ns) printf "Solution (delta=%d):\n" $! min_d let (sol_strs, cmd_strs) = Cluster.printSolution ns il solution putStr $ unlines $ sol_strs when (optShowCmds opts) $ do putStrLn "" putStrLn "Commands to run to reach the above solution:" putStr . Cluster.formatCmds . reverse $ cmd_strs when (optShowNodes opts) $ do let (orig_mem, orig_disk) = Cluster.totalResources nl (final_mem, final_disk) = Cluster.totalResources ns putStrLn "" putStrLn "Final cluster status:" putStrLn $ Cluster.printNodes ns printf "Original: mem=%d disk=%d\n" orig_mem orig_disk printf "Final: mem=%d disk=%d\n" final_mem final_disk