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

Commit daac37f

Browse files
Derik EvangelistaFelisiaM
authored andcommitted
v3(services): attempt to delete SIs being created
[#173149474](https://www.pivotaltracker.com/story/show/173149474)
1 parent 90e01de commit daac37f

6 files changed

Lines changed: 165 additions & 22 deletions

File tree

app/actions/v3/service_instance_delete.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ def delete(service_instance)
1717
cannot_delete_shared_instances! if service_instance.shared?
1818

1919
lock = DeleterLock.new(service_instance)
20-
lock.lock!
2120

2221
job = case service_instance
2322
when ManagedServiceInstance
2423
asynchronous_destroy(service_instance)
2524
when UserProvidedServiceInstance
25+
lock.lock!
2626
synchronous_destroy(service_instance, lock)
2727
end
2828

app/jobs/v3/delete_service_instance_job.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ def send_broker_request(client)
1919
rescue VCAP::Services::ServiceBrokers::V2::Errors::ServiceBrokerBadResponse => err
2020
@request_failed = true
2121
raise DeprovisionBadResponse.new(err.message)
22+
rescue CloudController::Errors::ApiError => err
23+
raise OperationAborted.new('The service broker rejected the request') if err.name == 'AsyncServiceInstanceOperationInProgress'
24+
25+
raise err
2226
end
2327

2428
def operation_succeeded

app/jobs/v3/service_instance_async_job.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ module V3
55
class LastOperationStateFailed < StandardError
66
end
77

8+
class OperationAborted < StandardError
9+
end
10+
811
class ServiceInstanceAsyncJob < VCAP::CloudController::Jobs::ReoccurringJob
912
MAX_RETRIES = 3
1013
attr_reader :warnings
@@ -48,6 +51,8 @@ def perform
4851
fail_and_raise!(err.message) unless restart_on_failure?
4952

5053
restart_job(err.message || 'no error description returned by the broker')
54+
rescue OperationAborted
55+
aborted!(service_instance.last_operation&.type)
5156
rescue => err
5257
fail!(err)
5358
end
@@ -102,6 +107,8 @@ def execute_request(client)
102107
def raise_if_cannot_proceed!
103108
last_operation_type = service_instance.last_operation&.type
104109

110+
return if operation_type == 'delete' && last_operation_type == 'create'
111+
105112
if service_instance.operation_in_progress? && last_operation_type != operation_type
106113
aborted!(last_operation_type)
107114
end

spec/request/service_instances_spec.rb

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,17 +2528,6 @@ def check_filtered_instances(*instances)
25282528
to_return(status: broker_status_code, body: broker_response.to_json, headers: {})
25292529
}
25302530

2531-
it 'sets the service instance last operation to delete in progress' do
2532-
api_call.call(admin_headers)
2533-
expect(last_response).to have_status_code(HTTP::Status::ACCEPTED)
2534-
2535-
instance.reload
2536-
2537-
expect(instance.last_operation).to_not be_nil
2538-
expect(instance.last_operation.type).to eq('delete')
2539-
expect(instance.last_operation.state).to eq('in progress')
2540-
end
2541-
25422531
it 'responds with job resource' do
25432532
api_call.call(admin_headers)
25442533
expect(last_response).to have_status_code(202)
@@ -2655,6 +2644,17 @@ def check_filtered_instances(*instances)
26552644
expect(Delayed::Job.count).to eq(1)
26562645
end
26572646

