Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit a54e49e

Browse files
authored
V3(services) Refactor handling last operation on binding (cloudfoundry#1907)
[#174748988](https://www.pivotaltracker.com/story/show/174748988) * Added error handling in the client layer for the last operation response By concentrating the logic in the client response - it keeps it closer to the response parser that is already doing some of that translation logic. The polling code should only care about the state field and any other raised error should be handled as fatal failure * Replace old poll implementation with the new one * Fix formatting
1 parent b8d3980 commit a54e49e

8 files changed

Lines changed: 246 additions & 72 deletions

File tree

app/actions/v3/service_binding_create.rb

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,63 +21,60 @@ def bind(binding, parameters: {}, accepts_incomplete: false)
2121
complete_binding_and_save(binding, details[:binding], { state: 'succeeded' })
2222
end
2323
rescue => e
24-
binding.save_with_attributes_and_new_operation(
25-
{},
26-
{
27-
type: 'create',
28-
state: 'failed',
29-
description: e.message,
30-
}
31-
)
24+
save_failed_state(binding, e)
3225

3326
raise e
3427
end
3528

3629
def poll(binding)
3730
client = VCAP::Services::ServiceClientProvider.provide(instance: binding.service_instance)
38-
details = fetch_last_operation(client, binding)
39-
return ContinuePolling.call(nil) unless details
31+
details = client.fetch_and_handle_service_binding_last_operation(binding)
4032

41-
if details[:last_operation][:state] == 'succeeded'
33+
case details[:last_operation][:state]
34+
when 'succeeded'
4235
params = client.fetch_service_binding(binding)
4336
complete_binding_and_save(binding, params, details[:last_operation])
4437
return PollingFinished
38+
when 'in progress'
39+
save_last_operation(binding, details)
40+
ContinuePolling.call(details[:retry_after])
41+
when 'failed'
42+
save_last_operation(binding, details)
43+
raise LastOperationFailedState
4544
end
45+
rescue LastOperationFailedState => e
46+
raise e
47+
rescue => e
48+
save_failed_state(binding, e)
49+
raise e
50+
end
51+
52+
class BindingNotRetrievable < StandardError; end
4653

54+
private
55+
56+
def save_failed_state(binding, e)
4757
binding.save_with_attributes_and_new_operation(
4858
{},
4959
{
5060
type: 'create',
51-
state: details[:last_operation][:state],
52-
description: details[:last_operation][:description],
61+
state: 'failed',
62+
description: e.message,
5363
}
5464
)
65+
end
5566

56-
if details[:last_operation][:state] == 'failed'
57-
raise LastOperationFailedState.new(details[:last_operation][:description])
58-
end
59-
60-
if binding.reload.terminal_state?
61-
PollingFinished
62-
else
63-
ContinuePolling.call(details[:retry_after])
64-
end
65-
rescue => e
67+
def save_last_operation(binding, details)
6668
binding.save_with_attributes_and_new_operation(
6769
{},
6870
{
6971
type: 'create',
70-
state: 'failed',
71-
description: e.message,
72+
state: details[:last_operation][:state],
73+
description: details[:last_operation][:description],
7274
}
7375
)
74-
raise e
7576
end
7677

77-
class BindingNotRetrievable < StandardError; end
78-
79-
private
80-
8178
def save_incomplete_binding(precursor, operation)
8279
precursor.save_with_attributes_and_new_operation(
8380
{},

lib/services/service_brokers/v2/client.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,24 @@ def fetch_service_binding_last_operation(service_binding)
282282
end
283283
end
284284

285+
def fetch_and_handle_service_binding_last_operation(service_binding)
286+
fetch_service_binding_last_operation(service_binding)
287+
rescue Errors::HttpClientTimeout,
288+
Errors::ServiceBrokerApiUnreachable,
289+
HttpRequestError,
290+
Errors::ServiceBrokerBadResponse,
291+
Errors::ServiceBrokerRequestRejected,
292+
Errors::ServiceBrokerApiAuthenticationFailed,
293+
Errors::ServiceBrokerResponseMalformed,
294+
HttpResponseError
295+
result = {}
296+
result[:last_operation] = {}
297+
result[:last_operation][:state] = 'in progress'
298+
result
299+
rescue => e
300+
raise e.exception("Service binding polling #{service_binding.guid}: #{e.message}")
301+
end
302+
285303
def fetch_service_instance(instance)
286304
path = service_instance_resource_path(instance)
287305
response = @http_client.get(path)

spec/support/shared_examples/jobs/create_binding_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
let(:action) do
2929
double('BindingAction', {
3030
bind: nil,
31-
poll: poll_response,
31+
poll: poll_response
3232
})
3333
end
3434

spec/support/shared_examples/request/last_operation_response_handling.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
binding.reload
3838
expect(binding.last_operation.type).to eq(operation_type)
3939
expect(binding.last_operation.state).to eq('in progress')
40-
expect(binding.last_operation.description).to include("Status Code: #{code}")
40+
expect(binding.last_operation.description).to be_nil
4141

4242
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::POLLING_STATE)
4343
end

spec/support/shared_examples/v3_service_binding_create.rb

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -125,51 +125,18 @@ class BadError < StandardError; end
125125

126126
before do
127127
allow(VCAP::Services::ServiceBrokers::V2::Client).to receive(:new).and_return(broker_client)
128-
allow(broker_client).to receive(:fetch_service_binding_last_operation).and_return(fetch_last_operation_response)
128+
allow(broker_client).to receive(:fetch_and_handle_service_binding_last_operation).and_return(fetch_last_operation_response)
129129

130130
action.bind(binding, accepts_incomplete: true)
131131
end
132132

133133
it 'fetches the last operation' do
134134
action.poll(binding)
135135

136-
expect(broker_client).to have_received(:fetch_service_binding_last_operation).with(binding)
136+
expect(broker_client).to have_received(:fetch_and_handle_service_binding_last_operation).with(binding)
137137
end
138138

139-
context 'fetching last operations fails' do
140-
let(:response) {
141-
double('resp', body: '{"description":"no no no"}', code: 422, message: 'failed')
142-
}
143-
it 'should continue polling for known errors' do
144-
[
145-
VCAP::Services::ServiceBrokers::V2::Errors::ServiceBrokerBadResponse.new('uri', 'PUT', response),
146-
VCAP::Services::ServiceBrokers::V2::Errors::ServiceBrokerRequestRejected.new('uri', 'PUT', response),
147-
HttpRequestError.new('no no no', 'uri', 'PUT', nil)
148-
].each do |error|
149-
allow(broker_client).to receive(:fetch_service_binding_last_operation).and_raise(error)
150-
151-
status = action.poll(binding)
152-
expect(status.finished).to be_falsey
153-
154-
binding.reload
155-
expect(binding.last_operation.state).to eq('in progress')
156-
expect(binding.last_operation.description).to include('no no no')
157-
end
158-
end
159-
160-
it 'should stop polling for other errors' do
161-
allow(broker_client).to receive(:fetch_service_binding_last_operation).and_raise(RuntimeError)
162-
163-
expect { action.poll(binding) }.to raise_error(RuntimeError)
164-
165-
binding.reload
166-
expect(binding.last_operation.type).to eq('create')
167-
expect(binding.last_operation.state).to eq('failed')
168-
expect(binding.last_operation.description).to eq('RuntimeError')
169-
end
170-
end
171-
172-
context 'response says complete' do
139+
context 'last operation state is complete' do
173140
let(:description) { Sham.description }
174141
let(:state) { 'succeeded' }
175142

@@ -211,7 +178,9 @@ class BadError < StandardError; end
211178
end
212179
end
213180

214-
context 'response says in progress' do
181+
context 'last operation state is in progress' do
182+
let(:state) { 'in progress' }
183+
215184
it 'returns false' do
216185
polling_status = action.poll(binding)
217186
expect(polling_status[:finished]).to be_falsey
@@ -226,7 +195,7 @@ class BadError < StandardError; end
226195
end
227196
end
228197

229-
context 'response says failed' do
198+
context 'last operation state is failed' do
230199
let(:state) { 'failed' }
231200

232201
it 'updates the last operation' do
@@ -238,6 +207,21 @@ class BadError < StandardError; end
238207
end
239208
end
240209

210+
context 'fetching last operations fails' do
211+
before do
212+
allow(broker_client).to receive(:fetch_and_handle_service_binding_last_operation).and_raise(RuntimeError.new('some error'))
213+
end
214+
215+
it 'should stop polling for other errors' do
216+
expect { action.poll(binding) }.to raise_error(RuntimeError)
217+
218+
binding.reload
219+
expect(binding.last_operation.type).to eq('create')
220+
expect(binding.last_operation.state).to eq('failed')
221+
expect(binding.last_operation.description).to eq('some error')
222+
end
223+
end
224+
241225
context 'retry interval' do
242226
context 'no retry interval' do
243227
it 'returns nil' do

spec/unit/actions/service_credential_binding_create_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ module V3
284284
VCAP::Services::ServiceBrokers::V2::Client,
285285
{
286286
bind: bind_response,
287-
fetch_service_binding_last_operation: fetch_last_operation_response,
287+
fetch_and_handle_service_binding_last_operation: fetch_last_operation_response,
288288
fetch_service_binding: fetch_binding_response,
289289
}
290290
)

spec/unit/actions/service_route_binding_create_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ module V3
256256
VCAP::Services::ServiceBrokers::V2::Client,
257257
{
258258
bind: bind_response,
259-
fetch_service_binding_last_operation: fetch_last_operation_response,
259+
fetch_and_handle_service_binding_last_operation: fetch_last_operation_response,
260260
fetch_service_binding: fetch_binding_response,
261261
}
262262
)

0 commit comments

Comments
 (0)