Skip to content

Commit 7bc6c3f

Browse files
committed
Filter active service bindings per instance on dataset level
- 'active' means latest in state 'create succeeded' (or without any operation) - filtering is done in SQL, not Ruby - no duplicate code where filtering is needed
1 parent 6a527ce commit 7bc6c3f

File tree

6 files changed

+64
-29
lines changed

6 files changed

+64
-29
lines changed

app/actions/service_credential_binding_app_create.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def event_repository
112112
end
113113

114114
def max_bindings_per_app_service_instance
115-
1
115+
# TODO: revert !!!
116+
2
116117
# NOTE: This is hard-coded to 1 for now to preserve the old uniqueness behavior.
117118
# TODO: Once the DB migration that drops the unique constraints for service bindings has been released,
118119
# this should be switched to read from config:

app/models/runtime/helpers/service_operation_mixin.rb

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module VCAP::CloudController
22
module ServiceOperationMixin
3+
CREATE = 'create'.freeze
4+
UPDATE = 'update'.freeze
5+
DELETE = 'delete'.freeze
6+
37
INITIAL = 'initial'.freeze
48
IN_PROGRESS = 'in progress'.freeze
59
SUCCEEDED = 'succeeded'.freeze
@@ -56,15 +60,15 @@ def last_operation?
5660
end
5761

5862
def create?
59-
last_operation&.type == 'create'
63+
last_operation&.type == CREATE
6064
end
6165

6266
def update?
63-
last_operation&.type == 'update'
67+
last_operation&.type == UPDATE
6468
end
6569

6670
def delete?
67-
last_operation&.type == 'delete'
71+
last_operation&.type == DELETE
6872
end
6973

7074
def initial?
@@ -83,4 +87,18 @@ def failed?
8387
last_operation&.state == FAILED
8488
end
8589
end
90+
91+
module ServiceOperationDatasetMixin
92+
def create_succeeded
93+
last_operation_table = Sequel[last_operation_association]
94+
95+
no_operation = Sequel.expr(last_operation_table[:id] => nil)
96+
create_and_succeeded = Sequel.expr(last_operation_table[:type] => ServiceOperationMixin::CREATE) &
97+
Sequel.expr(last_operation_table[:state] => ServiceOperationMixin::SUCCEEDED)
98+
99+
association_left_join(last_operation_association).
100+
where(no_operation | create_and_succeeded).
101+
select_all(first_source_alias)
102+
end
103+
end
86104
end

app/models/services/service_binding.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,26 @@ def save_with_new_operation(last_operation, attributes: {})
130130
service_binding_operation(reload: true)
131131
end
132132
end
133+
134+
dataset_module ServiceOperationDatasetMixin
135+
136+
dataset_module do
137+
def last_operation_association
138+
:service_binding_operation
139+
end
140+
141+
# 'active' means latest in state 'create succeeded' (or without any operation)
142+
def active_per_instance
143+
create_succeeded.
144+
from_self.
145+
select_append do
146+
row_number.function.over(
147+
partition: :service_instance_guid,
148+
order: [Sequel.desc(:created_at), Sequel.desc(:id)]
149+
).as(:_rn)
150+
end.
151+
from_self.where(_rn: 1)
152+
end
153+
end
133154
end
134155
end

app/presenters/system_environment/system_env_presenter.rb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
class SystemEnvPresenter
55
def initialize(app_or_process)
6+
@app_or_process = app_or_process
67
@service_binding_k8s_enabled = app_or_process.service_binding_k8s_enabled
78
@file_based_vcap_services_enabled = app_or_process.file_based_vcap_services_enabled
8-
@service_bindings = app_or_process.service_bindings
99
end
1010

1111
def system_env
@@ -22,14 +22,8 @@ def vcap_services
2222
private
2323

2424
def service_binding_env_variables
25-
latest_bindings = @service_bindings.
26-
select(&:create_succeeded?).
27-
group_by(&:service_instance_guid).
28-
values.
29-
map { |list| list.max_by(&:created_at) }
30-
3125
services_hash = {}
32-
latest_bindings.each do |service_binding|
26+
@app_or_process.service_bindings_dataset.active_per_instance.each do |service_binding|
3327
service_name = service_binding_label(service_binding)
3428
services_hash[service_name.to_sym] ||= []
3529
services_hash[service_name.to_sym] << service_binding_env_values(service_binding)

