batchd-0.1.1.0: src/Batchd/Daemon/Executor.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Batchd.Daemon.Executor where
import Control.Monad
import Control.Monad.Trans
import Control.Concurrent
import Data.Maybe (fromMaybe)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Text.Format.Heavy
import Data.Text.Format.Heavy.Parse.Shell
import Data.Text.Format.Heavy.Parse.Braces (parseFormat')
import qualified Database.Persist.Sql as Sql hiding (Single)
-- import Data.Time
import System.FilePath
import System.Exit (ExitCode (..))
import Network.SSH.Client.LibSSH2.Conduit (execCommand)
import Batchd.Core.Common.Types
import Batchd.Core.Common.Config
import Batchd.Core.Daemon.Logging
import Batchd.Common.Types
import Batchd.Common.Data
import Batchd.Daemon.Types
import Batchd.Core.Daemon.Hosts
import Batchd.Daemon.SSH
import Batchd.Daemon.Local (processOnLocalhost, withLocalScript)
import Batchd.Daemon.Hosts (loadHostController, withHost)
import Batchd.Daemon.Monitoring as Monitoring
getCommands :: GlobalConfig -> Maybe Host -> JobType -> JobInfo -> [T.Text]
getCommands cfg mbHost jt job =
let context = mkContext $ hostContext mbHost jt $ jiParams job
syntax = fromMaybe (dbcDefTemplateSyntax cfg) (jtSyntax jt)
parse = case syntax of
Shell -> parseShellFormat'
Python -> parseFormat'
in [TL.toStrict $ format (parse $ TL.fromStrict line) context | line <- jtTemplate jt]
where
mkContext m = optional $ m `ThenCheck` hostVars `ThenCheck` dbcVariables cfg
hostVars = maybe M.empty hVariables mbHost
getHostName :: Queue -> JobType -> JobInfo -> Maybe T.Text
getHostName q jt job = T.pack <$>
msum [jiHostName job, jtHostName jt, queueHostName q]
hostContext :: Maybe Host -> JobType -> JobParamInfo -> JobParamInfo
hostContext Nothing jt params = params
hostContext (Just host) jt params = M.fromList $ map update $ M.assocs params
where
update (key, value) =
case getParamType jt key of
Just InputFile -> (key, T.pack $ hInputDirectory host </> takeFileName (T.unpack value))
Just OutputFile -> (key, T.pack $ hOutputDirectory host </> takeFileName (T.unpack value))
_ -> (key, value)
processOnHost :: HostsPool -> Host -> JobType -> JobInfo -> ResultsChan -> FilePath -> [T.Text] -> Daemon ExitCode
processOnHost counters host jtype job resultChan scriptsDir commands = do
host' <- liftIO $ accountForDefaultHostKeys host
lts <- askLoggingStateM
controller <- liftIO $ loadHostController lts (hController host')
r <- withHost counters host' jtype $ do
withSshOnHost controller host' $ \session -> do
withRemoteScript session scriptsDir (jiId job) commands $ \command -> do
uploadFiles (getInputFiles jtype job) (hInputDirectory host') session
$info "EXECUTING: {}" (Single command)
(Just commandHandle, commandOutput) <- liftIO $ execCommand True session command
ec <- liftIO $ retrieveOutput job jtype commandHandle commandOutput resultChan
$info "Done, exit code is {}." (Single $ show ec)
downloadFiles (hOutputDirectory host') (getOutputFiles jtype job) session
return ec
case r of
Left e -> do
$reportError "Error while executing job at host `{}': {}" (hName host', show e)
liftIO $ writeChan resultChan (job, ExecError (T.pack $ show e) (jtOnFail jtype))
return $ ExitFailure (-1)
Right result -> return result
executeJob :: HostsPool -> Queue -> JobType -> JobInfo -> ResultsChan -> Daemon ()
executeJob counters q jt job resultChan = do
let mbHostName = getHostName q jt job
hostForMetric = fromMaybe "localhost" mbHostName
let metrics = ["batchd.job.duration",
"batchd.job.duration.host." <> hostForMetric,
"batchd.job.duration.type." <> T.pack (jtName jt)]
Monitoring.timedN metrics $ do
cfg <- askConfig
liftIO $ writeChan resultChan (job, StartExecution)
let jid = JobKey (Sql.SqlBackendKey $ jiId job)
case mbHostName of
Nothing -> do -- localhost
let commands = getCommands cfg Nothing jt job
let scriptsDir = dbcDefScriptsDirectory cfg
liftIO $ withLocalScript scriptsDir (jiId job) commands $ \script ->
processOnLocalhost job (jtOnFail jt) script resultChan
Just hostname -> do
hostR <- liftIO $ loadHost hostname
case hostR of
Right host -> do
let commands = getCommands cfg (Just host) jt job
let scriptsDir = fromMaybe (dbcDefScriptsDirectory cfg) (hScriptsDirectory host)
-- now <- liftIO $ getCurrentTime
-- let result = JobResult jid now (ExitFailure (-2)) T.empty T.empty
$(putMessage config_level) "Loaded host configuration: {}" (Single $ show host)
processOnHost counters host jt job resultChan scriptsDir commands
return ()
Left err -> do
$reportError "Error while executing job: {}" (Single $ Shown err)
liftIO $ writeChan resultChan (job, ExecError (T.pack $ show err) (jtOnFail jt))
return ()