Improve hspace shrinking strategy
[ganeti-local] / src / 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, 2012 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 Control.Exception
38 import Data.Maybe (isJust, fromJust)
39 import System.FilePath
40 import System.IO
41 import System.Time (getClockTime)
42 import Text.Printf (hPrintf)
43
44 import qualified Ganeti.HTools.Backend.Luxi as Luxi
45 import qualified Ganeti.HTools.Backend.Rapi as Rapi
46 import qualified Ganeti.HTools.Backend.Simu as Simu
47 import qualified Ganeti.HTools.Backend.Text as Text
48 import qualified Ganeti.HTools.Backend.IAlloc as IAlloc
49 import Ganeti.HTools.Loader (mergeData, checkData, ClusterData(..)
50                             , commonSuffix)
51
52 import Ganeti.BasicTypes
53 import Ganeti.HTools.Types
54 import Ganeti.HTools.CLI
55 import Ganeti.Utils (sepSplit, tryRead, exitIfBad, exitWhen)
56
57 -- | Error beautifier.
58 wrapIO :: IO (Result a) -> IO (Result a)
59 wrapIO = handle (\e -> return . Bad . show $ (e::IOException))
60
61 -- | Parses a user-supplied utilisation string.
62 parseUtilisation :: String -> Result (String, DynUtil)
63 parseUtilisation line =
64   case sepSplit ' ' line of
65     [name, cpu, mem, dsk, net] ->
66       do
67         rcpu <- tryRead name cpu
68         rmem <- tryRead name mem
69         rdsk <- tryRead name dsk
70         rnet <- tryRead name net
71         let du = DynUtil { cpuWeight = rcpu, memWeight = rmem
72                          , dskWeight = rdsk, netWeight = rnet }
73         return (name, du)
74     _ -> Bad $ "Cannot parse line " ++ line
75
76 -- | External tool data loader from a variety of sources.
77 loadExternalData :: Options
78                  -> IO ClusterData
79 loadExternalData opts = do
80   let mhost = optMaster opts
81       lsock = optLuxi opts
82       tfile = optDataFile opts
83       simdata = optNodeSim opts
84       iallocsrc = optIAllocSrc opts
85       setRapi = mhost /= ""
86       setLuxi = isJust lsock
87       setSim = (not . null) simdata
88       setFile = isJust tfile
89       setIAllocSrc = isJust iallocsrc
90       allSet = filter id [setRapi, setLuxi, setFile]
91       exTags = case optExTags opts of
92                  Nothing -> []
93                  Just etl -> map (++ ":") etl
94       selInsts = optSelInst opts
95       exInsts = optExInst opts
96
97   exitWhen (length allSet > 1) "Only one of the rapi, luxi, and data\
98                                \ files options should be given."
99
100   util_contents <- maybe (return "") readFile (optDynuFile opts)
101   util_data <- exitIfBad "can't parse utilisation data" .
102                mapM parseUtilisation $ lines util_contents
103   input_data <-
104     case () of
105       _ | setRapi -> wrapIO $ Rapi.loadData mhost
106         | setLuxi -> wrapIO . Luxi.loadData $ fromJust lsock
107         | setSim -> Simu.loadData simdata
108         | setFile -> wrapIO . Text.loadData $ fromJust tfile
109         | setIAllocSrc -> wrapIO . IAlloc.loadData $ fromJust iallocsrc
110         | otherwise -> return $ Bad "No backend selected! Exiting."
111   now <- getClockTime
112
113   let ldresult = input_data >>= mergeData util_data exTags selInsts exInsts now
114   cdata <- exitIfBad "failed to load data, aborting" ldresult
115   let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata)
116
117   unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs
118
119   return cdata {cdNodes = nl}
120
121 -- | Function to save the cluster data to a file.
122 maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to
123               -> String         -- ^ The suffix (extension) to add
124               -> String         -- ^ Informational message
125               -> ClusterData    -- ^ The cluster data
126               -> IO ()
127 maybeSaveData Nothing _ _ _ = return ()
128 maybeSaveData (Just path) ext msg cdata = do
129   let adata = Text.serializeCluster cdata
130       out_path = path <.> ext
131   writeFile out_path adata
132   hPrintf stderr "The cluster state %s has been written to file '%s'\n"
133           msg out_path