Skip to content
Snippets Groups Projects
hail.hs 5.55 KiB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
{-| Solver for N+1 cluster errors

-}

module Main (main) where

import Data.List
import Data.Function
Iustin Pop's avatar
Iustin Pop committed
import Data.Maybe (isJust, fromJust)
Iustin Pop's avatar
Iustin Pop committed
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
Iustin Pop's avatar
Iustin Pop committed
import qualified Ganeti.HTools.CLI as CLI
import Ganeti.HTools.IAlloc
Iustin Pop's avatar
Iustin Pop committed
import Ganeti.HTools.Types
Iustin Pop's avatar
Iustin Pop committed

-- | Command line options structure.
data Options = Options
Iustin Pop's avatar
Iustin Pop committed
    { optShowVer   :: Bool           -- ^ Just show the program version
Iustin Pop's avatar
Iustin Pop committed
    , optShowHelp  :: Bool           -- ^ Just show the help
    } deriving Show

-- | Default values for the command line options.
defaultOptions :: Options
defaultOptions  = Options
Iustin Pop's avatar
Iustin Pop committed
 { optShowVer   = False
Iustin Pop's avatar
Iustin Pop committed
 , optShowHelp  = False
 }

Iustin Pop's avatar
Iustin Pop committed
instance CLI.CLIOptions Options where
    showVersion = optShowVer
    showHelp    = optShowHelp

Iustin Pop's avatar
Iustin Pop committed
-- | Options list and functions
options :: [OptDescr (Options -> Options)]
options =
Iustin Pop's avatar
Iustin Pop committed
    [ Option ['V']     ["version"]
Iustin Pop's avatar
Iustin Pop committed
      (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"
                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)
Iustin Pop's avatar
Iustin Pop committed

-- | Main function.
main :: IO ()
main = do
  cmd_args <- System.getArgs
Iustin Pop's avatar
Iustin Pop committed
  (_, args) <- CLI.parseOpts cmd_args "hail" options defaultOptions
Iustin Pop's avatar
Iustin Pop committed

  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