Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / ConfigReader.hs @ c92b4671

History | View | Annotate | Download (10.8 kB)

1 218e3b0f Thomas Thrainer
{-# LANGUAGE BangPatterns #-}
2 218e3b0f Thomas Thrainer
3 218e3b0f Thomas Thrainer
{-| Implementation of configuration reader with watching support.
4 218e3b0f Thomas Thrainer
5 218e3b0f Thomas Thrainer
-}
6 218e3b0f Thomas Thrainer
7 218e3b0f Thomas Thrainer
{-
8 218e3b0f Thomas Thrainer
9 218e3b0f Thomas Thrainer
Copyright (C) 2011, 2012, 2013 Google Inc.
10 218e3b0f Thomas Thrainer
11 218e3b0f Thomas Thrainer
This program is free software; you can redistribute it and/or modify
12 218e3b0f Thomas Thrainer
it under the terms of the GNU General Public License as published by
13 218e3b0f Thomas Thrainer
the Free Software Foundation; either version 2 of the License, or
14 218e3b0f Thomas Thrainer
(at your option) any later version.
15 218e3b0f Thomas Thrainer
16 218e3b0f Thomas Thrainer
This program is distributed in the hope that it will be useful, but
17 218e3b0f Thomas Thrainer
WITHOUT ANY WARRANTY; without even the implied warranty of
18 218e3b0f Thomas Thrainer
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 218e3b0f Thomas Thrainer
General Public License for more details.
20 218e3b0f Thomas Thrainer
21 218e3b0f Thomas Thrainer
You should have received a copy of the GNU General Public License
22 218e3b0f Thomas Thrainer
along with this program; if not, write to the Free Software
23 218e3b0f Thomas Thrainer
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 218e3b0f Thomas Thrainer
02110-1301, USA.
25 218e3b0f Thomas Thrainer
26 218e3b0f Thomas Thrainer
-}
27 218e3b0f Thomas Thrainer
28 218e3b0f Thomas Thrainer
module Ganeti.ConfigReader
29 218e3b0f Thomas Thrainer
  ( ConfigReader
30 218e3b0f Thomas Thrainer
  , initConfigReader
31 218e3b0f Thomas Thrainer
  ) where
32 218e3b0f Thomas Thrainer
33 218e3b0f Thomas Thrainer
import Control.Concurrent
34 218e3b0f Thomas Thrainer
import Control.Exception
35 6fd8ceff Klaus Aehlig
import Control.Monad (unless)
36 218e3b0f Thomas Thrainer
import Data.IORef
37 218e3b0f Thomas Thrainer
import System.INotify
38 218e3b0f Thomas Thrainer
39 218e3b0f Thomas Thrainer
import Ganeti.BasicTypes
40 218e3b0f Thomas Thrainer
import Ganeti.Objects
41 218e3b0f Thomas Thrainer
import Ganeti.Confd.Utils
42 218e3b0f Thomas Thrainer
import Ganeti.Config
43 218e3b0f Thomas Thrainer
import Ganeti.Logging
44 218e3b0f Thomas Thrainer
import qualified Ganeti.Constants as C
45 218e3b0f Thomas Thrainer
import qualified Ganeti.Path as Path
46 218e3b0f Thomas Thrainer
import Ganeti.Utils
47 218e3b0f Thomas Thrainer
48 218e3b0f Thomas Thrainer
-- | A type for functions that can return the configuration when
49 218e3b0f Thomas Thrainer
-- executed.
50 218e3b0f Thomas Thrainer
type ConfigReader = IO (Result ConfigData)
51 218e3b0f Thomas Thrainer
52 218e3b0f Thomas Thrainer
53 218e3b0f Thomas Thrainer
-- | Reload model data type.
54 218e3b0f Thomas Thrainer
data ReloadModel = ReloadNotify      -- ^ We are using notifications
55 218e3b0f Thomas Thrainer
                 | ReloadPoll Int    -- ^ We are using polling
56 218e3b0f Thomas Thrainer
                   deriving (Eq, Show)
57 218e3b0f Thomas Thrainer
58 218e3b0f Thomas Thrainer
-- | Server state data type.
59 218e3b0f Thomas Thrainer
data ServerState = ServerState
60 218e3b0f Thomas Thrainer
  { reloadModel  :: ReloadModel
61 218e3b0f Thomas Thrainer
  , reloadTime   :: Integer      -- ^ Reload time (epoch) in microseconds
62 218e3b0f Thomas Thrainer
  , reloadFStat  :: FStat
63 218e3b0f Thomas Thrainer
  }
64 218e3b0f Thomas Thrainer
65 218e3b0f Thomas Thrainer
-- | Maximum no-reload poll rounds before reverting to inotify.
66 218e3b0f Thomas Thrainer
maxIdlePollRounds :: Int
67 218e3b0f Thomas Thrainer
maxIdlePollRounds = 3
68 218e3b0f Thomas Thrainer
69 218e3b0f Thomas Thrainer
-- | Reload timeout in microseconds.
70 218e3b0f Thomas Thrainer
watchInterval :: Int
71 218e3b0f Thomas Thrainer
watchInterval = C.confdConfigReloadTimeout * 1000000
72 218e3b0f Thomas Thrainer
73 218e3b0f Thomas Thrainer
-- | Ratelimit timeout in microseconds.
74 218e3b0f Thomas Thrainer
pollInterval :: Int
75 218e3b0f Thomas Thrainer
pollInterval = C.confdConfigReloadRatelimit
76 218e3b0f Thomas Thrainer
77 218e3b0f Thomas Thrainer
-- | Ratelimit timeout in microseconds, as an 'Integer'.
78 218e3b0f Thomas Thrainer
reloadRatelimit :: Integer
79 218e3b0f Thomas Thrainer
reloadRatelimit = fromIntegral C.confdConfigReloadRatelimit
80 218e3b0f Thomas Thrainer
81 218e3b0f Thomas Thrainer
-- | Initial poll round.
82 218e3b0f Thomas Thrainer
initialPoll :: ReloadModel
83 218e3b0f Thomas Thrainer
initialPoll = ReloadPoll 0
84 218e3b0f Thomas Thrainer
85 218e3b0f Thomas Thrainer
-- | Reload status data type.
86 218e3b0f Thomas Thrainer
data ConfigReload = ConfigToDate    -- ^ No need to reload
87 218e3b0f Thomas Thrainer
                  | ConfigReloaded  -- ^ Configuration reloaded
88 218e3b0f Thomas Thrainer
                  | ConfigIOError   -- ^ Error during configuration reload
89 218e3b0f Thomas Thrainer
                    deriving (Eq)
90 218e3b0f Thomas Thrainer
91 218e3b0f Thomas Thrainer
-- * Configuration handling
92 218e3b0f Thomas Thrainer
93 218e3b0f Thomas Thrainer
-- ** Helper functions
94 218e3b0f Thomas Thrainer
95 218e3b0f Thomas Thrainer
-- | Helper function for logging transition into polling mode.
96 218e3b0f Thomas Thrainer
moveToPolling :: String -> INotify -> FilePath -> (Result ConfigData -> IO ())
97 218e3b0f Thomas Thrainer
              -> MVar ServerState -> IO ReloadModel
98 218e3b0f Thomas Thrainer
moveToPolling msg inotify path save_fn mstate = do
99 218e3b0f Thomas Thrainer
  logInfo $ "Moving to polling mode: " ++ msg
100 218e3b0f Thomas Thrainer
  let inotiaction = addNotifier inotify path save_fn mstate
101 218e3b0f Thomas Thrainer
  _ <- forkIO $ onPollTimer inotiaction path save_fn mstate
102 218e3b0f Thomas Thrainer
  return initialPoll
103 218e3b0f Thomas Thrainer
104 218e3b0f Thomas Thrainer
-- | Helper function for logging transition into inotify mode.
105 218e3b0f Thomas Thrainer
moveToNotify :: IO ReloadModel
106 218e3b0f Thomas Thrainer
moveToNotify = do
107 218e3b0f Thomas Thrainer
  logInfo "Moving to inotify mode"
108 218e3b0f Thomas Thrainer
  return ReloadNotify
109 218e3b0f Thomas Thrainer
110 218e3b0f Thomas Thrainer
-- ** Configuration loading
111 218e3b0f Thomas Thrainer
112 218e3b0f Thomas Thrainer
-- | (Re)loads the configuration.
113 218e3b0f Thomas Thrainer
updateConfig :: FilePath -> (Result ConfigData -> IO ()) -> IO ()
114 218e3b0f Thomas Thrainer
updateConfig path save_fn = do
115 218e3b0f Thomas Thrainer
  newcfg <- loadConfig path
116 218e3b0f Thomas Thrainer
  let !newdata = case newcfg of
117 218e3b0f Thomas Thrainer
                   Ok !cfg -> Ok cfg
118 f0221cff Petr Pudlak
                   Bad msg -> Bad $ "Cannot load configuration from " ++ path
119 f0221cff Petr Pudlak
                                    ++ ": " ++ msg
120 218e3b0f Thomas Thrainer
  save_fn newdata
121 218e3b0f Thomas Thrainer
  case newcfg of
122 218e3b0f Thomas Thrainer
    Ok cfg -> logInfo ("Loaded new config, serial " ++
123 218e3b0f Thomas Thrainer
                       show (configSerial cfg))
124 218e3b0f Thomas Thrainer
    Bad msg -> logError $ "Failed to load config: " ++ msg
125 218e3b0f Thomas Thrainer
  return ()
126 218e3b0f Thomas Thrainer
127 218e3b0f Thomas Thrainer
-- | Wrapper over 'updateConfig' that handles IO errors.
128 218e3b0f Thomas Thrainer
safeUpdateConfig :: FilePath -> FStat -> (Result ConfigData -> IO ())
129 218e3b0f Thomas Thrainer
                 -> IO (FStat, ConfigReload)
130 218e3b0f Thomas Thrainer
safeUpdateConfig path oldfstat save_fn =
131 218e3b0f Thomas Thrainer
  Control.Exception.catch
132 218e3b0f Thomas Thrainer
        (do
133 218e3b0f Thomas Thrainer
          nt <- needsReload oldfstat path
134 218e3b0f Thomas Thrainer
          case nt of
135 218e3b0f Thomas Thrainer
            Nothing -> return (oldfstat, ConfigToDate)
136 218e3b0f Thomas Thrainer
            Just nt' -> do
137 218e3b0f Thomas Thrainer
                    updateConfig path save_fn
138 218e3b0f Thomas Thrainer
                    return (nt', ConfigReloaded)
139 218e3b0f Thomas Thrainer
        ) (\e -> do
140 218e3b0f Thomas Thrainer
             let msg = "Failure during configuration update: " ++
141 218e3b0f Thomas Thrainer
                       show (e::IOError)
142 218e3b0f Thomas Thrainer
             save_fn $ Bad msg
143 218e3b0f Thomas Thrainer
             return (nullFStat, ConfigIOError)
144 218e3b0f Thomas Thrainer
          )
145 218e3b0f Thomas Thrainer
146 218e3b0f Thomas Thrainer
-- ** Watcher threads
147 218e3b0f Thomas Thrainer
148 218e3b0f Thomas Thrainer
-- $watcher
149 218e3b0f Thomas Thrainer
-- We have three threads/functions that can mutate the server state:
150 218e3b0f Thomas Thrainer
--
151 218e3b0f Thomas Thrainer
-- 1. the long-interval watcher ('onWatcherTimer')
152 218e3b0f Thomas Thrainer
--
153 218e3b0f Thomas Thrainer
-- 2. the polling watcher ('onPollTimer')
154 218e3b0f Thomas Thrainer
--
155 218e3b0f Thomas Thrainer
-- 3. the inotify event handler ('onInotify')
156 218e3b0f Thomas Thrainer
--
157 218e3b0f Thomas Thrainer
-- All of these will mutate the server state under 'modifyMVar' or
158 218e3b0f Thomas Thrainer
-- 'modifyMVar_', so that server transitions are more or less
159 218e3b0f Thomas Thrainer
-- atomic. The inotify handler remains active during polling mode, but
160 218e3b0f Thomas Thrainer
-- checks for polling mode and doesn't do anything in this case (this
161 218e3b0f Thomas Thrainer
-- check is needed even if we would unregister the event handler due
162 218e3b0f Thomas Thrainer
-- to how events are serialised).
163 218e3b0f Thomas Thrainer
164 218e3b0f Thomas Thrainer
-- | Long-interval reload watcher.
165 218e3b0f Thomas Thrainer
--
166 218e3b0f Thomas Thrainer
-- This is on top of the inotify-based triggered reload.
167 218e3b0f Thomas Thrainer
onWatcherTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
168 218e3b0f Thomas Thrainer
               -> MVar ServerState -> IO ()
169 218e3b0f Thomas Thrainer
onWatcherTimer inotiaction path save_fn state = do
170 218e3b0f Thomas Thrainer
  threadDelay watchInterval
171 fe50bb65 Klaus Aehlig
  logDebug "Config-reader watcher timer fired"
172 218e3b0f Thomas Thrainer
  modifyMVar_ state (onWatcherInner path save_fn)
173 218e3b0f Thomas Thrainer
  _ <- inotiaction
174 218e3b0f Thomas Thrainer
  onWatcherTimer inotiaction path save_fn state
175 218e3b0f Thomas Thrainer
176 218e3b0f Thomas Thrainer
-- | Inner onWatcher handler.
177 218e3b0f Thomas Thrainer
--
178 218e3b0f Thomas Thrainer
-- This mutates the server state under a modifyMVar_ call. It never
179 218e3b0f Thomas Thrainer
-- changes the reload model, just does a safety reload and tried to
180 218e3b0f Thomas Thrainer
-- re-establish the inotify watcher.
181 218e3b0f Thomas Thrainer
onWatcherInner :: FilePath -> (Result ConfigData -> IO ()) -> ServerState
182 218e3b0f Thomas Thrainer
               -> IO ServerState
183 218e3b0f Thomas Thrainer
onWatcherInner path save_fn state  = do
184 218e3b0f Thomas Thrainer
  (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
185 218e3b0f Thomas Thrainer
  return state { reloadFStat = newfstat }
186 218e3b0f Thomas Thrainer
187 218e3b0f Thomas Thrainer
-- | Short-interval (polling) reload watcher.
188 218e3b0f Thomas Thrainer
--
189 218e3b0f Thomas Thrainer
-- This is only active when we're in polling mode; it will
190 218e3b0f Thomas Thrainer
-- automatically exit when it detects that the state has changed to
191 218e3b0f Thomas Thrainer
-- notification.
192 218e3b0f Thomas Thrainer
onPollTimer :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
193 218e3b0f Thomas Thrainer
            -> MVar ServerState -> IO ()
194 218e3b0f Thomas Thrainer
onPollTimer inotiaction path save_fn state = do
195 218e3b0f Thomas Thrainer
  threadDelay pollInterval
196 218e3b0f Thomas Thrainer
  logDebug "Poll timer fired"
197 218e3b0f Thomas Thrainer
  continue <- modifyMVar state (onPollInner inotiaction path save_fn)
198 218e3b0f Thomas Thrainer
  if continue
199 218e3b0f Thomas Thrainer
    then onPollTimer inotiaction path save_fn state
200 218e3b0f Thomas Thrainer
    else logDebug "Inotify watch active, polling thread exiting"
201 218e3b0f Thomas Thrainer
202 218e3b0f Thomas Thrainer
-- | Inner onPoll handler.
203 218e3b0f Thomas Thrainer
--
204 218e3b0f Thomas Thrainer
-- This again mutates the state under a modifyMVar call, and also
205 218e3b0f Thomas Thrainer
-- returns whether the thread should continue or not.
206 218e3b0f Thomas Thrainer
onPollInner :: IO Bool -> FilePath -> (Result ConfigData -> IO ())
207 218e3b0f Thomas Thrainer
            -> ServerState -> IO (ServerState, Bool)
208 218e3b0f Thomas Thrainer
onPollInner _ _ _ state@(ServerState { reloadModel = ReloadNotify } ) =
209 218e3b0f Thomas Thrainer
  return (state, False)
210 218e3b0f Thomas Thrainer
onPollInner inotiaction path save_fn
211 218e3b0f Thomas Thrainer
            state@(ServerState { reloadModel = ReloadPoll pround } ) = do
212 218e3b0f Thomas Thrainer
  (newfstat, reload) <- safeUpdateConfig path (reloadFStat state) save_fn
213 218e3b0f Thomas Thrainer
  let state' = state { reloadFStat = newfstat }
214 218e3b0f Thomas Thrainer
  -- compute new poll model based on reload data; however, failure to
215 218e3b0f Thomas Thrainer
  -- re-establish the inotifier means we stay on polling
216 218e3b0f Thomas Thrainer
  newmode <- case reload of
217 218e3b0f Thomas Thrainer
               ConfigToDate ->
218 218e3b0f Thomas Thrainer
                 if pround >= maxIdlePollRounds
219 218e3b0f Thomas Thrainer
                   then do -- try to switch to notify
220 218e3b0f Thomas Thrainer
                     result <- inotiaction
221 218e3b0f Thomas Thrainer
                     if result
222 218e3b0f Thomas Thrainer
                       then moveToNotify
223 218e3b0f Thomas Thrainer
                       else return initialPoll
224 218e3b0f Thomas Thrainer
                   else return (ReloadPoll (pround + 1))
225 218e3b0f Thomas Thrainer
               _ -> return initialPoll
226 218e3b0f Thomas Thrainer
  let continue = case newmode of
227 218e3b0f Thomas Thrainer
                   ReloadNotify -> False
228 218e3b0f Thomas Thrainer
                   _            -> True
229 218e3b0f Thomas Thrainer
  return (state' { reloadModel = newmode }, continue)
230 218e3b0f Thomas Thrainer
231 218e3b0f Thomas Thrainer
-- the following hint is because hlint doesn't understand our const
232 218e3b0f Thomas Thrainer
-- (return False) is so that we can give a signature to 'e'
233 218e3b0f Thomas Thrainer
{-# ANN addNotifier "HLint: ignore Evaluate" #-}
234 218e3b0f Thomas Thrainer
-- | Setup inotify watcher.
235 218e3b0f Thomas Thrainer
--
236 218e3b0f Thomas Thrainer
-- This tries to setup the watch descriptor; in case of any IO errors,
237 218e3b0f Thomas Thrainer
-- it will return False.
238 218e3b0f Thomas Thrainer
addNotifier :: INotify -> FilePath -> (Result ConfigData -> IO ())
239 218e3b0f Thomas Thrainer
            -> MVar ServerState -> IO Bool
240 218e3b0f Thomas Thrainer
addNotifier inotify path save_fn mstate =
241 218e3b0f Thomas Thrainer
  Control.Exception.catch
242 218e3b0f Thomas Thrainer
        (addWatch inotify [CloseWrite] path
243 218e3b0f Thomas Thrainer
            (onInotify inotify path save_fn mstate) >> return True)
244 218e3b0f Thomas Thrainer
        (\e -> const (return False) (e::IOError))
245 218e3b0f Thomas Thrainer
246 218e3b0f Thomas Thrainer
-- | Inotify event handler.
247 218e3b0f Thomas Thrainer
onInotify :: INotify -> String -> (Result ConfigData -> IO ())
248 218e3b0f Thomas Thrainer
          -> MVar ServerState -> Event -> IO ()
249 218e3b0f Thomas Thrainer
onInotify inotify path save_fn mstate Ignored = do
250 218e3b0f Thomas Thrainer
  logDebug "File lost, trying to re-establish notifier"
251 218e3b0f Thomas Thrainer
  modifyMVar_ mstate $ \state -> do
252 218e3b0f Thomas Thrainer
    result <- addNotifier inotify path save_fn mstate
253 218e3b0f Thomas Thrainer
    (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
254 218e3b0f Thomas Thrainer
    let state' = state { reloadFStat = newfstat }
255 218e3b0f Thomas Thrainer
    if result
256 218e3b0f Thomas Thrainer
      then return state' -- keep notify
257 218e3b0f Thomas Thrainer
      else do
258 218e3b0f Thomas Thrainer
        mode <- moveToPolling "cannot re-establish inotify watch" inotify
259 218e3b0f Thomas Thrainer
                  path save_fn mstate
260 218e3b0f Thomas Thrainer
        return state' { reloadModel = mode }
261 218e3b0f Thomas Thrainer
262 218e3b0f Thomas Thrainer
onInotify inotify path save_fn mstate _ =
263 218e3b0f Thomas Thrainer
  modifyMVar_ mstate $ \state ->
264 218e3b0f Thomas Thrainer
    if reloadModel state == ReloadNotify
265 218e3b0f Thomas Thrainer
       then do
266 218e3b0f Thomas Thrainer
         ctime <- getCurrentTimeUSec
267 218e3b0f Thomas Thrainer
         (newfstat, _) <- safeUpdateConfig path (reloadFStat state) save_fn
268 218e3b0f Thomas Thrainer
         let state' = state { reloadFStat = newfstat, reloadTime = ctime }
269 218e3b0f Thomas Thrainer
         if abs (reloadTime state - ctime) < reloadRatelimit
270 218e3b0f Thomas Thrainer
           then do
271 218e3b0f Thomas Thrainer
             mode <- moveToPolling "too many reloads" inotify path save_fn
272 218e3b0f Thomas Thrainer
                                   mstate
273 218e3b0f Thomas Thrainer
             return state' { reloadModel = mode }
274 218e3b0f Thomas Thrainer
           else return state'
275 218e3b0f Thomas Thrainer
      else return state
276 218e3b0f Thomas Thrainer
277 218e3b0f Thomas Thrainer
initConfigReader :: (Result ConfigData -> a) -> IORef a -> IO ()
278 218e3b0f Thomas Thrainer
initConfigReader cfg_transform ioref = do
279 218e3b0f Thomas Thrainer
  let save_fn = writeIORef ioref . cfg_transform
280 218e3b0f Thomas Thrainer
281 218e3b0f Thomas Thrainer
  -- Inotify setup
282 218e3b0f Thomas Thrainer
  inotify <- initINotify
283 218e3b0f Thomas Thrainer
  -- try to load the configuration, if possible
284 218e3b0f Thomas Thrainer
  conf_file <- Path.clusterConfFile
285 218e3b0f Thomas Thrainer
  (fstat, reloaded) <- safeUpdateConfig conf_file nullFStat save_fn
286 218e3b0f Thomas Thrainer
  ctime <- getCurrentTime
287 218e3b0f Thomas Thrainer
  statemvar <- newMVar $ ServerState ReloadNotify ctime fstat
288 218e3b0f Thomas Thrainer
  let inotiaction = addNotifier inotify conf_file save_fn statemvar
289 218e3b0f Thomas Thrainer
  has_inotify <- if reloaded == ConfigReloaded
290 218e3b0f Thomas Thrainer
                   then inotiaction
291 218e3b0f Thomas Thrainer
                   else return False
292 218e3b0f Thomas Thrainer
  if has_inotify
293 218e3b0f Thomas Thrainer
    then logInfo "Starting up in inotify mode"
294 218e3b0f Thomas Thrainer
    else do
295 218e3b0f Thomas Thrainer
      -- inotify was not enabled, we need to update the reload model
296 218e3b0f Thomas Thrainer
      logInfo "Starting up in polling mode"
297 218e3b0f Thomas Thrainer
      modifyMVar_ statemvar
298 218e3b0f Thomas Thrainer
        (\state -> return state { reloadModel = initialPoll })
299 218e3b0f Thomas Thrainer
  -- fork the timeout timer
300 218e3b0f Thomas Thrainer
  _ <- forkIO $ onWatcherTimer inotiaction conf_file save_fn statemvar
301 218e3b0f Thomas Thrainer
  -- fork the polling timer
302 218e3b0f Thomas Thrainer
  unless has_inotify $ do
303 218e3b0f Thomas Thrainer
    _ <- forkIO $ onPollTimer inotiaction conf_file save_fn statemvar
304 f0221cff Petr Pudlak
    return ()