Skip to content

Commit a4b02aa

Browse files
committed
Keep usage event records of running apps and services
AppUsageEvent and ServiceUsageEvent records can be used to track the lifecycle of apps and services in a foundation. However, if the start event is pruned before end event, the foundation no longer maintains an accurate state of its running apps and services. By holding on to the start events, consumers of these records are able to get an accurate picture of the current state whenever they choose to begin consuming usage event records.
1 parent 7b53e08 commit a4b02aa

File tree

6 files changed

+128
-13
lines changed

6 files changed

+128
-13
lines changed

app/repositories/app_usage_event_repository.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def purge_and_reseed_started_apps!
152152
end
153153

154154
def delete_events_older_than(cutoff_age_in_days)
155-
Database::OldRecordCleanup.new(AppUsageEvent, cutoff_age_in_days: cutoff_age_in_days, keep_at_least_one_record: true).delete
155+
Database::OldRecordCleanup.new(AppUsageEvent, cutoff_age_in_days: cutoff_age_in_days, keep_at_least_one_record: true, keep_running_records: true).delete
156156
end
157157

158158
private

app/repositories/service_usage_event_repository.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def purge_and_reseed_service_instances!
9292
end
9393

9494
def delete_events_older_than(cutoff_age_in_days)
95-
Database::OldRecordCleanup.new(ServiceUsageEvent, cutoff_age_in_days: cutoff_age_in_days, keep_at_least_one_record: true).delete
95+
Database::OldRecordCleanup.new(ServiceUsageEvent, cutoff_age_in_days: cutoff_age_in_days, keep_at_least_one_record: true, keep_running_records: true).delete
9696
end
9797
end
9898
end

lib/database/old_record_cleanup.rb

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
module Database
44
class OldRecordCleanup
55
class NoCurrentTimestampError < StandardError; end
6-
attr_reader :model, :days_ago, :keep_at_least_one_record
6+
attr_reader :model, :days_ago, :keep_at_least_one_record, :keep_running_records
77

8-
def initialize(model, cutoff_age_in_days:, keep_at_least_one_record: false)
8+
def initialize(model, cutoff_age_in_days:, keep_at_least_one_record: false, keep_running_records: false)
99
@model = model
1010
@days_ago = cutoff_age_in_days
1111
@keep_at_least_one_record = keep_at_least_one_record
12+
@keep_running_records = keep_running_records
1213
end
1314

1415
def delete
@@ -21,6 +22,8 @@ def delete
2122
end
2223
logger.info("Cleaning up #{old_records.count} #{model.table_name} table rows")
2324

25+
old_records = exclude_running_records(old_records) if keep_running_records
26+
2427
Database::BatchDelete.new(old_records, 1000).delete
2528
end
2629

@@ -35,5 +38,55 @@ def current_timestamp_from_database
3538
def logger
3639
@logger ||= Steno.logger('cc.old_record_cleanup')
3740
end
41+
42+
def exclude_running_records(old_records)
43+
return old_records unless has_duration?(model)
44+
45+
beginning_string = beginning_string(model)
46+
ending_string = ending_string(model)
47+
guid_symbol = guid_symbol(model)
48+
49+
raise "Invalid duration model: #{model}" if beginning_string.nil? || ending_string.nil? || guid_symbol.nil?
50+
51+
initial_records = old_records.where(state: beginning_string).from_self(alias: :initial_records)
52+
final_records = old_records.where(state: ending_string).from_self(alias: :final_records)
53+
54+
exists_condition = final_records.where(Sequel[:final_records][guid_symbol] => Sequel[:initial_records][guid_symbol]).where do
55+
Sequel[:final_records][:id] > Sequel[:initial_records][:id]
56+
end.select(1).exists
57+
58+
prunable_initial_records = initial_records.where(exists_condition)
59+
other_records = old_records.exclude(state: [beginning_string, ending_string])
60+
61+
prunable_initial_records.union(final_records, all: true).union(other_records, all: true)
62+
end
63+
64+
def has_duration?(model)
65+
return true if model == VCAP::CloudController::AppUsageEvent
66+
return true if model == VCAP::CloudController::ServiceUsageEvent
67+
68+
false
69+
end
70+
71+
def beginning_string(model)
72+
return VCAP::CloudController::ProcessModel::STARTED if model == VCAP::CloudController::AppUsageEvent
73+
return VCAP::CloudController::Repositories::ServiceUsageEventRepository::CREATED_EVENT_STATE if model == VCAP::CloudController::ServiceUsageEvent
74+
75+
nil
76+
end
77+
78+
def ending_string(model)
79+
return VCAP::CloudController::ProcessModel::STOPPED if model == VCAP::CloudController::AppUsageEvent
80+
return VCAP::CloudController::Repositories::ServiceUsageEventRepository::DELETED_EVENT_STATE if model == VCAP::CloudController::ServiceUsageEvent
81+
82+
nil
83+
end
84+
85+
def guid_symbol(model)
86+
return :app_guid if model == VCAP::CloudController::AppUsageEvent
87+
return :service_instance_guid if model == VCAP::CloudController::ServiceUsageEvent
88+
89+
nil
90+
end
3891
end
3992
end