lib/cloud_controller/diego/service_binding_files_builder.rb

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ def initialize(app_or_process)
1313
@app_or_process = app_or_process
1414
@service_binding_k8s_enabled = app_or_process.service_binding_k8s_enabled
1515
@file_based_vcap_services = app_or_process.file_based_vcap_services_enabled
16-
@service_bindings = app_or_process.service_bindings
1716
end
1817

1918
def build
@@ -27,21 +26,14 @@ def build
2726

2827
private
2928

30-
# rubocop:disable Metrics/CyclomaticComplexity
3129
def build_service_binding_k8s
3230
return nil unless @service_binding_k8s_enabled
3331

3432
service_binding_files = {}
3533
names = Set.new # to check for duplicate binding names
3634
total_bytesize = 0 # to check the total bytesize
3735

38-
latest_bindings = @service_bindings.
39-
select(&:create_succeeded?).
40-
group_by(&:service_instance_guid).
41-
values.
42-
map { |list| list.max_by(&:created_at) }
43-
44-
latest_bindings.each do |service_binding|
36+
@app_or_process.service_bindings_dataset.active_per_instance.each do |service_binding|
4537
sb_hash = ServiceBindingPresenter.new(service_binding, include_instance: true).to_hash
4638
name = sb_hash[:name]
4739
raise IncompatibleBindings.new("Invalid binding name: '#{name}'. Name must match #{binding_naming_convention.inspect}") unless valid_binding_name?(name)
@@ -58,8 +50,6 @@ def build_service_binding_k8s
5850
total_bytesize += add_file(service_binding_files, name, 'type', label)
5951
total_bytesize += add_file(service_binding_files, name, 'provider', label)
6052
end
61-
# rubocop:enable Metrics/CyclomaticComplexity
62-
6353
raise IncompatibleBindings.new("Bindings exceed the maximum allowed bytesize of #{MAX_ALLOWED_BYTESIZE}: #{total_bytesize}") if total_bytesize > MAX_ALLOWED_BYTESIZE
6454

6555
service_binding_files.values

spec/unit/lib/cloud_controller/diego/service_binding_files_builder_spec.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ module VCAP::CloudController::Diego
1818
end
1919

2020
context 'when there are multiple bindings with the same name for the same app and service instance' do
21-
before do
22-
# TODO: Remove skip when the service bindings unique constraints are removed
23-
skip 'this test can be enabled when the service bindings unique constraints are removed and max_bindings_per_app_service_instance can be configured'
24-
end
21+
# TODO: include !!!
22+
# before do
23+
# # TODO: Remove skip when the service bindings unique constraints are removed
24+
# skip 'this test can be enabled when the service bindings unique constraints are removed and max_bindings_per_app_service_instance can be configured'
25+
# end
2526

2627
let(:newer_binding_created_at) { Time.now.utc - 2.minutes }
2728

@@ -39,9 +40,19 @@ module VCAP::CloudController::Diego
3940
end
4041

4142
it 'uses the most recent binding' do
42-
expect(service_binding_files.find { |f| f.path == "#{directory}/binding-guid" }).to have_attributes(content: newer_binding.guid)
43+
expect(service_binding_files.find { |f| f.path == "#{directory}/binding_guid" }).to have_attributes(content: newer_binding.guid)
4344
expect(service_binding_files.find { |f| f.path == "#{directory}/name" }).to have_attributes(content: name || 'binding-name')
44-
expect(service_binding_files.find { |f| f.path == "#{directory}/binding-name" }).to have_attributes(content: 'binding-name') if name.nil?
45+
expect(service_binding_files.find { |f| f.path == "#{directory}/binding_name" }).to have_attributes(content: 'binding-name') if name.nil?
46+
end
47+
48+
context 'when the bindings have the same created_at timestamp' do
49+
let(:newer_binding_created_at) { binding_created_at }
50+
51+
it 'uses the most recent binding determined by highest id' do
52+
expect(service_binding_files.find { |f| f.path == "#{directory}/binding_guid" }).to have_attributes(content: newer_binding.guid)
53+
expect(service_binding_files.find { |f| f.path == "#{directory}/name" }).to have_attributes(content: name || 'binding-name')
54+
expect(service_binding_files.find { |f| f.path == "#{directory}/binding_name" }).to have_attributes(content: 'binding-name') if name.nil?
55+
end
4556
end
4657
end
4758
end

0 commit comments

Comments
 (0)