Fix a typo in hbal.hs
[ganeti-local] / hbal.hs
1 {-| Cluster rebalancer
2
3 -}
4
5 {-
6
7 Copyright (C) 2009 Google Inc.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 02110-1301, USA.
23
24 -}
25
26 module Main (main) where
27
28 import Data.List
29 import Data.Function
30 import Data.Maybe (isJust, fromJust)
31 import Monad
32 import System
33 import System.IO
34 import qualified System
35
36 import Text.Printf (printf, hPrintf)
37
38 import qualified Ganeti.HTools.Container as Container
39 import qualified Ganeti.HTools.Cluster as Cluster
40 import qualified Ganeti.HTools.Node as Node
41
42 import Ganeti.HTools.CLI
43 import Ganeti.HTools.Utils
44
45 -- | Options list and functions
46 options :: [OptType]
47 options =
48     [ oPrintNodes
49     , oPrintCommands
50     , oOneline
51     , oNodeFile
52     , oInstFile
53     , oRapiMaster
54     , oLuxiSocket
55     , oMaxSolLength
56     , oVerbose
57     , oQuiet
58     , oOfflineNode
59     , oMinScore
60     , oMaxCpu
61     , oMinDisk
62     , oShowVer
63     , oShowHelp
64     ]
65
66 {- | Start computing the solution at the given depth and recurse until
67 we find a valid solution or we exceed the maximum depth.
68
69 -}
70 iterateDepth :: Cluster.Table    -- ^ The starting table
71              -> Int              -- ^ Remaining length
72              -> Int              -- ^ Max node name len
73              -> Int              -- ^ Max instance name len
74              -> [[String]]       -- ^ Current command list
75              -> Bool             -- ^ Whether to be silent
76              -> Cluster.Score    -- ^ Score at which to stop
77              -> IO (Cluster.Table, [[String]]) -- ^ The resulting table and
78                                                -- commands
79 iterateDepth ini_tbl max_rounds nmlen imlen
80              cmd_strs oneline min_score =
81     let Cluster.Table ini_nl ini_il ini_cv ini_plc = ini_tbl
82         all_inst = Container.elems ini_il
83         node_idx = map Node.idx . filter (not . Node.offline) $
84                    Container.elems ini_nl
85         fin_tbl = Cluster.checkMove node_idx ini_tbl all_inst
86         (Cluster.Table _ _ fin_cv fin_plc) = fin_tbl
87         ini_plc_len = length ini_plc
88         fin_plc_len = length fin_plc
89         allowed_next = (max_rounds < 0 || length fin_plc < max_rounds)
90     in
91       do
92         let
93             (sol_line, cmds) = Cluster.printSolutionLine ini_nl ini_il
94                                nmlen imlen (head fin_plc) fin_plc_len
95             upd_cmd_strs = cmds:cmd_strs
96         unless (oneline || fin_plc_len == ini_plc_len) $ do
97           putStrLn sol_line
98           hFlush stdout
99         (if fin_cv < ini_cv then -- this round made success, try deeper
100              if allowed_next && fin_cv > min_score
101              then iterateDepth fin_tbl max_rounds
102                   nmlen imlen upd_cmd_strs oneline min_score
103              -- don't go deeper, but return the better solution
104              else return (fin_tbl, upd_cmd_strs)
105          else
106              return (ini_tbl, cmd_strs))
107
108 -- | Formats the solution for the oneline display
109 formatOneline :: Double -> Int -> Double -> String
110 formatOneline ini_cv plc_len fin_cv =
111     printf "%.8f %d %.8f %8.3f" ini_cv plc_len fin_cv
112                (if fin_cv == 0 then 1 else ini_cv / fin_cv)
113
114 -- | Main function.
115 main :: IO ()
116 main = do
117   cmd_args <- System.getArgs
118   (opts, args) <- parseOpts cmd_args "hbal" options
119
120   unless (null args) $ do
121          hPutStrLn stderr "Error: this program doesn't take any arguments."
122          exitWith $ ExitFailure 1
123
124   let oneline = optOneline opts
125       verbose = optVerbose opts
126
127   (fixed_nl, il, csf) <- loadExternalData opts
128
129   let offline_names = optOffline opts
130       all_nodes = Container.elems fixed_nl
131       all_names = map Node.name all_nodes
132       offline_wrong = filter (flip notElem all_names) offline_names
133       offline_indices = map Node.idx $
134                         filter (\n -> elem (Node.name n) offline_names)
135                                all_nodes
136       m_cpu = optMcpu opts
137       m_dsk = optMdsk opts
138
139   when (length offline_wrong > 0) $ do
140          hPrintf stderr "Wrong node name(s) set as offline: %s\n"
141                      (commaJoin offline_wrong)
142          exitWith $ ExitFailure 1
143
144   let nm = Container.map (\n -> if elem (Node.idx n) offline_indices
145                                 then Node.setOffline n True
146                                 else n) fixed_nl
147       nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
148            nm
149
150   when (Container.size il == 0) $ do
151          (if oneline then putStrLn $ formatOneline 0 0 0
152           else printf "Cluster is empty, exiting.\n")
153          exitWith ExitSuccess
154
155   unless oneline $ printf "Loaded %d nodes, %d instances\n"
156              (Container.size nl)
157              (Container.size il)
158
159   when (length csf > 0 && not oneline && verbose > 1) $
160        printf "Note: Stripping common suffix of '%s' from names\n" csf
161
162   let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
163   unless (oneline || verbose == 0) $ printf
164              "Initial check done: %d bad nodes, %d bad instances.\n"
165              (length bad_nodes) (length bad_instances)
166
167   when (length bad_nodes > 0) $
168          putStrLn "Cluster is not N+1 happy, continuing but no guarantee \
169                   \that the cluster will end N+1 happy."
170
171   when (optShowNodes opts) $
172        do
173          putStrLn "Initial cluster status:"
174          putStrLn $ Cluster.printNodes nl
175
176   let ini_cv = Cluster.compCV nl
177       ini_tbl = Cluster.Table nl il ini_cv []
178       min_cv = optMinScore opts
179
180   when (ini_cv < min_cv) $ do
181          (if oneline then
182               putStrLn $ formatOneline ini_cv 0 ini_cv
183           else printf "Cluster is already well balanced (initial score %.6g,\n\
184                       \minimum score %.6g).\nNothing to do, exiting\n"
185                       ini_cv min_cv)
186          exitWith ExitSuccess
187
188   unless oneline (if verbose > 2 then
189                       printf "Initial coefficients: overall %.8f, %s\n"
190                       ini_cv (Cluster.printStats nl)
191                   else
192                       printf "Initial score: %.8f\n" ini_cv)
193
194   unless oneline $ putStrLn "Trying to minimize the CV..."
195   let imlen = Container.maxNameLen il
196       nmlen = Container.maxNameLen nl
197
198   (fin_tbl, cmd_strs) <- iterateDepth ini_tbl (optMaxLength opts)
199                          nmlen imlen [] oneline min_cv
200   let (Cluster.Table fin_nl _ fin_cv fin_plc) = fin_tbl
201       ord_plc = reverse fin_plc
202       sol_msg = if null fin_plc
203                 then printf "No solution found\n"
204                 else if verbose > 2
205                      then printf "Final coefficients:   overall %.8f, %s\n"
206                           fin_cv (Cluster.printStats fin_nl)
207                      else printf "Cluster score improved from %.8f to %.8f\n"
208                           ini_cv fin_cv
209                               ::String
210
211   unless oneline $ putStr sol_msg
212
213   unless (oneline || verbose == 0) $
214          printf "Solution length=%d\n" (length ord_plc)
215
216   let cmd_data = Cluster.formatCmds . reverse $ cmd_strs
217
218   when (isJust $ optShowCmds opts) $
219        do
220          let out_path = fromJust $ optShowCmds opts
221          putStrLn ""
222          (if out_path == "-" then
223               printf "Commands to run to reach the above solution:\n%s"
224                      (unlines . map ("  " ++) .
225                       filter (/= "check") .
226                       lines $ cmd_data)
227           else do
228             writeFile out_path (shTemplate ++ cmd_data)
229             printf "The commands have been written to file '%s'\n" out_path)
230
231   when (optShowNodes opts) $
232        do
233          let ini_cs = Cluster.totalResources nl
234              fin_cs = Cluster.totalResources fin_nl
235          putStrLn ""
236          putStrLn "Final cluster status:"
237          putStrLn $ Cluster.printNodes fin_nl
238          when (verbose > 3) $
239               do
240                 printf "Original: mem=%d disk=%d\n"
241                        (Cluster.cs_fmem ini_cs) (Cluster.cs_fdsk ini_cs)
242                 printf "Final:    mem=%d disk=%d\n"
243                        (Cluster.cs_fmem fin_cs) (Cluster.cs_fdsk fin_cs)
244   when oneline $
245          putStrLn $ formatOneline ini_cv (length ord_plc) fin_cv