2647+
it 'sets the service instance last operation to delete in progress' do
2648+
api_call.call(admin_headers)
2649+
execute_all_jobs(expected_successes: 1, expected_failures: 0)
2650+
2651+
instance.reload
2652+
2653+
expect(instance.last_operation).to_not be_nil
2654+
expect(instance.last_operation.type).to eq('delete')
2655+
expect(instance.last_operation.state).to eq('in progress')
2656+
end
2657+
26582658
context 'when last operation eventually returns `delete succeeded`' do
26592659
before do
26602660
stub_request(:get, "#{instance.service_broker.broker_url}/v2/service_instances/#{instance.guid}/last_operation").
@@ -2983,6 +2983,86 @@ def check_filtered_instances(*instances)
29832983
expect(response).to include('detail' => include('Service instances must be unshared before they can be deleted.'))
29842984
end
29852985
end
2986+
2987+
context 'when the creation is still in progress' do
2988+
before do
2989+
instance.save_with_new_operation({}, {
2990+
type: 'create',
2991+
state: 'in progress',
2992+
broker_provided_operation: 'some create operation'
2993+
})
2994+
end
2995+
2996+
context 'and the broker confirms the deletion' do
2997+
it 'deletes the service instance' do
2998+
api_call.call(admin_headers)
2999+
execute_all_jobs(expected_successes: 1, expected_failures: 0)
3000+
3001+
expect(VCAP::CloudController::ServiceInstance.first(guid: instance.guid)).to be_nil
3002+
end
3003+
end
3004+
3005+
context 'and the broker accepts the delete' do
3006+
let(:broker_status_code) { 202 }
3007+
let(:broker_response) { { operation: 'some delete operation' } }
3008+
let(:last_operation_response) { { state: 'in progress', description: 'deleting si' } }
3009+
let(:last_operation_status_code) { 200 }
3010+
3011+
before do
3012+
stub_request(:get, "#{instance.service_broker.broker_url}/v2/service_instances/#{instance.guid}/last_operation").
3013+
with(
3014+
query: {
3015+
operation: 'some delete operation',
3016+
service_id: instance.service.broker_provided_id,
3017+
plan_id: instance.service_plan.broker_provided_id
3018+
}).
3019+
to_return(status: last_operation_status_code, body: last_operation_response.to_json, headers: {})
3020+
end
3021+
3022+
it 'triggers the delete process' do
3023+
api_call.call(admin_headers)
3024+
expect(last_response).to have_status_code(HTTP::Status::ACCEPTED)
3025+
execute_all_jobs(expected_successes: 1, expected_failures: 0)
3026+
3027+
instance.reload
3028+
3029+
expect(instance.last_operation).to_not be_nil
3030+
expect(instance.last_operation.type).to eq('delete')
3031+
expect(instance.last_operation.state).to eq('in progress')
3032+
expect(instance.last_operation.broker_provided_operation).to eq('some delete operation')
3033+
end
3034+
end
3035+
3036+
context 'but the broker rejects the delete' do
3037+
let(:broker_status_code) { 422 }
3038+
let(:broker_response) { { error: 'ConcurrencyError', description: 'Cannot delete right now' } }
3039+
3040+
it 'responds with an error' do
3041+
api_call.call(admin_headers)
3042+
expect(last_response).to have_status_code(HTTP::Status::ACCEPTED)
3043+
execute_all_jobs(expected_successes: 0, expected_failures: 1)
3044+
3045+
job = VCAP::CloudController::PollableJobModel.last
3046+
expect(job.state).to eq(VCAP::CloudController::PollableJobModel::FAILED_STATE)
3047+
3048+
expect(job.cf_api_error).to_not be_nil
3049+
api_error = YAML.safe_load(job.cf_api_error)['errors'].first
3050+
expect(api_error['title']).to eql('CF-UnableToPerform')
3051+
expect(api_error['detail']).to eql('delete could not be completed: create in progress')
3052+
end
3053+
3054+
it 'does not change the operation in progress' do
3055+
api_call.call(admin_headers)
3056+
expect(last_response).to have_status_code(HTTP::Status::ACCEPTED)
3057+
execute_all_jobs(expected_successes: 0, expected_failures: 1)
3058+
3059+
instance.reload
3060+
3061+
expect("#{instance.last_operation.type} #{instance.last_operation.state}").to eq('create in progress')
3062+
expect(instance.last_operation.broker_provided_operation).to eq('some create operation')
3063+
end
3064+
end
3065+
end
29863066
end
29873067

29883068
context 'when associations are not empty' do

spec/unit/actions/v3/service_instance_delete_spec.rb

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,6 @@ module CloudController
6363
context 'managed service instances' do
6464
let!(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make }
6565

