Files

205 lines
6.8 KiB
Go

package migrate
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"google.golang.org/grpc/metadata"
)
func TestStorageSwapper_StopReadingUnifiedStorage(t *testing.T) {
tests := []struct {
name string
setupMocks func(*MockBulkStoreClient, *dualwrite.MockService)
expectedError string
}{
{
name: "should update status for all resources",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
for _, gr := range resources.SupportedProvisioningResources {
status := dualwrite.StorageStatus{
ReadUnified: true,
Migrated: 123,
Migrating: 456,
}
dual.On("Status", mock.Anything, gr.GroupResource()).Return(status, nil)
dual.On("Update", mock.Anything, mock.MatchedBy(func(status dualwrite.StorageStatus) bool {
return !status.ReadUnified && status.Migrated == 0 && status.Migrating == 0
})).Return(dualwrite.StorageStatus{}, nil)
}
},
},
{
name: "should fail if status update fails",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
dual.On("Update", mock.Anything, mock.Anything).Return(dualwrite.StorageStatus{}, errors.New("update failed"))
},
expectedError: "update failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bulk := NewMockBulkStoreClient(t)
dual := dualwrite.NewMockService(t)
if tt.setupMocks != nil {
tt.setupMocks(bulk, dual)
}
swapper := NewStorageSwapper(bulk, dual)
err := swapper.StopReadingUnifiedStorage(context.Background())
if tt.expectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedError)
} else {
require.NoError(t, err)
}
})
}
}
func TestStorageSwapper_WipeUnifiedAndSetMigratedFlag(t *testing.T) {
tests := []struct {
name string
setupMocks func(*MockBulkStoreClient, *dualwrite.MockService)
expectedError string
}{
{
name: "should fail if already using unified storage",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
status := dualwrite.StorageStatus{
ReadUnified: true,
}
dual.On("Status", mock.Anything, gr.GroupResource()).Return(status, nil)
},
expectedError: "unexpected state - already using unified storage",
},
{
name: "should fail if migration is in progress",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
status := dualwrite.StorageStatus{
ReadUnified: false,
Migrating: time.Now().UnixMilli(),
}
dual.On("Status", mock.Anything, gr.GroupResource()).Return(status, nil)
},
expectedError: "another migration job is running",
},
{
name: "should fail if bulk process fails",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
bulk.On("BulkProcess", mock.Anything, mock.Anything).Return(nil, errors.New("bulk process failed"))
},
expectedError: "error clearing unified",
},
{
name: "should fail if status update fails after bulk process",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
mockStream := NewBulkStore_BulkProcessClient(t)
mockStream.On("CloseAndRecv").Return(&resourcepb.BulkResponse{}, nil)
bulk.On("BulkProcess", mock.Anything, mock.Anything).Return(mockStream, nil)
dual.On("Update", mock.Anything, mock.MatchedBy(func(status dualwrite.StorageStatus) bool {
return status.ReadUnified && !status.WriteLegacy && status.Migrated > 0
})).Return(dualwrite.StorageStatus{}, errors.New("update failed"))
},
expectedError: "update failed",
},
{
name: "should fail if bulk process stream close fails",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
gr := resources.SupportedProvisioningResources[0]
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
mockStream := NewBulkStore_BulkProcessClient(t)
mockStream.On("CloseAndRecv").Return(nil, errors.New("stream close failed"))
bulk.On("BulkProcess", mock.Anything, mock.Anything).Return(mockStream, nil)
},
expectedError: "error clearing unified",
},
{
name: "should succeed with complete workflow",
setupMocks: func(bulk *MockBulkStoreClient, dual *dualwrite.MockService) {
for _, gr := range resources.SupportedProvisioningResources {
dual.On("Status", mock.Anything, gr.GroupResource()).Return(dualwrite.StorageStatus{}, nil)
mockStream := NewBulkStore_BulkProcessClient(t)
mockStream.On("CloseAndRecv").Return(&resourcepb.BulkResponse{}, nil)
bulk.On("BulkProcess", mock.MatchedBy(func(ctx context.Context) bool {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
return false
}
//nolint:errcheck // hits the err != nil gotcha
settings, _ := resource.NewBulkSettings(md)
if !settings.RebuildCollection {
return false
}
if len(settings.Collection) != 1 {
return false
}
if settings.Collection[0].Namespace != "test-namespace" {
return false
}
if settings.Collection[0].Group != gr.Group {
return false
}
if settings.Collection[0].Resource != gr.Resource {
return false
}
return true
}), mock.Anything).Return(mockStream, nil)
dual.On("Update", mock.Anything, mock.MatchedBy(func(status dualwrite.StorageStatus) bool {
return status.ReadUnified && !status.WriteLegacy && status.Migrated > 0
})).Return(dualwrite.StorageStatus{}, nil)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bulk := NewMockBulkStoreClient(t)
dual := dualwrite.NewMockService(t)
if tt.setupMocks != nil {
tt.setupMocks(bulk, dual)
}
swapper := NewStorageSwapper(bulk, dual)
err := swapper.WipeUnifiedAndSetMigratedFlag(context.Background(), "test-namespace")
if tt.expectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedError)
} else {
require.NoError(t, err)
}
})
}
}