Skip to content

Commit d791e3d

Browse files
authored
File-based-service-bindings: improved errors (#4693)
* Convert IncompatibleBindings error to UnprocessableEntity (ApiError) The ServiceBindingFilesBuilder can raise an IncompatibleBindings exception in case of entries violating the specification for file names or if the overall file size is too big. When pushing an app these errors were shown as StagerErrors (with the correct error message); for other operations - i.e. restart, restage - an UnknownError was returned. This change ensures that in any case an UnprocessableEntity (ApiError) is returned (with the concrete error message). * Fix validation of conflicting manifest features Switching from service-binding-k8s to file-based-vcap-services via manifest entries (features) raised an error. This change fixes the wrong validation for this corner case.
1 parent 551429d commit d791e3d

File tree

6 files changed

+116
-71
lines changed

6 files changed

+116
-71
lines changed

‎app/actions/app_feature_update.rb‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def feature_column_name(feature_name)
3030
end
3131

3232
def check_invalid_combination!(app, flags)
33-
file_based_vcap_services_enabled = flags[feature_column_name(AppFeatures::FILE_BASED_VCAP_SERVICES_FEATURE)].present? || app.file_based_vcap_services_enabled
34-
service_binding_k8s_enabled = flags[feature_column_name(AppFeatures::SERVICE_BINDING_K8S_FEATURE)].present? || app.service_binding_k8s_enabled
33+
file_based_vcap_services_enabled = flags.fetch(feature_column_name(AppFeatures::FILE_BASED_VCAP_SERVICES_FEATURE), app.file_based_vcap_services_enabled)
34+
service_binding_k8s_enabled = flags.fetch(feature_column_name(AppFeatures::SERVICE_BINDING_K8S_FEATURE), app.service_binding_k8s_enabled)
3535
return unless file_based_vcap_services_enabled && service_binding_k8s_enabled
3636

3737
msg = "'#{AppFeatures::FILE_BASED_VCAP_SERVICES_FEATURE}' and '#{AppFeatures::SERVICE_BINDING_K8S_FEATURE}' features cannot be enabled at the same time."

‎lib/cloud_controller/diego/app_recipe_builder.rb‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def app_lrp_arguments
105105
image_password: process.desired_droplet.docker_receipt_password,
106106
volume_mounted_files: ServiceBindingFilesBuilder.build(process)
107107
}.compact
108+
rescue ServiceBindingFilesBuilder::IncompatibleBindings => e
109+
raise CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', "Cannot build service binding files for app - #{e.message}")
108110
end
109111

110112
def metric_tags(process)

‎lib/cloud_controller/diego/task_recipe_builder.rb‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def build_app_task(config, task)
5656
image_password: task.droplet.docker_receipt_password,
5757
volume_mounted_files: ServiceBindingFilesBuilder.build(task.app)
5858
}.compact)
59+
rescue ServiceBindingFilesBuilder::IncompatibleBindings => e
60+
raise CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', "Cannot build service binding files for app task - #{e.message}")
5961
end
6062

6163
def build_staging_task(config, staging_details)
@@ -95,6 +97,8 @@ def build_staging_task(config, staging_details)
9597
image_password: staging_details.package.docker_password,
9698
volume_mounted_files: ServiceBindingFilesBuilder.build(staging_details.package.app)
9799
}.compact)
100+
rescue ServiceBindingFilesBuilder::IncompatibleBindings => e
101+
raise CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', "Cannot build service binding files for staging task - #{e.message}")
98102
end
99103

100104
private

‎spec/unit/actions/app_feature_update_spec.rb‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,27 @@ module VCAP::CloudController
122122
end
123123
end
124124

125+
context 'when switching from service-binding-k8s to file-based-vcap-services' do
126+
before do
127+
app.update(service_binding_k8s_enabled: true)
128+
features[:'service-binding-k8s'] = false
129+
features[:'file-based-vcap-services'] = true
130+
end
131+
132+
it 'updates the corresponding columns in a single database call' do
133+
expect(app.service_binding_k8s_enabled).to be(true)
134+
expect(app.file_based_vcap_services_enabled).to be(false)
135+
136+
expect do
137+
AppFeatureUpdate.bulk_update(app, message)
138+
end.to have_queried_db_times(/update .apps./i, 1)
139+
140+
app.reload
141+
expect(app.service_binding_k8s_enabled).to be(false)
142+
expect(app.file_based_vcap_services_enabled).to be(true)
143+
end
144+
end
145+
125146
context 'when conflicting features are specified' do
126147
before do
127148
features[:'service-binding-k8s'] = true

