Skip to content

Commit

Permalink
add option for fallback post serve action
Browse files Browse the repository at this point in the history
  • Loading branch information
kapishmalik committed May 4, 2024
1 parent 2cb8acc commit 5ee1ba7
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 39 deletions.
12 changes: 2 additions & 10 deletions core/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"os"
"os/exec"
"path"
"strings"
"time"

v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2"
Expand All @@ -27,12 +26,9 @@ type Action struct {
DelayInMs int
}

func NewLocalAction(actionName, binary, scriptContent string, delayInMs int) (*Action, error) {
func NewLocalAction(binary, scriptContent string, delayInMs int) (*Action, error) {

scriptInfo := &Action{}
if strings.TrimSpace(actionName) == "" {
return nil, errors.New("empty action name passed")
}

scriptInfo.DelayInMs = delayInMs

Expand All @@ -46,11 +42,7 @@ func NewLocalAction(actionName, binary, scriptContent string, delayInMs int) (*A
return scriptInfo, nil
}

func NewRemoteAction(actionName, host string, delayInMs int) (*Action, error) {

if strings.TrimSpace(actionName) == "" {
return nil, errors.New("empty action name passed")
}
func NewRemoteAction(host string, delayInMs int) (*Action, error) {

if !isValidURL(host) {
return nil, errors.New("remote host is invalid")
Expand Down
20 changes: 10 additions & 10 deletions core/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const pythonBasicScript = "import sys\nprint(sys.stdin.readlines()[0])"
func Test_NewLocalActionMethod(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800)
newAction, err := action.NewLocalAction("python3", "dummy-script", 1800)

Expect(err).To(BeNil())
Expect(newAction).NotTo(BeNil())
Expand All @@ -33,7 +33,7 @@ func Test_NewLocalActionMethod(t *testing.T) {
func Test_NewRemoteActionMethodWithEmptyHost(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewRemoteAction("test-callback", "", 1800)
newAction, err := action.NewRemoteAction("", 1800)

Expect(err).NotTo(BeNil())
Expect(newAction).To(BeNil())
Expand All @@ -42,7 +42,7 @@ func Test_NewRemoteActionMethodWithEmptyHost(t *testing.T) {
func Test_NewRemoteActionMethodWithInvalidHost(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewRemoteAction("test-callback", "testing", 1800)
newAction, err := action.NewRemoteAction("testing", 1800)

Expect(err).NotTo(BeNil())
Expect(err.Error()).To(Equal("remote host is invalid"))
Expand All @@ -52,7 +52,7 @@ func Test_NewRemoteActionMethodWithInvalidHost(t *testing.T) {
func Test_NewRemoteActionMethodWithHttpHost(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewRemoteAction("test-callback", "http://localhost", 1800)
newAction, err := action.NewRemoteAction("http://localhost", 1800)

Expect(err).To(BeNil())
Expect(newAction).NotTo(BeNil())
Expand All @@ -63,7 +63,7 @@ func Test_NewRemoteActionMethodWithHttpHost(t *testing.T) {
func Test_NewRemoteActionMethodWithHttpsHost(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewRemoteAction("test-callback", "https://test.com", 1800)
newAction, err := action.NewRemoteAction("https://test.com", 1800)

Expect(err).To(BeNil())
Expect(newAction).NotTo(BeNil())
Expand All @@ -74,7 +74,7 @@ func Test_NewRemoteActionMethodWithHttpsHost(t *testing.T) {
func Test_GetLocalActionViewMethod(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800)
newAction, err := action.NewLocalAction("python3", "dummy-script", 1800)

Expect(err).To(BeNil())
actionView := newAction.GetActionView("test-callback")
Expand All @@ -88,7 +88,7 @@ func Test_GetLocalActionViewMethod(t *testing.T) {
func Test_GetRemoteActionViewMethod(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewRemoteAction("test-callback", "http://localhost:8000", 1800)
newAction, err := action.NewRemoteAction("http://localhost:8000", 1800)

Expect(err).To(BeNil())
actionView := newAction.GetActionView("test-callback")
Expand All @@ -102,7 +102,7 @@ func Test_GetRemoteActionViewMethod(t *testing.T) {

func Test_ExecuteLocalPostServeAction(t *testing.T) {
RegisterTestingT(t)
newAction, err := action.NewLocalAction("test-callback", "python3", pythonBasicScript, 0)
newAction, err := action.NewLocalAction("python3", pythonBasicScript, 0)

Expect(err).To(BeNil())

Expand Down Expand Up @@ -136,7 +136,7 @@ func Test_ExecuteRemotePostServeAction(t *testing.T) {
journalIDChannel := make(chan string, 1)
newJournal := journal.NewJournal()
journalIDChannel <- "1"
newAction, err := action.NewRemoteAction("test-callback", server.URL+"/process", 0)
newAction, err := action.NewRemoteAction(server.URL+"/process", 0)
close(journalIDChannel)
Expect(err).To(BeNil())
err = newAction.Execute(&originalPair, journalIDChannel, newJournal)
Expand All @@ -150,7 +150,7 @@ func Test_ExecuteRemotePostServeAction_WithUnReachableHost(t *testing.T) {
},
}

newAction, err := action.NewRemoteAction("test-callback", "http://test", 0)
newAction, err := action.NewRemoteAction("http://test", 0)
Expect(err).To(BeNil())

//not adding entry as update journal method will be tested in its file
Expand Down
22 changes: 15 additions & 7 deletions core/action/postserveactiondetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package action

import (
"fmt"
"strings"
"sync"
)

type PostServeActionDetails struct {
Actions map[string]Action
RWMutex sync.RWMutex
Actions map[string]Action
FallbackAction *Action
RWMutex sync.RWMutex
}

func NewPostServeActionDetails() *PostServeActionDetails {
Expand All @@ -20,12 +22,18 @@ func NewPostServeActionDetails() *PostServeActionDetails {
func (postServeActionDetails *PostServeActionDetails) SetAction(actionName string, newAction *Action) error {

postServeActionDetails.RWMutex.Lock()
//cleanup
if existingAction, ok := postServeActionDetails.Actions[actionName]; ok {
existingAction.DeleteScript()
delete(postServeActionDetails.Actions, actionName)
if strings.TrimSpace(actionName) == "" {
if postServeActionDetails.FallbackAction != nil {
postServeActionDetails.FallbackAction.DeleteScript()
}
postServeActionDetails.FallbackAction = newAction
} else {
if existingAction, ok := postServeActionDetails.Actions[actionName]; ok {
existingAction.DeleteScript()
delete(postServeActionDetails.Actions, actionName)
}
postServeActionDetails.Actions[actionName] = *newAction
}
postServeActionDetails.Actions[actionName] = *newAction
postServeActionDetails.RWMutex.Unlock()
return nil
}
Expand Down
17 changes: 15 additions & 2 deletions core/action/postserveactiondetails_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func Test_SetPostServeActionMethod(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800)
newAction, err := action.NewLocalAction("python3", "dummy script", 1800)
Expect(err).To(BeNil())

unit := action.NewPostServeActionDetails()
Expand All @@ -22,10 +22,23 @@ func Test_SetPostServeActionMethod(t *testing.T) {
Expect(unit.Actions["test-callback"].DelayInMs).To(Equal(1800))
}

func Test_SetPostServeActionMethod_WithEmptyActionName(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewLocalAction("python3", "dummy script", 1800)
Expect(err).To(BeNil())

unit := action.NewPostServeActionDetails()
err = unit.SetAction("", newAction)

Expect(err).To(BeNil())
Expect(unit.FallbackAction).NotTo(BeNil())
}

func Test_DeletePostServeActionMethod(t *testing.T) {
RegisterTestingT(t)

newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800)
newAction, err := action.NewLocalAction("python3", "dummy script", 1800)
Expect(err).To(BeNil())

unit := action.NewPostServeActionDetails()
Expand Down
41 changes: 40 additions & 1 deletion core/cmd/hoverfly/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ var (
cors = flag.Bool("cors", false, "Enable CORS support")
noImportCheck = flag.Bool("no-import-check", false, "Skip duplicate request check when importing simulations")

pacFile = flag.String("pac-file", "", "Path to the pac file to be imported on startup")
pacFile = flag.String("pac-file", "", "Path to the pac file to be imported on startup")
fallbackPostServeAction = flag.String("fallback-post-serve-action", "", "Set fallback post serve action by passing the binary and the path of the action script and delay in Ms separated by space or remote and deply in Ms separated by space. (i.e. '-fallback-post-serve-action \"http://localhost:8080 2000\"')")

clientAuthenticationDestination = flag.String("client-authentication-destination", "", "Regular expression of destination with client authentication")
clientAuthenticationClientCert = flag.String("client-authentication-client-cert", "", "Path to the client certification file used for authentication")
Expand Down Expand Up @@ -589,6 +590,44 @@ func main() {
}
}

if *fallbackPostServeAction != "" {
splitPostServeAction := strings.Split(*fallbackPostServeAction, " ")
if len(splitPostServeAction) == 3 {
delayInMs, err := strconv.Atoi(splitPostServeAction[2])
if err != nil {
//default to 1000 incase of error
delayInMs = 1000
}

if fileContents, err := ioutil.ReadFile(splitPostServeAction[1]); err == nil {
err = hoverfly.SetLocalPostServeAction("", splitPostServeAction[0], string(fileContents), delayInMs)
if err != nil {
log.WithFields(log.Fields{
"error": err.Error(),
"import": *fallbackPostServeAction,
}).Fatal("Failed to import fallback post serve action")
}
}
} else if len(splitPostServeAction) == 2 {
delayInMs, err := strconv.Atoi(splitPostServeAction[1])
if err != nil {
//default to 1000 in case of error
delayInMs = 1000
}
err = hoverfly.SetRemotePostServeAction(" ", splitPostServeAction[0], delayInMs)
if err != nil {
log.WithFields(log.Fields{
"error": err.Error(),
"import": *fallbackPostServeAction,
}).Fatal("Failed to import fallback post serve action")
}
} else {
log.WithFields(log.Fields{
"import": *fallbackPostServeAction,
}).Fatal("Failed to import fallback post serve action due to invalid input passed")
}
}

if len(templatingDataSourceFlags) > 0 {

for _, v := range templatingDataSourceFlags {
Expand Down
5 changes: 3 additions & 2 deletions core/handlers/v2/postserveactiondetails_views.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package v2

type PostServeActionDetailsView struct {
Actions []ActionView `json:"actions,omitempty"`
Actions []ActionView `json:"actions,omitempty"`
FallbackAction *ActionView `json:"fallbackAction,omitempty"`
}

type ActionView struct {
ActionName string `json:"actionName"`
ActionName string `json:"actionName,omitempty"`
Binary string `json:"binary,omitempty"`
ScriptContent string `json:"script,omitempty"`
Remote string `json:"remote,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions core/hoverfly.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ func (hf *Hoverfly) processRequest(req *http.Request) (*http.Response, chan stri
journalIDChannel := make(chan string, 1)
go postServeAction.Execute(result.PostServeActionInputDetails.Pair, journalIDChannel, hf.Journal)
return result.Response, journalIDChannel
} else if hf.PostServeActionDetails.FallbackAction != nil {
journalIDChannel := make(chan string, 1)
go hf.PostServeActionDetails.FallbackAction.Execute(result.PostServeActionInputDetails.Pair, journalIDChannel, hf.Journal)
return result.Response, journalIDChannel
}
}

Expand Down
13 changes: 10 additions & 3 deletions core/hoverfly_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,14 +467,21 @@ func (hf *Hoverfly) GetAllPostServeActions() v2.PostServeActionDetailsView {
for actionName, action := range hf.PostServeActionDetails.Actions {
actions = append(actions, action.GetActionView(actionName))
}

var fallbackActionView v2.ActionView
if hf.PostServeActionDetails.FallbackAction != nil {
fallbackActionView = hf.PostServeActionDetails.FallbackAction.GetActionView("")
}

return v2.PostServeActionDetailsView{
Actions: actions,
Actions: actions,
FallbackAction: &fallbackActionView,
}
}

func (hf *Hoverfly) SetLocalPostServeAction(actionName string, binary string, scriptContent string, delayInMs int) error {

action, err := action.NewLocalAction(actionName, binary, scriptContent, delayInMs)
action, err := action.NewLocalAction(binary, scriptContent, delayInMs)
if err != nil {
return err
}
Expand All @@ -488,7 +495,7 @@ func (hf *Hoverfly) SetLocalPostServeAction(actionName string, binary string, sc

func (hf *Hoverfly) SetRemotePostServeAction(actionName, remote string, delayInMs int) error {

action, err := action.NewRemoteAction(actionName, remote, delayInMs)
action, err := action.NewRemoteAction(remote, delayInMs)
if err != nil {
return err
}
Expand Down
59 changes: 59 additions & 0 deletions core/hoverfly_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,36 @@ func TestHoverfly_GetPostServeActions(t *testing.T) {
Expect(postServeActions.Actions[1].DelayInMs).To(Equal(1800))
}

func TestHoverfly_GetPostServeActions_WithFallback(t *testing.T) {

RegisterTestingT(t)

unit := NewHoverflyWithConfiguration(&Configuration{})
localActionDetails := action.Action{Binary: "python3", DelayInMs: 1900}
remoteActionDetails := action.Action{Remote: "http://localhost", DelayInMs: 1800}
fallbackActionDetails := action.Action{Remote: "http://localhost:8081", DelayInMs: 1800}
actionMap := map[string]action.Action{
"test-local-callback": localActionDetails,
"test-remote-callback": remoteActionDetails,
}

unit.PostServeActionDetails.Actions = actionMap
unit.PostServeActionDetails.FallbackAction = &fallbackActionDetails
postServeActions := unit.GetAllPostServeActions()

Expect(postServeActions).NotTo(BeNil())
Expect(postServeActions.Actions).To(HaveLen(2))
Expect(postServeActions.Actions[0].ActionName).To(Equal("test-local-callback"))
Expect(postServeActions.Actions[0].Binary).To(Equal("python3"))
Expect(postServeActions.Actions[0].DelayInMs).To(Equal(1900))
Expect(postServeActions.Actions[1].ActionName).To(Equal("test-remote-callback"))
Expect(postServeActions.Actions[1].Remote).To(Equal("http://localhost"))
Expect(postServeActions.Actions[1].DelayInMs).To(Equal(1800))
Expect(postServeActions.FallbackAction).NotTo(BeNil())
Expect(postServeActions.FallbackAction.Remote).To(Equal("http://localhost:8081"))
Expect(postServeActions.FallbackAction.DelayInMs).To(Equal(1800))
}

func TestHoverfly_SetLocalPostServeAction(t *testing.T) {

RegisterTestingT(t)
Expand Down Expand Up @@ -1409,6 +1439,35 @@ func TestHoverfly_SetRemotePostServeAction(t *testing.T) {
Expect(unit.PostServeActionDetails.Actions["test-callback"].DelayInMs).To(Equal(1800))
}

func TestHoverfly_SetFallbackLocalPostServeAction(t *testing.T) {

RegisterTestingT(t)

unit := NewHoverflyWithConfiguration(&Configuration{})

err := unit.SetLocalPostServeAction("", "script", "dummy script", 1800)

Expect(err).To(BeNil())
Expect(unit.PostServeActionDetails.FallbackAction).NotTo(BeNil())
Expect(unit.PostServeActionDetails.FallbackAction.Binary).To(Equal("script"))
Expect(unit.PostServeActionDetails.FallbackAction.DelayInMs).To(Equal(1800))

}

func TestHoverfly_SetFallbackRemotePostServeAction(t *testing.T) {

RegisterTestingT(t)

unit := NewHoverflyWithConfiguration(&Configuration{})

err := unit.SetRemotePostServeAction("", "http://localhost:8080", 1800)

Expect(err).To(BeNil())
Expect(unit.PostServeActionDetails.FallbackAction).NotTo(BeNil())
Expect(unit.PostServeActionDetails.FallbackAction.Remote).To(Equal("http://localhost:8080"))
Expect(unit.PostServeActionDetails.FallbackAction.DelayInMs).To(Equal(1800))
}

func TestHoverfly_DeleteLocalPostServeAction(t *testing.T) {

RegisterTestingT(t)
Expand Down
2 changes: 1 addition & 1 deletion core/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (hf *Hoverfly) importRequestResponsePairViewsWithCustomData(pairViews []v2.
failed := 0
for i, pairView := range pairViews {

if _, ok := hf.PostServeActionDetails.Actions[pairView.Response.PostServeAction]; pairView.Response.PostServeAction != "" && !ok {
if _, ok := hf.PostServeActionDetails.Actions[pairView.Response.PostServeAction]; pairView.Response.PostServeAction != "" && !ok && hf.PostServeActionDetails.FallbackAction == nil {
importResult.SetError(fmt.Errorf("invalid post server action name provided"))
break
}
Expand Down
Loading

0 comments on commit 5ee1ba7

Please sign in to comment.