Skip to content

Commit ef2aff4

Browse files
joyvuu-daveclaude
andcommitted
Recognize WAS_RUNNING as a beginning state in keep-running cleanup
Extend usage_lifecycle.beginning_state on AppUsageEvent and ServiceUsageEvent to be an array containing both the existing state (STARTED / CREATED) and WAS_RUNNING. Update OldRecordCleanup so the "other states" exclusion flattens the begin states correctly. Without this, synthetic WAS_RUNNING rows (added in a follow-up migration) would be pruned at 31 days even when their resource is still running. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 306242a commit ef2aff4

4 files changed

Lines changed: 88 additions & 4 deletions

File tree

app/models/runtime/app_usage_event.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class AppUsageEvent < Sequel::Model
1212

1313
def self.usage_lifecycle
1414
{
15-
beginning_state: ProcessModel::STARTED,
15+
beginning_state: [ProcessModel::STARTED, Repositories::AppUsageEventRepository::WAS_RUNNING_STATE],
1616
ending_state: ProcessModel::STOPPED,
1717
guid_column: :app_guid
1818
}.freeze

app/models/services/service_usage_event.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class ServiceUsageEvent < Sequel::Model
1010

1111
def self.usage_lifecycle
1212
{
13-
beginning_state: Repositories::ServiceUsageEventRepository::CREATED_EVENT_STATE,
13+
beginning_state: [Repositories::ServiceUsageEventRepository::CREATED_EVENT_STATE,
14+
Repositories::ServiceUsageEventRepository::WAS_RUNNING_EVENT_STATE],
1415
ending_state: Repositories::ServiceUsageEventRepository::DELETED_EVENT_STATE,
1516
guid_column: :service_instance_guid
1617
}.freeze

lib/database/old_record_cleanup.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ def exclude_running_records(old_records)
6060

6161
prunable_initial_records = initial_records.where(exists_condition)
6262

63-
# Include records with states other than START/STOP
64-
other_records = old_records.exclude(state: [beginning_state, ending_state])
63+
# Include records with states other than START/STOP. beginning_state may be a
64+
# scalar or an array; flatten so the exclude clause produces a flat list of states.
65+
other_records = old_records.exclude(state: Array(beginning_state) + [ending_state])
6566

6667
# Return the UNION of:
6768
# 1. START records that have a matching STOP (safe to delete)

spec/unit/lib/database/old_record_cleanup_spec.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,54 @@
108108
expect(VCAP::CloudController::AppUsageEvent.count).to eq(0)
109109
end
110110