‎spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ module Diego
5757
end
5858
end
5959

60+
shared_examples 'IncompatibleBindings error handling' do
61+
let(:incompatible_bindings_error) { VCAP::CloudController::Diego::ServiceBindingFilesBuilder::IncompatibleBindings.new('something is wrong') }
62+
63+
context 'when an IncompatibleBindings error is raised' do
64+
before do
65+
allow_any_instance_of(VCAP::CloudController::Diego::ServiceBindingFilesBuilder).to receive(:build).and_raise(incompatible_bindings_error)
66+
end
67+
68+
it 'results in an UnprocessableEntity error' do
69+
expect { _build }.to raise_error(CloudController::Errors::ApiError, /Cannot build service binding files .* - something is wrong/)
70+
end
71+
end
72+
end
73+
6074
shared_examples 'k8s service bindings' do
6175
context 'when k8s service bindings are enabled' do
6276
before do
@@ -69,6 +83,10 @@ module Diego
6983
lrp = builder.build_app_lrp
7084
expect(lrp.volume_mounted_files.size).to be > 1
7185
end
86+
87+
include_examples 'IncompatibleBindings error handling' do
88+
let(:_build) { builder.build_app_lrp }
89+
end
7290
end
7391
end
7492

@@ -85,6 +103,10 @@ module Diego
85103
expect(lrp.volume_mounted_files.size).to eq(1)
86104
expect(lrp.volume_mounted_files[0].path).to eq('vcap_services')
87105
end
106+
107+
include_examples 'IncompatibleBindings error handling' do
108+
let(:_build) { builder.build_app_lrp }
109+
end
88110
end
89111
end
90112

‎spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb‎

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,53 @@ module Diego
88
let(:space) { Space.make(name: 'MySpace', guid: 'space-guid', organization: org) }
99
let(:app) { AppModel.make(name: 'MyApp', guid: 'banana-guid', space: space) }
1010

11+
shared_examples 'IncompatibleBindings error handling' do
12+
let(:incompatible_bindings_error) { VCAP::CloudController::Diego::ServiceBindingFilesBuilder::IncompatibleBindings.new('something is wrong') }
13+
14+
context 'when an IncompatibleBindings error is raised' do
15+
before do
16+
allow_any_instance_of(VCAP::CloudController::Diego::ServiceBindingFilesBuilder).to receive(:build).and_raise(incompatible_bindings_error)
17+
end
18+
19+
it 'results in an UnprocessableEntity error' do
20+
expect { _build }.to raise_error(CloudController::Errors::ApiError, /Cannot build service binding files .* - something is wrong/)
21+
end
22+
end
23+
end
24+
25+
shared_examples 'k8s service bindings' do
26+
context 'when k8s service bindings are enabled' do
27+
before do
28+
_app.update(service_binding_k8s_enabled: true)
29+
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: _app.space), app: _app)
30+
end
31+
32+
it 'includes volume mounted files' do
33+
result = _build
34+
expect(result.volume_mounted_files.size).to be > 1
35+
end
36+
37+
include_examples 'IncompatibleBindings error handling'
38+
end
39+
end
40+
41+
shared_examples 'file-based VCAP service bindings' do
42+
context 'when file-based VCAP service bindings are enabled' do
43+
before do
44+
_app.update(file_based_vcap_services_enabled: true)
45+
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: _app.space), app: _app)
46+
end
47+
48+
it 'includes the vcap_services file' do
49+
result = _build
50+
expect(result.volume_mounted_files.size).to eq(1)
51+
expect(result.volume_mounted_files[0].path).to eq('vcap_services')
52+
end
53+
54+
include_examples 'IncompatibleBindings error handling'
55+
end
56+
end
57+
1158
describe '#build_staging_task' do
1259
let(:staging_details) do
1360
Diego::StagingDetails.new.tap do |details|
@@ -201,31 +248,14 @@ module Diego
201248
end
202249
end
203250