spec/unit/jobs/runtime/app_usage_events_cleanup_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Jobs::Runtime
55
RSpec.describe AppUsageEventsCleanup, job_context: :worker do
66
let(:cutoff_age_in_days) { 30 }
77
let(:logger) { double(Steno::Logger, info: nil) }
8-
let!(:event_before_threshold) { AppUsageEvent.make(created_at: (cutoff_age_in_days + 1).days.ago) }
8+
let!(:event_before_threshold) { AppUsageEvent.make(created_at: (cutoff_age_in_days + 1).days.ago, state: 'STOPPED') }
99
let!(:event_after_threshold) { AppUsageEvent.make(created_at: (cutoff_age_in_days - 1).days.ago) }
1010

1111
subject(:job) do

spec/unit/jobs/services/service_usage_events_cleanup_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Jobs::Services
55
RSpec.describe ServiceUsageEventsCleanup, job_context: :worker do
66
let(:cutoff_age_in_days) { 30 }
77
let(:logger) { double(Steno::Logger, info: nil) }
8-
let!(:event_before_threshold) { ServiceUsageEvent.make(created_at: (cutoff_age_in_days + 1).days.ago) }
8+
let!(:event_before_threshold) { ServiceUsageEvent.make(created_at: (cutoff_age_in_days + 1).days.ago, state: 'DELETED') }
99
let!(:event_after_threshold) { ServiceUsageEvent.make(created_at: (cutoff_age_in_days - 1).days.ago) }
1010

1111
subject(:job) do

spec/unit/lib/database/old_record_cleanup_spec.rb

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
RSpec.describe Database::OldRecordCleanup do
55
describe '#delete' do
6-
let!(:stale_event1) { VCAP::CloudController::Event.make(created_at: 1.day.ago - 1.minute) }
7-
let!(:stale_event2) { VCAP::CloudController::Event.make(created_at: 2.days.ago) }
6+
it 'deletes records older than specified days' do
7+
stale_event1 = VCAP::CloudController::Event.make(created_at: 1.day.ago - 1.minute)
8+
stale_event2 = VCAP::CloudController::Event.make(created_at: 2.days.ago)
89

9-
let!(:fresh_event) { VCAP::CloudController::Event.make(created_at: 1.day.ago + 1.minute) }
10+
fresh_event = VCAP::CloudController::Event.make(created_at: 1.day.ago + 1.minute)
1011

11-
it 'deletes records older than specified days' do
1212
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::Event, cutoff_age_in_days: 1)
1313

1414
expect do
@@ -22,10 +22,10 @@
2222

2323
context "when there are no records at all but you're trying to keep at least one" do
2424
it "doesn't keep one because there aren't any to keep" do
25-
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: true)
25+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppEvent, cutoff_age_in_days: 1, keep_at_least_one_record: true, keep_running_records: true)
2626

2727
expect { record_cleanup.delete }.not_to raise_error
28-
expect(VCAP::CloudController::ServiceUsageEvent.count).to eq(0)
28+
expect(VCAP::CloudController::AppEvent.count).to eq(0)
2929
end
3030
end
3131

@@ -36,7 +36,12 @@
3636
end
3737

3838
it 'keeps the last row when :keep_at_least_one_record is true even if it is older than the cutoff date' do
39-
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::Event, cutoff_age_in_days: 0, keep_at_least_one_record: true)
39+
stale_event1 = VCAP::CloudController::Event.make(created_at: 1.day.ago - 1.minute)
40+
stale_event2 = VCAP::CloudController::Event.make(created_at: 2.days.ago)
41+
42+
fresh_event = VCAP::CloudController::Event.make(created_at: 1.day.ago + 1.minute)
43+
44+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::Event, cutoff_age_in_days: 0, keep_at_least_one_record: true, keep_running_records: true)
4045

