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

Commit 846102b

Browse files
authored
v3(services): route binding last operation errors (cloudfoundry#1815)
[#174398276](https://www.pivotaltracker.com/story/show/174398276)
1 parent 534a742 commit 846102b

4 files changed

Lines changed: 128 additions & 15 deletions

File tree

app/actions/service_route_binding_create.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ def poll(binding)
7575
else
7676
PollingNotComplete.new(details[:retry_after])
7777
end
78+
rescue VCAP::Services::ServiceBrokers::V2::Errors::ServiceBrokerBadResponse,
79+
VCAP::Services::ServiceBrokers::V2::Errors::ServiceBrokerResponseMalformed => e
80+
81+
binding.save_with_new_operation({}, {
82+
type: 'create',
83+
state: 'failed',
84+
description: e.message,
85+
})
86+
87+
raise e
7888
rescue => e
7989
binding.save_with_new_operation({}, {
8090
type: 'create',

app/jobs/v3/create_route_binding_job.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ def perform
5757
self.polling_interval_seconds = polling_status.retry_after
5858
end
5959
end
60+
rescue ServiceRouteBindingCreate::BindingNotRetrievable
61+
raise CloudController::Errors::ApiError.new_from_details('ServiceBindingInvalid', 'The broker responded asynchronously but does not support fetching binding data')
62+
rescue => e
63+
raise CloudController::Errors::ApiError.new_from_details('UnableToPerform', 'bind', e.message)
6064
end
6165

6266
private

spec/request/service_route_bindings_spec.rb

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@
350350
expect(Delayed::Job.count).to eq(1)
351351
end
352352

353-
context 'last operation indicates success' do
353+
context 'last operation response is 200 OK and indicates success' do
354354
let(:state) { 'succeeded' }
355355
let(:fetch_binding_status_code) { 200 }
356356
let(:fetch_binding_body) do
@@ -381,13 +381,24 @@
381381
end
382382
end
383383

384-
context 'last operation indicates failure' do
384+
context 'last operation response is 200 OK and indicates failure' do
385385
let(:state) { 'failed' }
386386

387-
it 'does not queue another polling job' do
387+
it 'updates the binding and job' do
388388
execute_all_jobs(expected_successes: 1, expected_failures: 0)
389-
expect(Delayed::Job.count).to eq(0)
389+
390+
expect(binding.last_operation.type).to eq('create')
391+
expect(binding.last_operation.state).to eq(state)
392+
expect(binding.last_operation.description).to eq(description)
393+
394+
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::COMPLETE_STATE)
390395
end
396+
end
397+
398+
context 'last operation response is 400 Bad Request' do
399+
let(:last_operation_status_code) { 400 }
400+
let(:state) { 'failed' }
401+
let(:description) { 'a helpful description' }
391402

392403
it 'updates the binding and job' do
393404
execute_all_jobs(expected_successes: 1, expected_failures: 0)
@@ -399,6 +410,57 @@
399410
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::COMPLETE_STATE)
400411
end
401412
end
413+
414+
context 'last operation response is 404 Not Found' do
415+
let(:last_operation_status_code) { 404 }
416+
let(:last_operation_body) { 'cannot see it' }
417+
418+
it 'updates the binding and job' do
419+
execute_all_jobs(expected_successes: 1, expected_failures: 0)
420+
421+
expect(binding.last_operation.type).to eq('create')
422+
expect(binding.last_operation.state).to eq('failed')
423+
expect(binding.last_operation.description).to eq('The service broker rejected the request. Status Code: 404 Not Found, Body: "cannot see it"')
424+
425+
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::COMPLETE_STATE)
426+
end
427+
end
428+
429+
context 'last operation response is 500 Internal Server Error' do
430+
let(:last_operation_status_code) { 500 }
431+
let(:last_operation_body) { 'something awful' }
432+
433+
it 'updates the binding and job' do
434+
execute_all_jobs(expected_successes: 0, expected_failures: 1)
435+
436+
expect(binding.last_operation.type).to eq('create')
437+
expect(binding.last_operation.state).to eq('failed')
438+
expect(binding.last_operation.description).to eq('The service broker returned an invalid response. Status Code: 500 Internal Server Error, Body: "something awful"')
439+
440+
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::FAILED_STATE)
441+
end
442+
end
443+
444+
context 'binding not retrievable' do
445+
let(:offering) { VCAP::CloudController::Service.make(bindings_retrievable: false, requires: ['route_forwarding']) }
446+
447+
it 'fails the job with an appropriate error' do
448+
execute_all_jobs(expected_successes: 0, expected_failures: 1)
449+
450+
expect(binding.last_operation.type).to eq('create')
451+
expect(binding.last_operation.state).to eq('failed')
452+
expect(binding.last_operation.description).to eq('The broker responded asynchronously but does not support fetching binding data')
453+
454+
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::FAILED_STATE)
455+
expect(job.cf_api_error).not_to be_nil
456+
error = YAML.safe_load(job.cf_api_error)
457+
expect(error['errors'].first).to include({
458+
'code' => 90001,
459+
'title' => 'CF-ServiceBindingInvalid',
460+
'detail' => 'The service binding is invalid: The broker responded asynchronously but does not support fetching binding data',
461+
})
462+
end
463+
end
402464
end
403465
end
404466

spec/unit/jobs/v3/create_route_binding_job_spec.rb

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,6 @@ module V3
4747
allow(V3::ServiceRouteBindingCreate).to receive(:new).and_return(action)
4848
end
4949

50-
context 'binding not found' do
51-
it 'raises' do
52-
binding.destroy
53-
54-
expect { subject.perform }.to raise_error(
55-
CloudController::Errors::ApiError,
56-
/The binding could not be found/,
57-
)
58-
end
59-
end
60-
6150
context 'first time' do
6251
context 'synchronous response' do
6352
let(:state) { 'succeeded' }
@@ -89,6 +78,21 @@ module V3
8978

9079
expect(subject.finished).to be_falsey
9180
end
81+
82+
context 'bind fails with BindingNotRetrievable' do
83+
before do
84+
allow(action).to receive(:bind).and_raise(ServiceRouteBindingCreate::BindingNotRetrievable)
85+
end
86+
87+
it 'raises an API error' do
88+
expect {
89+
subject.perform
90+
}.to raise_error(
91+
CloudController::Errors::ApiError,
92+
'The service binding is invalid: The broker responded asynchronously but does not support fetching binding data'
93+
)
94+
end
95+
end
9296
end
9397
end
9498

@@ -140,6 +144,39 @@ def test_retry_after(value, expected)
140144
test_retry_after(25.hours, 24.hours) # above limit
141145
end
142146
end
147+
148+
context 'binding not found' do
149+
it 'raises an API error' do
150+
binding.destroy
151+
152+
expect { subject.perform }.to raise_error(
153+
CloudController::Errors::ApiError,
154+
/The binding could not be found/,
155+
)
156+
end
157+
end
158+
159+
context 'bind fails' do
160+
it 'raises an API error' do
161+
allow(action).to receive(:bind).and_raise(StandardError)
162+
163+
expect { subject.perform }.to raise_error(
164+
CloudController::Errors::ApiError,
165+
'bind could not be completed: StandardError',
166+
)
167+
end
168+
end
169+
170+
context 'poll fails' do
171+
it 'raises an API error' do
172+
allow(action).to receive(:poll).and_raise(StandardError)
173+
174+
expect { subject.perform }.to raise_error(
175+
CloudController::Errors::ApiError,
176+
'bind could not be completed: StandardError',
177+
)
178+
end
179+
end
143180
end
144181

145182
describe '#operation' do

0 commit comments

Comments
 (0)