From e8b3dc6562921d61e92ba449a3532e2a6d3f6c21 Mon Sep 17 00:00:00 2001 From: Sven Krieger <37476281+svkrieger@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:10:20 +0200 Subject: [PATCH] Prevent PackageUpload from downgrading a ready package to failed If a PackageBits job is retried after the worker was restarted mid-job, the retry may fail (e.g. upload file already cleaned up) and call fail_upload!, which would downgrade a READY package back to FAILED. Guard against this by checking the current state inside the DB transaction after acquiring the row lock. --- app/models/runtime/package_model.rb | 3 ++ .../unit/models/runtime/package_model_spec.rb | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/app/models/runtime/package_model.rb b/app/models/runtime/package_model.rb index f8749d9a066..14bd6824914 100644 --- a/app/models/runtime/package_model.rb +++ b/app/models/runtime/package_model.rb @@ -97,6 +97,9 @@ def succeed_upload!(checksums) def fail_upload!(err_msg) db.transaction do lock! + + return if ready? + self.state = VCAP::CloudController::PackageModel::FAILED_STATE self.error = err_msg save diff --git a/spec/unit/models/runtime/package_model_spec.rb b/spec/unit/models/runtime/package_model_spec.rb index b609f56fdfc..878b67ddbbf 100644 --- a/spec/unit/models/runtime/package_model_spec.rb +++ b/spec/unit/models/runtime/package_model_spec.rb @@ -79,6 +79,36 @@ module VCAP::CloudController end end + describe '#fail_upload!' do + context 'when the package is READY' do + let!(:package) { create(:package_model, state: PackageModel::READY_STATE) } + + it 'does not downgrade the state' do + package.fail_upload!('something went wrong') + expect(package.reload.state).to eq(PackageModel::READY_STATE) + end + + it 'does not set an error' do + package.fail_upload!('something went wrong') + expect(package.reload.error).to be_nil + end + end + + context 'when the package is PENDING' do + let!(:package) { create(:package_model, state: PackageModel::PENDING_STATE) } + + it 'marks the package as FAILED' do + package.fail_upload!('something went wrong') + expect(package.reload.state).to eq(PackageModel::FAILED_STATE) + end + + it 'sets the error' do + package.fail_upload!('something went wrong') + expect(package.reload.error).to eq('something went wrong') + end + end + end + describe 'metadata' do let(:package) { create(:package_model) } let(:annotation) { create(:package_annotation_model, package: package, key_name: 'test1', value: 'bommel') }