Skip to content

Commit 1567190

Browse files
kathapsvkrieger
andauthored
Add logging of service metadata during instance create/update/delete (#4386)
* Inform operators which service plan, offering and broker is involved in service instance creation/update/deletion * Refactor service plan lookup to eager load service and broker associations * simplify update managed with adding only 1 simple sql query * use more specific joins for delete request * Add tests for the log info * Eager load associations of service instance * Remove unnecessary reload of service_instance Reloading the service_instance just ensures that follow-up preflight checks run with the original service instance values. This can also be achieved by applying changes on a copy of the object. With that, we don't loose eagerly loaded associations and save a few DB queries. * Further reduce DB queries --------- Co-authored-by: Sven Krieger <[email protected]>
1 parent 52f70ca commit 1567190

File tree

4 files changed

+158
-7
lines changed

4 files changed

+158
-7
lines changed

app/actions/v3/service_instance_update_managed.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,12 @@ def raise_if_cannot_proceed!
268268
def raise_if_invalid_update!
269269
return unless message.updates.any?
270270

271-
service_instance.set(message.updates)
272-
return service_instance.reload if service_instance.valid?
271+
service_instance_copy = service_instance.dup
272+
service_instance_copy.set(message.updates)
273+
return if service_instance_copy.valid?
273274

274-
service_instance_name_errors = service_instance.errors.on(:name).to_a
275-
service_plan_errors = service_instance.errors.on(:service_plan).to_a
275+
service_instance_name_errors = service_instance_copy.errors.on(:name).to_a
276+
service_plan_errors = service_instance_copy.errors.on(:service_plan).to_a
276277

277278
if service_instance_name_errors.include?(:unique)
278279
raise UnprocessableUpdate.new_from_details('ServiceInstanceNameTaken', message.name)
@@ -282,7 +283,7 @@ def raise_if_invalid_update!
282283
raise UnprocessableUpdate.new_from_details('ServiceInstanceServicePlanNotAllowed')
283284
end
284285

285-
raise Sequel::ValidationFailed.new(service_instance)
286+
raise Sequel::ValidationFailed.new(service_instance_copy)
286287
end
287288

288289
def raise_if_renaming_shared_service_instance!

app/controllers/v3/service_instances_controller.rb

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@ def create_user_provided(message)
253253
end
254254

255255
def create_managed(message, space:)
256-
service_plan = ServicePlan.first(guid: message.service_plan_guid)
256+
service_plan_relations = ServicePlan.eager_graph(service: :service_broker).
257+
where(Sequel[:service_plans][:guid] => message.service_plan_guid).
258+
all
259+
service_plan = service_plan_relations[0]
260+
257261
service_plan_does_not_exist! unless service_plan
258262
service_plan_not_visible_to_user!(service_plan) unless visible_to_current_user?(plan: service_plan)
259263
unavailable_service_plan!(service_plan) unless service_plan_active?(service_plan)
@@ -270,6 +274,16 @@ def create_managed(message, space:)
270274
audit_hash: message.audit_hash
271275
)
272276

277+
service_name = service_plan.service.name
278+
broker_name = service_plan.service.service_broker.name
279+
280+
logger.info(
281+
"Creating managed service instance with name '#{instance.name}' " \
282+
"using service plan '#{service_plan.name}' " \
283+
"from service offering '#{service_name}' " \
284+
"provided by broker '#{broker_name}'."
285+
)
286+
273287
pollable_job = Jobs::Enqueuer.new(queue: Jobs::Queues.generic).enqueue_pollable(provision_job)
274288

275289
head :accepted, 'Location' => url_builder.build_url(path: "/v3/jobs/#{pollable_job.guid}")
@@ -296,8 +310,12 @@ def update_managed(service_instance)
296310

297311
action = V3::ServiceInstanceUpdateManaged.new(service_instance, message, user_audit_info, message.audit_hash)
298312
action.preflight!
313+
299314
if action.update_broker_needed?
315+
# enqueue_update will reload the service instance and we loose the eagerly loaded associations
316+
service_instance_with_eager_load = service_instance.dup
300317
update_job = action.enqueue_update
318+
log_service_instance_update(message, service_instance_with_eager_load)
301319
head :accepted, 'Location' => url_builder.build_url(path: "/v3/jobs/#{update_job.guid}")
302320
else
303321
service_instance = action.update_sync
@@ -309,6 +327,26 @@ def update_managed(service_instance)
309327
raise CloudController::Errors::ApiError.new_from_details('AsyncServiceBindingOperationInProgress', e.service_binding.app.name, e.service_binding.service_instance.name)
310328
end
311329

