mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-28 10:45:44 +00:00
Motivation: Forget and BatchForget in pkg/repository/manager/manager.go accept a caller-provided context.Context but ignore it, hardcoding context.Background() when calling into the repository provider. This means cancellation and timeouts set by callers (e.g. the backup deletion controller) are silently dropped during repository connection and snapshot deletion. Additionally, BatchForget returned a wrapped nil instead of the real connection error when prd.BoostRepoConnect failed, because it referenced an unrelated, already-nil err variable instead of connectErr. Approach: Pass the caller's ctx through to prd.BoostRepoConnect, prd.Forget, and prd.BatchForget in both Forget and BatchForget, instead of substituting context.Background(). Fix BatchForget's connection-failure branch to wrap and return connectErr instead of the stale err. Other methods on manager (InitRepo, ConnectToRepo, PrepareRepo, PruneRepo, UnlockRepo) don't accept a ctx parameter at all, so they are unaffected and out of scope for this change. Validation: - go build ./pkg/repository/... and go build ./... pass. - go vet ./pkg/repository/... is clean. - go test ./pkg/repository/... passes, including three new tests added to pkg/repository/manager/manager_test.go. - golangci-lint run ./pkg/repository/... is clean. - Confirmed the new tests reproduce both bugs: temporarily reverting only manager.go and re-running go test ./pkg/repository/manager/... made all three new tests fail (missing propagated context value and cancellation, and a nil error returned where the real connect error was expected); re-applying the fix makes them pass. This is a silent behavior bug (broken context propagation and a swallowed error), not a crash. Report: https://github.com/velero-io/velero/issues/10551 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5 (via Claude Code)
176 lines
5.1 KiB
Go
176 lines
5.1 KiB
Go
/*
|
|
Copyright the Velero contributors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
|
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
"github.com/vmware-tanzu/velero/pkg/repository"
|
|
"github.com/vmware-tanzu/velero/pkg/repository/provider"
|
|
)
|
|
|
|
// fakeProvider is a minimal provider.Provider implementation used to
|
|
// observe the context passed by the manager into the provider calls.
|
|
type fakeProvider struct {
|
|
provider.Provider
|
|
|
|
gotConnectCtx context.Context
|
|
gotForgetCtx context.Context
|
|
|
|
connectErr error
|
|
forgetErr error
|
|
forgetErrs []error
|
|
}
|
|
|
|
func (f *fakeProvider) BoostRepoConnect(ctx context.Context, _ provider.RepoParam) error {
|
|
f.gotConnectCtx = ctx
|
|
return f.connectErr
|
|
}
|
|
|
|
func (f *fakeProvider) Forget(ctx context.Context, _ string, _ provider.RepoParam) error {
|
|
f.gotForgetCtx = ctx
|
|
return f.forgetErr
|
|
}
|
|
|
|
func (f *fakeProvider) BatchForget(ctx context.Context, _ []string, _ provider.RepoParam) []error {
|
|
f.gotForgetCtx = ctx
|
|
return f.forgetErrs
|
|
}
|
|
|
|
func newTestManager(t *testing.T, prd provider.Provider) *manager {
|
|
t.Helper()
|
|
|
|
scheme := runtime.NewScheme()
|
|
require.NoError(t, velerov1.AddToScheme(scheme))
|
|
|
|
bsl := &velerov1.BackupStorageLocation{}
|
|
bsl.Namespace = "velero"
|
|
bsl.Name = "fake-bsl"
|
|
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(bsl).Build()
|
|
|
|
mgr := NewManager("velero", fakeClient, repository.NewRepoLocker(), nil, nil, nil).(*manager)
|
|
mgr.providers[velerov1.BackupRepositoryTypeKopia] = prd
|
|
return mgr
|
|
}
|
|
|
|
func newTestRepo() *velerov1.BackupRepository {
|
|
repo := &velerov1.BackupRepository{}
|
|
repo.Spec.RepositoryType = velerov1.BackupRepositoryTypeKopia
|
|
repo.Spec.BackupStorageLocation = "fake-bsl"
|
|
return repo
|
|
}
|
|
|
|
func TestGetRepositoryProvider(t *testing.T) {
|
|
var fakeClient kbclient.Client
|
|
mgr := NewManager("", fakeClient, nil, nil, nil, nil).(*manager)
|
|
repo := &velerov1.BackupRepository{}
|
|
|
|
// empty repository type
|
|
_, err := mgr.getRepositoryProvider(repo)
|
|
require.Error(t, err)
|
|
|
|
// invalid repository type
|
|
repo.Spec.RepositoryType = "restic"
|
|
_, err = mgr.getRepositoryProvider(repo)
|
|
require.Error(t, err)
|
|
|
|
// invalid repository type
|
|
repo.Spec.RepositoryType = "unknown"
|
|
_, err = mgr.getRepositoryProvider(repo)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestGetRepositoryConfigProvider(t *testing.T) {
|
|
mgr := NewConfigManager(nil).(*configManager)
|
|
|
|
// empty repository type
|
|
_, err := mgr.getRepositoryProvider("")
|
|
require.Error(t, err)
|
|
|
|
// valid repository type
|
|
provider, err := mgr.getRepositoryProvider(velerov1.BackupRepositoryTypeKopia)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
|
|
// invalid repository type
|
|
_, err = mgr.getRepositoryProvider("restic")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestForgetPropagatesCallerContext(t *testing.T) {
|
|
prd := &fakeProvider{}
|
|
mgr := newTestManager(t, prd)
|
|
|
|
type ctxKeyType string
|
|
key := ctxKeyType("test-key")
|
|
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), key, "test-value"))
|
|
defer cancel()
|
|
|
|
err := mgr.Forget(ctx, newTestRepo(), "snapshot-1")
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, prd.gotConnectCtx)
|
|
require.NotNil(t, prd.gotForgetCtx)
|
|
assert.Equal(t, "test-value", prd.gotConnectCtx.Value(key))
|
|
assert.Equal(t, "test-value", prd.gotForgetCtx.Value(key))
|
|
|
|
// canceling the caller's context must be observed by the provider calls,
|
|
// proving the manager no longer substitutes context.Background().
|
|
cancel()
|
|
require.Error(t, prd.gotConnectCtx.Err())
|
|
require.Error(t, prd.gotForgetCtx.Err())
|
|
}
|
|
|
|
func TestBatchForgetPropagatesCallerContext(t *testing.T) {
|
|
prd := &fakeProvider{forgetErrs: []error{}}
|
|
mgr := newTestManager(t, prd)
|
|
|
|
type ctxKeyType string
|
|
key := ctxKeyType("test-key")
|
|
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), key, "test-value"))
|
|
defer cancel()
|
|
|
|
errs := mgr.BatchForget(ctx, newTestRepo(), []string{"snapshot-1", "snapshot-2"})
|
|
require.Empty(t, errs)
|
|
|
|
require.NotNil(t, prd.gotConnectCtx)
|
|
require.NotNil(t, prd.gotForgetCtx)
|
|
assert.Equal(t, "test-value", prd.gotConnectCtx.Value(key))
|
|
assert.Equal(t, "test-value", prd.gotForgetCtx.Value(key))
|
|
}
|
|
|
|
func TestBatchForgetReturnsConnectError(t *testing.T) {
|
|
connectErr := errors.New("boom: connection refused")
|
|
prd := &fakeProvider{connectErr: connectErr}
|
|
mgr := newTestManager(t, prd)
|
|
|
|
errs := mgr.BatchForget(context.Background(), newTestRepo(), []string{"snapshot-1"})
|
|
|
|
require.Len(t, errs, 1)
|
|
require.ErrorContains(t, errs[0], "boom: connection refused")
|
|
}
|