Revision c32c4e4d src/Ganeti/HTools/ExtLoader.hs
b/src/Ganeti/HTools/ExtLoader.hs | ||
---|---|---|
1 |
{-# LANGUAGE BangPatterns #-} |
|
2 |
|
|
1 | 3 |
{-| External data loader. |
2 | 4 |
|
3 | 5 |
This module holds the external data loading, and thus is the only one |
... | ... | |
31 | 33 |
( loadExternalData |
32 | 34 |
, commonSuffix |
33 | 35 |
, maybeSaveData |
36 |
, queryAllMonDDCs |
|
34 | 37 |
) where |
35 | 38 |
|
36 | 39 |
import Control.Monad |
37 | 40 |
import Control.Exception |
38 |
import Data.Maybe (isJust, fromJust) |
|
41 |
import Data.Maybe (isJust, fromJust, catMaybes) |
|
42 |
import Network.Curl |
|
39 | 43 |
import System.FilePath |
40 | 44 |
import System.IO |
41 | 45 |
import System.Time (getClockTime) |
42 | 46 |
import Text.Printf (hPrintf) |
43 | 47 |
|
48 |
import qualified Text.JSON as J |
|
49 |
|
|
50 |
import qualified Ganeti.Constants as C |
|
51 |
import qualified Ganeti.DataCollectors.CPUload as CPUload |
|
52 |
import qualified Ganeti.HTools.Container as Container |
|
44 | 53 |
import qualified Ganeti.HTools.Backend.Luxi as Luxi |
45 | 54 |
import qualified Ganeti.HTools.Backend.Rapi as Rapi |
46 | 55 |
import qualified Ganeti.HTools.Backend.Simu as Simu |
47 | 56 |
import qualified Ganeti.HTools.Backend.Text as Text |
48 | 57 |
import qualified Ganeti.HTools.Backend.IAlloc as IAlloc |
58 |
import qualified Ganeti.HTools.Node as Node |
|
59 |
import qualified Ganeti.HTools.Instance as Instance |
|
49 | 60 |
import Ganeti.HTools.Loader (mergeData, checkData, ClusterData(..) |
50 | 61 |
, commonSuffix, clearDynU) |
51 | 62 |
|
52 | 63 |
import Ganeti.BasicTypes |
64 |
import Ganeti.Cpu.Types |
|
65 |
import Ganeti.DataCollectors.Types |
|
53 | 66 |
import Ganeti.HTools.Types |
54 | 67 |
import Ganeti.HTools.CLI |
68 |
import Ganeti.JSON |
|
69 |
import Ganeti.Logging (logWarning) |
|
55 | 70 |
import Ganeti.Utils (sepSplit, tryRead, exitIfBad, exitWhen) |
56 | 71 |
|
57 | 72 |
-- | Error beautifier. |
... | ... | |
115 | 130 |
ldresult = input_data >>= (if ignoreDynU then clearDynU else return) |
116 | 131 |
>>= mergeData eff_u exTags selInsts exInsts now |
117 | 132 |
cdata <- exitIfBad "failed to load data, aborting" ldresult |
118 |
let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata) |
|
133 |
cdata' <- if optMonD opts then queryAllMonDDCs cdata opts else return cdata |
|
134 |
let (fix_msgs, nl) = checkData (cdNodes cdata') (cdInstances cdata') |
|
119 | 135 |
|
120 | 136 |
unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs |
121 | 137 |
|
122 |
return cdata {cdNodes = nl} |
|
138 |
return cdata' {cdNodes = nl}
|
|
123 | 139 |
|
124 | 140 |
-- | Function to save the cluster data to a file. |
125 | 141 |
maybeSaveData :: Maybe FilePath -- ^ The file prefix to save to |
... | ... | |
134 | 150 |
writeFile out_path adata |
135 | 151 |
hPrintf stderr "The cluster state %s has been written to file '%s'\n" |
136 | 152 |
msg out_path |
153 |
|
|
154 |
-- | Type describing a data collector basic information. |
|
155 |
data DataCollector = DataCollector |
|
156 |
{ dName :: String -- ^ Name of the data collector |
|
157 |
, dCategory :: Maybe DCCategory -- ^ The name of the category |
|
158 |
} |
|
159 |
|
|
160 |
-- | The actual data types for MonD's Data Collectors. |
|
161 |
data Report = CPUavgloadReport CPUavgload |
|
162 |
|
|
163 |
-- | The list of Data Collectors used by hail and hbal. |
|
164 |
collectors :: Options -> [DataCollector] |
|
165 |
collectors opts = |
|
166 |
if optIgnoreDynu opts |
|
167 |
then [] |
|
168 |
else [ DataCollector CPUload.dcName CPUload.dcCategory ] |
|
169 |
|
|
170 |
-- | Query all MonDs for all Data Collector. |
|
171 |
queryAllMonDDCs :: ClusterData -> IO ClusterData |
|
172 |
queryAllMonDDCs cdata = do |
|
173 |
let (ClusterData _ nl il _ _) = cdata |
|
174 |
(nl', il') <- foldM queryAllMonDs (nl, il) (collectors opts) |
|
175 |
return $ cdata {cdNodes = nl', cdInstances = il'} |
|
176 |
|
|
177 |
-- | Query all MonDs for a single Data Collector. |
|
178 |
queryAllMonDs :: (Node.List, Instance.List) -> DataCollector |
|
179 |
-> IO (Node.List, Instance.List) |
|
180 |
queryAllMonDs (nl, il) dc = do |
|
181 |
elems <- mapM (queryAMonD dc) (Container.elems nl) |
|
182 |
let elems' = catMaybes elems |
|
183 |
if length elems == length elems' |
|
184 |
then |
|
185 |
let il' = foldl updateUtilData il elems' |
|
186 |
nl' = zip (Container.keys nl) elems' |
|
187 |
in return (Container.fromList nl', il') |
|
188 |
else do |
|
189 |
logWarning $ "Didn't receive an answer by all MonDs, " ++ dName dc |
|
190 |
++ "'s data will be ignored." |
|
191 |
return (nl,il) |
|
192 |
|
|
193 |
-- | Query a specified MonD for a Data Collector. |
|
194 |
fromCurl :: DataCollector -> Node.Node -> IO (Maybe DCReport) |
|
195 |
fromCurl dc node = do |
|
196 |
(code, !body) <- curlGetString (prepareUrl dc node) [] |
|
197 |
case code of |
|
198 |
CurlOK -> |
|
199 |
case J.decodeStrict body :: J.Result DCReport of |
|
200 |
J.Ok r -> return $ Just r |
|
201 |
J.Error _ -> return Nothing |
|
202 |
_ -> do |
|
203 |
logWarning $ "Failed to contact node's " ++ Node.name node |
|
204 |
++ " MonD for DC " ++ dName dc |
|
205 |
return Nothing |
|
206 |
|
|
207 |
-- | Return the data from correct combination of a Data Collector |
|
208 |
-- and a DCReport. |
|
209 |
mkReport :: DataCollector -> Maybe DCReport -> Maybe Report |
|
210 |
mkReport dc dcr = |
|
211 |
case dcr of |
|
212 |
Nothing -> Nothing |
|
213 |
Just dcr' -> |
|
214 |
case () of |
|
215 |
_ | CPUload.dcName == dName dc -> |
|
216 |
case fromJVal (dcReportData dcr') :: Result CPUavgload of |
|
217 |
Ok cav -> Just $ CPUavgloadReport cav |
|
218 |
Bad _ -> Nothing |
|
219 |
| otherwise -> Nothing |
|
220 |
|
|
221 |
-- | Query a MonD for a single Data Collector. |
|
222 |
queryAMonD :: DataCollector -> Node.Node -> IO (Maybe Node.Node) |
|
223 |
queryAMonD dc node = do |
|
224 |
dcReport <- fromCurl dc node |
|
225 |
case mkReport dc dcReport of |
|
226 |
Nothing -> return Nothing |
|
227 |
Just report -> |
|
228 |
case report of |
|
229 |
CPUavgloadReport cav -> |
|
230 |
let ct = cavCpuTotal cav |
|
231 |
du = Node.utilLoad node |
|
232 |
du' = du {cpuWeight = ct} |
|
233 |
in return $ Just node {Node.utilLoad = du'} |
|
234 |
|
|
235 |
-- | Update utilization data. |
|
236 |
updateUtilData :: Instance.List -> Node.Node -> Instance.List |
|
237 |
updateUtilData il node = |
|
238 |
let ct = cpuWeight (Node.utilLoad node) |
|
239 |
n_uCpu = Node.uCpu node |
|
240 |
upd inst = |
|
241 |
if Node.idx node == Instance.pNode inst |
|
242 |
then |
|
243 |
let i_vcpus = Instance.vcpus inst |
|
244 |
i_util = ct / fromIntegral n_uCpu * fromIntegral i_vcpus |
|
245 |
i_du = Instance.util inst |
|
246 |
i_du' = i_du {cpuWeight = i_util} |
|
247 |
in inst {Instance.util = i_du'} |
|
248 |
else inst |
|
249 |
in Container.map upd il |
|
250 |
|
|
251 |
-- | Prepare url to query a single collector. |
|
252 |
prepareUrl :: DataCollector -> Node.Node -> URLString |
|
253 |
prepareUrl dc node = |
|
254 |
Node.name node ++ ":" ++ show C.defaultMondPort ++ "/" |
|
255 |
++ show C.mondLatestApiVersion ++ "/report/" ++ |
|
256 |
getDCCName (dCategory dc) ++ "/" ++ dName dc |
|
257 |
|
|
258 |
-- | Get Category Name. |
|
259 |
getDCCName :: Maybe DCCategory -> String |
|
260 |
getDCCName dcc = |
|
261 |
case dcc of |
|
262 |
Nothing -> "default" |
|
263 |
Just c -> getCategoryName c |
Also available in: Unified diff