4146
expect do
4247
record_cleanup.delete
@@ -46,5 +51,62 @@
4651
expect { stale_event1.reload }.to raise_error(Sequel::NoExistingObject)
4752
expect { stale_event2.reload }.to raise_error(Sequel::NoExistingObject)
4853
end
54+
55+
# Testing keep_running_records feature
56+
it 'keeps AppUsageEvent start record when there is no corresponding stop record' do
57+
stale_app_usage_event_start = VCAP::CloudController::AppUsageEvent.make(created_at: 2.days.ago, state: 'STARTED', app_guid: 'guid1')
58+
59+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
60+
record_cleanup.delete
61+
expect(stale_app_usage_event_start.reload).to be_present
62+
end
63+
64+
it 'keeps AppUsageEvent start record when stop record is fresh' do
65+
stale_app_usage_event_start = VCAP::CloudController::AppUsageEvent.make(created_at: 2.days.ago, state: 'STARTED', app_guid: 'guid1')
66+
fresh_app_usage_event_stop = VCAP::CloudController::AppUsageEvent.make(created_at: 1.day.ago + 1.minute, state: 'STOPPED', app_guid: 'guid1')
67+
68+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
69+
record_cleanup.delete
70+
expect(stale_app_usage_event_start.reload).to be_present
71+
expect(fresh_app_usage_event_stop.reload).to be_present
72+
end
73+
74+
it 'keeps AppUsageEvent start record when stop record is newer' do
75+
stale_app_usage_event_stop = VCAP::CloudController::AppUsageEvent.make(created_at: 3.days.ago, state: 'STOPPED', app_guid: 'guid1')
76+
stale_app_usage_event_start = VCAP::CloudController::AppUsageEvent.make(created_at: 2.days.ago, state: 'STARTED', app_guid: 'guid1')
77+
78+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::AppUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
79+
record_cleanup.delete
80+
expect(stale_app_usage_event_start.reload).to be_present
81+
expect { stale_app_usage_event_stop.reload }.to raise_error(Sequel::NoExistingObject)
82+
end
83+
84+
it 'keeps ServiceUsageEvent create record when there is no corresponding delete record' do
85+
stale_service_usage_event_create = VCAP::CloudController::ServiceUsageEvent.make(created_at: 2.days.ago, state: 'CREATED', service_instance_guid: 'guid1')
86+
87+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
88+
record_cleanup.delete
89+
expect(stale_service_usage_event_create.reload).to be_present
90+
end
91+
92+
it 'keeps ServiceUsageEvent create record when delete record is fresh' do
93+
stale_service_usage_event_create = VCAP::CloudController::ServiceUsageEvent.make(created_at: 2.days.ago, state: 'CREATED', service_instance_guid: 'guid1')
94+
fresh_service_usage_event_delete = VCAP::CloudController::ServiceUsageEvent.make(created_at: 1.day.ago + 1.minute, state: 'DELETED', service_instance_guid: 'guid1')
95+
96+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
97+
record_cleanup.delete
98+
expect(stale_service_usage_event_create.reload).to be_present
99+
expect(fresh_service_usage_event_delete.reload).to be_present
100+
end
101+
102+
it 'keeps ServiceUsageEvent create record when delete record is newer' do
103+
stale_service_usage_event_delete = VCAP::CloudController::ServiceUsageEvent.make(created_at: 3.days.ago, state: 'DELETED', service_instance_guid: 'guid1')
104+
stale_service_usage_event_create = VCAP::CloudController::ServiceUsageEvent.make(created_at: 2.days.ago, state: 'CREATED', service_instance_guid: 'guid1')
105+
106+
record_cleanup = Database::OldRecordCleanup.new(VCAP::CloudController::ServiceUsageEvent, cutoff_age_in_days: 1, keep_at_least_one_record: false, keep_running_records: true)
107+
record_cleanup.delete
108+
expect(stale_service_usage_event_create.reload).to be_present
109+
expect { stale_service_usage_event_delete.reload }.to raise_error(Sequel::NoExistingObject)
110+
end
49111
end
50112
end

0 commit comments

Comments
 (0)