Skip to content

Commit 330f0e0

Browse files
author
Luis Urraca
committed
Support async unbind
* API Client now has an acceptsIncomplete flag for async unbinding - also returns the current binding when acceptsIncomplete is true * update minimum async version to mininum for async unbind * update service broker to return an async unbind response * added API client helper into integration helpers to poll state of async binding operations * update API documentation [Finishes #158448710]
1 parent a56cff9 commit 330f0e0

File tree

15 files changed

+418
-124
lines changed

15 files changed

+418
-124
lines changed

actor/v2action/cloud_controller_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type CloudControllerClient interface {
1919
DeleteRouteApplication(routeGUID string, appGUID string) (ccv2.Warnings, error)
2020
DeleteSecurityGroupSpace(securityGroupGUID string, spaceGUID string) (ccv2.Warnings, error)
2121
DeleteSecurityGroupStagingSpace(securityGroupGUID string, spaceGUID string) (ccv2.Warnings, error)
22-
DeleteServiceBinding(serviceBindingGUID string) (ccv2.Warnings, error)
22+
DeleteServiceBinding(serviceBindingGUID string, acceptsIncomplete bool) (ccv2.ServiceBinding, ccv2.Warnings, error)
2323
DeleteSpaceJob(spaceGUID string) (ccv2.Job, ccv2.Warnings, error)
2424
DoesRouteExist(route ccv2.Route) (bool, ccv2.Warnings, error)
2525
GetApplication(guid string) (ccv2.Application, ccv2.Warnings, error)

actor/v2action/service_binding.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,31 @@ func (actor Actor) GetServiceBindingByApplicationAndServiceInstance(appGUID stri
7474

7575
// UnbindServiceBySpace deletes the service binding between an application and
7676
// service instance for a given space.
77-
func (actor Actor) UnbindServiceBySpace(appName string, serviceInstanceName string, spaceGUID string) (Warnings, error) {
77+
func (actor Actor) UnbindServiceBySpace(appName string, serviceInstanceName string, spaceGUID string) (ServiceBinding, Warnings, error) {
7878
var allWarnings Warnings
7979

8080
app, warnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID)
8181
allWarnings = append(allWarnings, warnings...)
8282
if err != nil {
83-
return allWarnings, err
83+
return ServiceBinding{}, allWarnings, err
8484
}
8585

8686
serviceInstance, warnings, err := actor.GetServiceInstanceByNameAndSpace(serviceInstanceName, spaceGUID)
8787
allWarnings = append(allWarnings, warnings...)
8888
if err != nil {
89-
return allWarnings, err
89+
return ServiceBinding{}, allWarnings, err
9090
}
9191

9292
serviceBinding, warnings, err := actor.GetServiceBindingByApplicationAndServiceInstance(app.GUID, serviceInstance.GUID)
9393
allWarnings = append(allWarnings, warnings...)
9494
if err != nil {
95-
return allWarnings, err
95+
return ServiceBinding{}, allWarnings, err
9696
}
9797

98-
ccWarnings, err := actor.CloudControllerClient.DeleteServiceBinding(serviceBinding.GUID)
98+
deletedBinding, ccWarnings, err := actor.CloudControllerClient.DeleteServiceBinding(serviceBinding.GUID, true)
9999
allWarnings = append(allWarnings, ccWarnings...)
100100

101-
return allWarnings, err
101+
return ServiceBinding(deletedBinding), allWarnings, err
102102
}
103103

104104
func (actor Actor) GetServiceBindingsByServiceInstance(serviceInstanceGUID string) ([]ServiceBinding, Warnings, error) {

actor/v2action/service_binding_test.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ var _ = Describe("Service Binding Actions", func() {
5151
executeErr error
5252
warnings Warnings
5353
)
54+
5455
BeforeEach(func() {
5556
applicationGUID = "some-app-guid"
5657
serviceInstanceGUID = "some-service-instance-guid"
@@ -264,6 +265,16 @@ var _ = Describe("Service Binding Actions", func() {
264265
})
265266

266267
Describe("UnbindServiceBySpace", func() {
268+
var (
269+
executeErr error
270+
warnings Warnings
271+
serviceBinding ServiceBinding
272+
)
273+
274+
JustBeforeEach(func() {
275+
serviceBinding, warnings, executeErr = actor.UnbindServiceBySpace("some-app", "some-service-instance", "some-space-guid")
276+
})
277+
267278
Context("when the service binding exists", func() {
268279
BeforeEach(func() {
269280
fakeCloudControllerClient.GetApplicationsReturns(
@@ -297,31 +308,33 @@ var _ = Describe("Service Binding Actions", func() {
297308
)
298309

299310
fakeCloudControllerClient.DeleteServiceBindingReturns(
311+
ccv2.ServiceBinding{GUID: "deleted-service-binding-guid"},
300312
ccv2.Warnings{"foo-4", "foo-5"},
301313
nil,
302314
)
303315
})
304316

305317
It("deletes the service binding", func() {
306-
warnings, err := actor.UnbindServiceBySpace("some-app", "some-service-instance", "some-space-guid")
307-
Expect(err).NotTo(HaveOccurred())
318+
Expect(executeErr).NotTo(HaveOccurred())
308319
Expect(warnings).To(ConsistOf(Warnings{"foo-1", "foo-2", "foo-3", "foo-4", "foo-5"}))
320+
Expect(serviceBinding).To(Equal(ServiceBinding{GUID: "deleted-service-binding-guid"}))
309321

310322
Expect(fakeCloudControllerClient.DeleteServiceBindingCallCount()).To(Equal(1))
311-
Expect(fakeCloudControllerClient.DeleteServiceBindingArgsForCall(0)).To(Equal("some-service-binding-guid"))
323+
passedGUID, acceptsIncomplete := fakeCloudControllerClient.DeleteServiceBindingArgsForCall(0)
324+
Expect(passedGUID).To(Equal("some-service-binding-guid"))
325+
Expect(acceptsIncomplete).To(BeTrue())
312326
})
313327

314328
Context("when the cloud controller API returns warnings and an error", func() {
315329
var expectedError error
316330

317331
BeforeEach(func() {
318332
expectedError = errors.New("I am a CC error")
319-
fakeCloudControllerClient.DeleteServiceBindingReturns(ccv2.Warnings{"foo-4", "foo-5"}, expectedError)
333+
fakeCloudControllerClient.DeleteServiceBindingReturns(ccv2.ServiceBinding{}, ccv2.Warnings{"foo-4", "foo-5"}, expectedError)
320334
})
321335

322336
It("returns the warnings and the error", func() {
323-
warnings, err := actor.UnbindServiceBySpace("some-app", "some-service-instance", "some-space-guid")
324-
Expect(err).To(MatchError(expectedError))
337+
Expect(executeErr).To(MatchError(expectedError))
325338
Expect(warnings).To(ConsistOf(Warnings{"foo-1", "foo-2", "foo-3", "foo-4", "foo-5"}))
326339
})
327340
})

actor/v2action/v2actionfakes/fake_cloud_controller_client.go

Lines changed: 30 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/cloudcontroller/ccv2/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var _ = Describe("Cloud Controller Client", func() {
3434
client.WrapConnection(fakeConnectionWrapper)
3535
Expect(fakeConnectionWrapper.WrapCallCount()).To(Equal(1))
3636

37-
client.DeleteServiceBinding("does-not-matter")
37+
client.DeleteServiceBinding("does-not-matter", true)
3838
Expect(fakeConnectionWrapper.MakeCallCount()).To(Equal(1))
3939
})
4040
})

api/cloudcontroller/ccv2/service_binding.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,31 @@ func (client *Client) CreateServiceBinding(appGUID string, serviceInstanceGUID s
9393
return serviceBinding, response.Warnings, nil
9494
}
9595

96-
// DeleteServiceBinding will destroy the requested Service Binding.
97-
func (client *Client) DeleteServiceBinding(serviceBindingGUID string) (Warnings, error) {
96+
// DeleteServiceBinding deletes the specified Service Binding. An updated
97+
// service binding is returned only if acceptsIncomplete is true.
98+
func (client *Client) DeleteServiceBinding(serviceBindingGUID string, acceptsIncomplete bool) (ServiceBinding, Warnings, error) {
9899
request, err := client.newHTTPRequest(requestOptions{
99100
RequestName: internal.DeleteServiceBindingRequest,
100101
URIParams: map[string]string{"service_binding_guid": serviceBindingGUID},
102+
Query: url.Values{"accepts_incomplete": {fmt.Sprint(acceptsIncomplete)}},
101103
})
102104
if err != nil {
103-
return nil, err
105+
return ServiceBinding{}, nil, err
104106
}
105107

106108
var response cloudcontroller.Response
109+
var serviceBinding ServiceBinding
110+
if acceptsIncomplete {
111+
response = cloudcontroller.Response{
112+
Result: &serviceBinding,
113+
}
114+
}
115+
107116
err = client.connection.Make(request, &response)
108-
return response.Warnings, err
117+
return serviceBinding, response.Warnings, err
109118
}
110119

111-
// GetServiceBinding returns back a service binding with the proviced guid
120+
// GetServiceBinding returns back a service binding with the provided GUID.
112121
func (client *Client) GetServiceBinding(guid string) (ServiceBinding, Warnings, error) {
113122
request, err := client.newHTTPRequest(requestOptions{
114123
RequestName: internal.GetServiceBindingRequest,

api/cloudcontroller/ccv2/service_binding_test.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ccv2_test
22

33
import (
4+
"fmt"
45
"net/http"
56

67
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
@@ -177,20 +178,81 @@ var _ = Describe("Service Binding", func() {
177178
})
178179

179180
Describe("DeleteServiceBinding", func() {
181+
var (
182+
serviceBindingGUID string
183+
acceptsIncomplete bool
184+
185+
serviceBinding ServiceBinding
186+
warnings Warnings
187+
executeErr error
188+
)
189+
190+
BeforeEach(func() {
191+
serviceBindingGUID = "some-service-binding-guid"
192+
})
193+
194+
JustBeforeEach(func() {
195+
serviceBinding, warnings, executeErr = client.DeleteServiceBinding(serviceBindingGUID, acceptsIncomplete)
196+
})
197+
180198
Context("when the service binding exist", func() {
181-
BeforeEach(func() {
182-
server.AppendHandlers(CombineHandlers(VerifyRequest(http.MethodDelete, "/v2/service_bindings/some-service-binding-guid"), RespondWith(http.StatusNoContent, "{}", http.Header{"X-Cf-Warnings": {"this is a warning"}})))
199+
Context("when accepts_incomplete is true", func() {
200+
BeforeEach(func() {
201+
acceptsIncomplete = true
202+
response := fmt.Sprintf(`{
203+
"metadata": {
204+
"guid": "%s"
205+
},
206+
"entity": {
207+
"app_guid": "63af8eb4-6ac6-4baa-b97d-da32d473131c",
208+
"service_instance_guid": "637a1734-3eec-408e-aeaa-bfc577d893b7",
209+
"last_operation": {
210+
"state": "in progress"
211+
}
212+
}
213+
}`, serviceBindingGUID)
214+
server.AppendHandlers(
215+
CombineHandlers(
216+
VerifyRequest(http.MethodDelete, "/v2/service_bindings/some-service-binding-guid", "accepts_incomplete=true"),
217+
RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
218+
),
219+
)
220+
})
221+
222+
It("returns the service binding and warnings", func() {
223+
Expect(executeErr).NotTo(HaveOccurred())
224+
Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
225+
Expect(serviceBinding).To(Equal(ServiceBinding{
226+
GUID: serviceBindingGUID,
227+
AppGUID: "63af8eb4-6ac6-4baa-b97d-da32d473131c",
228+
ServiceInstanceGUID: "637a1734-3eec-408e-aeaa-bfc577d893b7",
229+
LastOperation: LastOperation{
230+
State: constant.LastOperationInProgress,
231+
},
232+
}))
233+
})
183234
})
184235

185-
It("deletes the service binding", func() {
186-
warnings, err := client.DeleteServiceBinding("some-service-binding-guid")
187-
Expect(err).NotTo(HaveOccurred())
188-
Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
236+
Context("when accepts_incomplete is false", func() {
237+
BeforeEach(func() {
238+
acceptsIncomplete = false
239+
server.AppendHandlers(
240+
CombineHandlers(
241+
VerifyRequest(http.MethodDelete, "/v2/service_bindings/some-service-binding-guid"),
242+
RespondWith(http.StatusNoContent, "{}", http.Header{"X-Cf-Warnings": {"this is a warning"}}),
243+
))
244+
})
245+
246+
It("deletes the service binding", func() {
247+
Expect(executeErr).NotTo(HaveOccurred())
248+
Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
249+
})
189250
})
190251
})
191252

192253
Context("when the service binding does not exist", func() {
193254
BeforeEach(func() {
255+
acceptsIncomplete = false
194256
response := `{
195257
"code": 90004,
196258
"description": "The service binding could not be found: some-service-binding-guid",
@@ -205,8 +267,7 @@ var _ = Describe("Service Binding", func() {
205267
})
206268

207269
It("returns a not found error", func() {
208-
warnings, err := client.DeleteServiceBinding("some-service-binding-guid")
209-
Expect(err).To(MatchError(ccerror.ResourceNotFoundError{
270+
Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{
210271
Message: "The service binding could not be found: some-service-binding-guid",
211272
}))
212273
Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))

api/cloudcontroller/ccversion/minimum_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const (
1010
MinVersionSymlinkedFilesV2 = "2.107.0"
1111
MinVersionZeroAppInstancesV2 = "2.70.0"
1212
MinVersionUserProvidedServiceTagsV2 = "2.104.0"
13-
MinVersionAsyncBindingsV2 = "2.112.0"
13+
MinVersionAsyncBindingsV2 = "2.115.0"
1414

1515
MinVersionHTTPRoutePath = "2.36.0"
1616
MinVersionTCPRouting = "2.53.0"

0 commit comments

Comments
 (0)