mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-28 03:46:14 +00:00
Run the E2E test on kind / setup-test-matrix (push) Failing after 4s
e2e-test-kind.yaml / extract (push) Failing after 11s
Run the E2E test on kind / get-go-version (push) Failing after 12s
Run the E2E test on kind / build (push) Skipped
Run the E2E test on kind / run-e2e-test (push) Skipped
push.yml / extract (push) Failing after 7s
Main CI / get-go-version (push) Failing after 8s
Main CI / Build (push) Skipped
* Report a measured zero incremental instead of erasing it A CBT incremental with an exactly zero delta -- nothing changed since the parent -- was reported identically to a backup that moved the whole device. `velero backup describe --details` printed only "Moved data Size (bytes): 3221225472" with no incremental line, and status.incrementalBytes was absent, for a run that transferred nothing. The best possible CBT outcome displayed as the worst, and was indistinguishable from a genuine full, a whole-device fallback, or a backup predating incremental accounting. The zero was being erased twice. Besides the API status fields, datapath.BackupResult also carried omitempty, and that struct crosses a JSON boundary from the data mover pod to the controller (see micro_service_watcher.go), so the value was destroyed before the controller could persist it. Every uploader always reports a figure there, so 0 internally always means "transferred nothing" -- dropping omitempty is sufficient and correct for that hop. The API fields move to *int64 rather than just dropping omitempty. The field shipped in v1.18.0-v1.18.2, so backups exist whose stored volume info has no incrementalSize at all; with a plain int64 those unmarshal to 0 and would render "Incremental data Size (bytes): 0", a false claim of a perfect incremental on a run that never measured one. nil means not measured, a pointer to 0 means measured zero. Both fields already carry +optional, so the generated CRD schema is unchanged and no regeneration is required. Display gates relax from > 0 to != nil in all three places, including volumesByPod.Add, whose signature takes *int64 now; the restore describer passes nil, which is correct since restores measure no incremental. Verified live: the same zero-delta scenario that reported <none> now reports 0 and renders "Incremental data Size (bytes): 0", while an older backup described with the new client still correctly prints no incremental line at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> (cherry picked from commit 6c7aa9d588f6d5eab134d4ce19c92b838f45557c) Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> * gofmt: fix import ordering in backup_test.go Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> * Regenerate CRDs for IncrementalBytes pointer type make update-crd was missed in the original commit. Regenerated with the pinned controller-gen v0.16.5 to avoid unrelated version-annotation churn across other CRDs. Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> * Add changelog for #10309 Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> * Address review: make IncrementalBytes a pointer to preserve backward compat Per Lyndon-Li's review on #10309: dropping omitempty on the plain int64 field breaks compatibility with a data mover from release-1.17 or earlier that predates IncrementalBytes and never writes the key -- the new controller would unmarshal a zero value ("nothing transferred") instead of recognizing the field is simply absent ("not measured"). Switch to *int64 with omitempty restored: - an old mover's omitted key unmarshals to nil ("not measured") - a current mover's genuine zero still serializes the key, unmarshaling to a non-nil pointer to 0 ("measured zero") - nonzero values work exactly as before - an old controller can still unmarshal a numeric value from a new mover pkg/controller/data_upload_controller.go and pod_volume_backup_controller.go assign the wire-struct field directly to their already-*int64,omitempty CRD status field instead of re-wrapping it with ptr.To, since both are now the same pointer type. Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> * Fix CI: update marshal-fail test assertions for IncrementalBytes pointer Both backup_micro_service_test.go files hardcoded the %v-formatted zero-value BackupResult struct in an error-message assertion. Now that IncrementalBytes is *int64, its zero value prints as <nil> instead of 0. Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> --------- Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
214 lines
6.0 KiB
Go
214 lines
6.0 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 datapath
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/utils/ptr"
|
|
|
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
|
"github.com/vmware-tanzu/velero/pkg/uploader/provider"
|
|
providerMock "github.com/vmware-tanzu/velero/pkg/uploader/provider/mocks"
|
|
)
|
|
|
|
func TestAsyncBackup(t *testing.T) {
|
|
var asyncErr error
|
|
var asyncResult Result
|
|
finish := make(chan struct{})
|
|
var failErr = errors.New("fake-fail-error")
|
|
tests := []struct {
|
|
name string
|
|
uploaderProv provider.Provider
|
|
callbacks Callbacks
|
|
err error
|
|
result Result
|
|
path string
|
|
}{
|
|
{
|
|
name: "async backup fail",
|
|
callbacks: Callbacks{
|
|
OnCompleted: nil,
|
|
OnCancelled: nil,
|
|
OnFailed: func(ctx context.Context, namespace string, job string, err error) {
|
|
asyncErr = failErr
|
|
asyncResult = Result{}
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
err: failErr,
|
|
},
|
|
{
|
|
name: "async backup cancel",
|
|
callbacks: Callbacks{
|
|
OnCompleted: nil,
|
|
OnFailed: nil,
|
|
OnCancelled: func(ctx context.Context, namespace string, job string) {
|
|
asyncErr = provider.ErrorCanceled
|
|
asyncResult = Result{}
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
err: provider.ErrorCanceled,
|
|
},
|
|
{
|
|
name: "async backup complete",
|
|
callbacks: Callbacks{
|
|
OnFailed: nil,
|
|
OnCancelled: nil,
|
|
OnCompleted: func(ctx context.Context, namespace string, job string, result Result) {
|
|
asyncResult = result
|
|
asyncErr = nil
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
result: Result{
|
|
Backup: BackupResult{
|
|
SnapshotID: "fake-snapshot",
|
|
EmptySnapshot: false,
|
|
Source: AccessPoint{ByPath: "fake-path"},
|
|
TotalBytes: 1000,
|
|
IncrementalBytes: ptr.To(int64(0)),
|
|
},
|
|
},
|
|
path: "fake-path",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
dp := newGeneralDataPath("job-1", "test", nil, "velero", Callbacks{}, velerotest.NewLogger()).(*generalDataPath)
|
|
mockProvider := providerMock.NewProvider(t)
|
|
var incrementalBytes int64
|
|
if test.result.Backup.IncrementalBytes != nil {
|
|
incrementalBytes = *test.result.Backup.IncrementalBytes
|
|
}
|
|
mockProvider.On("RunBackup", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(test.result.Backup.SnapshotID, test.result.Backup.EmptySnapshot, test.result.Backup.TotalBytes, incrementalBytes, test.err)
|
|
mockProvider.On("Close", mock.Anything).Return(nil)
|
|
dp.uploaderProv = mockProvider
|
|
dp.initialized = true
|
|
dp.callbacks = test.callbacks
|
|
|
|
err := dp.StartBackup(AccessPoint{ByPath: test.path}, map[string]string{}, &BackupStartParam{})
|
|
require.NoError(t, err)
|
|
|
|
<-finish
|
|
|
|
// Ensure the goroutine finishes so deferred fs.close executes, satisfying mock expectations.
|
|
dp.wgDataPath.Wait()
|
|
|
|
assert.Equal(t, test.err, asyncErr)
|
|
assert.Equal(t, test.result, asyncResult)
|
|
})
|
|
}
|
|
|
|
close(finish)
|
|
}
|
|
|
|
func TestAsyncRestore(t *testing.T) {
|
|
var asyncErr error
|
|
var asyncResult Result
|
|
finish := make(chan struct{})
|
|
var failErr = errors.New("fake-fail-error")
|
|
tests := []struct {
|
|
name string
|
|
uploaderProv provider.Provider
|
|
callbacks Callbacks
|
|
err error
|
|
result Result
|
|
path string
|
|
snapshot string
|
|
}{
|
|
{
|
|
name: "async restore fail",
|
|
callbacks: Callbacks{
|
|
OnCompleted: nil,
|
|
OnCancelled: nil,
|
|
OnFailed: func(ctx context.Context, namespace string, job string, err error) {
|
|
asyncErr = failErr
|
|
asyncResult = Result{}
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
err: failErr,
|
|
},
|
|
{
|
|
name: "async restore cancel",
|
|
callbacks: Callbacks{
|
|
OnCompleted: nil,
|
|
OnFailed: nil,
|
|
OnCancelled: func(ctx context.Context, namespace string, job string) {
|
|
asyncErr = provider.ErrorCanceled
|
|
asyncResult = Result{}
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
err: provider.ErrorCanceled,
|
|
},
|
|
{
|
|
name: "async restore complete",
|
|
callbacks: Callbacks{
|
|
OnFailed: nil,
|
|
OnCancelled: nil,
|
|
OnCompleted: func(ctx context.Context, namespace string, job string, result Result) {
|
|
asyncResult = result
|
|
asyncErr = nil
|
|
finish <- struct{}{}
|
|
},
|
|
},
|
|
result: Result{
|
|
Restore: RestoreResult{
|
|
Target: AccessPoint{ByPath: "fake-path"},
|
|
TotalBytes: 1000,
|
|
},
|
|
},
|
|
path: "fake-path",
|
|
snapshot: "fake-snapshot",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
dp := newGeneralDataPath("job-1", "test", nil, "velero", Callbacks{}, velerotest.NewLogger()).(*generalDataPath)
|
|
mockProvider := providerMock.NewProvider(t)
|
|
mockProvider.On("RunRestore", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(test.result.Restore.TotalBytes, test.err)
|
|
mockProvider.On("Close", mock.Anything).Return(nil)
|
|
dp.uploaderProv = mockProvider
|
|
dp.initialized = true
|
|
dp.callbacks = test.callbacks
|
|
|
|
err := dp.StartRestore(test.snapshot, AccessPoint{ByPath: test.path}, map[string]string{}, &RestoreStartParam{})
|
|
require.NoError(t, err)
|
|
|
|
<-finish
|
|
|
|
// Ensure the goroutine finishes so deferred fs.close executes, satisfying mock expectations.
|
|
dp.wgDataPath.Wait()
|
|
|
|
assert.Equal(t, asyncErr, test.err)
|
|
assert.Equal(t, asyncResult, test.result)
|
|
})
|
|
}
|
|
|
|
close(finish)
|
|
}
|