From: Tom Clegg Date: Fri, 12 Nov 2021 22:00:57 +0000 (-0500) Subject: 18339: Move trash-sweep from background thread to controller action. X-Git-Tag: 2.4.0~158^2~11 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/685db28b50225cde7dbb03aa2275f7a165d888a3 18339: Move trash-sweep from background thread to controller action. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- diff --git a/services/api/lib/sweep_trashed_objects.rb b/services/api/app/controllers/sys_controller.rb similarity index 64% rename from services/api/lib/sweep_trashed_objects.rb rename to services/api/app/controllers/sys_controller.rb index c09896567f..ecc02e83dc 100644 --- a/services/api/lib/sweep_trashed_objects.rb +++ b/services/api/app/controllers/sys_controller.rb @@ -2,33 +2,12 @@ # # SPDX-License-Identifier: AGPL-3.0 -require 'current_api_client' +class SysController < ApplicationController + skip_before_action :find_object_by_uuid + skip_before_action :render_404_if_no_object + before_action :admin_required -module SweepTrashedObjects - extend CurrentApiClient - - def self.delete_project_and_contents(p_uuid) - p = Group.find_by_uuid(p_uuid) - if !p || p.group_class != 'project' - raise "can't sweep group '#{p_uuid}', it may not exist or not be a project" - end - # First delete sub projects - Group.where({group_class: 'project', owner_uuid: p_uuid}).each do |sub_project| - delete_project_and_contents(sub_project.uuid) - end - # Next, iterate over all tables which have owner_uuid fields, with some - # exceptions, and delete records owned by this project - skipped_classes = ['Group', 'User'] - ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass| - if !skipped_classes.include?(klass.name) && klass.columns.collect(&:name).include?('owner_uuid') - klass.where({owner_uuid: p_uuid}).destroy_all - end - end - # Finally delete the project itself - p.destroy - end - - def self.sweep_now + def sweep_trash act_as_system_user do # Sweep trashed collections Collection. @@ -54,29 +33,26 @@ module SweepTrashedObjects end end - def self.sweep_if_stale - return if Rails.configuration.Collections.TrashSweepInterval <= 0 - exp = Rails.configuration.Collections.TrashSweepInterval.seconds - need = false - Rails.cache.fetch('SweepTrashedObjects', expires_in: exp) do - need = true + protected + + def delete_project_and_contents(p_uuid) + p = Group.find_by_uuid(p_uuid) + if !p || p.group_class != 'project' + raise "can't sweep group '#{p_uuid}', it may not exist or not be a project" + end + # First delete sub projects + Group.where({group_class: 'project', owner_uuid: p_uuid}).each do |sub_project| + delete_project_and_contents(sub_project.uuid) end - if need - Thread.new do - Thread.current.abort_on_exception = false - begin - sweep_now - rescue => e - Rails.logger.error "#{e.class}: #{e}\n#{e.backtrace.join("\n\t")}" - ensure - # Rails 5.1+ makes test threads share a database connection, so we can't - # close a connection shared with other threads. - # https://github.com/rails/rails/commit/deba47799ff905f778e0c98a015789a1327d5087 - if Rails.env != "test" - ActiveRecord::Base.connection.close - end - end + # Next, iterate over all tables which have owner_uuid fields, with some + # exceptions, and delete records owned by this project + skipped_classes = ['Group', 'User'] + ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass| + if !skipped_classes.include?(klass.name) && klass.columns.collect(&:name).include?('owner_uuid') + klass.where({owner_uuid: p_uuid}).destroy_all end end + # Finally delete the project itself + p.destroy end end diff --git a/services/api/app/models/collection.rb b/services/api/app/models/collection.rb index a98cde4446..b4660dbd35 100644 --- a/services/api/app/models/collection.rb +++ b/services/api/app/models/collection.rb @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0 require 'arvados/keep' -require 'sweep_trashed_objects' require 'trashable' class Collection < ArvadosModel @@ -616,11 +615,6 @@ class Collection < ArvadosModel super - ["manifest_text", "storage_classes_desired", "storage_classes_confirmed", "current_version_uuid"] end - def self.where *args - SweepTrashedObjects.sweep_if_stale - super - end - protected # Although the defaults for these columns is already set up on the schema, diff --git a/services/api/config/routes.rb b/services/api/config/routes.rb index 738426b1d8..a0c3cafe13 100644 --- a/services/api/config/routes.rb +++ b/services/api/config/routes.rb @@ -92,6 +92,8 @@ Rails.application.routes.draw do end end + post '/sys/sweep_trash', to: 'sys#sweep_trash' + if Rails.env == 'test' post '/database/reset', to: 'database#reset' end diff --git a/services/api/test/unit/api_client_authorization_test.rb b/services/api/test/unit/api_client_authorization_test.rb index fb90418b84..e043f8914a 100644 --- a/services/api/test/unit/api_client_authorization_test.rb +++ b/services/api/test/unit/api_client_authorization_test.rb @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0 require 'test_helper' -require 'sweep_trashed_objects' class ApiClientAuthorizationTest < ActiveSupport::TestCase include CurrentApiClient @@ -20,12 +19,6 @@ class ApiClientAuthorizationTest < ActiveSupport::TestCase end end - test "delete expired in SweepTrashedObjects" do - assert_not_empty ApiClientAuthorization.where(uuid: api_client_authorizations(:expired).uuid) - SweepTrashedObjects.sweep_now - assert_empty ApiClientAuthorization.where(uuid: api_client_authorizations(:expired).uuid) - end - test "accepts SystemRootToken" do assert_nil ApiClientAuthorization.validate(token: "xxxSystemRootTokenxxx") diff --git a/services/api/test/unit/collection_test.rb b/services/api/test/unit/collection_test.rb index de0f1d360c..e7134a5be5 100644 --- a/services/api/test/unit/collection_test.rb +++ b/services/api/test/unit/collection_test.rb @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0 require 'test_helper' -require 'sweep_trashed_objects' require 'fix_collection_versions_timestamps' class CollectionTest < ActiveSupport::TestCase @@ -1058,60 +1057,6 @@ class CollectionTest < ActiveSupport::TestCase assert_includes(coll_uuids, collections(:docker_image).uuid) end - test "move collections to trash in SweepTrashedObjects" do - c = collections(:trashed_on_next_sweep) - refute_empty Collection.where('uuid=? and is_trashed=false', c.uuid) - assert_raises(ActiveRecord::RecordNotUnique) do - act_as_user users(:active) do - Collection.create!(owner_uuid: c.owner_uuid, - name: c.name) - end - end - SweepTrashedObjects.sweep_now - c = Collection.where('uuid=? and is_trashed=true', c.uuid).first - assert c - act_as_user users(:active) do - assert Collection.create!(owner_uuid: c.owner_uuid, - name: c.name) - end - end - - test "delete collections in SweepTrashedObjects" do - uuid = 'zzzzz-4zz18-3u1p5umicfpqszp' # deleted_on_next_sweep - assert_not_empty Collection.where(uuid: uuid) - SweepTrashedObjects.sweep_now - assert_empty Collection.where(uuid: uuid) - end - - test "delete referring links in SweepTrashedObjects" do - uuid = collections(:trashed_on_next_sweep).uuid - act_as_system_user do - assert_raises ActiveRecord::RecordInvalid do - # Cannot create because :trashed_on_next_sweep is already trashed - Link.create!(head_uuid: uuid, - tail_uuid: system_user_uuid, - link_class: 'whatever', - name: 'something') - end - - # Bump trash_at to now + 1 minute - Collection.where(uuid: uuid). - update(trash_at: db_current_time + (1).minute) - - # Not considered trashed now - Link.create!(head_uuid: uuid, - tail_uuid: system_user_uuid, - link_class: 'whatever', - name: 'something') - end - past = db_current_time - Collection.where(uuid: uuid). - update_all(is_trashed: true, trash_at: past, delete_at: past) - assert_not_empty Collection.where(uuid: uuid) - SweepTrashedObjects.sweep_now - assert_empty Collection.where(uuid: uuid) - end - test "empty names are exempt from name uniqueness" do act_as_user users(:active) do c1 = Collection.new(name: nil, manifest_text: '', owner_uuid: groups(:aproject).uuid) diff --git a/services/api/test/unit/group_test.rb b/services/api/test/unit/group_test.rb index 017916f48b..10932e116d 100644 --- a/services/api/test/unit/group_test.rb +++ b/services/api/test/unit/group_test.rb @@ -228,50 +228,6 @@ class GroupTest < ActiveSupport::TestCase assert User.readable_by(users(:admin)).where(uuid: u_bar.uuid).any? end - test "move projects to trash in SweepTrashedObjects" do - p = groups(:trashed_on_next_sweep) - assert_empty Group.where('uuid=? and is_trashed=true', p.uuid) - SweepTrashedObjects.sweep_now - assert_not_empty Group.where('uuid=? and is_trashed=true', p.uuid) - end - - test "delete projects and their contents in SweepTrashedObjects" do - g_foo = groups(:trashed_project) - g_bar = groups(:trashed_subproject) - g_baz = groups(:trashed_subproject3) - col = collections(:collection_in_trashed_subproject) - job = jobs(:job_in_trashed_project) - cr = container_requests(:cr_in_trashed_project) - # Save how many objects were before the sweep - user_nr_was = User.all.length - coll_nr_was = Collection.all.length - group_nr_was = Group.where('group_class<>?', 'project').length - project_nr_was = Group.where(group_class: 'project').length - cr_nr_was = ContainerRequest.all.length - job_nr_was = Job.all.length - assert_not_empty Group.where(uuid: g_foo.uuid) - assert_not_empty Group.where(uuid: g_bar.uuid) - assert_not_empty Group.where(uuid: g_baz.uuid) - assert_not_empty Collection.where(uuid: col.uuid) - assert_not_empty Job.where(uuid: job.uuid) - assert_not_empty ContainerRequest.where(uuid: cr.uuid) - SweepTrashedObjects.sweep_now - assert_empty Group.where(uuid: g_foo.uuid) - assert_empty Group.where(uuid: g_bar.uuid) - assert_empty Group.where(uuid: g_baz.uuid) - assert_empty Collection.where(uuid: col.uuid) - assert_empty Job.where(uuid: job.uuid) - assert_empty ContainerRequest.where(uuid: cr.uuid) - # No unwanted deletions should have happened - assert_equal user_nr_was, User.all.length - assert_equal coll_nr_was-2, # collection_in_trashed_subproject - Collection.all.length # & deleted_on_next_sweep collections - assert_equal group_nr_was, Group.where('group_class<>?', 'project').length - assert_equal project_nr_was-3, Group.where(group_class: 'project').length - assert_equal cr_nr_was-1, ContainerRequest.all.length - assert_equal job_nr_was-1, Job.all.length - end - test "project names must be displayable in a filesystem" do set_user_from_auth :active ["", "{SOLIDUS}"].each do |subst|