Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / ConfigReader.hs @ 07e68848

History | View | Annotate | Download (11.7 kB)

1
{-# LANGUAGE BangPatterns #-}
2

    
3
{-| Implementation of configuration reader with watching support.
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2011, 2012, 2013 Google Inc.
10

    
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of the GNU General Public License as published by
13
the Free Software Foundation; either version 2 of the License, or
14
(at your option) any later version.
15

    
16
This program is distributed in the hope that it will be useful, but
17
WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19
General Public License for more details.
20

    
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Ganeti.ConfigReader
29
  ( ConfigReader
30
  , initConfigReader
31
  ) where
32

    
33
import Control.Concurrent
34
import Control.Exception
35
import Control.Monad (liftM, unless)
36
import Data.IORef
37
import System.Posix.Files
38
import System.Posix.Types
39
import System.INotify
40

    
41
import Ganeti.BasicTypes
42
import Ganeti.Objects
43
import Ganeti.Confd.Utils
44
import Ganeti.Config
45
import Ganeti.Logging
46
import qualified Ganeti.Constants as C
47
import qualified Ganeti.Path as Path
48
import Ganeti.Utils
49

    
50
-- | A type for functions that can return the configuration when
51
-- executed.
52
type ConfigReader = IO (Result ConfigData)
53

    
54
-- | File stat identifier.
55
type FStat = (EpochTime, FileID, FileOffset)
56

    
57
-- | Null 'FStat' value.
58
nullFStat :: FStat
59
nullFStat = (-1, -1, -1)
60

    
61
-- | Reload model data type.
62
data ReloadModel = ReloadNotify      -- ^ We are using notifications
63
                 | ReloadPoll Int    -- ^ We are using polling
64
                   deriving (Eq, Show)
65

    
66
-- | Server state data type.
67
data ServerState = ServerState
68
  { reloadModel  :: ReloadModel
69
  , reloadTime   :: Integer      -- ^ Reload time (epoch) in microseconds
70
  , reloadFStat  :: FStat
71
  }
72

    
73
-- | Maximum no-reload poll rounds before reverting to inotify.
74
maxIdlePollRounds :: Int
75
maxIdlePollRounds = 3
76

    
77
-- | Reload timeout in microseconds.
78
watchInterval :: Int
79
watchInterval = C.confdConfigReloadTimeout * 1000000
80

    
81
-- | Ratelimit timeout in microseconds.
82
pollInterval :: Int
83
pollInterval = C.confdConfigReloadRatelimit
84

    
85
-- | Ratelimit timeout in microseconds, as an 'Integer'.
86
reloadRatelimit :: Integer
87
reloadRatelimit = fromIntegral C.confdConfigReloadRatelimit
88

    
89
-- | Initial poll round.
90
initialPoll :: ReloadModel
91
initialPoll = ReloadPoll 0
92

    
93
-- | Reload status data type.
94
data ConfigReload = ConfigToDate    -- ^ No need to reload
95
                  | ConfigReloaded  -- ^ Configuration reloaded
96
                  | ConfigIOError   -- ^ Error during configuration reload
97
                    deriving (Eq)
98

    
99
-- * Configuration handling
100

    
101
-- ** Helper functions
102

    
103
-- | Helper function for logging transition into polling mode.
104
moveToPolling :: String -> INotify -> FilePath -> (Result ConfigData -> IO ())
105
              -> MVar ServerState -> IO ReloadModel
106
moveToPolling msg inotify path save_fn mstate = do
107
  logInfo $ "Moving to polling mode: " ++ msg
108
  let inotiaction = addNotifier inotify path save_fn mstate
109
  _ <- forkIO $ onPollTimer inotiaction path save_fn mstate
110
  return initialPoll
111

    
112
-- | Helper function for logging transition into inotify mode.
113
moveToNotify :: IO ReloadModel
114
moveToNotify = do
115
  logInfo "Moving to inotify mode"
116
  return ReloadNotify
117

    
118
-- ** Configuration loading
119

    
120
-- | (Re)loads the configuration.
121
updateConfig :: FilePath -> (Result ConfigData -> IO ()) -> IO ()
122
updateConfig path save_fn = do
123
  newcfg <- loadConfig path
124
  let !newdata = case newcfg of
125
                   Ok !cfg -> Ok cfg
126
                   Bad msg -> Bad $ "Cannot load configuration from " ++ path
127
                                    ++ ": " ++ msg
128
  save_fn newdata
129
  case newcfg of
130
    Ok cfg -> logInfo ("Loaded new config, serial " ++
131
                       show (configSerial cfg))
132
    Bad msg -> logError $ "Failed to load config: " ++ msg
133
  return ()
134

    
135
-- | Wrapper over 'updateConfig' that handles IO errors.
136
safeUpdateConfig :: FilePath -> FStat -> (Result ConfigData -> IO ())
137
                 -> IO (FStat, ConfigReload)
138
safeUpdateConfig path oldfstat save_fn =
139
  Control.Exception.catch
140
        (do
141
          nt <- needsReload oldfstat path
142
          case nt of
143
            Nothing -> return (oldfstat, ConfigToDate)
144
            Just nt' -> do
145
                    updateConfig path save_fn
146
                    return (nt', ConfigReloaded)
147
        ) (\e -> do
148
             let msg = "Failure during configuration update: " ++
149
                       show (e::IOError)
150
             save_fn $ Bad msg
151
             return (nullFStat, ConfigIOError)
152
          )
153

    
154
-- | Computes the file cache data from a FileStatus structure.
155
buildFileStatus :: FileStatus -> FStat
156
buildFileStatus ofs =
157
    let modt = modificationTime ofs
158
        inum = fileID ofs
159
        fsize = fileSize ofs
160
    in (modt, inum, fsize)
161

    
162
-- | Wrapper over 'buildFileStatus'. This reads the data from the
163
-- filesystem and then builds our cache structure.
164
getFStat :: FilePath -> IO FStat
165
getFStat p = liftM buildFileStatus (getFileStatus p)
166

    
167
-- | Check if the file needs reloading
168
needsReload :: FStat -> FilePath -> IO (Maybe FStat)
169
needsReload oldstat path = do
170
  newstat <- getFStat path
171
  return $ if newstat /= oldstat
172
             then Just newstat
173
             else Nothing
174

    
175
-- ** Watcher threads
176

    
177
-- $watcher
178
-- We have three threads/functions that can mutate the server state:
179
--
180
-- 1. the long-interval watcher ('onWatcherTimer')
181
--
182
-- 2. the polling watcher ('onPollTimer')
183
--
184
-- 3. the inotify event handler ('onInotify')
185
--
186
-- All of these will mutate the server state under 'modifyMVar' or
187
-- 'modifyMVar_', so that server transitions are more or less
188
-- atomic. The inotify handler remains active during polling mode, but
189
-- checks for polling mode and doesn't do anything in this case (this
190
-- check is needed even if we would unregister the event handler due
191
-- to how events are serialised).
192

    
193
-- | Long-interval reload watcher.
194
--
195
-- This is on top of the inotify-based triggered reload.
196
onWatcherTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
197
               -> MVar ServerState -> IO ()
198
onWatcherTimer inotiaction path save_fn state = do
199
  threadDelay watchInterval
200
  logDebug "Watcher timer fired"
201
  modifyMVar_ state (onWatcherInner path save_fn)
202
  _ <- inotiaction
203
  onWatcherTimer inotiaction path save_fn state
204

    
205
-- | Inner onWatcher handler.
206
--
207
-- This mutates the server state under a modifyMVar_ call. It never
208
-- changes the reload model, just does a safety reload and tried to
209
-- re-establish the inotify watcher.
210
onWatcherInner :: FilePath -> (Result ConfigData -> IO ()) -> ServerState
211
               -> IO ServerState
212
onWatcherInner path save_fn state  = do
213
  (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
214
  return state { reloadFStat = newfstat }
215

    
216
-- | Short-interval (polling) reload watcher.
217
--
218
-- This is only active when we're in polling mode; it will
219
-- automatically exit when it detects that the state has changed to
220
-- notification.
221
onPollTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
222
            -> MVar ServerState -> IO ()
223
onPollTimer inotiaction path save_fn state = do
224
  threadDelay pollInterval
225
  logDebug "Poll timer fired"
226
  continue <- modifyMVar state (onPollInner inotiaction path save_fn)
227
  if continue
228
    then onPollTimer inotiaction path save_fn state
229
    else logDebug "Inotify watch active, polling thread exiting"
230

    
231
-- | Inner onPoll handler.
232
--
233
-- This again mutates the state under a modifyMVar call, and also
234
-- returns whether the thread should continue or not.
235
onPollInner :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
236
            -> ServerState -> IO (ServerState, Bool)
237
onPollInner _ _ _ state@(ServerState { reloadModel = ReloadNotify } ) =
238
  return (state, False)
239
onPollInner inotiaction path save_fn
240
            state@(ServerState { reloadModel = ReloadPoll pround } ) = do
241
  (newfstat, reload) <- safeUpdateConfig path (reloadFStat state) save_fn
242
  let state' = state { reloadFStat = newfstat }
243
  -- compute new poll model based on reload data; however, failure to
244
  -- re-establish the inotifier means we stay on polling
245
  newmode <- case reload of
246
               ConfigToDate ->
247
                 if pround >= maxIdlePollRounds
248
                   then do -- try to switch to notify
249
                     result <- inotiaction
250
                     if result
251
                       then moveToNotify
252
                       else return initialPoll
253
                   else return (ReloadPoll (pround + 1))
254
               _ -> return initialPoll
255
  let continue = case newmode of
256
                   ReloadNotify -> False
257
                   _            -> True
258
  return (state' { reloadModel = newmode }, continue)
259

    
260
-- the following hint is because hlint doesn't understand our const
261
-- (return False) is so that we can give a signature to 'e'
262
{-# ANN addNotifier "HLint: ignore Evaluate" #-}
263
-- | Setup inotify watcher.
264
--
265
-- This tries to setup the watch descriptor; in case of any IO errors,
266
-- it will return False.
267
addNotifier :: INotify -> FilePath -> (Result ConfigData -> IO ())
268
            -> MVar ServerState -> IO Bool
269
addNotifier inotify path save_fn mstate =
270
  Control.Exception.catch
271
        (addWatch inotify [CloseWrite] path
272
            (onInotify inotify path save_fn mstate) >> return True)
273
        (\e -> const (return False) (e::IOError))
274

    
275
-- | Inotify event handler.
276
onInotify :: INotify -> String -> (Result ConfigData -> IO ())
277
          -> MVar ServerState -> Event -> IO ()
278
onInotify inotify path save_fn mstate Ignored = do
279
  logDebug "File lost, trying to re-establish notifier"
280
  modifyMVar_ mstate $ \state -> do
281
    result <- addNotifier inotify path save_fn mstate
282
    (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
283
    let state' = state { reloadFStat = newfstat }
284
    if result
285
      then return state' -- keep notify
286
      else do
287
        mode <- moveToPolling "cannot re-establish inotify watch" inotify
288
                  path save_fn mstate
289
        return state' { reloadModel = mode }
290

    
291
onInotify inotify path save_fn mstate _ =
292
  modifyMVar_ mstate $ \state ->
293
    if reloadModel state == ReloadNotify
294
       then do
295
         ctime <- getCurrentTimeUSec
296
         (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
297
         let state' = state { reloadFStat = newfstat, reloadTime = ctime }
298
         if abs (reloadTime state - ctime) < reloadRatelimit
299
           then do
300
             mode <- moveToPolling "too many reloads" inotify path save_fn
301
                                   mstate
302
             return state' { reloadModel = mode }
303
           else return state'
304
      else return state
305

    
306
initConfigReader :: (Result ConfigData -> a) -> IORef a -> IO ()
307
initConfigReader cfg_transform ioref = do
308
  let save_fn = writeIORef ioref . cfg_transform
309

    
310
  -- Inotify setup
311
  inotify <- initINotify
312
  -- try to load the configuration, if possible
313
  conf_file <- Path.clusterConfFile
314
  (fstat, reloaded) <- safeUpdateConfig conf_file nullFStat save_fn
315
  ctime <- getCurrentTime
316
  statemvar <- newMVar $ ServerState ReloadNotify ctime fstat
317
  let inotiaction = addNotifier inotify conf_file save_fn statemvar
318
  has_inotify <- if reloaded == ConfigReloaded
319
                   then inotiaction
320
                   else return False
321
  if has_inotify
322
    then logInfo "Starting up in inotify mode"
323
    else do
324
      -- inotify was not enabled, we need to update the reload model
325
      logInfo "Starting up in polling mode"
326
      modifyMVar_ statemvar
327
        (\state -> return state { reloadModel = initialPoll })
328
  -- fork the timeout timer
329
  _ <- forkIO $ onWatcherTimer inotiaction conf_file save_fn statemvar
330
  -- fork the polling timer
331
  unless has_inotify $ do
332
    _ <- forkIO $ onPollTimer inotiaction conf_file save_fn statemvar
333
    return ()