330+
def log_service_instance_update(message, service_instance)
331+
if message.service_plan_guid && message.service_plan_guid != service_instance.service_plan.guid
332+
new_service_plan_name = ServicePlan.first(guid: message.service_plan_guid).name
333+
334+
logger.info(
335+
"Updating managed service instance with name '#{service_instance.name}' " \
336+
"changing plan from '#{service_instance.service_plan.name}' to '#{new_service_plan_name}' " \
337+
"from service offering '#{service_instance.service_plan.service.label}' " \
338+
"provided by broker '#{service_instance.service_plan.service.service_broker.name}'."
339+
)
340+
else
341+
logger.info(
342+
"Updating managed service instance with name '#{service_instance.name}' " \
343+
"using service plan '#{service_instance.service_plan.name}' " \
344+
"from service offering '#{service_instance.service_plan.service.label}' " \
345+
"provided by broker '#{service_instance.service_plan.service.service_broker.name}'."
346+
)
347+
end
348+
end
349+
312350
def check_spaces_exist_and_are_writeable!(service_instance, request_guids, found_spaces)
313351
unreadable_spaces = found_spaces.reject { |s| can_read_from_space?(s) }
314352
unwriteable_spaces = found_spaces.reject { |s| (can_write_to_active_space?(s) && is_space_active?(s)) || unreadable_spaces.include?(s) }
@@ -342,7 +380,10 @@ def build_create_message(params)
342380
end
343381

344382
def fetch_writable_service_instance(guid)
345-
service_instance = ServiceInstance.first(guid:)
383+
service_instances = ManagedServiceInstance.eager_graph(service_plan: { service: :service_broker }).where(Sequel[:service_instances][:guid] => guid).all
384+
service_instance = service_instances[0] unless service_instances.empty?
385+
service_instance = UserProvidedServiceInstance.first(guid:) if service_instance.nil?
386+
346387
service_instance_not_found! unless service_instance && can_read_service_instance?(service_instance)
347388
unauthorized! unless can_write_to_active_space?(service_instance.space)
348389
suspended! unless is_space_active?(service_instance.space)
@@ -352,6 +393,18 @@ def fetch_writable_service_instance(guid)
352393

353394
def enqueue_delete_job(service_instance)
354395
delete_job = V3::DeleteServiceInstanceJob.new(service_instance.guid, user_audit_info)
396+
397+
plan = service_instance.service_plan
398+
service = plan.service
399+
broker = service.service_broker
400+
401+
logger.info(
402+
"Deleting managed service instance with name '#{service_instance.name}' " \
403+
"using service plan '#{plan.name}' " \
404+
"from service offering '#{service.label}' " \
405+
"provided by broker '#{broker.name}'."
406+
)
407+
355408
pollable_job = Jobs::Enqueuer.new(queue: Jobs::Queues.generic).enqueue_pollable(delete_job)
356409
pollable_job.guid
357410
end

spec/request/service_instances_spec.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,12 @@ def check_filtered_instances(*instances)
10801080
end
10811081
let(:instance) { VCAP::CloudController::ServiceInstance.last }
10821082
let(:job) { VCAP::CloudController::PollableJobModel.last }
1083+
let(:mock_logger) { instance_double(Steno::Logger, info: nil) }
1084+
1085+
before do
1086+
allow(Steno).to receive(:logger).and_call_original
1087+
allow(Steno).to receive(:logger).with('cc.api').and_return(mock_logger)
1088+
end
10831089

10841090
it 'creates a service instance in the database' do
10851091
api_call.call(space_dev_headers)
@@ -1107,6 +1113,17 @@ def check_filtered_instances(*instances)
11071113
expect(job.resource_type).to eq('service_instances')
11081114
end
11091115

1116+
it 'logs the correct names when creating a managed service instance' do
1117+
api_call.call(space_dev_headers)
1118+
1119+
expect(mock_logger).to have_received(:info).with(
1120+
"Creating managed service instance with name '#{instance.name}' " \
1121+
"using service plan '#{service_plan.name}' " \
1122+
"from service offering '#{service_plan.service.name}' " \
1123+
"provided by broker '#{service_plan.service.service_broker.name}'."
1124+
)
1125+
end
1126+
11101127
context 'when the name has already been taken' do
11111128
it 'fails when the same name is already used in this space' do
11121129
VCAP::CloudController::ServiceInstance.make(name:, space:)
@@ -1847,6 +1864,12 @@ def check_filtered_instances(*instances)
18471864
}
18481865
end
18491866
let(:job) { VCAP::CloudController::PollableJobModel.last }
1867+
let(:mock_logger) { instance_double(Steno::Logger, info: nil) }
1868+
1869+
before do
1870+
allow(Steno).to receive(:logger).and_call_original
1871+
allow(Steno).to receive(:logger).with('cc.api').and_return(mock_logger)
1872+
end
18501873

