Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / ConfigReader.hs @ 6fd8ceff

History | View | Annotate | Download (10.8 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 (unless)
36
import Data.IORef
37
import System.INotify
38

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

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

    
52

    
53
-- | Reload model data type.
54
data ReloadModel = ReloadNotify      -- ^ We are using notifications
55
                 | ReloadPoll Int    -- ^ We are using polling
56
                   deriving (Eq, Show)
57

    
58
-- | Server state data type.
59
data ServerState = ServerState
60
  { reloadModel  :: ReloadModel
61
  , reloadTime   :: Integer      -- ^ Reload time (epoch) in microseconds
62
  , reloadFStat  :: FStat
63
  }
64

    
65
-- | Maximum no-reload poll rounds before reverting to inotify.
66
maxIdlePollRounds :: Int
67
maxIdlePollRounds = 3
68

    
69
-- | Reload timeout in microseconds.
70
watchInterval :: Int
71
watchInterval = C.confdConfigReloadTimeout * 1000000
72

    
73
-- | Ratelimit timeout in microseconds.
74
pollInterval :: Int
75
pollInterval = C.confdConfigReloadRatelimit
76

    
77
-- | Ratelimit timeout in microseconds, as an 'Integer'.
78
reloadRatelimit :: Integer
79
reloadRatelimit = fromIntegral C.confdConfigReloadRatelimit
80

    
81
-- | Initial poll round.
82
initialPoll :: ReloadModel
83
initialPoll = ReloadPoll 0
84

    
85
-- | Reload status data type.
86
data ConfigReload = ConfigToDate    -- ^ No need to reload
87
                  | ConfigReloaded  -- ^ Configuration reloaded
88
                  | ConfigIOError   -- ^ Error during configuration reload
89
                    deriving (Eq)
90

    
91
-- * Configuration handling
92

    
93
-- ** Helper functions
94

    
95
-- | Helper function for logging transition into polling mode.
96
moveToPolling :: String -> INotify -> FilePath -> (Result ConfigData -> IO ())
97
              -> MVar ServerState -> IO ReloadModel
98
moveToPolling msg inotify path save_fn mstate = do
99
  logInfo $ "Moving to polling mode: " ++ msg
100
  let inotiaction = addNotifier inotify path save_fn mstate
101
  _ <- forkIO $ onPollTimer inotiaction path save_fn mstate
102
  return initialPoll
103

    
104
-- | Helper function for logging transition into inotify mode.
105
moveToNotify :: IO ReloadModel
106
moveToNotify = do
107
  logInfo "Moving to inotify mode"
108
  return ReloadNotify
109

    
110
-- ** Configuration loading
111

    
112
-- | (Re)loads the configuration.
113
updateConfig :: FilePath -> (Result ConfigData -> IO ()) -> IO ()
114
updateConfig path save_fn = do
115
  newcfg <- loadConfig path
116
  let !newdata = case newcfg of
117
                   Ok !cfg -> Ok cfg
118
                   Bad msg -> Bad $ "Cannot load configuration from " ++ path
119
                                    ++ ": " ++ msg
120
  save_fn newdata
121
  case newcfg of
122
    Ok cfg -> logInfo ("Loaded new config, serial " ++
123
                       show (configSerial cfg))
124
    Bad msg -> logError $ "Failed to load config: " ++ msg
125
  return ()
126

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

    
146
-- ** Watcher threads
147

    
148
-- $watcher
149
-- We have three threads/functions that can mutate the server state:
150
--
151
-- 1. the long-interval watcher ('onWatcherTimer')
152
--
153
-- 2. the polling watcher ('onPollTimer')
154
--
155
-- 3. the inotify event handler ('onInotify')
156
--
157
-- All of these will mutate the server state under 'modifyMVar' or
158
-- 'modifyMVar_', so that server transitions are more or less
159
-- atomic. The inotify handler remains active during polling mode, but
160
-- checks for polling mode and doesn't do anything in this case (this
161
-- check is needed even if we would unregister the event handler due
162
-- to how events are serialised).
163

    
164
-- | Long-interval reload watcher.
165
--
166
-- This is on top of the inotify-based triggered reload.
167
onWatcherTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
168
               -> MVar ServerState -> IO ()
169
onWatcherTimer inotiaction path save_fn state = do
170
  threadDelay watchInterval
171
  logDebug "Watcher timer fired"
172
  modifyMVar_ state (onWatcherInner path save_fn)
173
  _ <- inotiaction
174
  onWatcherTimer inotiaction path save_fn state
175

    
176
-- | Inner onWatcher handler.
177
--
178
-- This mutates the server state under a modifyMVar_ call. It never
179
-- changes the reload model, just does a safety reload and tried to
180
-- re-establish the inotify watcher.
181
onWatcherInner :: FilePath -> (Result ConfigData -> IO ()) -> ServerState
182
               -> IO ServerState