66-
it 'locks the service instance' do
67-
action.delete(service_instance)
68-
69-
lo = service_instance.reload.last_operation
70-
expect(lo).to be
71-
expect(lo.type).to eq('delete')
72-
expect(lo.state).to eq('in progress')
73-
end
74-
7566
it 'enqueues a job and returns the job guid' do
7667
job_guid = subject.delete(service_instance)
7768
job = VCAP::CloudController::PollableJobModel.last

spec/unit/jobs/v3/delete_service_instance_job_spec.rb

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module V3
2222
end
2323

2424
describe '#perform' do
25-
let(:client) { double('BrokerClient', deprovision: 'some response') }
25+
let(:client) { double('BrokerClient', deprovision: { instance: {}, last_operation: {} }) }
2626

2727
before do
2828
allow(VCAP::Services::ServiceClientProvider).to receive(:provide).and_return(client)
@@ -44,6 +44,16 @@ module V3
4444
end
4545
end
4646

47+
context 'when there is a create operation in progress' do
48+
before do
49+
service_instance.save_with_new_operation({}, { type: 'create', state: 'in progress', description: 'barz' })
50+
end
51+
52+
it 'attempts to delete anyways' do
53+
expect { subject.perform }.to_not raise_error
54+
end
55+
end
56+
4757
context 'when the client raises a ServiceBrokerBadResponse' do
4858
let(:r) do
4959
VCAP::Services::ServiceBrokers::V2::HttpResponse.new(code: '204', body: 'unexpected failure!')
@@ -100,6 +110,48 @@ module V3
100110
end
101111
end
102112

113+
context 'when the client raises an API Error' do
114+
before do
115+
allow(client).to receive(:deprovision).and_raise(err)
116+
service_instance.save_with_new_operation({}, {
117+
type: 'create',
118+
state: 'in progress',
119+
broker_provided_operation: 'some create operation'
120+
})
121+
end
122+
123+
let(:err) do
124+
CloudController::Errors::ApiError.new_from_details('NotFound')
125+
end
126+
127+
it 'fails the job and update the service instance last operation' do
128+
expect { subject.perform }.to raise_error(CloudController::Errors::ApiError, /Unknown request/)
129+
expect(subject.instance_variable_get(:@attempts)).to eq(0)
130+
131+
service_instance.reload
132+
133+
expect(service_instance.last_operation.type).to eq('delete')
134+
expect(service_instance.last_operation.state).to eq('failed')
135+
end
136+
137+
context 'and the error name is AsyncServiceInstanceOperationInProgress' do
138+
let(:err) do
139+
CloudController::Errors::ApiError.new_from_details('AsyncServiceInstanceOperationInProgress', 'some name')
140+
end
141+
142+
it 'fails the job but do not update the service instance last operation' do
143+
expect { subject.perform }.to raise_error(CloudController::Errors::ApiError, /create in progress/)
144+
expect(subject.instance_variable_get(:@attempts)).to eq(0)
145+
146+
service_instance.reload
147+
148+
expect(service_instance.last_operation.type).to eq('create')
149+
expect(service_instance.last_operation.state).to eq('in progress')
150+
expect(service_instance.last_operation.broker_provided_operation).to eq('some create operation')
151+
end
152+
end
153+
end
154+
103155
context 'when the client raises a general error' do
104156
let(:err) { StandardError.new('random error') }
105157

@@ -163,6 +215,15 @@ module V3
163215
end
164216
end
165217

218+
context 'when the client raises a AsyncServiceInstanceOperationInProgress' do
219+
it 'raises a DeprovisionBadResponse error' do
220+
err = CloudController::Errors::ApiError.new_from_details('AsyncServiceInstanceOperationInProgress', 'some instance name')
221+
allow(client).to receive(:deprovision).and_raise(err)
222+
223+
expect { subject.send_broker_request(client) }.to raise_error(OperationAborted, /rejected the request/)
224+
end
225+
end
226+
166227
context 'when the client raises an unknown error' do
167228
it 'raises the error' do
168229
allow(client).to receive(:deprovision).and_raise(RuntimeError.new('oh boy'))

0 commit comments

Comments
 (0)