1 {-| Solver for N+1 cluster errors
5 module Main (main) where
9 import Data.Maybe (isJust, fromJust)
13 import System.Console.GetOpt
14 import qualified System
16 import Text.Printf (printf)
18 import qualified Ganeti.HTools.Container as Container
19 import qualified Ganeti.HTools.Cluster as Cluster
20 import qualified Ganeti.HTools.Node as Node
21 import qualified Ganeti.HTools.Instance as Instance
22 import qualified Ganeti.HTools.CLI as CLI
23 import Ganeti.HTools.IAlloc
24 import Ganeti.HTools.Types
25 import Ganeti.HTools.Loader (RqType(..), Request(..))
27 -- | Command line options structure.
28 data Options = Options
29 { optShowVer :: Bool -- ^ Just show the program version
30 , optShowHelp :: Bool -- ^ Just show the help
33 -- | Default values for the command line options.
34 defaultOptions :: Options
35 defaultOptions = Options
40 instance CLI.CLIOptions Options where
41 showVersion = optShowVer
42 showHelp = optShowHelp
44 -- | Options list and functions
45 options :: [OptDescr (Options -> Options)]
47 [ Option ['V'] ["version"]
48 (NoArg (\ opts -> opts { optShowVer = True}))
49 "show the version of the program"
50 , Option ['h'] ["help"]
51 (NoArg (\ opts -> opts { optShowHelp = True}))
55 -- | Compute online nodes from a Node.List
56 getOnline :: Node.List -> [Node.Node]
57 getOnline = filter (not . Node.offline) . Container.elems
59 -- | Try to allocate an instance on the cluster
60 tryAlloc :: (Monad m) =>
65 -> m [(Maybe Node.List, [Node.Node])]
66 tryAlloc nl _ inst 2 =
67 let all_nodes = getOnline nl
68 all_pairs = liftM2 (,) all_nodes all_nodes
69 ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs
70 sols = map (\(p, s) ->
71 (fst $ Cluster.allocateOnPair nl inst p s, [p, s]))
75 tryAlloc nl _ inst 1 =
76 let all_nodes = getOnline nl
77 sols = map (\p -> (fst $ Cluster.allocateOnSingle nl inst p, [p]))
81 tryAlloc _ _ _ reqn = fail $ "Unsupported number of alllocation \
82 \destinations required (" ++ (show reqn) ++
83 "), only two supported"
85 -- | Try to allocate an instance on the cluster
86 tryReloc :: (Monad m) =>
92 -> m [(Maybe Node.List, [Node.Node])]
93 tryReloc nl il xid 1 ex_idx =
94 let all_nodes = getOnline nl
95 inst = Container.find xid il
96 ex_idx' = (Instance.pnode inst):ex_idx
97 valid_nodes = filter (not . flip elem ex_idx' . Node.idx) all_nodes
98 valid_idxes = map Node.idx valid_nodes
99 sols1 = map (\x -> let (mnl, _, _, _) =
100 Cluster.applyMove nl inst
101 (Cluster.ReplaceSecondary x)
102 in (mnl, [Container.find x nl])
106 tryReloc _ _ _ reqn _ = fail $ "Unsupported number of relocation \
107 \destinations required (" ++ (show reqn) ++
108 "), only one supported"
110 filterFails :: (Monad m) => [(Maybe Node.List, [Node.Node])]
111 -> m [(Node.List, [Node.Node])]
113 if null sols then fail "No nodes onto which to allocate at all"
114 else let sols' = filter (isJust . fst) sols
115 in if null sols' then
116 fail "No valid allocation solutions"
118 return $ map (\(x, y) -> (fromJust x, y)) sols'
120 processResults :: (Monad m) => [(Node.List, [Node.Node])]
121 -> m (String, [Node.Node])
122 processResults sols =
123 let sols' = map (\(nl', ns) -> (Cluster.compCV nl', ns)) sols
124 sols'' = sortBy (compare `on` fst) sols'
125 (best, w) = head sols''
126 (worst, l) = last sols''
127 info = printf "Valid results: %d, best score: %.8f for node(s) %s, \
128 \worst score: %.8f for node(s) %s" (length sols'')
129 best (intercalate "/" . map Node.name $ w)
130 worst (intercalate "/" . map Node.name $ l)
136 cmd_args <- System.getArgs
137 (_, args) <- CLI.parseOpts cmd_args "hail" options defaultOptions
139 when (null args) $ do
140 hPutStrLn stderr "Error: this program needs an input file."
141 exitWith $ ExitFailure 1
143 let input_file = head args
144 input_data <- readFile input_file
146 request <- case (parseData input_data) of
148 putStrLn $ "Error: " ++ err
149 exitWith $ ExitFailure 1
152 let Request rqtype nl il csf = request
153 new_nodes = case rqtype of
154 Allocate xi reqn -> tryAlloc nl il xi reqn
155 Relocate idx reqn exnodes ->
156 tryReloc nl il idx reqn exnodes
157 let sols = new_nodes >>= filterFails >>= processResults
158 let (ok, info, rn) = case sols of
159 Ok (info, sn) -> (True, "Request successful: " ++ info,
160 map ((++ csf) . Node.name) sn)
161 Bad s -> (False, "Request failed: " ++ s, [])
162 resp = formatResponse ok info rn