{-| Solver for N+1 cluster errors -} module Main (main) where import Data.List import Data.Function import Data.Maybe (isJust, fromJust) 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.Cluster as Cluster import qualified Ganeti.HTools.Node as Node import qualified Ganeti.HTools.Instance as Instance import qualified Ganeti.HTools.CLI as CLI import Ganeti.HTools.IAlloc import Ganeti.HTools.Types -- | Command line options structure. data Options = Options { optShowVer :: Bool -- ^ Just show the program version , optShowHelp :: Bool -- ^ Just show the help } deriving Show -- | Default values for the command line options. defaultOptions :: Options defaultOptions = Options { optShowVer = False , optShowHelp = False } instance CLI.CLIOptions Options where showVersion = optShowVer showHelp = optShowHelp -- | Options list and functions options :: [OptDescr (Options -> Options)] options = [ Option ['V'] ["version"] (NoArg (\ opts -> opts { optShowVer = True})) "show the version of the program" , Option ['h'] ["help"] (NoArg (\ opts -> opts { optShowHelp = True})) "show help" ] -- | Compute online nodes from a NodeList getOnline :: NodeList -> [Node.Node] getOnline = filter (not . Node.offline) . Container.elems -- | Try to allocate an instance on the cluster tryAlloc :: (Monad m) => NodeList -> InstanceList -> Instance.Instance -> Int -> m [(Maybe NodeList, [Node.Node])] tryAlloc nl _ inst 2 = let all_nodes = getOnline nl all_pairs = liftM2 (,) all_nodes all_nodes ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs sols = map (\(p, s) -> (fst $ Cluster.allocateOnPair nl inst p s, [p, s])) ok_pairs in return sols tryAlloc nl _ inst 1 = let all_nodes = getOnline nl sols = map (\p -> (fst $ Cluster.allocateOnSingle nl inst p, [p])) all_nodes in return sols tryAlloc _ _ _ reqn = fail $ "Unsupported number of alllocation \ \destinations required (" ++ (show reqn) ++ "), only two supported" -- | Try to allocate an instance on the cluster tryReloc :: (Monad m) => NodeList -> InstanceList -> Int -> Int -> [Int] -> m [(Maybe NodeList, [Node.Node])] tryReloc nl il xid 1 ex_idx = let all_nodes = getOnline nl inst = Container.find xid il valid_nodes = filter (not . flip elem ex_idx . idx) all_nodes valid_idxes = map Node.idx valid_nodes nl' = Container.map (\n -> if elem (Node.idx n) ex_idx then Node.setOffline n True else n) nl sols1 = map (\x -> let (mnl, _, _, _) = Cluster.applyMove nl' inst (Cluster.ReplaceSecondary x) in (mnl, [Container.find x nl']) ) valid_idxes in return sols1 tryReloc _ _ _ reqn _ = fail $ "Unsupported number of relocation \ \destinations required (" ++ (show reqn) ++ "), only one supported" filterFails :: (Monad m) => [(Maybe NodeList, [Node.Node])] -> m [(NodeList, [Node.Node])] filterFails sols = if null sols then fail "No nodes onto which to allocate at all" else let sols' = filter (isJust . fst) sols in if null sols' then fail "No valid allocation solutions" else return $ map (\(x, y) -> (fromJust x, y)) sols' processResults :: (Monad m) => [(NodeList, [Node.Node])] -> m (String, [Node.Node]) processResults sols = let sols' = map (\(nl', ns) -> (Cluster.compCV nl', ns)) sols sols'' = sortBy (compare `on` fst) sols' (best, w) = head sols'' (worst, l) = last sols'' info = printf "Valid results: %d, best score: %.8f for node(s) %s, \ \worst score: %.8f for node(s) %s" (length sols'') best (intercalate "/" . map Node.name $ w) worst (intercalate "/" . map Node.name $ l) in return (info, w) -- | Main function. main :: IO () main = do cmd_args <- System.getArgs (_, args) <- CLI.parseOpts cmd_args "hail" options defaultOptions when (null args) $ do hPutStrLn stderr "Error: this program needs an input file." exitWith $ ExitFailure 1 let input_file = head args input_data <- readFile input_file request <- case (parseData input_data) of Bad err -> do putStrLn $ "Error: " ++ err exitWith $ ExitFailure 1 Ok rq -> return rq let Request rqtype nl il csf = request new_nodes = case rqtype of Allocate xi reqn -> tryAlloc nl il xi reqn Relocate idx reqn exnodes -> tryReloc nl il idx reqn exnodes let sols = new_nodes >>= filterFails >>= processResults let (ok, info, rn) = case sols of Ok (info, sn) -> (True, "Request successful: " ++ info, map ((++ csf) . name) sn) Bad s -> (False, "Request failed: " ++ s, []) resp = formatResponse ok info rn putStrLn resp