Add test for CLI.parseYesNo
[ganeti-local] / htools / Ganeti / HTools / ExtLoader.hs
1 {-| External data loader.
2
3 This module holds the external data loading, and thus is the only one
4 depending (via the specialized Text\/Rapi\/Luxi modules) on the actual
5 libraries implementing the low-level protocols.
6
7 -}
8
9 {-
10
11 Copyright (C) 2009, 2010, 2011 Google Inc.
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 02110-1301, USA.
27
28 -}
29
30 module Ganeti.HTools.ExtLoader
31   ( loadExternalData
32   , commonSuffix
33   , maybeSaveData
34   ) where
35
36 import Control.Monad
37 import Data.Maybe (isJust, fromJust)
38 import System.FilePath
39 import System.IO
40 import System.Exit
41 import Text.Printf (hPrintf)
42
43 import qualified Ganeti.HTools.Luxi as Luxi
44 import qualified Ganeti.HTools.Rapi as Rapi
45 import qualified Ganeti.HTools.Simu as Simu
46 import qualified Ganeti.HTools.Text as Text
47 import Ganeti.HTools.Loader (mergeData, checkData, ClusterData(..)
48                             , commonSuffix)
49
50 import Ganeti.HTools.Types
51 import Ganeti.HTools.CLI
52 import Ganeti.HTools.Utils (sepSplit, tryRead)
53
54 -- | Error beautifier.
55 wrapIO :: IO (Result a) -> IO (Result a)
56 wrapIO = flip catch (return . Bad . show)
57
58 -- | Parses a user-supplied utilisation string.
59 parseUtilisation :: String -> Result (String, DynUtil)
60 parseUtilisation line =
61   case sepSplit ' ' line of
62     [name, cpu, mem, dsk, net] ->
63       do
64         rcpu <- tryRead name cpu
65         rmem <- tryRead name mem
66         rdsk <- tryRead name dsk
67         rnet <- tryRead name net
68         let du = DynUtil { cpuWeight = rcpu, memWeight = rmem
69                          , dskWeight = rdsk, netWeight = rnet }
70         return (name, du)
71     _ -> Bad $ "Cannot parse line " ++ line
72
73 -- | External tool data loader from a variety of sources.
74 loadExternalData :: Options
75                  -> IO ClusterData
76 loadExternalData opts = do
77   let mhost = optMaster opts
78       lsock = optLuxi opts
79       tfile = optDataFile opts
80       simdata = optNodeSim opts
81       setRapi = mhost /= ""
82       setLuxi = isJust lsock
83       setSim = (not . null) simdata
84       setFile = isJust tfile
85       allSet = filter id [setRapi, setLuxi, setFile]
86       exTags = case optExTags opts of
87                  Nothing -> []
88                  Just etl -> map (++ ":") etl
89       selInsts = optSelInst opts
90       exInsts = optExInst opts
91
92   when (length allSet > 1) $
93        do
94          hPutStrLn stderr ("Error: Only one of the rapi, luxi, and data" ++
95                            " files options should be given.")
96          exitWith $ ExitFailure 1
97
98   util_contents <- maybe (return "") readFile (optDynuFile opts)
99   let util_data = mapM parseUtilisation $ lines util_contents
100   util_data' <- case util_data of
101                   Ok x  -> return x
102                   Bad y -> do
103                     hPutStrLn stderr ("Error: can't parse utilisation" ++
104                                       " data: " ++ show y)
105                     exitWith $ ExitFailure 1
106   input_data <-
107     case () of
108       _ | setRapi -> wrapIO $ Rapi.loadData mhost
109         | setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock
110         | setSim -> Simu.loadData simdata
111         | setFile -> wrapIO $ Text.loadData $ fromJust tfile
112         | otherwise -> return $ Bad "No backend selected! Exiting."
113
114   let ldresult = input_data >>= mergeData util_data' exTags selInsts exInsts
115   cdata <-
116     case ldresult of
117       Ok x -> return x
118       Bad s -> do
119         hPrintf stderr
120           "Error: failed to load data, aborting. Details:\n%s\n" s:: IO ()
121         exitWith $ ExitFailure 1
122   let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata)
123
124   unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs
125
126   return cdata {cdNodes = nl}
127
128 -- | Function to save the cluster data to a file.
129 maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to
130               -> String         -- ^ The suffix (extension) to add
131               -> String         -- ^ Informational message
132               -> ClusterData    -- ^ The cluster data
133               -> IO ()
134 maybeSaveData Nothing _ _ _ = return ()
135 maybeSaveData (Just path) ext msg cdata = do
136   let adata = Text.serializeCluster cdata
137       out_path = path <.> ext
138   writeFile out_path adata
139   hPrintf stderr "The cluster state %s has been written to file '%s'\n"
140           msg out_path