Statistics
| Branch: | Tag: | Revision:

root / hail.hs @ 4a340313

History | View | Annotate | Download (9.2 kB)

1
{-| Solver for N+1 cluster errors
2

    
3
-}
4

    
5
module Main (main) where
6

    
7
import Data.List
8
import Data.Function
9
import Data.Maybe (isJust, fromJust, fromMaybe)
10
import Monad
11
import System
12
import System.IO
13
import System.Console.GetOpt
14
import qualified System
15

    
16
import Text.Printf (printf)
17

    
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.Utils
25
import Ganeti.HTools.Types
26

    
27
-- | Command line options structure.
28
data Options = Options
29
    { optShowNodes :: Bool           -- ^ Whether to show node status
30
    , optShowCmds  :: Maybe FilePath -- ^ Whether to show the command list
31
    , optOneline   :: Bool           -- ^ Switch output to a single line
32
    , optNodef     :: FilePath       -- ^ Path to the nodes file
33
    , optNodeSet   :: Bool           -- ^ The nodes have been set by options
34
    , optInstf     :: FilePath       -- ^ Path to the instances file
35
    , optInstSet   :: Bool           -- ^ The insts have been set by options
36
    , optMaxLength :: Int            -- ^ Stop after this many steps
37
    , optMaster    :: String         -- ^ Collect data from RAPI
38
    , optVerbose   :: Int            -- ^ Verbosity level
39
    , optOffline   :: [String]       -- ^ Names of offline nodes
40
    , optMinScore  :: Cluster.Score  -- ^ The minimum score we aim for
41
    , optShowVer   :: Bool           -- ^ Just show the program version
42
    , optShowHelp  :: Bool           -- ^ Just show the help
43
    } deriving Show
44

    
45
instance CLI.CLIOptions Options where
46
    showVersion = optShowVer
47
    showHelp    = optShowHelp
48

    
49
-- | Default values for the command line options.
50
defaultOptions :: Options
51
defaultOptions  = Options
52
 { optShowNodes = False
53
 , optShowCmds  = Nothing
54
 , optOneline   = False
55
 , optNodef     = "nodes"
56
 , optNodeSet   = False
57
 , optInstf     = "instances"
58
 , optInstSet   = False
59
 , optMaxLength = -1
60
 , optMaster    = ""
61
 , optVerbose   = 1
62
 , optOffline   = []
63
 , optMinScore  = 1e-9
64
 , optShowVer   = False
65
 , optShowHelp  = False
66
 }
67

    
68
-- | Options list and functions
69
options :: [OptDescr (Options -> Options)]
70
options =
71
    [ Option ['p']     ["print-nodes"]
72
      (NoArg (\ opts -> opts { optShowNodes = True }))
73
      "print the final node list"
74
    , Option ['C']     ["print-commands"]
75
      (OptArg ((\ f opts -> opts { optShowCmds = Just f }) . fromMaybe "-")
76
                  "FILE")
77
      "print the ganeti command list for reaching the solution,\
78
      \if an argument is passed then write the commands to a file named\
79
      \ as such"
80
    , Option ['o']     ["oneline"]
81
      (NoArg (\ opts -> opts { optOneline = True }))
82
      "print the ganeti command list for reaching the solution"
83
    , Option ['n']     ["nodes"]
84
      (ReqArg (\ f opts -> opts { optNodef = f, optNodeSet = True }) "FILE")
85
      "the node list FILE"
86
    , Option ['i']     ["instances"]
87
      (ReqArg (\ f opts -> opts { optInstf =  f, optInstSet = True }) "FILE")
88
      "the instance list FILE"
89
    , Option ['m']     ["master"]
90
      (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS")
91
      "collect data via RAPI at the given ADDRESS"
92
    , Option ['l']     ["max-length"]
93
      (ReqArg (\ i opts -> opts { optMaxLength =  (read i)::Int }) "N")
94
      "cap the solution at this many moves (useful for very unbalanced \
95
      \clusters)"
96
    , Option ['v']     ["verbose"]
97
      (NoArg (\ opts -> opts { optVerbose = (optVerbose opts) + 1 }))
98
      "increase the verbosity level"
99
    , Option ['q']     ["quiet"]
100
      (NoArg (\ opts -> opts { optVerbose = (optVerbose opts) - 1 }))
101
      "decrease the verbosity level"
102
    , Option ['O']     ["offline"]
103
      (ReqArg (\ n opts -> opts { optOffline = n:optOffline opts }) "NODE")
104
      " set node as offline"
105
    , Option ['e']     ["min-score"]
106
      (ReqArg (\ e opts -> opts { optMinScore = read e }) "EPSILON")
107
      " mininum score to aim for"
108
    , Option ['V']     ["version"]
109
      (NoArg (\ opts -> opts { optShowVer = True}))
110
      "show the version of the program"
111
    , Option ['h']     ["help"]
112
      (NoArg (\ opts -> opts { optShowHelp = True}))
113
      "show help"
114
    ]
115

    
116
-- | Try to allocate an instance on the cluster
117
tryAlloc :: NodeList
118
         -> InstanceList
119
         -> Instance.Instance
