# SPDX-License-Identifier: AGPL-3.0
require 'arvados/keep'
-require 'sweep_trashed_collections'
+require 'sweep_trashed_objects'
require 'trashable'
class Collection < ArvadosModel
end
def self.where *args
- SweepTrashedCollections.sweep_if_stale
+ SweepTrashedObjects.sweep_if_stale
super
end
+++ /dev/null
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'current_api_client'
-
-module SweepTrashedCollections
- extend CurrentApiClient
-
- def self.sweep_now
- act_as_system_user do
- Collection.
- where('delete_at is not null and delete_at < statement_timestamp()').
- destroy_all
- Collection.
- where('is_trashed = false and trash_at < statement_timestamp()').
- update_all('is_trashed = true')
- end
- end
-
- def self.sweep_if_stale
- return if Rails.configuration.trash_sweep_interval <= 0
- exp = Rails.configuration.trash_sweep_interval.seconds
- need = false
- Rails.cache.fetch('SweepTrashedCollections', expires_in: exp) do
- need = true
- 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
- ActiveRecord::Base.connection.close
- end
- end
- end
- end
-end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'current_api_client'
+
+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
+ act_as_system_user do
+ # Sweep trashed collections
+ Collection.
+ where('delete_at is not null and delete_at < statement_timestamp()').
+ destroy_all
+ Collection.
+ where('is_trashed = false and trash_at < statement_timestamp()').
+ update_all('is_trashed = true')
+
+ # Sweep trashed projects and their contents
+ Group.
+ where({group_class: 'project'}).
+ where('delete_at is not null and delete_at < statement_timestamp()').each do |project|
+ delete_project_and_contents(project.uuid)
+ end
+ Group.
+ where({group_class: 'project'}).
+ where('is_trashed = false and trash_at < statement_timestamp()').
+ update_all('is_trashed = true')
+ end
+ end
+
+ def self.sweep_if_stale
+ return if Rails.configuration.trash_sweep_interval <= 0
+ exp = Rails.configuration.trash_sweep_interval.seconds
+ need = false
+ Rails.cache.fetch('SweepTrashedObjects', expires_in: exp) do
+ need = true
+ 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
+ ActiveRecord::Base.connection.close
+ end
+ end
+ end
+ end
+end
name: trashed project
group_class: project
trash_at: 2001-01-01T00:00:00Z
- delete_at: 2038-03-01T00:00:00Z
+ delete_at: 2008-03-01T00:00:00Z
is_trashed: true
modified_at: 2001-01-01T00:00:00Z
trash_at: 2001-01-01T00:00:00Z
delete_at: 2038-03-01T00:00:00Z
is_trashed: true
+ modified_at: 2001-01-01T00:00:00Z
+
+trashed_on_next_sweep:
+ uuid: zzzzz-j7d0g-soontobetrashed
+ owner_uuid: zzzzz-j7d0g-xurymjxw79nv3jz
+ name: soon to be trashed project
+ group_class: project
+ trash_at: 2001-01-01T00:00:00Z
+ delete_at: 2038-03-01T00:00:00Z
+ is_trashed: false
modified_at: 2001-01-01T00:00:00Z
\ No newline at end of file
state: Complete
script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
+job_in_trashed_project:
+ uuid: zzzzz-8i9sb-subprojectjob02
+ created_at: 2014-10-15 12:00:00
+ owner_uuid: zzzzz-j7d0g-trashedproject2
+ log: ~
+ repository: active/foo
+ script: hash
+ script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
+ state: Complete
+ script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
+
running_will_be_completed:
uuid: zzzzz-8i9sb-rshmckwoma9pjh8
owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
# SPDX-License-Identifier: AGPL-3.0
require 'test_helper'
-require 'sweep_trashed_collections'
+require 'sweep_trashed_objects'
class CollectionTest < ActiveSupport::TestCase
include DbCurrentTime
assert_includes(coll_uuids, collections(:docker_image).uuid)
end
- test "move to trash in SweepTrashedCollections" do
+ 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
name: c.name)
end
end
- SweepTrashedCollections.sweep_now
+ SweepTrashedObjects.sweep_now
c = Collection.where('uuid=? and is_trashed=true', c.uuid).first
assert c
act_as_user users(:active) do
end
end
- test "delete in SweepTrashedCollections" do
+ test "delete collections in SweepTrashedObjects" do
uuid = 'zzzzz-4zz18-3u1p5umicfpqszp' # deleted_on_next_sweep
assert_not_empty Collection.where(uuid: uuid)
- SweepTrashedCollections.sweep_now
+ SweepTrashedObjects.sweep_now
assert_empty Collection.where(uuid: uuid)
end
- test "delete referring links in SweepTrashedCollections" do
+ test "delete referring links in SweepTrashedObjects" do
uuid = collections(:trashed_on_next_sweep).uuid
act_as_system_user do
Link.create!(head_uuid: uuid,
Collection.where(uuid: uuid).
update_all(is_trashed: true, trash_at: past, delete_at: past)
assert_not_empty Collection.where(uuid: uuid)
- SweepTrashedCollections.sweep_now
+ SweepTrashedObjects.sweep_now
assert_empty Collection.where(uuid: uuid)
end
end
assert g_foo.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/)
end
- test "delete group hides contents" do
+ test "trash group hides contents" do
set_user_from_auth :active_trustedclient
g_foo = Group.create!(name: "foo")
assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
end
- test "delete group" do
+ test "trash group" do
set_user_from_auth :active_trustedclient
g_foo = Group.create!(name: "foo")
end
- test "delete subgroup" do
+ test "trash subgroup" do
set_user_from_auth :active_trustedclient
g_foo = Group.create!(name: "foo")
assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
end
- test "delete subsubgroup" do
+ test "trash subsubgroup" do
set_user_from_auth :active_trustedclient
g_foo = Group.create!(name: "foo")
end
- test "delete group propagates to subgroups" do
+ test "trash group propagates to subgroups" do
set_user_from_auth :active_trustedclient
g_foo = groups(:trashed_project)
assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
- # this one should still be deleted.
+ # this one should still be trashed.
assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
g_baz.update! is_trashed: false
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
end