htools: fix corner case in prop_Text_Load_Instance
[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
41 import Text.Printf (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 parseUtilisation :: String -> Result (String, DynUtil)
59 parseUtilisation line =
60     let columns = sepSplit ' ' line
61     in case columns of
62          [name, cpu, mem, dsk, net] -> do
63                       rcpu <- tryRead name cpu
64                       rmem <- tryRead name mem
65                       rdsk <- tryRead name dsk
66                       rnet <- tryRead name net
67                       let du = DynUtil { cpuWeight = rcpu, memWeight = rmem
68                                        , dskWeight = rdsk, netWeight = rnet }
69                       return (name, du)
70          _ -> Bad $ "Cannot parse line " ++ line
71
72 -- | External tool data loader from a variety of sources.
73 loadExternalData :: Options
74                  -> IO ClusterData
75 loadExternalData opts = do
76   let mhost = optMaster opts
77       lsock = optLuxi opts
78       tfile = optDataFile opts
79       simdata = optNodeSim opts
80       setRapi = mhost /= ""
81       setLuxi = isJust lsock
82       setSim = (not . null) simdata
83       setFile = isJust tfile
84       allSet = filter id [setRapi, setLuxi, setFile]
85       exTags = case optExTags opts of
86                  Nothing -> []
87                  Just etl -> map (++ ":") etl
88       selInsts = optSelInst opts
89       exInsts = optExInst opts
90
91   when (length allSet > 1) $
92        do
93          hPutStrLn stderr ("Error: Only one of the rapi, luxi, and data" ++
94                            " files options should be given.")
95          exitWith $ ExitFailure 1
96
97   util_contents <- (case optDynuFile opts of
98                       Just path -> readFile path
99                       Nothing -> return "")
100   let util_data = mapM parseUtilisation $ lines util_contents
101   util_data' <- (case util_data of
102                    Ok x -> return x
103                    Bad y -> do
104                      hPutStrLn stderr ("Error: can't parse utilisation" ++
105                                        " data: " ++ show y)
106                      exitWith $ ExitFailure 1)
107   input_data <-
108       case () of
109         _ | setRapi -> wrapIO $ Rapi.loadData mhost
110           | setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock
111           | setSim -> Simu.loadData simdata
112           | setFile -> wrapIO $ Text.loadData $ fromJust tfile
113           | otherwise -> return $ Bad "No backend selected! Exiting."
114
115   let ldresult = input_data >>= mergeData util_data' exTags selInsts exInsts
116   cdata <-
117       (case ldresult of
118          Ok x -> return x
119          Bad s -> do
120            hPrintf stderr
121              "Error: failed to load data, aborting. Details:\n%s\n" s:: IO ()
122            exitWith $ ExitFailure 1
123       )
124   let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata)
125
126   unless (null fix_msgs || optVerbose opts == 0) $ do
127          hPutStrLn stderr "Warning: cluster has inconsistent data:"
128          hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
129
130   return cdata {cdNodes = nl}
131
132 -- | Function to save the cluster data to a file.
133 maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to
134               -> String         -- ^ The suffix (extension) to add
135               -> String         -- ^ Informational message
136               -> ClusterData    -- ^ The cluster data
137               -> IO ()
138 maybeSaveData Nothing _ _ _ = return ()
139 maybeSaveData (Just path) ext msg cdata = do
140   let adata = Text.serializeCluster cdata
141       out_path = path <.> ext
142   writeFile out_path adata
143   hPrintf stderr "The cluster state %s has been written to file '%s'\n"
144           msg out_path