120
         -> Int
121
         -> Result (String, [Node.Node])
122
tryAlloc nl il inst 2 =
123
    let all_nodes = Container.elems nl
124
        all_nidx = map Node.idx all_nodes
125
        all_pairs = liftM2 (,) all_nodes all_nodes
126
        ok_pairs = filter (\(x, y) -> Node.idx x /= Node.idx y) all_pairs
127
        sols1 = map (\(p, s) -> let pdx = Node.idx p
128
                                    sdx = Node.idx s
129
                                    (mnl, _) = Cluster.allocateOn nl
130
                                               inst pdx sdx
131
                                in (mnl, (p, s))
132
                     ) ok_pairs
133
        sols2 = filter (isJust . fst) sols1
134
    in if null sols1 then
135
           Bad "No pairs onto which to allocate at all"
136
       else if null sols2 then
137
                Bad "No valid allocation solutions"
138
            else
139
                let sols3 = map (\(x, (y, z)) ->
140
                                      (Cluster.compCV $ fromJust x,
141
                                                  (fromJust x, y, z)))
142
                             sols2
143
                    sols4 = sortBy (compare `on` fst) sols3
144
                    (best, (final_nl, w1, w2)) = head sols4
145
                    (worst, (_, l1, l2)) = last sols4
146
                    info = printf "Valid results: %d, best score: %.8f \
147
                                  \(nodes %s/%s), worst score: %.8f (nodes \
148
                                  \%s/%s)"
149
                                  (length sols3)
150
                                  best (Node.name w1) (Node.name w2)
151
                                  worst (Node.name l1) (Node.name w2)
152
                in Ok (info, [w1, w2])
153

    
154

    
155
tryAlloc _ _ _ reqn = Bad $ "Unsupported number of alllocation \
156
                               \destinations required (" ++ (show reqn) ++
157
                                                 "), only two supported"
158

    
159
-- | Try to allocate an instance on the cluster
160
tryReloc :: NodeList
161
         -> InstanceList
162
         -> Int
163
         -> Int
164
         -> [Int]
165
         -> Result (String, [Node.Node])
166
tryReloc nl il xid 1 ex_idx =
167
    let all_nodes = Container.elems nl
168
        inst = Container.find xid il
169
        valid_nodes = filter (not . flip elem ex_idx . idx) all_nodes
170
        valid_idxes = map Node.idx valid_nodes
171
        nl' = Container.map (\n -> if elem (Node.idx n) ex_idx then
172
                                       Node.setOffline n True
173
                                   else n) nl
174
        sols1 = map (\x -> let (mnl, _, _, _) =
175
                                    Cluster.applyMove nl' inst
176
                                               (Cluster.ReplaceSecondary x)
177
                            in (mnl, x)
178
                     ) valid_idxes
179
        sols2 = filter (isJust . fst) sols1
180
    in if null sols1 then
181
           Bad "No nodes onto which to relocate at all"
182
       else if null sols2 then
183
                Bad "No valid solutions"
184
            else
185
                let sols3 = map (\(x, y) ->
186
                                      (Cluster.compCV $ fromJust x,
187
                                                  (fromJust x, y)))
188
                             sols2
189
                    sols4 = sortBy (compare `on` fst) sols3
190
                    (best, (final_nl, winner)) = head sols4
191
                    (worst, (_, loser)) = last sols4
192
                    wnode = Container.find winner final_nl
193
                    lnode = Container.find loser nl
194
                    info = printf "Valid results: %d, best score: %.8f \
195
                                  \(node %s), worst score: %.8f (node %s)"
196
                                  (length sols3) best (Node.name wnode)
197
                                  worst (Node.name lnode)
198
                in Ok (info, [wnode])
199

    
200
tryReloc _ _ _ reqn _  = Bad $ "Unsupported number of relocation \
201
                               \destinations required (" ++ (show reqn) ++
202
                                                 "), only one supported"
203

    
204
-- | Main function.
205
main :: IO ()
206
main = do
207
  cmd_args <- System.getArgs
208
  (opts, args) <- CLI.parseOpts cmd_args "hail" options
209
                  defaultOptions
210

    
211
  when (null args) $ do
212
         hPutStrLn stderr "Error: this program needs an input file."
213
         exitWith $ ExitFailure 1
214

    
215
  let input_file = head args
216
  input_data <- readFile input_file
217

    
218
  request <- case (parseData input_data) of
219
               Bad err -> do
220
                 putStrLn $ "Error: " ++ err
221
                 exitWith $ ExitFailure 1
222
               Ok rq -> return rq
223

    
224
  let Request rqtype nl il csf = request
225
      new_nodes = case rqtype of
226
                    Allocate xi reqn -> tryAlloc nl il xi reqn
227
                    Relocate idx reqn exnodes ->
228
                        tryReloc nl il idx reqn exnodes
229
  let (ok, info, rn) = case new_nodes of
230
               Ok (info, sn) -> (True, "Request successful: " ++ info,
231
                                     map name sn)
232
               Bad s -> (False, "Request failed: " ++ s, [])
233
      resp = formatResponse ok info rn
234
  putStrLn resp