Files
velero/pkg/cbtservice/csi_service_impl_test.go
Xun Jiang 0bc06323bf Support change-id and volume-id in backup workflow.
* Add change-id and volume-id retrieve logic for both vks and vanilla k8s environment.
* Add change-id and volume-id support code in exposer.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-07-01 15:19:52 +08:00

259 lines
7.8 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 cbtservice
import (
"context"
"errors"
"testing"
"github.com/kubernetes-csi/external-snapshot-metadata/pkg/api"
"github.com/kubernetes-csi/external-snapshot-metadata/pkg/iterator"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/client-go/rest"
)
func TestEmitterImplSnapshotMetadataIteratorRecord(t *testing.T) {
t.Run("records block metadata as ranges", func(t *testing.T) {
var got [][]Range
emitter := &emitterImpl{
logger: logrus.New(),
recordCallBack: func(ranges []Range) error {
got = append(got, ranges)
return nil
},
}
err := emitter.SnapshotMetadataIteratorRecord(7, iterator.IteratorMetadata{
BlockMetadata: []*api.BlockMetadata{
{ByteOffset: 10, SizeBytes: 20},
{ByteOffset: 40, SizeBytes: 50},
},
})
require.NoError(t, err)
assert.Equal(t, [][]Range{
{{Offset: 10, Length: 20}},
{{Offset: 40, Length: 50}},
}, got)
})
t.Run("rejects negative block metadata", func(t *testing.T) {
callbackCalled := false
emitter := &emitterImpl{
logger: logrus.New(),
recordCallBack: func(ranges []Range) error {
callbackCalled = true
return nil
},
}
err := emitter.SnapshotMetadataIteratorRecord(3, iterator.IteratorMetadata{
BlockMetadata: []*api.BlockMetadata{{ByteOffset: -1, SizeBytes: 20}},
})
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid CBT metadata")
assert.False(t, callbackCalled)
})
t.Run("returns callback error", func(t *testing.T) {
wantErr := errors.New("record failed")
emitter := &emitterImpl{
logger: logrus.New(),
recordCallBack: func(ranges []Range) error {
return wantErr
},
}
err := emitter.SnapshotMetadataIteratorRecord(5, iterator.IteratorMetadata{
BlockMetadata: []*api.BlockMetadata{{ByteOffset: 1, SizeBytes: 2}},
})
require.ErrorIs(t, err, wantErr)
})
}
func TestEmitterImplSnapshotMetadataIteratorDone(t *testing.T) {
emitter := &emitterImpl{logger: logrus.New()}
assert.NoError(t, emitter.SnapshotMetadataIteratorDone(4))
}
func TestNewServiceInitializesFields(t *testing.T) {
logger := logrus.New()
cfg := &rest.Config{Host: "https://example.com"}
svc := NewService(logger, "velero-ns", "velero-sa", cfg)
impl, ok := svc.(*ServiceImpl)
require.True(t, ok)
assert.Same(t, logger, impl.logger)
assert.Equal(t, "velero-ns", impl.vsNamespace)
assert.Equal(t, "velero-sa", impl.SAName)
assert.Same(t, cfg, impl.clientConfig)
}
func TestNewServiceForwardsSANameToArgs(t *testing.T) {
originalBuildClients := buildClients
originalGetSnapshotMetadata := getSnapshotMetadata
t.Cleanup(func() {
buildClients = originalBuildClients
getSnapshotMetadata = originalGetSnapshotMetadata
})
buildClients = func(config *rest.Config) (iterator.Clients, error) {
return iterator.Clients{}, nil
}
var capturedArgs iterator.Args
getSnapshotMetadata = func(ctx context.Context, args iterator.Args) error {
capturedArgs = args
return nil
}
svc := NewService(logrus.New(), "velero-ns", "velero-sa", &rest.Config{Host: "https://example.com"})
err := svc.GetAllocatedBlocks(t.Context(), "snap-1", func([]Range) error { return nil })
require.NoError(t, err)
assert.Equal(t, "velero-ns", capturedArgs.Namespace)
assert.Equal(t, "velero-ns", capturedArgs.SANamespace)
assert.Equal(t, "velero-sa", capturedArgs.SAName)
}
func TestServiceImplGetAllocatedBlocks(t *testing.T) {
originalBuildClients := buildClients
originalGetSnapshotMetadata := getSnapshotMetadata
t.Cleanup(func() {
buildClients = originalBuildClients
getSnapshotMetadata = originalGetSnapshotMetadata
})
t.Run("build clients error is returned", func(t *testing.T) {
wantErr := errors.New("build clients failed")
getSnapshotCalled := false
buildClients = func(config *rest.Config) (iterator.Clients, error) {
return iterator.Clients{}, wantErr
}
getSnapshotMetadata = func(ctx context.Context, args iterator.Args) error {
getSnapshotCalled = true
return nil
}
service := &ServiceImpl{logger: logrus.New(), clientConfig: &rest.Config{Host: "https://example.com"}}
err := service.GetAllocatedBlocks(t.Context(), "snap-1", func([]Range) error { return nil })
require.ErrorIs(t, err, wantErr)
assert.False(t, getSnapshotCalled)
})
t.Run("forwards args and callback", func(t *testing.T) {
cfg := &rest.Config{Host: "https://example.com"}
fakeClients := iterator.Clients{}
var capturedArgs iterator.Args
var recorded [][]Range
buildClients = func(config *rest.Config) (iterator.Clients, error) {
assert.Same(t, cfg, config)
return fakeClients, nil
}
getSnapshotMetadata = func(ctx context.Context, args iterator.Args) error {
capturedArgs = args
return args.Emitter.SnapshotMetadataIteratorRecord(1, iterator.IteratorMetadata{
BlockMetadata: []*api.BlockMetadata{{ByteOffset: 11, SizeBytes: 22}},
})
}
service := &ServiceImpl{
logger: logrus.New(),
vsNamespace: "velero-ns",
SAName: "sa-name",
clientConfig: cfg,
}
err := service.GetAllocatedBlocks(t.Context(), "snap-1", func(ranges []Range) error {
recorded = append(recorded, ranges)
return nil
})
require.NoError(t, err)
assert.Equal(t, fakeClients, capturedArgs.Clients)
assert.Equal(t, "snap-1", capturedArgs.SnapshotName)
assert.Empty(t, capturedArgs.PrevSnapshotName)
assert.Equal(t, "velero-ns", capturedArgs.Namespace)
assert.Equal(t, "velero-ns", capturedArgs.SANamespace)
assert.Equal(t, "sa-name", capturedArgs.SAName)
assert.Equal(t, iterator.DefaultTokenExpirySeconds, capturedArgs.TokenExpirySecs)
assert.Zero(t, capturedArgs.MaxResults)
assert.Equal(t, [][]Range{{{Offset: 11, Length: 22}}}, recorded)
})
}
func TestServiceImplGetChangedBlocks(t *testing.T) {
originalBuildClients := buildClients
originalGetSnapshotMetadata := getSnapshotMetadata
t.Cleanup(func() {
buildClients = originalBuildClients
getSnapshotMetadata = originalGetSnapshotMetadata
})
buildClients = func(config *rest.Config) (iterator.Clients, error) {
return iterator.Clients{}, nil
}
t.Run("sets previous snapshot name", func(t *testing.T) {
var capturedArgs iterator.Args
getSnapshotMetadata = func(ctx context.Context, args iterator.Args) error {
capturedArgs = args
return nil
}
service := &ServiceImpl{
logger: logrus.New(),
vsNamespace: "velero-ns",
SAName: "sa-name",
clientConfig: &rest.Config{Host: "https://example.com"},
}
err := service.GetChangedBlocks(t.Context(), "snap-2", "snap-1", func([]Range) error { return nil })
require.NoError(t, err)
assert.Equal(t, "snap-2", capturedArgs.SnapshotName)
assert.Equal(t, "snap-1", capturedArgs.PrevSnapshotID)
assert.Equal(t, "velero-ns", capturedArgs.Namespace)
assert.Equal(t, iterator.DefaultTokenExpirySeconds, capturedArgs.TokenExpirySecs)
assert.Zero(t, capturedArgs.MaxResults)
})
t.Run("returns snapshot metadata error", func(t *testing.T) {
wantErr := errors.New("metadata failed")
getSnapshotMetadata = func(ctx context.Context, args iterator.Args) error {
return wantErr
}
service := &ServiceImpl{
logger: logrus.New(),
clientConfig: &rest.Config{Host: "https://example.com"},
}
err := service.GetChangedBlocks(t.Context(), "snap-2", "snap-1", func([]Range) error { return nil })
require.ErrorIs(t, err, wantErr)
})
}