18511874
it 'responds with a pollable job' do
18521875
api_call.call(space_dev_headers)
@@ -1883,6 +1906,41 @@ def check_filtered_instances(*instances)
18831906
)
18841907
end
18851908

1909+
describe 'logging of updates' do
1910+
it 'logs info including the change of service plans' do
1911+
api_call.call(space_dev_headers)
1912+
1913+
expect(mock_logger).to have_received(:info).with(
1914+
"Updating managed service instance with name '#{service_instance.name}' " \
1915+
"changing plan from '#{original_service_plan.name}' to '#{new_service_plan.name}' " \
1916+
"from service offering '#{service_offering.name}' " \
1917+
"provided by broker '#{original_service_plan.service.service_broker.name}'."
1918+
)
1919+
end
1920+
1921+
context 'when service plan does not change' do
1922+
let(:request_body) do
1923+
{
1924+
parameters: {
1925+
foo: 'bar',
1926+
baz: 'qux'
1927+
}
1928+
}
1929+
end
1930+
1931+
it 'logs info accordingly' do
1932+
api_call.call(space_dev_headers)
1933+
1934+
expect(mock_logger).to have_received(:info).with(
1935+
"Updating managed service instance with name '#{service_instance.name}' " \
1936+
"using service plan '#{original_service_plan.name}' " \
1937+
"from service offering '#{service_offering.name}' " \
1938+
"provided by broker '#{original_service_plan.service.service_broker.name}'."
1939+
)
1940+
end
1941+
end
1942+
end
1943+
18861944
describe 'the pollable job' do
18871945
let(:broker_response) { { dashboard_url: 'http://new-dashboard.url' } }
18881946
let(:broker_status_code) { 200 }
@@ -2970,6 +3028,12 @@ def check_filtered_instances(*instances)
29703028
}).
29713029
to_return(status: broker_status_code, body: broker_response.to_json, headers: {})
29723030
end
3031+
let(:mock_logger) { instance_double(Steno::Logger, info: nil) }
3032+
3033+
before do
3034+
allow(Steno).to receive(:logger).and_call_original
3035+
allow(Steno).to receive(:logger).with('cc.api').and_return(mock_logger)
3036+
end
29733037

29743038
it 'responds with job resource' do
29753039
api_call.call(admin_headers)
@@ -2984,6 +3048,17 @@ def check_filtered_instances(*instances)
29843048
expect(job.resource_type).to eq('service_instance')
29853049
end
29863050

3051+
it 'logs the correct names when deleting a managed service instance' do
3052+
api_call.call(admin_headers)
3053+
3054+
expect(mock_logger).to have_received(:info).with(
3055+
"Deleting managed service instance with name '#{instance.name}' " \
3056+
"using service plan '#{instance.service_plan.name}' " \
3057+
"from service offering '#{instance.service_plan.service.name}' " \
3058+
"provided by broker '#{instance.service_plan.service.service_broker.name}'."
3059+
)
3060+
end
3061+
29873062
describe 'the pollable job' do
29883063
it 'sends a delete request with the right arguments to the service broker' do
29893064
api_call.call(headers_for(user, scopes: %w[cloud_controller.admin]))

spec/unit/actions/v3/service_instance_update_managed_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ module V3
7575
end
7676

7777
describe '#preflight!' do
78+
context 'when changes were requested' do
79+
let(:body) do
80+
{
81+
name: 'new-name',
82+
relationships: {
83+
service_plan: {
84+
data: {
85+
guid: new_plan.guid
86+
}
87+
}
88+
}
89+
}
90+
end
91+
92+
let(:new_plan) { ServicePlan.make(service: original_service_offering) }
93+
94+
it 'does not change the original service instance object' do
95+
action.preflight!
96+
expect(original_instance.changed_columns).not_to be_any
97+
end
98+
end
99+
78100
describe 'invalid name updates' do
79101
context 'when the new name is already taken' do
80102
let(:instance_in_same_space) { ServiceInstance.make(space: original_instance.space) }

0 commit comments

Comments
 (0)