Introduce a class for CLI options
[ganeti-local] / hscan.hs
1 {-| Scan clusters via RAPI and write instance/node data files.
2
3 -}
4
5 module Main (main) where
6
7 import Data.List
8 import Data.Function
9 import Data.Maybe(fromJust)
10 import Monad
11 import System
12 import System.IO
13 import System.FilePath
14 import System.Console.GetOpt
15 import qualified System
16
17 import Text.Printf (printf)
18
19 import qualified Ganeti.HTools.Container as Container
20 import qualified Ganeti.HTools.Cluster as Cluster
21 import qualified Ganeti.HTools.Node as Node
22 import qualified Ganeti.HTools.Instance as Instance
23 import qualified Ganeti.HTools.CLI as CLI
24 import qualified Ganeti.HTools.Rapi as Rapi
25 import qualified Ganeti.HTools.Loader as Loader
26 import Ganeti.HTools.Types
27
28 -- | Command line options structure.
29 data Options = Options
30     { optShowNodes :: Bool     -- ^ Whether to show node status
31     , optOutPath   :: FilePath -- ^ Path to the output directory
32     , optVerbose   :: Int      -- ^ Verbosity level
33     , optNoHeader  :: Bool     -- ^ Do not show a header line
34     , optShowVer   :: Bool     -- ^ Just show the program version
35     , optShowHelp  :: Bool     -- ^ Just show the help
36     } deriving Show
37
38 instance CLI.CLIOptions Options where
39     showVersion = optShowVer
40     showHelp    = optShowHelp
41
42 -- | Default values for the command line options.
43 defaultOptions :: Options
44 defaultOptions  = Options
45  { optShowNodes = False
46  , optOutPath   = "."
47  , optVerbose   = 0
48  , optNoHeader  = False
49  , optShowVer   = False
50  , optShowHelp  = False
51  }
52
53 -- | Options list and functions
54 options :: [OptDescr (Options -> Options)]
55 options =
56     [ Option ['p']     ["print-nodes"]
57       (NoArg (\ opts -> opts { optShowNodes = True }))
58       "print the final node list"
59     , Option ['d']     ["output-dir"]
60       (ReqArg (\ d opts -> opts { optOutPath = d }) "PATH")
61       "directory in which to write output files"
62     , Option ['v']     ["verbose"]
63       (NoArg (\ opts -> opts { optVerbose = (optVerbose opts) + 1 }))
64       "increase the verbosity level"
65     , Option []        ["no-headers"]
66       (NoArg (\ opts -> opts { optNoHeader = True }))
67       "do not show a header line"
68     , Option ['V']     ["version"]
69       (NoArg (\ opts -> opts { optShowVer = True}))
70       "show the version of the program"
71     , Option ['h']     ["help"]
72       (NoArg (\ opts -> opts { optShowHelp = True}))
73       "show help"
74     ]
75
76 -- | Generate node file data from node objects
77 serializeNodes :: Cluster.NodeList -> String -> Cluster.NameList -> String
78 serializeNodes nl csf ktn =
79     let etn = map (\(idx, name) -> (idx, name ++ csf)) ktn
80         nodes = Container.elems nl
81         nlines = map
82                  (\node ->
83                       let name = (fromJust $ lookup (Node.idx node) etn)
84                           t_mem = (truncate $ Node.t_mem node)::Int
85                           t_dsk = (truncate $ Node.t_dsk node)::Int
86                       in
87                         printf "%s|%d|%d|%d|%d|%d|%c" name
88                                    t_mem (Node.n_mem node) (Node.f_mem node)
89                                    t_dsk (Node.f_dsk node)
90                                    (if Node.offline node then 'Y' else 'N')
91                  )
92                  nodes
93     in unlines nlines
94
95 -- | Generate instance file data from instance objects
96 serializeInstances :: Cluster.InstanceList -> String
97                    -> Cluster.NameList -> Cluster.NameList -> String
98 serializeInstances il csf ktn kti =
99     let etn = map (\(idx, name) -> (idx, name ++ csf)) ktn
100         eti = map (\(idx, name) -> (idx, name ++ csf)) kti
101         instances = Container.elems il
102         nlines = map
103                  (\inst ->
104                       let
105                           iname = fromJust $ lookup (Instance.idx inst) eti
106                           pnode = fromJust $ lookup (Instance.pnode inst) etn
107                           snode = fromJust $ lookup (Instance.snode inst) etn
108                       in
109                         printf "%s|%d|%d|%s|%s|%s"
110                                iname (Instance.mem inst) (Instance.dsk inst)
111                                (Instance.run_st inst)
112                                pnode snode
113                  )
114                  instances
115     in unlines nlines
116
117 -- | Return a one-line summary of cluster state
118 printCluster :: Cluster.NodeList -> Cluster.InstanceList
119              -> Cluster.NameList -> Cluster.NameList
120              -> String
121 printCluster nl il ktn kti =
122     let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
123         ccv = Cluster.compCV nl
124         nodes = Container.elems nl
125         t_ram = truncate . sum . map Node.t_mem $ nodes
126         t_dsk = truncate . sum . map Node.t_dsk $ nodes
127         f_ram = sum . map Node.f_mem $ nodes
128         f_dsk = sum . map Node.f_dsk $ nodes
129     in
130       printf "%5d %5d %5d %5d %6d %6d %6d %6d %.8f"
131                  (length ktn) (length kti)
132                  (length bad_nodes) (length bad_instances)
133                  (t_ram::Integer) f_ram
134                  ((t_dsk::Integer) `div` 1024) (f_dsk `div` 1024)
135                  ccv
136
137
138 -- | Replace slashes with underscore for saving to filesystem
139
140 fixSlash :: String -> String
141 fixSlash = map (\x -> if x == '/' then '_' else x)
142
143 -- | Main function.
144 main :: IO ()
145 main = do
146   cmd_args <- System.getArgs
147   (opts, clusters) <- CLI.parseOpts cmd_args "hscan" options
148                       defaultOptions
149
150   let odir = optOutPath opts
151       nlen = maximum . map length $ clusters
152
153   unless (optNoHeader opts) $
154          printf "%-*s %5s %5s %5s %5s %6s %6s %6s %6s %10s\n" nlen
155                 "Name" "Nodes" "Inst" "BNode" "BInst" "t_mem" "f_mem"
156                 "t_disk" "f_disk" "Score"
157
158   mapM_ (\ name ->
159             do
160               printf "%-*s " nlen name
161               hFlush stdout
162               input_data <- Rapi.loadData name
163               let ldresult = input_data >>= Loader.mergeData
164               (case ldresult of
165                  Bad err -> printf "\nError: failed to load data. \
166                                    \Details:\n%s\n" err
167                  Ok x -> do
168                    let (nl, il, csf, ktn, kti) = x
169                        (_, fix_nl) = Cluster.checkData nl il ktn kti
170                    putStrLn $ printCluster fix_nl il ktn kti
171                    when (optShowNodes opts) $ do
172                            putStr $ Cluster.printNodes ktn fix_nl
173                    let ndata = serializeNodes nl csf ktn
174                        idata = serializeInstances il csf ktn kti
175                        oname = odir </> (fixSlash name)
176                    writeFile (oname <.> "nodes") ndata
177                    writeFile (oname <.> "instances") idata)
178        ) clusters