Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ease interpretation of errors by HTTP status code #47

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,71 @@ package fargo

// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"fmt"
"net/http"
)

type httpOperation byte

const (
opRegistration httpOperation = iota
opDeregistration
opMetadataUpdate
opStatusUpdate
opLeaseRenewal
opRetrieval
)

type unsuccessfulHTTPResponse struct {
op httpOperation
statusCode int
messagePrefix string
}

func (u unsuccessfulHTTPResponse) Error() string {
return fmt.Sprint(u.messagePrefix, ", rcode = ", u.statusCode)
}

func httpResponseIndicatesInvalidInstance(op httpOperation, statusCode int) bool {
switch op {
case opRegistration:
return statusCode == http.StatusBadRequest
default:
return false
}
}

// InstanceWasInvalid returns true if the error arose during an instance registration attempt
// due to Eureka rejecting the proposed instance as invalid.
func InstanceWasInvalid(err error) bool {
if u, ok := err.(*unsuccessfulHTTPResponse); ok {
return httpResponseIndicatesInvalidInstance(u.op, u.statusCode)
}
return false
}

func httpResponseIndicatesMissingInstance(op httpOperation, statusCode int) bool {
switch op {
case opDeregistration, opStatusUpdate, opLeaseRenewal, opRetrieval:
return statusCode == http.StatusNotFound
case opMetadataUpdate:
return statusCode == http.StatusInternalServerError
default:
return false
}
}

// InstanceWasMissing returns true if the error arose during an instance retrieval, status update,
// metadata update, or lease renewal, or deregistration attempt due to the target instance not being
// registered, being unknown to Eureka.
func InstanceWasMissing(err error) bool {
if u, ok := err.(*unsuccessfulHTTPResponse); ok {
return httpResponseIndicatesMissingInstance(u.op, u.statusCode)
}
return false
}

type AppNotFoundError struct {
specific string
}
60 changes: 60 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package fargo

// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"errors"
"net/http"
"sort"
"testing"
)

func isMember(haystack []int, needle int) bool {
i := sort.SearchInts(haystack, needle)
return i != len(haystack) && haystack[i] == needle
}

func predicateOnlyTrueFor(t *testing.T, pred func(error) bool, codesByOp map[httpOperation][]int) {
makeError := func(op httpOperation, code int) error {
return &unsuccessfulHTTPResponse{op, code, ""}
}
for op := opRegistration; op <= opRetrieval; op++ {
if codes, ok := codesByOp[op]; ok {
if !sort.IntsAreSorted(codes) {
t.Fatalf("codes for op %d are not sorted: %d", op, codes)
}
for c := 100; c != 600; c++ {
if got, want := pred(makeError(op, c)), isMember(codes, c); got != want {
t.Errorf("op %d, code %d: got %t, want %t", op, c, got, want)
}
}
} else {
// None of the codes should provoke a true return value.
for c := 100; c != 600; c++ {
if pred(makeError(op, c)) {
t.Errorf("op %d, code %d: got true, want false", op, c)
}
}
}
}

if pred(errors.New("other")) {
t.Errorf("non HTTP-related error: got true, want false")
}
}

func TestInvalidInstanceDetection(t *testing.T) {
predicateOnlyTrueFor(t, InstanceWasInvalid, map[httpOperation][]int{
opRegistration: {http.StatusBadRequest},
})
}

