diff --git a/app/models/container_quota.rb b/app/models/container_quota.rb index da56e1f0756..b81323330d4 100644 --- a/app/models/container_quota.rb +++ b/app/models/container_quota.rb @@ -10,6 +10,7 @@ class ContainerQuota < ApplicationRecord # - ContainerQuotaItems can be added/deleted/changed, and we use archiving to # record changes too! include ArchivedMixin + include_concern 'Purging' belongs_to :ext_management_system, :foreign_key => "ems_id" belongs_to :container_project diff --git a/app/models/container_quota/purging.rb b/app/models/container_quota/purging.rb new file mode 100644 index 00000000000..29376db5984 --- /dev/null +++ b/app/models/container_quota/purging.rb @@ -0,0 +1,25 @@ +class ContainerQuota < ApplicationRecord + module Purging + extend ActiveSupport::Concern + include PurgingMixin + + module ClassMethods + def purge_date + ::Settings.container_entities.history.keep_archived_quotas.to_i_with_method.seconds.ago.utc + end + + def purge_window_size + ::Settings.container_entities.history.purge_window_size + end + + def purge_scope(older_than) + where(arel_table[:deleted_on].lteq(older_than)) + end + + def purge_associated_records(ids) + ContainerQuotaScope.where(:container_quota_id => ids).delete_all + ContainerQuotaItem.where(:container_quota_id => ids).delete_all + end + end + end +end diff --git a/app/models/container_quota_item.rb b/app/models/container_quota_item.rb index 87a234dc100..bcd318d4efa 100644 --- a/app/models/container_quota_item.rb +++ b/app/models/container_quota_item.rb @@ -2,6 +2,7 @@ class ContainerQuotaItem < ApplicationRecord # This model is unusual in using archiving not only to record deletions but also changes in quota_desired, quota_enforced, quota_observed. # Instead of updating in-place, we archive the old record and create a new one. include ArchivedMixin + include_concern 'Purging' belongs_to :container_quota has_many :container_quota_scopes, :through => :container_quota diff --git a/app/models/container_quota_item/purging.rb b/app/models/container_quota_item/purging.rb new file mode 100644 index 00000000000..62ee4160f58 --- /dev/null +++ b/app/models/container_quota_item/purging.rb @@ -0,0 +1,20 @@ +class ContainerQuotaItem < ApplicationRecord + module Purging + extend ActiveSupport::Concern + include PurgingMixin + + module ClassMethods + def purge_date + ::Settings.container_entities.history.keep_archived_quotas.to_i_with_method.seconds.ago.utc + end + + def purge_window_size + ::Settings.container_entities.history.purge_window_size + end + + def purge_scope(older_than) + where(arel_table[:deleted_on].lteq(older_than)) + end + end + end +end diff --git a/app/models/miq_schedule_worker/jobs.rb b/app/models/miq_schedule_worker/jobs.rb index b08f88bf129..7043e46538b 100644 --- a/app/models/miq_schedule_worker/jobs.rb +++ b/app/models/miq_schedule_worker/jobs.rb @@ -113,6 +113,8 @@ def archived_entities_purge_timer queue_work(:class_name => "ContainerGroup", :method_name => "purge_timer", :zone => nil) queue_work(:class_name => "ContainerImage", :method_name => "purge_timer", :zone => nil) queue_work(:class_name => "ContainerProject", :method_name => "purge_timer", :zone => nil) + queue_work(:class_name => "ContainerQuota", :method_name => "purge_timer", :zone => nil) + queue_work(:class_name => "ContainerQuotaItem", :method_name => "purge_timer", :zone => nil) end def binary_blob_purge_timer diff --git a/config/settings.yml b/config/settings.yml index d3bb6352cde..96688206491 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -967,6 +967,7 @@ :container_entities: :history: :keep_archived_entities: 6.months + :keep_archived_quotas: 6.months :purge_window_size: 1000 :product: :maindb: ExtManagementSystem diff --git a/spec/models/container_quota/purging_spec.rb b/spec/models/container_quota/purging_spec.rb new file mode 100644 index 00000000000..5b1ea79ee42 --- /dev/null +++ b/spec/models/container_quota/purging_spec.rb @@ -0,0 +1,63 @@ +describe ContainerQuota do + context "::Purging" do + context ".purge_queue" do + before do + EvmSpecHelper.create_guid_miq_server_zone + end + let(:purge_time) { (Time.zone.now + 10).round } + + it "submits to the queue" do + expect(described_class).to receive(:purge_date).and_return(purge_time) + described_class.purge_timer + + q = MiqQueue.all + expect(q.length).to eq(1) + expect(q.first).to have_attributes( + :class_name => described_class.name, + :method_name => "purge_by_date", + :args => [purge_time] + ) + end + end + + context ".purge" do + let(:deleted_date) { 6.months.ago } + + before do + @old_quota = FactoryGirl.create(:container_quota, :deleted_on => deleted_date - 1.day) + @old_quota_scope = FactoryGirl.create(:container_quota_scope, :container_quota => @old_quota) + @old_quota_old_item = FactoryGirl.create(:container_quota_item, :container_quota => @old_quota, + :deleted_on => deleted_date - 1.day) + @old_quota_active_item = FactoryGirl.create(:container_quota_item, :container_quota => @old_quota, + :deleted_on => nil) + + @purge_date_quota = FactoryGirl.create(:container_quota, :deleted_on => deleted_date) + + @new_quota = FactoryGirl.create(:container_quota, :deleted_on => deleted_date + 1.day) + @new_quota_scope = FactoryGirl.create(:container_quota_scope, :container_quota => @new_quota) + @new_quota_old_item = FactoryGirl.create(:container_quota_item, :container_quota => @new_quota, + :deleted_on => deleted_date - 1.day) + end + + def assert_unpurged_ids(model, unpurged_ids) + expect(model.order(:id).pluck(:id)).to eq(Array(unpurged_ids).sort) + end + + it "purge_date and older" do + described_class.purge(deleted_date) + assert_unpurged_ids(ContainerQuota, @new_quota.id) + assert_unpurged_ids(ContainerQuotaScope, @new_quota_scope.id) + # This quota item is itself due for purging, but not as part of ContainerQuota::Purging. + assert_unpurged_ids(ContainerQuotaItem, @new_quota_old_item.id) + end + + it "with a window" do + described_class.purge(deleted_date, 1) + assert_unpurged_ids(ContainerQuota, @new_quota.id) + assert_unpurged_ids(ContainerQuotaScope, @new_quota_scope.id) + # This quota item is itself due for purging, but not as part of ContainerQuota::Purging. + assert_unpurged_ids(ContainerQuotaItem, @new_quota_old_item.id) + end + end + end +end diff --git a/spec/models/container_quota_item/purging_spec.rb b/spec/models/container_quota_item/purging_spec.rb new file mode 100644 index 00000000000..18b7d26d9b1 --- /dev/null +++ b/spec/models/container_quota_item/purging_spec.rb @@ -0,0 +1,70 @@ +describe ContainerQuotaItem do + context "::Purging" do + context ".purge_queue" do + before do + EvmSpecHelper.create_guid_miq_server_zone + end + let(:purge_time) { (Time.zone.now + 10).round } + + it "submits to the queue" do + expect(described_class).to receive(:purge_date).and_return(purge_time) + described_class.purge_timer + + q = MiqQueue.all + expect(q.length).to eq(1) + expect(q.first).to have_attributes( + :class_name => described_class.name, + :method_name => "purge_by_date", + :args => [purge_time] + ) + end + end + + context ".purge" do + let(:deleted_date) { 6.months.ago } + + before do + @old_quota = FactoryGirl.create(:container_quota, :deleted_on => deleted_date - 1.day) + @old_quota_scope = FactoryGirl.create(:container_quota_scope, :container_quota => @old_quota) + @old_quota_old_item = FactoryGirl.create(:container_quota_item, :container_quota => @old_quota, + :deleted_on => deleted_date - 1.day) + @old_quota_purge_date_item = FactoryGirl.create(:container_quota_item, :container_quota => @old_quota, + :deleted_on => deleted_date) + @old_quota_new_item = FactoryGirl.create(:container_quota_item, :container_quota => @old_quota, + :deleted_on => deleted_date + 1.day) + + # Quota items may get archived as result of quota edits, while parent quota remains active. + @active_quota = FactoryGirl.create(:container_quota, :deleted_on => nil) + @active_quota_scope = FactoryGirl.create(:container_quota_scope, :container_quota => @active_quota) + @active_quota_old_item = FactoryGirl.create(:container_quota_item, :container_quota => @active_quota, + :deleted_on => deleted_date - 1.day) + @active_quota_purge_date_item = FactoryGirl.create(:container_quota_item, :container_quota => @active_quota, + :deleted_on => deleted_date) + @active_quota_new_item = FactoryGirl.create(:container_quota_item, :container_quota => @active_quota, + :deleted_on => deleted_date + 1.day) + @active_quota_active_item = FactoryGirl.create(:container_quota_item, :container_quota => @active_quota, + :deleted_on => nil) + end + + def assert_unpurged_ids(model, unpurged_ids) + expect(model.order(:id).pluck(:id)).to eq(Array(unpurged_ids).sort) + end + + it "purge_date and older" do + described_class.purge(deleted_date) + # @old_quota is itself due for purging, but not as part of ContainerQuotaItem::Purging. + assert_unpurged_ids(ContainerQuota, [@old_quota.id, @active_quota.id]) + assert_unpurged_ids(ContainerQuotaScope, [@old_quota_scope.id, @active_quota_scope.id]) + assert_unpurged_ids(ContainerQuotaItem, [@old_quota_new_item.id, @active_quota_new_item.id, @active_quota_active_item.id]) + end + + it "with a window" do + described_class.purge(deleted_date, 1) + # @old_quota is itself due for purging, but not as part of ContainerQuotaItem::Purging. + assert_unpurged_ids(ContainerQuota, [@old_quota.id, @active_quota.id]) + assert_unpurged_ids(ContainerQuotaScope, [@old_quota_scope.id, @active_quota_scope.id]) + assert_unpurged_ids(ContainerQuotaItem, [@old_quota_new_item.id, @active_quota_new_item.id, @active_quota_active_item.id]) + end + end + end +end