Statistics
| Branch: | Tag: | Revision:

root / hbal.hs @ 0427285d

History | View | Annotate | Download (8.5 kB)

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             -- ^ Wheter 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