func TestMissingInstanceDetection(t *testing.T) {
predicateOnlyTrueFor(t, InstanceWasMissing, map[httpOperation][]int{
opDeregistration: {http.StatusNotFound},
opMetadataUpdate: {http.StatusInternalServerError},
opStatusUpdate: {http.StatusNotFound},
opLeaseRenewal: {http.StatusNotFound},
opRetrieval: {http.StatusNotFound},
})
}
20 changes: 10 additions & 10 deletions net.go
Original file line number Diff line number Diff line change
@@ -131,7 +131,7 @@ func (e *EurekaConnection) RegisterInstance(ins *Instance) error {
ins.Id(), ins.App, err.Error())
return err
}
if rcode == 200 {
if rcode == http.StatusOK {
log.Noticef("Instance=%s already exists in App=%s, aborting registration", ins.Id(), ins.App)
return nil
}
@@ -164,7 +164,7 @@ func (e *EurekaConnection) ReregisterInstance(ins *Instance) error {
if rcode != 204 {
log.Warningf("HTTP returned %d registering Instance=%s App=%s Body=\"%s\"", rcode,
ins.Id(), ins.App, string(body))
return fmt.Errorf("http returned %d possible failure registering instance\n", rcode)
return &unsuccessfulHTTPResponse{opRegistration, rcode, "possible failure registering instance"}
}

// read back our registration to pick up eureka-supplied values
@@ -182,8 +182,8 @@ func (e *EurekaConnection) GetInstance(app, insId string) (*Instance, error) {
if err != nil {
return nil, err
}
if rcode != 200 {
return nil, fmt.Errorf("Error getting instance, rcode = %d", rcode)
if rcode != http.StatusOK {
return nil, &unsuccessfulHTTPResponse{opRetrieval, rcode, "unable to retrieve instance"}
}
var ins *Instance
if e.UseJson {
@@ -217,9 +217,9 @@ func (e *EurekaConnection) DeregisterInstance(ins *Instance) error {
log.Errorf("Could not complete deregistration, error: %s", err.Error())
return err
}
if rcode != 204 {
if rcode != http.StatusOK {
log.Warningf("HTTP returned %d deregistering Instance=%s App=%s", rcode, ins.Id(), ins.App)
return fmt.Errorf("http returned %d possible failure deregistering instance\n", rcode)
return &unsuccessfulHTTPResponse{opDeregistration, rcode, "possible failure deregistering instance"}
}

return nil
@@ -241,7 +241,7 @@ func (e EurekaConnection) AddMetadataString(ins *Instance, key, value string) er
if rcode < 200 || rcode >= 300 {
log.Warningf("HTTP returned %d updating metadata Instance=%s App=%s Body=\"%s\"", rcode,
ins.Id(), ins.App, string(body))
return fmt.Errorf("http returned %d possible failure updating instance metadata ", rcode)
return &unsuccessfulHTTPResponse{opMetadataUpdate, rcode, "possible failure updating instance metadata"}
}
ins.SetMetadataString(key, value)
return nil
@@ -263,7 +263,7 @@ func (e EurekaConnection) UpdateInstanceStatus(ins *Instance, status StatusType)
if rcode < 200 || rcode >= 300 {
log.Warningf("HTTP returned %d updating status Instance=%s App=%s Body=\"%s\"", rcode,
ins.Id(), ins.App, string(body))
return fmt.Errorf("http returned %d possible failure updating instance status ", rcode)
return &unsuccessfulHTTPResponse{opStatusUpdate, rcode, "possible failure updating instance status"}
}
return nil
}
@@ -284,9 +284,9 @@ func (e *EurekaConnection) HeartBeatInstance(ins *Instance) error {
log.Errorf("Error sending heartbeat for Instance=%s App=%s, error: %s", ins.Id(), ins.App, err.Error())
return err
}
if rcode != 200 {
if rcode != http.StatusOK {
log.Errorf("Sending heartbeat for Instance=%s App=%s returned code %d", ins.Id(), ins.App, rcode)
return fmt.Errorf("heartbeat returned code %d\n", rcode)
return &unsuccessfulHTTPResponse{opLeaseRenewal, rcode, "heartbeat failed"}
}
return nil
}
7 changes: 6 additions & 1 deletion tests/net_test.go
Original file line number Diff line number Diff line change
@@ -3,9 +3,10 @@ package fargo_test
// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"testing"

"github.com/hudl/fargo"
. "github.com/smartystreets/goconvey/convey"
"testing"
)

func TestConnectionCreation(t *testing.T) {
@@ -64,6 +65,8 @@ func TestRegistration(t *testing.T) {
}
err := e.HeartBeatInstance(&j)
So(err, ShouldNotBeNil)
So(fargo.InstanceWasMissing(err), ShouldBeTrue)
So(fargo.InstanceWasInvalid(err), ShouldBeFalse)
})
Convey("Register an instance to TESTAPP", t, func() {
Convey("Instance registers correctly", func() {
@@ -149,6 +152,8 @@ func DontTestDeregistration(t *testing.T) {
Convey("Instance cannot check in", func() {
err := e.HeartBeatInstance(&i)
So(err, ShouldNotBeNil)
So(fargo.InstanceWasMissing(err), ShouldBeTrue)
So(fargo.InstanceWasInvalid(err), ShouldBeFalse)
})
})
}