183
onWatcherInner path save_fn state  = do
184
  (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
185
  return state { reloadFStat = newfstat }
186

    
187
-- | Short-interval (polling) reload watcher.
188
--
189
-- This is only active when we're in polling mode; it will
190
-- automatically exit when it detects that the state has changed to
191
-- notification.
192
onPollTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
193
            -> MVar ServerState -> IO ()
194
onPollTimer inotiaction path save_fn state = do
195
  threadDelay pollInterval
196
  logDebug "Poll timer fired"
197
  continue <- modifyMVar state (onPollInner inotiaction path save_fn)
198
  if continue
199
    then onPollTimer inotiaction path save_fn state
200
    else logDebug "Inotify watch active, polling thread exiting"
201

    
202
-- | Inner onPoll handler.
203
--
204
-- This again mutates the state under a modifyMVar call, and also
205
-- returns whether the thread should continue or not.
206
onPollInner :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
207
            -> ServerState -> IO (ServerState, Bool)
208
onPollInner _ _ _ state@(ServerState { reloadModel = ReloadNotify } ) =
209
  return (state, False)
210
onPollInner inotiaction path save_fn
211
            state@(ServerState { reloadModel = ReloadPoll pround } ) = do
212
  (newfstat, reload) <- safeUpdateConfig path (reloadFStat state) save_fn
213
  let state' = state { reloadFStat = newfstat }
214
  -- compute new poll model based on reload data; however, failure to
215
  -- re-establish the inotifier means we stay on polling
216
  newmode <- case reload of
217
               ConfigToDate ->
218
                 if pround >= maxIdlePollRounds
219
                   then do -- try to switch to notify
220
                     result <- inotiaction
221
                     if result
222
                       then moveToNotify
223
                       else return initialPoll
224
                   else return (ReloadPoll (pround + 1))
225
               _ -> return initialPoll
226
  let continue = case newmode of
227
                   ReloadNotify -> False
228
                   _            -> True
229
  return (state' { reloadModel = newmode }, continue)
230

    
231
-- the following hint is because hlint doesn't understand our const
232
-- (return False) is so that we can give a signature to 'e'
233
{-# ANN addNotifier "HLint: ignore Evaluate" #-}
234
-- | Setup inotify watcher.
235
--
236
-- This tries to setup the watch descriptor; in case of any IO errors,
237
-- it will return False.
238
addNotifier :: INotify -> FilePath -> (Result ConfigData -> IO ())
239
            -> MVar ServerState -> IO Bool
240
addNotifier inotify path save_fn mstate =
241
  Control.Exception.catch
242
        (addWatch inotify [CloseWrite] path
243
            (onInotify inotify path save_fn mstate) >> return True)
244
        (\e -> const (return False) (e::IOError))
245

    
246
-- | Inotify event handler.
247
onInotify :: INotify -> String -> (Result ConfigData -> IO ())
248
          -> MVar ServerState -> Event -> IO ()
249
onInotify inotify path save_fn mstate Ignored = do
250
  logDebug "File lost, trying to re-establish notifier"
251
  modifyMVar_ mstate $ \state -> do
252
    result <- addNotifier inotify path save_fn mstate
253
    (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
254
    let state' = state { reloadFStat = newfstat }
255
    if result
256
      then return state' -- keep notify
257
      else do
258
        mode <- moveToPolling "cannot re-establish inotify watch" inotify
259
                  path save_fn mstate
260
        return state' { reloadModel = mode }
261

    
262
onInotify inotify path save_fn mstate _ =
263
  modifyMVar_ mstate $ \state ->
264
    if reloadModel state == ReloadNotify
265
       then do
266
         ctime <- getCurrentTimeUSec
267
         (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
268
         let state' = state { reloadFStat = newfstat, reloadTime = ctime }
269
         if abs (reloadTime state - ctime) < reloadRatelimit
270
           then do
271
             mode <- moveToPolling "too many reloads" inotify path save_fn
272
                                   mstate
273
             return state' { reloadModel = mode }
274
           else return state'
275
      else return state
276

    
277
initConfigReader :: (Result ConfigData -> a) -> IORef a -> IO ()
278
initConfigReader cfg_transform ioref = do
279
  let save_fn = writeIORef ioref . cfg_transform
280

    
281
  -- Inotify setup
282
  inotify <- initINotify
283
  -- try to load the configuration, if possible
284
  conf_file <- Path.clusterConfFile
285
  (fstat, reloaded) <- safeUpdateConfig conf_file nullFStat save_fn
286
  ctime <- getCurrentTime
287
  statemvar <- newMVar $ ServerState ReloadNotify ctime fstat
288
  let inotiaction = addNotifier inotify conf_file save_fn statemvar
289
  has_inotify <- if reloaded == ConfigReloaded
290
                   then inotiaction
291
                   else return False
292
  if has_inotify
293
    then logInfo "Starting up in inotify mode"
294
    else do
295
      -- inotify was not enabled, we need to update the reload model
296
      logInfo "Starting up in polling mode"
297
      modifyMVar_ statemvar
298
        (\state -> return state { reloadModel = initialPoll })
299
  -- fork the timeout timer
300
  _ <- forkIO $ onWatcherTimer inotiaction conf_file save_fn statemvar
301
  -- fork the polling timer
302
  unless has_inotify $ do
303
    _ <- forkIO $ onPollTimer inotiaction conf_file save_fn statemvar
304
    return ()