Skip to content

Commit

Permalink
Feature/181 asynchronous return execution (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucamrgs authored Jul 23, 2024
1 parent 9c05d07 commit 90e1f3d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 17 deletions.
30 changes: 28 additions & 2 deletions internal/decomposer/decomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ExecutionDetails struct {
}

type IDecomposer interface {
ExecuteAsync(playbook cacao.Playbook, detailsch chan ExecutionDetails)
Execute(playbook cacao.Playbook) (*ExecutionDetails, error)
}

Expand Down Expand Up @@ -69,11 +70,35 @@ type Decomposer struct {
}

// Execute a Playbook
func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetails, error) {
func (decomposer *Decomposer) ExecuteAsync(playbook cacao.Playbook, detailsch chan ExecutionDetails) {
executionId := decomposer.guid.New()
log.Debugf("Starting execution %s for Playbook %s", executionId, playbook.ID)

details := ExecutionDetails{executionId, playbook.ID, playbook.PlaybookVariables}
decomposer.details = details

if detailsch != nil {
detailsch <- details
}

_ = decomposer.execute(playbook)

}

func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetails, error) {

executionId := decomposer.guid.New()
log.Debugf("Starting execution %s for Playbook %s", executionId, playbook.ID)
decomposer.details = ExecutionDetails{executionId, playbook.ID, playbook.PlaybookVariables}

err := decomposer.execute(playbook)

return &decomposer.details, err

}

func (decomposer *Decomposer) execute(playbook cacao.Playbook) error {

decomposer.playbook = playbook

stepId := playbook.WorkflowStart
Expand All @@ -88,7 +113,8 @@ func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetail
decomposer.details.Variables = outputVariables
// Reporting workflow end
decomposer.reporter.ReportWorkflowEnd(decomposer.details.ExecutionId, playbook, err)
return &decomposer.details, err

return err
}

// Execute a Workflow branch of a Playbook
Expand Down
43 changes: 31 additions & 12 deletions routes/trigger/trigger_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"io"
"net/http"
"reflect"
"time"

"soarca/internal/controller/decomposer_controller"
"soarca/internal/decomposer"
"soarca/logger"
"soarca/models/decoder"
"soarca/routes/error"
Expand All @@ -26,12 +28,15 @@ func init() {
}

type TriggerApi struct {
controller decomposer_controller.IController
controller decomposer_controller.IController
Executionsch chan decomposer.ExecutionDetails
}

func New(controller decomposer_controller.IController) *TriggerApi {
instance := TriggerApi{}
instance.controller = controller
// Channel to get back execution details
instance.Executionsch = make(chan decomposer.ExecutionDetails)
return &instance
}

Expand All @@ -54,17 +59,31 @@ func (trigger *TriggerApi) Execute(context *gin.Context) {
"POST /trigger/playbook", "")
return
}
executionDetail, errDecomposer := decomposer.Execute(*playbook)
if errDecomposer != nil {
error.SendErrorResponse(context, http.StatusBadRequest,
"Failed to decode playbook",
"POST /trigger/playbook",
executionDetail.ExecutionId.String())
} else {
msg := gin.H{
"execution_id": executionDetail.ExecutionId.String(),
"payload": executionDetail.PlaybookId,

go decomposer.ExecuteAsync(*playbook, trigger.Executionsch)

// Hard coding the timer to return execution id
timer := time.NewTimer(time.Duration(3) * time.Second)
for {
select {
case <-timer.C:
msg := gin.H{
"execution_id": nil,
"payload": playbook.ID,
}
context.JSON(http.StatusRequestTimeout, msg)
log.Error("async execution timed out for playbook ", playbook.ID)
case exec_details := <-trigger.Executionsch:
playbook_id := exec_details.PlaybookId
exec_id := exec_details.ExecutionId
if playbook_id == playbook.ID {
msg := gin.H{
"execution_id": exec_id,
"payload": playbook_id,
}
context.JSON(http.StatusOK, msg)
return
}
}
context.JSON(http.StatusOK, msg)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ func TestExecutePlaybook(t *testing.T) {
Variables: cacao.NewVariables(returnedVariables)}

playbook2 := cacao.Playbook{ID: playbookId, PlaybookVariables: cacao.NewVariables(expectedVariables)}

mockDecomposer.On("Execute", playbook2).Return(&details, nil)

results, err := executerObject.Execute(metadata, step, cacao.NewVariables(addedVariables))

mockDecomposer.AssertExpectations(t)
mock_reporter.AssertExpectations(t)
assert.Equal(t, err, nil)
assert.Equal(t, results, cacao.NewVariables(returnedVariables))
Expand Down
8 changes: 8 additions & 0 deletions test/unittest/mocks/mock_decomposer/mock_decomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import (
"soarca/internal/decomposer"
"soarca/models/cacao"

"github.com/google/uuid"
"github.com/stretchr/testify/mock"
)

type Mock_Decomposer struct {
mock.Mock
}

func (mock *Mock_Decomposer) ExecuteAsync(playbook cacao.Playbook, detailsch chan decomposer.ExecutionDetails) {
args := mock.Called(playbook, detailsch)
if detailsch != nil {
details := decomposer.ExecutionDetails{ExecutionId: args.Get(2).(uuid.UUID), PlaybookId: playbook.ID, Variables: cacao.NewVariables()}
detailsch <- details
}
}
func (mock *Mock_Decomposer) Execute(playbook cacao.Playbook) (*decomposer.ExecutionDetails, error) {
args := mock.Called(playbook)
return args.Get(0).(*decomposer.ExecutionDetails), args.Error(1)
Expand Down
12 changes: 9 additions & 3 deletions test/unittest/routes/trigger_api/tigger_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import (

"github.com/gin-gonic/gin"
"github.com/go-playground/assert/v2"
"github.com/google/uuid"
)

func TestExecutionOfPlaybook(t *testing.T) {
func TestTriggerExecutionOfPlaybook(t *testing.T) {
jsonFile, err := os.Open("../playbook.json")
if err != nil {
fmt.Println(err)
Expand All @@ -34,17 +35,22 @@ func TestExecutionOfPlaybook(t *testing.T) {
mock_controller := new(mock_decomposer_controller.Mock_Controller)
mock_controller.On("NewDecomposer").Return(mock_decomposer)
playbook := cacao.Decode(byteValue)
mock_decomposer.On("Execute", *playbook).Return(&decomposer.ExecutionDetails{}, nil)

recorder := httptest.NewRecorder()
trigger_api := trigger.New(mock_controller)
recorder := httptest.NewRecorder()
trigger.Routes(app, trigger_api)

executionId, _ := uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
mock_decomposer.On("ExecuteAsync", *playbook, trigger_api.Executionsch).Return(&decomposer.ExecutionDetails{}, nil, executionId)

request, err := http.NewRequest("POST", "/trigger/playbook", bytes.NewBuffer(byteValue))
if err != nil {
t.Fail()
}

expected_return_string := `{"execution_id":"6ba7b810-9dad-11d1-80b4-00c04fd430c8","payload":"playbook--61a6c41e-6efc-4516-a242-dfbc5c89d562"}`
app.ServeHTTP(recorder, request)
assert.Equal(t, expected_return_string, recorder.Body.String())
assert.Equal(t, 200, recorder.Code)
mock_decomposer.AssertExpectations(t)
}

0 comments on commit 90e1f3d

Please sign in to comment.