204-
context 'when k8s service bindings are enabled' do
205-
before do
206-
app = staging_details.package.app
207-
app.update(service_binding_k8s_enabled: true)
208-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
209-
end
210-
211-
it 'includes volume mounted files' do
212-
result = task_recipe_builder.build_staging_task(config, staging_details)
213-
expect(result.volume_mounted_files.size).to be > 1
214-
end
251+
include_examples 'k8s service bindings' do
252+
let(:_app) { staging_details.package.app }
253+
let(:_build) { task_recipe_builder.build_staging_task(config, staging_details) }
215254
end
216255

217-
context 'when file-based VCAP service bindings are enabled' do
218-
before do
219-
app = staging_details.package.app
220-
app.update(file_based_vcap_services_enabled: true)
221-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
222-
end
223-
224-
it 'includes volume mounted files' do
225-
result = task_recipe_builder.build_staging_task(config, staging_details)
226-
expect(result.volume_mounted_files.size).to eq(1)
227-
expect(result.volume_mounted_files[0].path).to eq('vcap_services')
228-
end
256+
include_examples 'file-based VCAP service bindings' do
257+
let(:_app) { staging_details.package.app }
258+
let(:_build) { task_recipe_builder.build_staging_task(config, staging_details) }
229259
end
230260
end
231261

@@ -615,31 +645,14 @@ module Diego
615645
end
616646
end
617647

618-
context 'when k8s service bindings are enabled' do
619-
before do
620-
app = task.app
621-
app.update(service_binding_k8s_enabled: true)
622-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
623-
end
624-
625-
it 'includes volume mounted files' do
626-
result = task_recipe_builder.build_app_task(config, task)
627-
expect(result.volume_mounted_files.size).to be > 1
628-
end
648+
include_examples 'k8s service bindings' do
649+
let(:_app) { task.app }
650+
let(:_build) { task_recipe_builder.build_app_task(config, task) }
629651
end
630652

631-
context 'when file-based VCAP service bindings are enabled' do
632-
before do
633-
app = task.app
634-
app.update(file_based_vcap_services_enabled: true)
635-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
636-
end
637-
638-
it 'includes volume mounted files' do
639-
result = task_recipe_builder.build_app_task(config, task)
640-
expect(result.volume_mounted_files.size).to eq(1)
641-
expect(result.volume_mounted_files[0].path).to eq('vcap_services')
642-
end
653+
include_examples 'file-based VCAP service bindings' do
654+
let(:_app) { task.app }
655+
let(:_build) { task_recipe_builder.build_app_task(config, task) }
643656
end
644657
end
645658

@@ -797,31 +810,14 @@ module Diego
797810
end
798811
end
799812

800-
context 'when k8s service bindings are enabled' do
801-
before do
802-
app = task.app
803-
app.update(service_binding_k8s_enabled: true)
804-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
805-
end
806-
807-
it 'includes volume mounted files' do
808-
result = task_recipe_builder.build_app_task(config, task)
809-
expect(result.volume_mounted_files.size).to be > 1
810-
end
813+
include_examples 'k8s service bindings' do
814+
let(:_app) { task.app }
815+
let(:_build) { task_recipe_builder.build_app_task(config, task) }
811816
end
812817

813-
context 'when file-based VCAP service bindings are enabled' do
814-
before do
815-
app = task.app
816-
app.update(file_based_vcap_services_enabled: true)
817-
VCAP::CloudController::ServiceBinding.make(service_instance: ManagedServiceInstance.make(space: app.space), app: app)
818-
end
819-
820-
it 'includes volume mounted files' do
821-
result = task_recipe_builder.build_app_task(config, task)
822-
expect(result.volume_mounted_files.size).to eq(1)
823-
expect(result.volume_mounted_files[0].path).to eq('vcap_services')
824-
end
818+
include_examples 'file-based VCAP service bindings' do
819+
let(:_app) { task.app }
820+
let(:_build) { task_recipe_builder.build_app_task(config, task) }
825821
end
826822
end
827823
end

0 commit comments

Comments
 (0)