Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / ExtLoader.hs @ 44ffd981

History | View | Annotate | Download (10.6 kB)

1 c32c4e4d Spyros Trigazis
{-# LANGUAGE BangPatterns #-}
2 c32c4e4d Spyros Trigazis
3 525bfb36 Iustin Pop
{-| External data loader.
4 e8f89bb6 Iustin Pop
5 e8f89bb6 Iustin Pop
This module holds the external data loading, and thus is the only one
6 cf924b6d Iustin Pop
depending (via the specialized Text\/Rapi\/Luxi modules) on the actual
7 e8f89bb6 Iustin Pop
libraries implementing the low-level protocols.
8 e8f89bb6 Iustin Pop
9 e8f89bb6 Iustin Pop
-}
10 e8f89bb6 Iustin Pop
11 e8f89bb6 Iustin Pop
{-
12 e8f89bb6 Iustin Pop
13 88a10df5 Iustin Pop
Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
14 e8f89bb6 Iustin Pop
15 e8f89bb6 Iustin Pop
This program is free software; you can redistribute it and/or modify
16 e8f89bb6 Iustin Pop
it under the terms of the GNU General Public License as published by
17 e8f89bb6 Iustin Pop
the Free Software Foundation; either version 2 of the License, or
18 e8f89bb6 Iustin Pop
(at your option) any later version.
19 e8f89bb6 Iustin Pop
20 e8f89bb6 Iustin Pop
This program is distributed in the hope that it will be useful, but
21 e8f89bb6 Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
22 e8f89bb6 Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23 e8f89bb6 Iustin Pop
General Public License for more details.
24 e8f89bb6 Iustin Pop
25 e8f89bb6 Iustin Pop
You should have received a copy of the GNU General Public License
26 e8f89bb6 Iustin Pop
along with this program; if not, write to the Free Software
27 e8f89bb6 Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 e8f89bb6 Iustin Pop
02110-1301, USA.
29 e8f89bb6 Iustin Pop
30 e8f89bb6 Iustin Pop
-}
31 e8f89bb6 Iustin Pop
32 e8f89bb6 Iustin Pop
module Ganeti.HTools.ExtLoader
33 ebf38064 Iustin Pop
  ( loadExternalData
34 ebf38064 Iustin Pop
  , commonSuffix
35 ebf38064 Iustin Pop
  , maybeSaveData
36 c32c4e4d Spyros Trigazis
  , queryAllMonDDCs
37 33ce4d2d Spyros Trigazis
  , pMonDData
38 ebf38064 Iustin Pop
  ) where
39 e8f89bb6 Iustin Pop
40 cc532bdd Iustin Pop
import Control.Monad
41 30d25dd8 Iustin Pop
import Control.Exception
42 c32c4e4d Spyros Trigazis
import Data.Maybe (isJust, fromJust, catMaybes)
43 c32c4e4d Spyros Trigazis
import Network.Curl
44 4188449c Iustin Pop
import System.FilePath
45 e8f89bb6 Iustin Pop
import System.IO
46 ef947a42 Dato Simó
import System.Time (getClockTime)
47 8cd36391 Iustin Pop
import Text.Printf (hPrintf)
48 e8f89bb6 Iustin Pop
49 c32c4e4d Spyros Trigazis
import qualified Text.JSON as J
50 c62bec27 Spyros Trigazis
import qualified Data.Map as Map
51 c62bec27 Spyros Trigazis
import qualified Data.List as L
52 c32c4e4d Spyros Trigazis
53 c32c4e4d Spyros Trigazis
import qualified Ganeti.Constants as C
54 c32c4e4d Spyros Trigazis
import qualified Ganeti.DataCollectors.CPUload as CPUload
55 c32c4e4d Spyros Trigazis
import qualified Ganeti.HTools.Container as Container
56 879d9290 Iustin Pop
import qualified Ganeti.HTools.Backend.Luxi as Luxi
57 879d9290 Iustin Pop
import qualified Ganeti.HTools.Backend.Rapi as Rapi
58 879d9290 Iustin Pop
import qualified Ganeti.HTools.Backend.Simu as Simu
59 879d9290 Iustin Pop
import qualified Ganeti.HTools.Backend.Text as Text
60 879d9290 Iustin Pop
import qualified Ganeti.HTools.Backend.IAlloc as IAlloc
61 c32c4e4d Spyros Trigazis
import qualified Ganeti.HTools.Node as Node
62 c32c4e4d Spyros Trigazis
import qualified Ganeti.HTools.Instance as Instance
63 017a0c3d Iustin Pop
import Ganeti.HTools.Loader (mergeData, checkData, ClusterData(..)
64 81b6fa23 Klaus Aehlig
                            , commonSuffix, clearDynU)
65 e8f89bb6 Iustin Pop
66 01e52493 Iustin Pop
import Ganeti.BasicTypes
67 c32c4e4d Spyros Trigazis
import Ganeti.Cpu.Types
68 c32c4e4d Spyros Trigazis
import Ganeti.DataCollectors.Types
69 e8f89bb6 Iustin Pop
import Ganeti.HTools.Types
70 e8f89bb6 Iustin Pop
import Ganeti.HTools.CLI
71 c32c4e4d Spyros Trigazis
import Ganeti.JSON
72 c32c4e4d Spyros Trigazis
import Ganeti.Logging (logWarning)
73 26d62e4c Iustin Pop
import Ganeti.Utils (sepSplit, tryRead, exitIfBad, exitWhen)
74 e8f89bb6 Iustin Pop
75 525bfb36 Iustin Pop
-- | Error beautifier.
76 e8f89bb6 Iustin Pop
wrapIO :: IO (Result a) -> IO (Result a)
77 2cdaf225 Iustin Pop
wrapIO = handle (\e -> return . Bad . show $ (e::IOException))
78 e8f89bb6 Iustin Pop
79 179c0828 Iustin Pop
-- | Parses a user-supplied utilisation string.
80 aa8d2e71 Iustin Pop
parseUtilisation :: String -> Result (String, DynUtil)
81 aa8d2e71 Iustin Pop
parseUtilisation line =
82 ebf38064 Iustin Pop
  case sepSplit ' ' line of
83 ebf38064 Iustin Pop
    [name, cpu, mem, dsk, net] ->
84 ebf38064 Iustin Pop
      do
85 ebf38064 Iustin Pop
        rcpu <- tryRead name cpu
86 ebf38064 Iustin Pop
        rmem <- tryRead name mem
87 ebf38064 Iustin Pop
        rdsk <- tryRead name dsk
88 ebf38064 Iustin Pop
        rnet <- tryRead name net
89 ebf38064 Iustin Pop
        let du = DynUtil { cpuWeight = rcpu, memWeight = rmem
90 ebf38064 Iustin Pop
                         , dskWeight = rdsk, netWeight = rnet }
91 ebf38064 Iustin Pop
        return (name, du)
92 ebf38064 Iustin Pop
    _ -> Bad $ "Cannot parse line " ++ line
93 aa8d2e71 Iustin Pop
94 e8f89bb6 Iustin Pop
-- | External tool data loader from a variety of sources.
95 e8f89bb6 Iustin Pop
loadExternalData :: Options
96 017a0c3d Iustin Pop
                 -> IO ClusterData
97 e8f89bb6 Iustin Pop
loadExternalData opts = do
98 16c2369c Iustin Pop
  let mhost = optMaster opts
99 e8f89bb6 Iustin Pop
      lsock = optLuxi opts
100 16c2369c Iustin Pop
      tfile = optDataFile opts
101 e8f89bb6 Iustin Pop
      simdata = optNodeSim opts
102 4892d955 René Nussbaumer
      iallocsrc = optIAllocSrc opts
103 e8f89bb6 Iustin Pop
      setRapi = mhost /= ""
104 e8f89bb6 Iustin Pop
      setLuxi = isJust lsock
105 9983063b Iustin Pop
      setSim = (not . null) simdata
106 16c2369c Iustin Pop
      setFile = isJust tfile
107 4892d955 René Nussbaumer
      setIAllocSrc = isJust iallocsrc
108 16c2369c Iustin Pop
      allSet = filter id [setRapi, setLuxi, setFile]
109 0f15cc76 Iustin Pop
      exTags = case optExTags opts of
110 0f15cc76 Iustin Pop
                 Nothing -> []
111 0f15cc76 Iustin Pop
                 Just etl -> map (++ ":") etl
112 2d1708e0 Guido Trotter
      selInsts = optSelInst opts
113 39f979b8 Iustin Pop
      exInsts = optExInst opts
114 0f15cc76 Iustin Pop
115 88a10df5 Iustin Pop
  exitWhen (length allSet > 1) "Only one of the rapi, luxi, and data\
116 707cd3d7 Helga Velroyen
                               \ files options should be given."
117 e8f89bb6 Iustin Pop
118 3603605a Iustin Pop
  util_contents <- maybe (return "") readFile (optDynuFile opts)
119 88a10df5 Iustin Pop
  util_data <- exitIfBad "can't parse utilisation data" .
120 88a10df5 Iustin Pop
               mapM parseUtilisation $ lines util_contents
121 e8f89bb6 Iustin Pop
  input_data <-
122 ebf38064 Iustin Pop
    case () of
123 ebf38064 Iustin Pop
      _ | setRapi -> wrapIO $ Rapi.loadData mhost
124 2cdaf225 Iustin Pop
        | setLuxi -> wrapIO . Luxi.loadData $ fromJust lsock
125 ebf38064 Iustin Pop
        | setSim -> Simu.loadData simdata
126 2cdaf225 Iustin Pop
        | setFile -> wrapIO . Text.loadData $ fromJust tfile
127 2cdaf225 Iustin Pop
        | setIAllocSrc -> wrapIO . IAlloc.loadData $ fromJust iallocsrc
128 ebf38064 Iustin Pop
        | otherwise -> return $ Bad "No backend selected! Exiting."
129 ef947a42 Dato Simó
  now <- getClockTime
130 e8f89bb6 Iustin Pop
131 81b6fa23 Klaus Aehlig
  let ignoreDynU = optIgnoreDynu opts
132 81b6fa23 Klaus Aehlig
      eff_u = if ignoreDynU then [] else util_data
133 81b6fa23 Klaus Aehlig
      ldresult = input_data >>= (if ignoreDynU then clearDynU else return)
134 81b6fa23 Klaus Aehlig
                            >>= mergeData eff_u exTags selInsts exInsts now
135 88a10df5 Iustin Pop
  cdata <- exitIfBad "failed to load data, aborting" ldresult
136 c32c4e4d Spyros Trigazis
  cdata' <- if optMonD opts then queryAllMonDDCs cdata opts else return cdata
137 c32c4e4d Spyros Trigazis
  let (fix_msgs, nl) = checkData (cdNodes cdata') (cdInstances cdata')
138 e8f89bb6 Iustin Pop
139 8cd36391 Iustin Pop
  unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs
140 e8f89bb6 Iustin Pop
141 c32c4e4d Spyros Trigazis
  return cdata' {cdNodes = nl}
142 4188449c Iustin Pop
143 4188449c Iustin Pop
-- | Function to save the cluster data to a file.
144 4188449c Iustin Pop
maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to
145 4188449c Iustin Pop
              -> String         -- ^ The suffix (extension) to add
146 4188449c Iustin Pop
              -> String         -- ^ Informational message
147 4188449c Iustin Pop
              -> ClusterData    -- ^ The cluster data
148 4188449c Iustin Pop
              -> IO ()
149 4188449c Iustin Pop
maybeSaveData Nothing _ _ _ = return ()
150 4188449c Iustin Pop
maybeSaveData (Just path) ext msg cdata = do
151 4188449c Iustin Pop
  let adata = Text.serializeCluster cdata
152 4188449c Iustin Pop
      out_path = path <.> ext
153 4188449c Iustin Pop
  writeFile out_path adata
154 4188449c Iustin Pop
  hPrintf stderr "The cluster state %s has been written to file '%s'\n"
155 4188449c Iustin Pop
          msg out_path
156 c32c4e4d Spyros Trigazis
157 c32c4e4d Spyros Trigazis
-- | Type describing a data collector basic information.
158 c32c4e4d Spyros Trigazis
data DataCollector = DataCollector
159 c32c4e4d Spyros Trigazis
  { dName     :: String           -- ^ Name of the data collector
160 c32c4e4d Spyros Trigazis
  , dCategory :: Maybe DCCategory -- ^ The name of the category
161 c32c4e4d Spyros Trigazis
  }
162 c32c4e4d Spyros Trigazis
163 c32c4e4d Spyros Trigazis
-- | The actual data types for MonD's Data Collectors.
164 c32c4e4d Spyros Trigazis
data Report = CPUavgloadReport CPUavgload
165 c32c4e4d Spyros Trigazis
166 c32c4e4d Spyros Trigazis
-- | The list of Data Collectors used by hail and hbal.
167 c32c4e4d Spyros Trigazis
collectors :: Options -> [DataCollector]
168 c32c4e4d Spyros Trigazis
collectors opts =
169 c32c4e4d Spyros Trigazis
  if optIgnoreDynu opts
170 c32c4e4d Spyros Trigazis
    then []
171 c32c4e4d Spyros Trigazis
    else [ DataCollector CPUload.dcName CPUload.dcCategory ]
172 c32c4e4d Spyros Trigazis
173 c62bec27 Spyros Trigazis
-- | MonDs Data parsed by a mock file. Representing (node name, list of reports
174 c62bec27 Spyros Trigazis
-- produced by MonDs Data Collectors).
175 c62bec27 Spyros Trigazis
type MonDData = (String, [DCReport])
176 c62bec27 Spyros Trigazis
177 c62bec27 Spyros Trigazis
-- | A map storing MonDs data.
178 c62bec27 Spyros Trigazis
type MapMonDData = Map.Map String [DCReport]
179 c62bec27 Spyros Trigazis
180 c62bec27 Spyros Trigazis
-- | Parse MonD data file contents.
181 c62bec27 Spyros Trigazis
pMonDData :: String -> Result [MonDData]
182 c62bec27 Spyros Trigazis
pMonDData input =
183 c62bec27 Spyros Trigazis
  loadJSArray "Parsing MonD's answer" input >>=
184 c62bec27 Spyros Trigazis
  mapM (pMonDN . J.fromJSObject)
185 c62bec27 Spyros Trigazis
186 c62bec27 Spyros Trigazis
-- | Parse a node's JSON record.
187 c62bec27 Spyros Trigazis
pMonDN :: JSRecord -> Result MonDData
188 c62bec27 Spyros Trigazis
pMonDN a = do
189 c62bec27 Spyros Trigazis
  node <- tryFromObj "Parsing node's name" a "node"
190 c62bec27 Spyros Trigazis
  reports <- tryFromObj "Parsing node's reports" a "reports"
191 c62bec27 Spyros Trigazis
  return (node, reports)
192 c62bec27 Spyros Trigazis
193 c32c4e4d Spyros Trigazis
-- | Query all MonDs for all Data Collector.
194 c62bec27 Spyros Trigazis
queryAllMonDDCs :: ClusterData -> Options -> IO ClusterData
195 c62bec27 Spyros Trigazis
queryAllMonDDCs cdata opts = do
196 c62bec27 Spyros Trigazis
  map_mDD <-
197 c62bec27 Spyros Trigazis
    case optMonDFile opts of
198 c62bec27 Spyros Trigazis
      Nothing -> return Nothing
199 c62bec27 Spyros Trigazis
      Just fp -> do
200 c62bec27 Spyros Trigazis
        monDData_contents <- readFile fp
201 c62bec27 Spyros Trigazis
        monDData <- exitIfBad "can't parse MonD data"
202 c62bec27 Spyros Trigazis
                    . pMonDData $ monDData_contents
203 c62bec27 Spyros Trigazis
        return . Just $ Map.fromList monDData
204 c32c4e4d Spyros Trigazis
  let (ClusterData _ nl il _ _) = cdata
205 c62bec27 Spyros Trigazis
  (nl', il') <- foldM (queryAllMonDs map_mDD) (nl, il) (collectors opts)
206 c32c4e4d Spyros Trigazis
  return $ cdata {cdNodes = nl', cdInstances = il'}
207 c32c4e4d Spyros Trigazis
208 c32c4e4d Spyros Trigazis
-- | Query all MonDs for a single Data Collector.
209 c62bec27 Spyros Trigazis
queryAllMonDs :: Maybe MapMonDData -> (Node.List, Instance.List)
210 c62bec27 Spyros Trigazis
                 -> DataCollector -> IO (Node.List, Instance.List)
211 c62bec27 Spyros Trigazis
queryAllMonDs m (nl, il) dc = do
212 c62bec27 Spyros Trigazis
  elems <- mapM (queryAMonD m dc) (Container.elems nl)
213 c32c4e4d Spyros Trigazis
  let elems' = catMaybes elems
214 c32c4e4d Spyros Trigazis
  if length elems == length elems'
215 c32c4e4d Spyros Trigazis
    then
216 c32c4e4d Spyros Trigazis
      let il' = foldl updateUtilData il elems'
217 c32c4e4d Spyros Trigazis
          nl' = zip (Container.keys nl) elems'
218 c32c4e4d Spyros Trigazis
      in return (Container.fromList nl', il')
219 c32c4e4d Spyros Trigazis
    else do
220 c32c4e4d Spyros Trigazis
      logWarning $ "Didn't receive an answer by all MonDs, " ++ dName dc
221 c32c4e4d Spyros Trigazis
                   ++ "'s data will be ignored."
222 c32c4e4d Spyros Trigazis
      return (nl,il)
223 c32c4e4d Spyros Trigazis
224 c32c4e4d Spyros Trigazis
-- | Query a specified MonD for a Data Collector.
225 c32c4e4d Spyros Trigazis
fromCurl :: DataCollector -> Node.Node -> IO (Maybe DCReport)
226 c32c4e4d Spyros Trigazis
fromCurl dc node = do
227 c32c4e4d Spyros Trigazis
  (code, !body) <-  curlGetString (prepareUrl dc node) []
228 c32c4e4d Spyros Trigazis
  case code of
229 c32c4e4d Spyros Trigazis
    CurlOK ->
230 c32c4e4d Spyros Trigazis
      case J.decodeStrict body :: J.Result DCReport of
231 c32c4e4d Spyros Trigazis
        J.Ok r -> return $ Just r
232 c32c4e4d Spyros Trigazis
        J.Error _ -> return Nothing
233 c32c4e4d Spyros Trigazis
    _ -> do
234 c32c4e4d Spyros Trigazis
      logWarning $ "Failed to contact node's " ++ Node.name node
235 c32c4e4d Spyros Trigazis
                   ++ " MonD for DC " ++ dName dc
236 c32c4e4d Spyros Trigazis
      return Nothing
237 c32c4e4d Spyros Trigazis
238 c32c4e4d Spyros Trigazis
-- | Return the data from correct combination of a Data Collector
239 c32c4e4d Spyros Trigazis
-- and a DCReport.
240 c32c4e4d Spyros Trigazis
mkReport :: DataCollector -> Maybe DCReport -> Maybe Report
241 c32c4e4d Spyros Trigazis
mkReport dc dcr =
242 c32c4e4d Spyros Trigazis
  case dcr of
243 c32c4e4d Spyros Trigazis
    Nothing -> Nothing
244 c32c4e4d Spyros Trigazis
    Just dcr' ->
245 c32c4e4d Spyros Trigazis
      case () of
246 c32c4e4d Spyros Trigazis
           _ | CPUload.dcName == dName dc ->
247 c32c4e4d Spyros Trigazis
                 case fromJVal (dcReportData dcr') :: Result CPUavgload of
248 c32c4e4d Spyros Trigazis
                   Ok cav -> Just $ CPUavgloadReport cav
249 c32c4e4d Spyros Trigazis
                   Bad _ -> Nothing
250 c32c4e4d Spyros Trigazis
             | otherwise -> Nothing
251 c32c4e4d Spyros Trigazis
252 c62bec27 Spyros Trigazis
-- | Get data report for the specified Data Collector and Node from the map.
253 c62bec27 Spyros Trigazis
fromFile :: DataCollector -> Node.Node -> MapMonDData -> Maybe DCReport
254 c62bec27 Spyros Trigazis
fromFile dc node m =
255 c62bec27 Spyros Trigazis
  let matchDCName dcr = dName dc == dcReportName dcr
256 c62bec27 Spyros Trigazis
  in maybe Nothing (L.find matchDCName) $ Map.lookup (Node.name node) m
257 c62bec27 Spyros Trigazis
258 c32c4e4d Spyros Trigazis
-- | Query a MonD for a single Data Collector.
259 c62bec27 Spyros Trigazis
queryAMonD :: Maybe MapMonDData -> DataCollector -> Node.Node
260 c62bec27 Spyros Trigazis
              -> IO (Maybe Node.Node)
261 c62bec27 Spyros Trigazis
queryAMonD m dc node = do
262 c62bec27 Spyros Trigazis
  dcReport <-
263 c62bec27 Spyros Trigazis
    case m of
264 c62bec27 Spyros Trigazis
      Nothing -> fromCurl dc node
265 c62bec27 Spyros Trigazis
      Just m' -> return $ fromFile dc node m'
266 c32c4e4d Spyros Trigazis
  case mkReport dc dcReport of
267 c32c4e4d Spyros Trigazis
    Nothing -> return Nothing
268 c32c4e4d Spyros Trigazis
    Just report ->
269 c32c4e4d Spyros Trigazis
      case report of
270 c32c4e4d Spyros Trigazis
        CPUavgloadReport cav ->
271 c32c4e4d Spyros Trigazis
          let ct = cavCpuTotal cav
272 c32c4e4d Spyros Trigazis
              du = Node.utilLoad node
273 c32c4e4d Spyros Trigazis
              du' = du {cpuWeight = ct}
274 c32c4e4d Spyros Trigazis
          in return $ Just node {Node.utilLoad = du'}
275 c32c4e4d Spyros Trigazis
276 c32c4e4d Spyros Trigazis
-- | Update utilization data.
277 c32c4e4d Spyros Trigazis
updateUtilData :: Instance.List -> Node.Node -> Instance.List
278 c32c4e4d Spyros Trigazis
updateUtilData il node =
279 c32c4e4d Spyros Trigazis
  let ct = cpuWeight (Node.utilLoad node)
280 c32c4e4d Spyros Trigazis
      n_uCpu = Node.uCpu node
281 c32c4e4d Spyros Trigazis
      upd inst =
282 c32c4e4d Spyros Trigazis
        if Node.idx node == Instance.pNode inst
283 c32c4e4d Spyros Trigazis
          then
284 c32c4e4d Spyros Trigazis
            let i_vcpus = Instance.vcpus inst
285 c32c4e4d Spyros Trigazis
                i_util = ct / fromIntegral n_uCpu * fromIntegral i_vcpus
286 c32c4e4d Spyros Trigazis
                i_du = Instance.util inst
287 c32c4e4d Spyros Trigazis
                i_du' = i_du {cpuWeight = i_util}
288 c32c4e4d Spyros Trigazis
            in inst {Instance.util = i_du'}
289 c32c4e4d Spyros Trigazis
          else inst
290 c32c4e4d Spyros Trigazis
  in Container.map upd il
291 c32c4e4d Spyros Trigazis
292 c32c4e4d Spyros Trigazis
-- | Prepare url to query a single collector.
293 c32c4e4d Spyros Trigazis
prepareUrl :: DataCollector -> Node.Node -> URLString
294 c32c4e4d Spyros Trigazis
prepareUrl dc node =
295 c32c4e4d Spyros Trigazis
  Node.name node ++ ":" ++ show C.defaultMondPort ++ "/"
296 c32c4e4d Spyros Trigazis
  ++ show C.mondLatestApiVersion ++ "/report/" ++
297 c32c4e4d Spyros Trigazis
  getDCCName (dCategory dc) ++ "/" ++ dName dc
298 c32c4e4d Spyros Trigazis
299 c32c4e4d Spyros Trigazis
-- | Get Category Name.
300 c32c4e4d Spyros Trigazis
getDCCName :: Maybe DCCategory -> String
301 c32c4e4d Spyros Trigazis
getDCCName dcc =
302 c32c4e4d Spyros Trigazis
  case dcc of
303 c32c4e4d Spyros Trigazis
    Nothing -> "default"
304 c32c4e4d Spyros Trigazis
    Just c -> getCategoryName c