diff --git a/.rubocop_todo/layout/line_break_after_final_mixin.yml b/.rubocop_todo/layout/line_break_after_final_mixin.yml index 2bc4acd4fef..886e5802730 100644 --- a/.rubocop_todo/layout/line_break_after_final_mixin.yml +++ b/.rubocop_todo/layout/line_break_after_final_mixin.yml @@ -21,7 +21,6 @@ Layout/LineBreakAfterFinalMixin: - 'app/workers/purge_dependency_proxy_cache_worker.rb' - 'app/workers/remove_unreferenced_lfs_objects_worker.rb' - 'app/workers/repository_archive_cache_worker.rb' - - 'app/workers/stuck_ci_jobs_worker.rb' - 'app/workers/stuck_export_jobs_worker.rb' - 'app/workers/user_status_cleanup/batch_worker.rb' - 'app/workers/users/create_statistics_worker.rb' diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 2804d47be87..b5c72c489db 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -270,6 +270,25 @@ module Projects end delete_commit_statuses + destroy_orphaned_ci_job_artifacts! + end + + # This method will delete all orphaned CI Job artifacts for the project, which are job artifacts + # whose jobs do not exist anymore. The reason these artifacts might still exist is because of + # https://gitlab.com/gitlab-org/gitlab/-/issues/508672. + # TODO: remove this method after we have a working & valid FK. + def destroy_orphaned_ci_job_artifacts! + orphaned_job_artifacts = ::Ci::JobArtifact.for_project(project) + return if orphaned_job_artifacts.none? + + service = orphaned_job_artifacts.begin_fast_destroy + orphaned_job_artifacts.finalize_fast_destroy(service) + + Gitlab::AppLogger.info( + class: self.class.name, + project_id: project.id, + message: 'Orphaned CI job artifacts deleted' + ) end def delete_commit_statuses diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index ffdc9db1aca..8139107a081 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -8,6 +8,7 @@ class StuckCiJobsWorker # rubocop:disable Scalability/IdempotentWorker # This is an instance-wide cleanup query, so there's no meaningful # scope to consider this in the context of. include CronjobQueue + # rubocop:enable Scalability/CronWorkerContext data_consistency :always diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 8714c57e567..629056a65db 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -978,6 +978,62 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi end end + describe '#destroy_orphaned_ci_job_artifacts!' do + let(:service) { described_class.new(project, user) } + + context 'when there are no orphaned job artifacts' do + let(:no_job_artifacts) { Ci::JobArtifact.none } + + before do + allow(Ci::JobArtifact).to receive(:for_project).with(project).and_return(no_job_artifacts) + end + + it 'returns early without performing any destroy operations' do + expect(no_job_artifacts).not_to receive(:begin_fast_destroy) + expect(no_job_artifacts).not_to receive(:finalize_fast_destroy) + + service.send(:destroy_orphaned_ci_job_artifacts!) + end + end + + context 'when there are orphaned job artifacts' do + let(:job) { create(:ci_build, project: project) } + let(:orphaned_job_artifact) { create(:ci_job_artifact, job: job, project: project) } + + before do + orphaned_job_artifact.connection.transaction do + orphaned_job_artifact.connection.execute(<<~SQL) + ALTER TABLE p_ci_job_artifacts DISABLE TRIGGER ALL; + SQL + + orphaned_job_artifact.update_column(:job_id, non_existing_record_id) + + orphaned_job_artifact.connection.execute(<<~SQL) + ALTER TABLE p_ci_job_artifacts ENABLE TRIGGER ALL; + SQL + end + end + + it 'destroys orphaned artifacts' do + expect { destroy_project(project, user) }.to change { Ci::JobArtifact.count }.by(-1) + + expect(Ci::JobArtifact.exists?(orphaned_job_artifact.id)).to be_falsey + end + + it 'logs that the artifacts have been destroyed' do + allow(Gitlab::AppLogger).to receive(:info) # Logged during artifact deletion + + expect(Gitlab::AppLogger).to receive(:info).with( + class: described_class.name, + project_id: project.id, + message: 'Orphaned CI job artifacts deleted' + ) + + service.send(:destroy_orphaned_ci_job_artifacts!) + end + end + end + def destroy_project(project, user, params = {}) described_class.new(project, user, params).public_send(async ? :async_execute : :execute) end