module Ganeti.WConfd.ConfigWriter
( loadConfigFromFile
, readConfig
, writeConfig
, saveConfigAsyncTask
, distMCsAsyncTask
, distSSConfAsyncTask
) where
import Prelude ()
import Ganeti.Prelude
import Control.Monad ((>=>), liftM, unless)
import Control.Monad.Base
import Control.Monad.Error.Class (MonadError)
import qualified Control.Monad.State.Strict as S
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Control
import Data.Monoid
import qualified Data.Set as Set
import Ganeti.BasicTypes
import Ganeti.Errors
import Ganeti.Config
import Ganeti.Logging
import Ganeti.Objects
import Ganeti.Rpc
import Ganeti.Runtime
import Ganeti.Utils
import Ganeti.Utils.Atomic
import Ganeti.Utils.AsyncWorker
import Ganeti.WConfd.ConfigState
import Ganeti.WConfd.Monad
import Ganeti.WConfd.Ssconf
targetToPredicate :: DistributionTarget -> Node -> Bool
targetToPredicate Everywhere = const True
targetToPredicate (ToGroups gs) = (`Set.member` gs) . nodeGroup
loadConfigFromFile :: FilePath
-> ResultG (ConfigData, FStat)
loadConfigFromFile path = withLockedFile path $ \_ -> do
stat <- liftBase $ getFStat path
cd <- mkResultT (loadConfig path)
return (cd, stat)
writeConfigToFile :: (MonadBase IO m, MonadError GanetiException m, MonadLog m)
=> ConfigData -> FilePath -> FStat -> m FStat
writeConfigToFile cfg path oldstat = do
logDebug $ "Async. config. writer: Commencing write\
\ serial no " ++ show (serialOf cfg)
r <- toErrorBase $ atomicUpdateLockedFile_ path oldstat doWrite
logDebug "Async. config. writer: written"
return r
where
doWrite fname fh = do
setOwnerAndGroupFromNames fname GanetiWConfd
(DaemonGroup GanetiConfd)
setOwnerWGroupR fname
saveConfig fh cfg
readConfig :: WConfdMonad ConfigData
readConfig = csConfigData <$> readConfigState
writeConfig :: ConfigData -> WConfdMonad ()
writeConfig cd = modifyConfigState $ const ((), mkConfigState cd)
finishOrLog :: (Show e, MonadLog m)
=> Priority
-> String
-> (a -> m ())
-> GenericResult e a
-> m ()
finishOrLog logPrio logPrefix =
genericResult (logAt logPrio . (++) (logPrefix ++ ": ") . show)
mkStatelessAsyncTask :: (MonadBaseControl IO m, MonadLog m, Show e, Monoid i)
=> Priority
-> String
-> (i -> ResultT e m ())
-> m (AsyncWorker i ())
mkStatelessAsyncTask logPrio logPrefix action =
mkAsyncWorker $ runResultT . action
>=> finishOrLog logPrio logPrefix return
mkStatefulAsyncTask :: (MonadBaseControl IO m, MonadLog m, Show e, Monoid i)
=> Priority
-> String
-> s
-> (s -> i -> ResultT e m s)
-> m (AsyncWorker i ())
mkStatefulAsyncTask logPrio logPrefix start action =
flip S.evalStateT start . mkAsyncWorker $ \i ->
S.get >>= lift . runResultT . flip action i
>>= finishOrLog logPrio logPrefix S.put
saveConfigAsyncTask :: FilePath
-> FStat
-> IO ConfigState
-> [AsyncWorker DistributionTarget ()]
-> ResultG (AsyncWorker (Any, DistributionTarget) ())
saveConfigAsyncTask fpath fstat cdRef workers =
lift . mkStatefulAsyncTask
EMERGENCY "Can't write the master configuration file" fstat
$ \oldstat (Any flush, target) -> do
cd <- liftBase (csConfigData `liftM` cdRef)
writeConfigToFile cd fpath oldstat
<* if flush then logDebug "Running distribution synchronously"
>> triggerAndWaitMany target workers
else logDebug "Running distribution asynchronously"
>> mapM (trigger target) workers
execRpcCallAndLog :: (Rpc a b) => [Node] -> a -> ResultG ()
execRpcCallAndLog nodes req = do
rs <- liftIO $ executeRpcCall nodes req
es <- logRpcErrors rs
unless (null es) $ failError "At least one of the RPC calls failed"
distMCsAsyncTask :: RuntimeEnts
-> FilePath
-> IO ConfigState
-> ResultG (AsyncWorker DistributionTarget ())
distMCsAsyncTask ents cpath cdRef =
lift . mkStatelessAsyncTask ERROR "Can't distribute the configuration\
\ to master candidates"
$ \target -> do
cd <- liftBase (csConfigData <$> cdRef) :: ResultG ConfigData
logDebug $ "Distributing the configuration to master candidates,\
\ serial no " ++ show (serialOf cd) ++ ", " ++ show target
fupload <- prepareRpcCallUploadFile ents cpath
execRpcCallAndLog
(filter (targetToPredicate target) $ getMasterCandidates cd) fupload
logDebug "Successfully finished distributing the configuration"
distSSConfAsyncTask
:: IO ConfigState
-> ResultG (AsyncWorker DistributionTarget ())
distSSConfAsyncTask cdRef =
lift . mkStatefulAsyncTask ERROR "Can't distribute Ssconf" emptySSConf
$ \oldssc target -> do
cd <- liftBase (csConfigData <$> cdRef) :: ResultG ConfigData
let ssc = mkSSConf cd
if oldssc == ssc
then logDebug "SSConf unchanged, not distributing"
else do
logDebug $ "Starting the distribution of SSConf\
\ serial no " ++ show (serialOf cd)
++ ", " ++ show target
execRpcCallAndLog (filter (targetToPredicate target)
$ getOnlineNodes cd)
(RpcCallWriteSsconfFiles ssc)
logDebug "Successfully finished distributing SSConf"
return ssc