wolf-0.3.0: src/Network/AWS/Flow.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Network.AWS.Flow
( register
, execute
, act
, decide
, flowEnv
, runFlowT
, runDecide
, nextEvent
, select
, maybeThrow
, Uid
, Queue
, Metadata
, Artifact
, Blob
, Task (..)
, Timer (..)
, Start (..)
, Spec (..)
, End (..)
, Plan (..)
) where
import Network.AWS.Flow.Env
import Network.AWS.Flow.Logger
import Network.AWS.Flow.Prelude hiding (ByteString, Metadata, handle)
import Network.AWS.Flow.S3
import Network.AWS.Flow.SWF
import Network.AWS.Flow.Types
import Network.AWS.Flow.Uid
import Control.Monad.Catch
import Data.Char
import qualified Data.HashMap.Strict as Map
import Data.Text (pack)
import Formatting hiding (string)
import Network.AWS.SWF
import Network.HTTP.Types
import Safe
import Text.Regex.Applicative
-- Interface
serviceError :: MonadFlow m => ErrorCode -> Error -> m ()
serviceError code = \case
e@(ServiceError s) ->
unless check $ throwM e where
check =
s ^. serviceStatus == badRequest400 &&
s ^. serviceAbbrev == "SWF" &&
s ^. serviceCode == code
e -> throwM e
register :: MonadFlow m => Plan -> m ()
register Plan{..} = do
logInfo' "event=register"
handle (serviceError "DomainAlreadyExists") registerDomainAction
handle (serviceError "TypeAlreadyExists") $ registerWorkflowTypeAction
(tskName $ strtTask plnStart)
(tskVersion $ strtTask plnStart)
(tskTimeout $ strtTask plnStart)
mapM_ go plnSpecs where
go Work{..} =
handle (serviceError "TypeAlreadyExists") $ registerActivityTypeAction
(tskName wrkTask)
(tskVersion wrkTask)
(tskTimeout wrkTask)
go Sleep{..} =
return ()
execute :: MonadFlow m => Task -> Metadata -> m ()
execute Task{..} input = do
uid <- newUid
logInfo' $ sformat ("event=execute uid=" % stext) uid
startWorkflowExecutionAction uid tskName tskVersion tskQueue input
exitCode :: RE Char Int
exitCode =
many anySym *> string "exit status: " *> num <* many anySym where
num = read . pack <$> many (psym isDigit)
actException :: MonadFlow m => Token -> SomeException -> m ()
actException token e = do
logError' $ sformat ("event=act-exception " % stext) $ show e
maybe' (textToString (show e) =~ exitCode) (respondActivityTaskFailedAction token) $ \code ->
if code == 255 then respondActivityTaskCanceledAction token else
respondActivityTaskFailedAction token
act :: MonadFlow m => Queue -> (Uid -> Metadata -> [Blob] -> m (Metadata, [Artifact], Maybe SomeException)) -> m ()
act queue action = do
logInfo' "event=act"
(token', uid', input) <- pollForActivityTaskAction queue
maybe_ token' $ \token ->
maybe_ uid' $ \uid -> do
logInfo' $ sformat ("event=act-begin uid=" % stext) uid
keys <- listObjectsAction uid
unless (null keys) $ logInfo' $ sformat ("event=list-blobs uid=" % stext) uid
blobs <- forM keys $ getObjectAction uid
unless (null blobs) $ logInfo' $ sformat ("event=blobs uid=" % stext) uid
(output, artifacts, e) <- action uid input blobs
logInfo' $ sformat ("event=act-finish uid=" % stext) uid
forM_ artifacts $ putObjectAction uid
unless (null artifacts) $ logInfo' $ sformat ("event=artifacts uid=" % stext) uid
maybe (respondActivityTaskCompletedAction token output) (actException token) e
decide :: MonadFlow m => Plan -> m ()
decide plan@Plan{..} = do
logInfo' "event=decide"
(token', events) <- pollForDecisionTaskAction (tskQueue $ strtTask plnStart)
maybe_ token' $ \token -> do
logger <- asks feLogger
decisions <- runDecide logger plan events select
respondDecisionTaskCompletedAction token decisions
-- Decisions
runDecide :: Log -> Plan -> [HistoryEvent] -> DecideT m a -> m a
runDecide logger plan events =
runDecideT env where
env = DecideEnv logger plan events findEvent where
findEvent =
flip Map.lookup $ Map.fromList $ flip map events $ \e ->
(e ^. heEventId, e)
nextEvent :: MonadDecide m => [EventType] -> m HistoryEvent
nextEvent ets = do
events <- asks deEvents
maybeThrow (userError "No Next Event") $ flip find events $ \e ->
e ^. heEventType `elem` ets
workNext :: MonadDecide m => Name -> m (Maybe Spec)
workNext name = do
specs <- asks (plnSpecs . dePlan)
return $ tailMay (dropWhile p specs) >>= headMay where
p Work{..} = tskName wrkTask /= name
p _ = True
sleepNext :: MonadDecide m => Name -> m (Maybe Spec)
sleepNext name = do
specs <- asks (plnSpecs . dePlan)
return $ tailMay (dropWhile p specs) >>= headMay where
p Sleep{..} = tmrName slpTimer /= name
p _ = True
select :: MonadDecide m => m [Decision]
select = do
event <- nextEvent [ WorkflowExecutionStarted
, ActivityTaskCompleted
, ActivityTaskFailed
, ActivityTaskCanceled
, TimerFired
, StartChildWorkflowExecutionInitiated ]
case event ^. heEventType of
WorkflowExecutionStarted -> start event
ActivityTaskCompleted -> completed event
ActivityTaskFailed -> failed event
ActivityTaskCanceled -> canceled event
TimerFired -> timer event
StartChildWorkflowExecutionInitiated -> child
_ -> throwM (userError "Unknown Select Event")
start :: MonadDecide m => HistoryEvent -> m [Decision]
start event = do
logInfo' "event=start"
input <- maybeThrow (userError "No Start Information") $ do
attrs <- event ^. heWorkflowExecutionStartedEventAttributes
return $ attrs ^. weseaInput
specs <- asks (plnSpecs . dePlan)
schedule input $ headMay specs
completed :: MonadDecide m => HistoryEvent -> m [Decision]
completed event = do
logInfo' "event=completed"
findEvent <- asks deFindEvent
(input, name) <- maybeThrow (userError "No Completed Information") $ do
attrs <- event ^. heActivityTaskCompletedEventAttributes
event' <- findEvent $ attrs ^. atceaScheduledEventId
attrs' <- event' ^. heActivityTaskScheduledEventAttributes
return (attrs ^. atceaResult, attrs' ^. atseaActivityType ^. atName)
next <- workNext name
schedule input next
failed :: MonadDecide m => HistoryEvent -> m [Decision]
failed _event = do
logInfo' "event=failed"
return [failWorkflowExecutionDecision]
canceled :: MonadDecide m => HistoryEvent -> m [Decision]
canceled _event = do
logInfo' "event=canceled"
return [cancelWorkflowExecutionDecision]
timer :: MonadDecide m => HistoryEvent -> m [Decision]
timer event = do
logInfo' "event=timer"
findEvent <- asks deFindEvent
name <- maybeThrow (userError "No Timer Information") $ do
attrs <- event ^. heTimerFiredEventAttributes
event' <- findEvent $ attrs ^. tfeaStartedEventId
attrs' <- event' ^. heTimerStartedEventAttributes
attrs' ^. tseaControl
event' <- nextEvent [WorkflowExecutionStarted, ActivityTaskCompleted]
case event' ^. heEventType of
WorkflowExecutionStarted -> timerStart event' name
ActivityTaskCompleted -> timerCompleted event' name
_ -> throwM (userError "Unknown Timer Event")
timerStart :: MonadDecide m => HistoryEvent -> Name -> m [Decision]
timerStart event name = do
logInfo' $ sformat ("event=timer-start name=" % stext) name
input <- maybeThrow (userError "No Timer Start Information") $ do
attrs <- event ^. heWorkflowExecutionStartedEventAttributes
return $ attrs ^. weseaInput
next <- sleepNext name
schedule input next
timerCompleted :: MonadDecide m => HistoryEvent -> Name -> m [Decision]
timerCompleted event name = do
logInfo' $ sformat ("event=timer-completed name=" % stext) name
input <- maybeThrow (userError "No Timer Completed Information") $ do
attrs <- event ^. heActivityTaskCompletedEventAttributes
return $ attrs ^. atceaResult
next <- sleepNext name
schedule input next
schedule :: MonadDecide m => Metadata -> Maybe Spec -> m [Decision]
schedule input = maybe (scheduleEnd input) (scheduleSpec input)
scheduleSpec :: MonadDecide m => Metadata -> Spec -> m [Decision]
scheduleSpec input spec = do
uid <- newUid
logInfo' $ sformat ("event=schedule-spec uid=" % stext) uid
case spec of
Work{..} ->
return [scheduleActivityTaskDecision uid
(tskName wrkTask)
(tskVersion wrkTask)
(tskQueue wrkTask)
input]
Sleep{..} ->
return [startTimerDecision uid
(tmrName slpTimer)
(tmrTimeout slpTimer)]
scheduleEnd :: MonadDecide m => Metadata -> m [Decision]
scheduleEnd input = do
logInfo' "event=schedule-end"
end <- asks (plnEnd . dePlan)
case end of
Stop -> return [completeWorkflowExecutionDecision input]
Continue -> scheduleContinue
scheduleContinue :: MonadDecide m => m [Decision]
scheduleContinue = do
logInfo' "event=schedule-continue"
event <- nextEvent [WorkflowExecutionStarted]
input <- maybeThrow (userError "No Continue Start Information") $ do
attrs <- event ^. heWorkflowExecutionStartedEventAttributes
return $ attrs ^. weseaInput
uid <- newUid
task <- asks (strtTask . plnStart . dePlan)
return [startChildWorkflowExecutionDecision uid
(tskName task)
(tskVersion task)
(tskQueue task)
input]
child :: MonadDecide m => m [Decision]
child = do
event <- nextEvent [WorkflowExecutionStarted, ActivityTaskCompleted]
case event ^. heEventType of
WorkflowExecutionStarted -> childStart event
ActivityTaskCompleted -> childCompleted event
_ -> throwM (userError "Unknown Child Event")
childStart :: MonadDecide m => HistoryEvent -> m [Decision]
childStart event = do
logInfo' "event=child-start"
input <- maybeThrow (userError "No Child Start Information") $ do
attrs <- event ^. heWorkflowExecutionStartedEventAttributes
return $ attrs ^. weseaInput
return [completeWorkflowExecutionDecision input]
childCompleted :: MonadDecide m => HistoryEvent -> m [Decision]
childCompleted event = do
logInfo' "event=child-completed"
input <- maybeThrow (userError "No Child Completed Information") $ do
attrs <- event ^. heActivityTaskCompletedEventAttributes
return $ attrs ^. atceaResult
return [completeWorkflowExecutionDecision input]
-- Helpers
maybeThrow :: (MonadThrow m, Exception e) => e -> Maybe a -> m a
maybeThrow e = maybe (throwM e) return