Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / WConfd / ConfigWriter.hs @ 94c7e022

History | View | Annotate | Download (6.1 kB)

1
{-# LANGUAGE RankNTypes, FlexibleContexts #-}
2

    
3
{-| Implementation of functions specific to configuration management.
4

    
5
TODO: Detect changes in SSConf and distribute only if it changes
6
TODO: distribute ssconf configuration, if it has changed
7

    
8
-}
9

    
10
{-
11

    
12
Copyright (C) 2013 Google Inc.
13

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

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

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

    
29
-}
30

    
31
module Ganeti.WConfd.ConfigWriter
32
  ( loadConfigFromFile
33
  , readConfig
34
  , writeConfig
35
  , saveConfigAsyncTask
36
  , distMCsAsyncTask
37
  ) where
38

    
39
import Control.Applicative
40
import Control.Monad.Base
41
import Control.Monad.Error
42
import qualified Control.Monad.State.Strict as S
43
import Control.Monad.Trans.Control
44

    
45
import Ganeti.BasicTypes
46
import Ganeti.Errors
47
import Ganeti.Config
48
import Ganeti.Logging
49
import Ganeti.Objects
50
import Ganeti.Rpc
51
import Ganeti.Runtime
52
import Ganeti.Utils
53
import Ganeti.Utils.Atomic
54
import Ganeti.Utils.AsyncWorker
55
import Ganeti.WConfd.ConfigState
56
import Ganeti.WConfd.Monad
57

    
58
-- | Loads the configuration from the file, if it hasn't been loaded yet.
59
-- The function is internal and isn't thread safe.
60
loadConfigFromFile :: FilePath
61
                   -> ResultG (ConfigData, FStat)
62
loadConfigFromFile path = withLockedFile path $ \_ -> do
63
    stat <- liftBase $ getFStat path
64
    cd <- mkResultT (loadConfig path)
65
    return (cd, stat)
66

    
67
-- | Writes the current configuration to the file. The function isn't thread
68
-- safe.
69
-- Neither distributes the configuration (to nodes and ssconf) nor
70
-- updates the serial number.
71
writeConfigToFile :: (MonadBase IO m, MonadError GanetiException m, MonadLog m)
72
                  => ConfigData -> FilePath -> FStat -> m FStat
73
writeConfigToFile cfg path oldstat = do
74
    logDebug "Async. config. writer: Commencing write"
75
    r <- toErrorBase $ atomicUpdateLockedFile_ path oldstat doWrite
76
    logDebug "Async. config. writer: written"
77
    return r
78
  where
79
    doWrite fname fh = do
80
      setOwnerAndGroupFromNames fname GanetiWConfd
81
                                (DaemonGroup GanetiConfd)
82
      setOwnerWGroupR fname
83
      saveConfig fh cfg
84

    
85
-- Reads the current configuration state in the 'WConfdMonad'.
86
readConfig :: WConfdMonad ConfigData
87
readConfig = csConfigData <$> readConfigState
88

    
89
-- Replaces the current configuration state within the 'WConfdMonad'.
90
writeConfig :: ConfigData -> WConfdMonad ()
91
writeConfig cd = modifyConfigState $ const (mkConfigState cd, ())
92

    
93
-- * Asynchronous tasks
94

    
95
-- | Runs the given action on success, or logs an error on failure.
96
finishOrLog :: (Show e, MonadLog m)
97
            => Priority
98
            -> String
99
            -> (a -> m ())
100
            -> GenericResult e a
101
            -> m ()
102
finishOrLog logPrio logPrefix =
103
  genericResult (logAt logPrio . (++) (logPrefix ++ ": ") . show)
104

    
105
-- | Creates a stateless asynchronous task that handles errors in its actions.
106
mkStatelessAsyncTask :: (MonadBaseControl IO m, MonadLog m, Show e)
107
                     => Priority
108
                     -> String
109
                     -> ResultT e m ()
110
                     -> m (AsyncWorker ())
111
mkStatelessAsyncTask logPrio logPrefix action =
112
    mkAsyncWorker $ runResultT action >>= finishOrLog logPrio logPrefix return
113

    
114
-- | Creates an asynchronous task that handles errors in its actions.
115
-- If an error occurs, it's logged and the internal state remains unchanged.
116
mkStatefulAsyncTask :: (MonadBaseControl IO m, MonadLog m, Show e)
117
                    => Priority
118
                    -> String
119
                    -> s
120
                    -> (s -> ResultT e m s)
121
                    -> m (AsyncWorker ())
122
mkStatefulAsyncTask logPrio logPrefix start action =
123
    flip S.evalStateT start . mkAsyncWorker $
124
      S.get >>= lift . runResultT . action
125
            >>= finishOrLog logPrio logPrefix S.put -- put on success
126

    
127
-- | Construct an asynchronous worker whose action is to save the
128
-- configuration to the master file.
129
-- The worker's action reads the configuration using the given @IO@ action
130
-- and uses 'FStat' to check if the configuration hasn't been modified by
131
-- another process.
132
saveConfigAsyncTask :: FilePath -- ^ Path to the config file
133
                    -> FStat  -- ^ The initial state of the config. file
134
                    -> IO ConfigState -- ^ An action to read the current config
135
                    -> ResultG (AsyncWorker ())
136
saveConfigAsyncTask fpath fstat cdRef =
137
  lift . mkStatefulAsyncTask
138
           EMERGENCY "Can't write the master configuration file" fstat
139
       $ \oldstat -> do
140
            cd <- liftBase (csConfigData `liftM` cdRef)
141
            writeConfigToFile cd fpath oldstat
142

    
143
-- | Performs a RPC call on the given list of nodes and logs any failures.
144
-- If any of the calls fails, fail the computation with 'failError'.
145
execRpcCallAndLog :: (Rpc a b) => [Node] -> a -> ResultG ()
146
execRpcCallAndLog nodes req = do
147
  rs <- liftIO $ executeRpcCall nodes req
148
  es <- logRpcErrors rs
149
  unless (null es) $ failError "At least one of the RPC calls failed"
150

    
151
-- | Construct an asynchronous worker whose action is to distribute the
152
-- configuration to master candidates.
153
distMCsAsyncTask :: RuntimeEnts
154
                 -> FilePath -- ^ Path to the config file
155
                 -> IO ConfigState -- ^ An action to read the current config
156
                 -> ResultG (AsyncWorker ())
157
distMCsAsyncTask ents cpath cdRef =
158
  lift . mkStatelessAsyncTask ERROR "Can't distribute the configuration\
159
                                    \ to master candidates"
160
       $ do
161
          cd <- liftBase (csConfigData <$> cdRef) :: ResultG ConfigData
162
          fupload <- prepareRpcCallUploadFile ents cpath
163
          execRpcCallAndLog (getMasterCandidates cd) fupload