111+
it 'keeps AppUsageEvent WAS_RUNNING record when there is no corresponding stop record' do
112+
stale_was_running = VCAP::CloudController::AppUsageEvent.make(created_at: 2.days.ago, state: 'WAS_RUNNING', app_guid: 'guid1')
113+
114+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
115+
record_cleanup.delete
116+
117+
expect(stale_was_running.reload).to be_present
118+
expect(VCAP::CloudController::AppUsageEvent.count).to eq(1)
119+
end
120+
121+
it 'deletes AppUsageEvent WAS_RUNNING record when there is a corresponding stop record' do
122+
app_guid = 'stopped-app'
123+
stale_was_running = VCAP::CloudController::AppUsageEvent.make(created_at: 5.days.ago, state: 'WAS_RUNNING', app_guid: app_guid)
124+
stale_stop = VCAP::CloudController::AppUsageEvent.make(created_at: 4.days.ago, state: 'STOPPED', app_guid: app_guid)
125+
126+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
127+
record_cleanup.delete
128+
129+
expect { stale_was_running.reload }.to raise_error(Sequel::NoExistingObject)
130+
expect { stale_stop.reload }.to raise_error(Sequel::NoExistingObject)
131+
end
132+
133+
it 'keeps both STARTED and WAS_RUNNING records for the same app while it is running' do
134+
app_guid = 'running-app'
135+
stale_started = VCAP::CloudController::AppUsageEvent.make(created_at: 10.days.ago, state: 'STARTED', app_guid: app_guid)
136+
stale_was_running = VCAP::CloudController::AppUsageEvent.make(created_at: 5.days.ago, state: 'WAS_RUNNING', app_guid: app_guid)
137+
138+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
139+
record_cleanup.delete
140+
141+
expect(stale_started.reload).to be_present
142+
expect(stale_was_running.reload).to be_present
143+
end
144+
145+
it 'deletes both STARTED and WAS_RUNNING records when a STOPPED record exists' do
146+
app_guid = 'restarted-app'
147+
stale_started = VCAP::CloudController::AppUsageEvent.make(created_at: 10.days.ago, state: 'STARTED', app_guid: app_guid)
148+
stale_was_running = VCAP::CloudController::AppUsageEvent.make(created_at: 8.days.ago, state: 'WAS_RUNNING', app_guid: app_guid)
149+
stale_stop = VCAP::CloudController::AppUsageEvent.make(created_at: 5.days.ago, state: 'STOPPED', app_guid: app_guid)
150+
151+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
152+
record_cleanup.delete
153+
154+
expect { stale_started.reload }.to raise_error(Sequel::NoExistingObject)
155+
expect { stale_was_running.reload }.to raise_error(Sequel::NoExistingObject)
156+
expect { stale_stop.reload }.to raise_error(Sequel::NoExistingObject)
157+
end
158+
111159
# ==================== KEEP_RUNNING_RECORDS: ServiceUsageEvent ====================
112160

113161
it 'keeps ServiceUsageEvent create record when there is no corresponding delete record' do
@@ -177,6 +225,40 @@
177225
expect { orphan_delete.reload }.to raise_error(Sequel::NoExistingObject)
178226
end
179227

228+
it 'keeps ServiceUsageEvent WAS_RUNNING record when there is no corresponding delete record' do
229+
stale_was_running = VCAP::CloudController::ServiceUsageEvent.make(created_at: 2.days.ago, state: 'WAS_RUNNING', service_instance_guid: 'guid1')
230+
231+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
232+
record_cleanup.delete
233+
234+
expect(stale_was_running.reload).to be_present
235+
expect(VCAP::CloudController::ServiceUsageEvent.count).to eq(1)
236+
end
237+
238+
it 'deletes ServiceUsageEvent WAS_RUNNING record when there is a corresponding delete record' do
239+
service_guid = 'deleted-instance'
240+
stale_was_running = VCAP::CloudController::ServiceUsageEvent.make(created_at: 5.days.ago, state: 'WAS_RUNNING', service_instance_guid: service_guid)
241+
stale_delete = VCAP::CloudController::ServiceUsageEvent.make(created_at: 4.days.ago, state: 'DELETED', service_instance_guid: service_guid)
242+
243+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
244+
record_cleanup.delete
245+
246+
expect { stale_was_running.reload }.to raise_error(Sequel::NoExistingObject)
247+
expect { stale_delete.reload }.to raise_error(Sequel::NoExistingObject)
248+
end
249+
250+
it 'keeps both CREATED and WAS_RUNNING records for the same service instance while it exists' do
251+
service_guid = 'running-service'
252+
stale_created = VCAP::CloudController::ServiceUsageEvent.make(created_at: 10.days.ago, state: 'CREATED', service_instance_guid: service_guid)
253+
stale_was_running = VCAP::CloudController::ServiceUsageEvent.make(created_at: 5.days.ago, state: 'WAS_RUNNING', service_instance_guid: service_guid)
254+
255+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
256+
record_cleanup.delete
257+
258+
expect(stale_created.reload).to be_present
259+
expect(stale_was_running.reload).to be_present
260+
end
261+
180262
# ==================== EDGE CASES & DATA INTEGRITY ====================
181263

182264
it 'deletes records with non-lifecycle states when keep_running_records is true' do

0 commit comments

Comments
 (0)