Revision ef947a42
b/src/Ganeti/HTools/Backend/IAlloc.hs | ||
---|---|---|
34 | 34 |
import Data.Maybe (fromMaybe, isJust, fromJust) |
35 | 35 |
import Data.List |
36 | 36 |
import Control.Monad |
37 |
import System.Time |
|
37 | 38 |
import Text.JSON (JSObject, JSValue(JSArray), |
38 | 39 |
makeObj, encodeStrict, decodeStrict, fromJSObject, showJSON) |
39 | 40 |
|
... | ... | |
138 | 139 |
-- The result is a tuple of eventual warning messages and the parsed |
139 | 140 |
-- request; if parsing the input data fails, we'll return a 'Bad' |
140 | 141 |
-- value. |
141 |
parseData :: String -- ^ The JSON message as received from Ganeti |
|
142 |
parseData :: ClockTime -- ^ The current time |
|
143 |
-> String -- ^ The JSON message as received from Ganeti |
|
142 | 144 |
-> Result ([String], Request) -- ^ Result tuple |
143 |
parseData body = do |
|
145 |
parseData now body = do
|
|
144 | 146 |
decoded <- fromJResult "Parsing input IAllocator message" (decodeStrict body) |
145 | 147 |
let obj = fromJSObject decoded |
146 | 148 |
extrObj x = tryFromObj "invalid iallocator message" obj x |
... | ... | |
165 | 167 |
let (kti, il) = assignIndices iobj |
166 | 168 |
-- cluster tags |
167 | 169 |
ctags <- extrObj "cluster_tags" |
168 |
cdata1 <- mergeData [] [] [] [] (ClusterData gl nl il ctags defIPolicy) |
|
170 |
cdata1 <- mergeData [] [] [] [] now (ClusterData gl nl il ctags defIPolicy)
|
|
169 | 171 |
let (msgs, fix_nl) = checkData (cdNodes cdata1) (cdInstances cdata1) |
170 | 172 |
cdata = cdata1 { cdNodes = fix_nl } |
171 | 173 |
map_n = cdNodes cdata |
... | ... | |
380 | 382 |
-- | Reads the request from the data file(s). |
381 | 383 |
readRequest :: FilePath -> IO Request |
382 | 384 |
readRequest fp = do |
385 |
now <- getClockTime |
|
383 | 386 |
input_data <- case fp of |
384 | 387 |
"-" -> getContents |
385 | 388 |
_ -> readFile fp |
386 |
case parseData input_data of |
|
389 |
case parseData now input_data of
|
|
387 | 390 |
Bad err -> exitErr err |
388 | 391 |
Ok (fix_msgs, rq) -> maybeShowWarnings fix_msgs >> return rq |
389 | 392 |
|
b/src/Ganeti/HTools/ExtLoader.hs | ||
---|---|---|
38 | 38 |
import Data.Maybe (isJust, fromJust) |
39 | 39 |
import System.FilePath |
40 | 40 |
import System.IO |
41 |
import System.Time (getClockTime) |
|
41 | 42 |
import Text.Printf (hPrintf) |
42 | 43 |
|
43 | 44 |
import qualified Ganeti.HTools.Backend.Luxi as Luxi |
... | ... | |
107 | 108 |
| setFile -> wrapIO . Text.loadData $ fromJust tfile |
108 | 109 |
| setIAllocSrc -> wrapIO . IAlloc.loadData $ fromJust iallocsrc |
109 | 110 |
| otherwise -> return $ Bad "No backend selected! Exiting." |
111 |
now <- getClockTime |
|
110 | 112 |
|
111 |
let ldresult = input_data >>= mergeData util_data exTags selInsts exInsts |
|
113 |
let ldresult = input_data >>= mergeData util_data exTags selInsts exInsts now
|
|
112 | 114 |
cdata <- exitIfBad "failed to load data, aborting" ldresult |
113 | 115 |
let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata) |
114 | 116 |
|
b/src/Ganeti/HTools/Loader.hs | ||
---|---|---|
182 | 182 |
-> Group.List -- ^ List of node groups |
183 | 183 |
-> Node.List -- ^ List of nodes |
184 | 184 |
-> Instance.List -- ^ List of instances |
185 |
-> ClockTime -- ^ Current timestamp, to evaluate ArSuspended |
|
185 | 186 |
-> Instance.List -- ^ Updated list of instances |
186 |
setArPolicy ctags gl nl il = |
|
187 |
let cpol = fromMaybe ArNotEnabled $ getArPolicy ctags |
|
188 |
gpols = Container.map (fromMaybe cpol . getArPolicy . Group.allTags) gl |
|
189 |
ipolfn = getArPolicy . Instance.allTags |
|
187 |
setArPolicy ctags gl nl il time = |
|
188 |
let getArPolicy' = flip getArPolicy time |
|
189 |
cpol = fromMaybe ArNotEnabled $ getArPolicy' ctags |
|
190 |
gpols = Container.map (fromMaybe cpol . getArPolicy' . Group.allTags) gl |
|
191 |
ipolfn = getArPolicy' . Instance.allTags |
|
190 | 192 |
nlookup = flip Container.find nl . Instance.pNode |
191 | 193 |
glookup = flip Container.find gpols . Node.group . nlookup |
192 | 194 |
updateInstance inst = inst { |
... | ... | |
199 | 201 |
-- This examines the ganeti:watcher:autorepair and |
200 | 202 |
-- ganeti:watcher:autorepair:suspend tags to determine the policy. If none of |
201 | 203 |
-- these tags are present, Nothing (and not ArNotEnabled) is returned. |
202 |
getArPolicy :: [String] -> Maybe AutoRepairPolicy |
|
203 |
getArPolicy tags = |
|
204 |
getArPolicy :: [String] -> ClockTime -> Maybe AutoRepairPolicy
|
|
205 |
getArPolicy tags time =
|
|
204 | 206 |
let enabled = mapMaybe (autoRepairTypeFromRaw <=< |
205 | 207 |
chompPrefix C.autoRepairTagEnabled) tags |
206 | 208 |
suspended = mapMaybe (chompPrefix C.autoRepairTagSuspended) tags |
207 |
suspTime = if "" `elem` suspended |
|
208 |
then Forever |
|
209 |
else Until . flip TOD 0 . maximum $ |
|
210 |
mapMaybe (tryRead "auto-repair suspend time") suspended |
|
209 |
futureTs = filter (> time) . map (flip TOD 0) $ |
|
210 |
mapMaybe (tryRead "auto-repair suspend time") suspended |
|
211 | 211 |
in |
212 | 212 |
case () of |
213 | 213 |
-- Note how we must return ArSuspended even if "enabled" is empty, so that |
214 | 214 |
-- node groups or instances can suspend repairs that were enabled at an |
215 | 215 |
-- upper scope (cluster or node group). |
216 |
_ | not $ null suspended -> Just $ ArSuspended suspTime |
|
217 |
| not $ null enabled -> Just $ ArEnabled (minimum enabled) |
|
218 |
| otherwise -> Nothing |
|
216 |
_ | "" `elem` suspended -> Just $ ArSuspended Forever |
|
217 |
| not $ null futureTs -> Just . ArSuspended . Until . maximum $ futureTs |
|
218 |
| not $ null enabled -> Just $ ArEnabled (minimum enabled) |
|
219 |
| otherwise -> Nothing |
|
219 | 220 |
|
220 | 221 |
-- | Compute the longest common suffix of a list of strings that |
221 | 222 |
-- starts with a dot. |
... | ... | |
244 | 245 |
-> [String] -- ^ Exclusion tags |
245 | 246 |
-> [String] -- ^ Selected instances (if not empty) |
246 | 247 |
-> [String] -- ^ Excluded instances |
248 |
-> ClockTime -- ^ The current timestamp |
|
247 | 249 |
-> ClusterData -- ^ Data from backends |
248 | 250 |
-> Result ClusterData -- ^ Fixed cluster data |
249 |
mergeData um extags selinsts exinsts cdata@(ClusterData gl nl il ctags _) = |
|
250 |
let il2 = setArPolicy ctags gl nl il |
|
251 |
mergeData um extags selinsts exinsts time cdata@(ClusterData gl nl il ctags _) =
|
|
252 |
let il2 = setArPolicy ctags gl nl il time
|
|
251 | 253 |
il3 = foldl' (\im (name, n_util) -> |
252 | 254 |
case Container.findByName im name of |
253 | 255 |
Nothing -> im -- skipping unknown instance |
b/src/Ganeti/HTools/Program/Hscan.hs | ||
---|---|---|
34 | 34 |
import System.Exit |
35 | 35 |
import System.IO |
36 | 36 |
import System.FilePath |
37 |
import System.Time |
|
37 | 38 |
|
38 | 39 |
import Text.Printf (printf) |
39 | 40 |
|
... | ... | |
89 | 90 |
fixSlash = map (\x -> if x == '/' then '_' else x) |
90 | 91 |
|
91 | 92 |
-- | Generates serialized data from loader input. |
92 |
processData :: ClusterData -> Result ClusterData |
|
93 |
processData input_data = do |
|
94 |
cdata@(ClusterData _ nl il _ _) <- mergeData [] [] [] [] input_data |
|
93 |
processData :: ClockTime -> ClusterData -> Result ClusterData
|
|
94 |
processData now input_data = do
|
|
95 |
cdata@(ClusterData _ nl il _ _) <- mergeData [] [] [] [] now input_data
|
|
95 | 96 |
let (_, fix_nl) = checkData nl il |
96 | 97 |
return cdata { cdNodes = fix_nl } |
97 | 98 |
|
... | ... | |
106 | 107 |
return False |
107 | 108 |
|
108 | 109 |
writeData nlen name opts (Ok cdata) = do |
109 |
let fixdata = processData cdata |
|
110 |
now <- getClockTime |
|
111 |
let fixdata = processData now cdata |
|
110 | 112 |
case fixdata of |
111 | 113 |
Bad err -> printf "\nError for %s: failed to process data. Details:\n%s\n" |
112 | 114 |
name err >> return False |
b/test/hs/Test/Ganeti/HTools/Backend/Text.hs | ||
---|---|---|
33 | 33 |
import qualified Data.Map as Map |
34 | 34 |
import Data.List |
35 | 35 |
import Data.Maybe |
36 |
import System.Time (ClockTime(..)) |
|
36 | 37 |
|
37 | 38 |
import Test.Ganeti.TestHelper |
38 | 39 |
import Test.Ganeti.TestCommon |
... | ... | |
191 | 192 |
let cdata = Loader.ClusterData defGroupList nl' il' ctags |
192 | 193 |
Types.defIPolicy |
193 | 194 |
saved = Text.serializeCluster cdata |
194 |
in case Text.parseData saved >>= Loader.mergeData [] [] [] [] of |
|
195 |
in case Text.parseData saved >>= Loader.mergeData [] [] [] [] (TOD 0 0) |
|
196 |
of |
|
195 | 197 |
Bad msg -> failTest $ "Failed to load/merge: " ++ msg |
196 | 198 |
Ok (Loader.ClusterData gl2 nl2 il2 ctags2 cpol2) -> |
197 | 199 |
conjoin [ ctags ==? ctags2 |
b/test/hs/Test/Ganeti/HTools/Loader.hs | ||
---|---|---|
33 | 33 |
import qualified Data.IntMap as IntMap |
34 | 34 |
import qualified Data.Map as Map |
35 | 35 |
import Data.List |
36 |
import System.Time (ClockTime(..)) |
|
36 | 37 |
|
37 | 38 |
import Test.Ganeti.TestHelper |
38 | 39 |
import Test.Ganeti.TestCommon |
... | ... | |
71 | 72 |
prop_mergeData :: [Node.Node] -> Bool |
72 | 73 |
prop_mergeData ns = |
73 | 74 |
let na = Container.fromList $ map (\n -> (Node.idx n, n)) ns |
74 |
in case Loader.mergeData [] [] [] [] |
|
75 |
in case Loader.mergeData [] [] [] [] (TOD 0 0)
|
|
75 | 76 |
(Loader.emptyCluster {Loader.cdNodes = na}) of |
76 | 77 |
BasicTypes.Bad _ -> False |
77 | 78 |
BasicTypes.Ok (Loader.ClusterData _ nl il _ _) -> |
Also available in: Unified diff