Add more unit tests for allocation/balance
[ganeti-local] / hscan.hs
1 {-# LANGUAGE CPP #-}
2
3 {-| Scan clusters via RAPI or LUXI and write state data files.
4
5 -}
6
7 {-
8
9 Copyright (C) 2009 Google Inc.
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 02110-1301, USA.
25
26 -}
27
28 module Main (main) where
29
30 import Data.List
31 import Data.Maybe (isJust, fromJust, fromMaybe)
32 import Monad
33 #ifdef NO_CURL
34 import System (exitWith, ExitCode(..))
35 #endif
36 import System.IO
37 import System.FilePath
38 import qualified System
39
40 import Text.Printf (printf)
41
42 import qualified Ganeti.HTools.Container as Container
43 import qualified Ganeti.HTools.Cluster as Cluster
44 import qualified Ganeti.HTools.Node as Node
45 import qualified Ganeti.HTools.Instance as Instance
46 #ifndef NO_CURL
47 import qualified Ganeti.HTools.Rapi as Rapi
48 #endif
49 import qualified Ganeti.HTools.Luxi as Luxi
50 import qualified Ganeti.HTools.Loader as Loader
51
52 import Ganeti.HTools.CLI
53 import Ganeti.HTools.Types
54
55 -- | Options list and functions
56 options :: [OptType]
57 options =
58     [ oPrintNodes
59     , oOutputDir
60     , oLuxiSocket
61     , oVerbose
62     , oNoHeaders
63     , oShowVer
64     , oShowHelp
65     ]
66
67 -- | Serialize a single node
68 serializeNode :: Node.Node -> String
69 serializeNode node =
70     printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c" (Node.name node)
71                (Node.tMem node) (Node.nMem node) (Node.fMem node)
72                (Node.tDsk node) (Node.fDsk node) (Node.tCpu node)
73                (if Node.offline node then 'Y' else 'N')
74
75 -- | Generate node file data from node objects
76 serializeNodes :: Node.List -> String
77 serializeNodes = unlines . map serializeNode . Container.elems
78
79 -- | Serialize a single instance
80 serializeInstance :: Node.List -> Instance.Instance -> String
81 serializeInstance nl inst =
82     let
83         iname = Instance.name inst
84         pnode = Container.nameOf nl (Instance.pNode inst)
85         sidx = Instance.sNode inst
86         snode = (if sidx == Node.noSecondary
87                     then ""
88                     else Container.nameOf nl sidx)
89     in
90       printf "%s|%d|%d|%d|%s|%s|%s|%s"
91              iname (Instance.mem inst) (Instance.dsk inst)
92              (Instance.vcpus inst) (Instance.runSt inst)
93              pnode snode (intercalate "," (Instance.tags inst))
94
95 -- | Generate instance file data from instance objects
96 serializeInstances :: Node.List -> Instance.List -> String
97 serializeInstances nl =
98     unlines . map (serializeInstance nl) . Container.elems
99
100 -- | Return a one-line summary of cluster state
101 printCluster :: Node.List -> Instance.List
102              -> String
103 printCluster nl il =
104     let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
105         ccv = Cluster.compCV nl
106         nodes = Container.elems nl
107         insts = Container.elems il
108         t_ram = sum . map Node.tMem $ nodes
109         t_dsk = sum . map Node.tDsk $ nodes
110         f_ram = sum . map Node.fMem $ nodes
111         f_dsk = sum . map Node.fDsk $ nodes
112     in
113       printf "%5d %5d %5d %5d %6.0f %6d %6.0f %6d %.8f"
114                  (length nodes) (length insts)
115                  (length bad_nodes) (length bad_instances)
116                  t_ram f_ram
117                  (t_dsk / 1024) (f_dsk `div` 1024)
118                  ccv
119
120
121 -- | Replace slashes with underscore for saving to filesystem
122 fixSlash :: String -> String
123 fixSlash = map (\x -> if x == '/' then '_' else x)
124
125
126 -- | Generates serialized data from loader input
127 processData :: Result (Node.AssocList, Instance.AssocList, [String])
128             -> Result (Node.List, Instance.List, String)
129 processData input_data = do
130   (nl, il, _) <- input_data >>= Loader.mergeData [] [] []
131   let (_, fix_nl) = Loader.checkData nl il
132   let ndata = serializeNodes nl
133       idata = serializeInstances nl il
134       adata = ndata ++ ['\n'] ++ idata
135   return (fix_nl, il, adata)
136
137 -- | Writes cluster data out
138 writeData :: Int
139           -> String
140           -> Options
141           -> Result (Node.List, Instance.List, String)
142           -> IO ()
143 writeData _ name _ (Bad err) =
144     printf "\nError for %s: failed to load data. Details:\n%s\n" name err
145
146 writeData nlen name opts (Ok (nl, il, adata)) = do
147   printf "%-*s " nlen name :: IO ()
148   hFlush stdout
149   let shownodes = optShowNodes opts
150       odir = optOutPath opts
151       oname = odir </> fixSlash name
152   putStrLn $ printCluster nl il
153   hFlush stdout
154   when (isJust shownodes) $
155        putStr $ Cluster.printNodes nl (fromJust shownodes)
156   writeFile (oname <.> "data") adata
157
158
159 -- | Main function.
160 main :: IO ()
161 main = do
162   cmd_args <- System.getArgs
163   (opts, clusters) <- parseOpts cmd_args "hscan" options
164   let local = "LOCAL"
165
166   let nlen = if null clusters
167              then length local
168              else maximum . map length $ clusters
169
170   unless (optNoHeaders opts) $
171          printf "%-*s %5s %5s %5s %5s %6s %6s %6s %6s %10s\n" nlen
172                 "Name" "Nodes" "Inst" "BNode" "BInst" "t_mem" "f_mem"
173                 "t_disk" "f_disk" "Score"
174
175   when (null clusters) $ do
176          let lsock = fromMaybe defaultLuxiSocket (optLuxi opts)
177          let name = local
178          input_data <- Luxi.loadData lsock
179          writeData nlen name opts (processData input_data)
180
181 #ifndef NO_CURL
182   mapM_ (\ name ->
183             do
184               input_data <- Rapi.loadData name
185               writeData nlen name opts (processData input_data)
186         ) clusters
187 #else
188   when (not $ null clusters) $ do
189     putStrLn "RAPI/curl backend disabled at compile time, cannot scan clusters"
190     exitWith $ ExitFailure 1
191 #endif