Skip to content

Commit 1c9f8c4

Browse files
authored
Merge pull request #109 from SAP/102-bug-losing-track-of-created-instances
fix[102]: serviceinstance job polling returns no error on http client timeout
2 parents dc97391 + d75eecd commit 1c9f8c4

2 files changed

Lines changed: 94 additions & 10 deletions

File tree

internal/clients/serviceinstance/serviceinstance.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package serviceinstance
33
import (
44
"context"
55
"encoding/json"
6+
"net/url"
67
"time"
78

89
"github.com/cloudfoundry/go-cfclient/v3/client"
@@ -51,8 +52,17 @@ func (c *Client) pollJobComplete(ctx context.Context, job string) error {
5152

5253
err := c.Job.PollComplete(ctx, job, newPollingOptions())
5354

54-
if err != nil && errors.Is(err, client.AsyncProcessTimeoutError) { // because we have logic to observe job state, we can safely ignore timeout error
55-
return nil
55+
if err != nil {
56+
isTimeoutError := false
57+
urlErr := &url.Error{}
58+
if errors.As(err, &urlErr) {
59+
// urlErr.Timeout() is true if the http client
60+
// experienced timeout error
61+
isTimeoutError = urlErr.Timeout()
62+
}
63+
if errors.Is(err, client.AsyncProcessTimeoutError) || isTimeoutError { // because we have logic to observe job state, we can safely ignore timeout error
64+
return nil
65+
}
5666
}
5767
return err
5868
}

internal/controller/serviceinstance/controller_test.go

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package serviceinstance
22

33
import (
44
"context"
5+
"net/url"
56
"testing"
67

78
"github.com/google/go-cmp/cmp"
@@ -476,6 +477,13 @@ func TestObserve(t *testing.T) {
476477
}
477478
}
478479

480+
type timeoutError struct {
481+
timeout bool
482+
}
483+
484+
func (e *timeoutError) Error() string { return "timeout error" }
485+
func (e *timeoutError) Timeout() bool { return e.timeout }
486+
479487
func TestCreate(t *testing.T) {
480488
type service func() *fake.MockServiceInstance
481489
type job func() *fake.MockJob
@@ -558,6 +566,39 @@ func TestCreate(t *testing.T) {
558566
return m
559567
},
560568
},
569+
"HTTPClientTimeout": {
570+
args: args{
571+
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withCredentials(&jsonCredentials)),
572+
},
573+
want: want{
574+
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withCredentials(&jsonCredentials), withConditions(xpv1.Creating()), withExternalName(guid), withStatus(v1alpha1.ServiceInstanceObservation{Credentials: iSha256([]byte(jsonCredentials))})),
575+
obs: managed.ExternalCreation{},
576+
err: nil,
577+
},
578+
service: func() *fake.MockServiceInstance {
579+
m := &fake.MockServiceInstance{}
580+
m.On("CreateManaged").Return(
581+
"JOB123",
582+
nil,
583+
)
584+
m.On("Single").Return(
585+
&fake.NewServiceInstance("managed").SetName(name).SetGUID(guid).SetServicePlan(servicePlan).ServiceInstance,
586+
nil,
587+
)
588+
m.On("GetManagedParameters", guid).Return(
589+
fake.JSONRawMessage(jsonCredentials),
590+
nil, // no error
591+
)
592+
return m
593+
},
594+
job: func() *fake.MockJob {
595+
m := &fake.MockJob{}
596+
m.On("PollComplete").Return(&url.Error{
597+
Err: &timeoutError{true},
598+
})
599+
return m
600+
},
601+
},
561602
"CannotPollCreationJob": {
562603
args: args{
563604
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan})),
@@ -640,18 +681,18 @@ func TestCreate(t *testing.T) {
640681
if tc.want.err != nil && err != nil {
641682
// the case where our mock server returns error.
642683
if diff := cmp.Diff(tc.want.err.Error(), err.Error()); diff != "" {
643-
t.Errorf("Observe(...): want error string != got error string:\n%s", diff)
684+
t.Errorf("Create(...): want error string != got error string:\n%s", diff)
644685
}
645686
} else {
646687
if diff := cmp.Diff(tc.want.err, err); diff != "" {
647-
t.Errorf("Observe(...): want error != got error:\n%s", diff)
688+
t.Errorf("Create(...): want error != got error:\n%s", diff)
648689
}
649690
}
650691
if diff := cmp.Diff(tc.want.obs, obs); diff != "" {
651-
t.Errorf("Observe(...): -want, +got:\n%s", diff)
692+
t.Errorf("Create(...): -want, +got:\n%s", diff)
652693
}
653694
if diff := cmp.Diff(tc.want.mg, tc.args.mg); diff != "" {
654-
t.Errorf("Observe(...): -want, +got:\n%s", diff)
695+
t.Errorf("Create(...): -want, +got:\n%s", diff)
655696
}
656697
})
657698
}
@@ -739,6 +780,39 @@ func TestUpdate(t *testing.T) {
739780
return m
740781
},
741782
},
783+
"HTTPClientTimeout": {
784+
args: args{
785+
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withExternalName(guid), withStatus(v1alpha1.ServiceInstanceObservation{ID: &guid}), withCredentials(&jsonCredentials)),
786+
},
787+
want: want{
788+
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withCredentials(&jsonCredentials), withExternalName(guid), withStatus(v1alpha1.ServiceInstanceObservation{ID: &guid, Credentials: iSha256([]byte(jsonCredentials))})),
789+
obs: managed.ExternalUpdate{},
790+
err: nil,
791+
},
792+
service: func() *fake.MockServiceInstance {
793+
m := &fake.MockServiceInstance{}
794+
m.On("UpdateManaged", guid).Return(
795+
"JOB123",
796+
nil,
797+
)
798+
m.On("Get", guid).Return(
799+
&fake.NewServiceInstance("managed").SetName(name).SetGUID(guid).SetServicePlan(servicePlan).ServiceInstance,
800+
nil,
801+
)
802+
m.On("GetManagedParameters", guid).Return(
803+
fake.JSONRawMessage(jsonCredentials),
804+
nil, // no error
805+
)
806+
return m
807+
},
808+
job: func() *fake.MockJob {
809+
m := &fake.MockJob{}
810+
m.On("PollComplete").Return(&url.Error{
811+
Err: &timeoutError{true},
812+
})
813+
return m
814+
},
815+
},
742816
"CannotPollCreationJob": {
743817
args: args{
744818
mg: serviceInstance("managed", withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withExternalName(guid), withStatus(v1alpha1.ServiceInstanceObservation{ID: &guid})),
@@ -817,18 +891,18 @@ func TestUpdate(t *testing.T) {
817891
if tc.want.err != nil && err != nil {
818892
// the case where our mock server returns error.
819893
if diff := cmp.Diff(tc.want.err.Error(), err.Error()); diff != "" {
820-
t.Errorf("Observe(...): want error string != got error string:\n%s", diff)
894+
t.Errorf("Update(...): want error string != got error string:\n%s", diff)
821895
}
822896
} else {
823897
if diff := cmp.Diff(tc.want.err, err); diff != "" {
824-
t.Errorf("Observe(...): want error != got error:\n%s", diff)
898+
t.Errorf("Update(...): want error != got error:\n%s", diff)
825899
}
826900
}
827901
if diff := cmp.Diff(tc.want.obs, obs); diff != "" {
828-
t.Errorf("Observe(...): -want, +got:\n%s", diff)
902+
t.Errorf("Update(...): -want, +got:\n%s", diff)
829903
}
830904
if diff := cmp.Diff(tc.want.mg, tc.args.mg); diff != "" {
831-
t.Errorf("Observe(...): -want, +got:\n%s", diff)
905+
t.Errorf("Update(...): -want, +got:\n%s", diff)
832906
}
833907
})
834908
}

0 commit comments

Comments
 (0)