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