Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package backup | |
import ( | |
"context" | |
"encoding/json" | |
"errors" | |
"fmt" | |
"strings" | |
"testing" | |
"time" | |
"github.com/sirupsen/logrus" | |
"github.com/sirupsen/logrus/hooks/test" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/mock" | |
"github.com/weaviate/weaviate/entities/backup" | |
"github.com/weaviate/weaviate/entities/models" | |
) | |
func TestSchedulerValidateCreateBackup(t *testing.T) { | |
t.Parallel() | |
var ( | |
cls = "C1" | |
backendName = "s3" | |
s = newFakeScheduler(nil).scheduler() | |
ctx = context.Background() | |
id = "123" | |
path = "root/123" | |
) | |
t.Run("ValidateEmptyID", func(t *testing.T) { | |
_, err := s.Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "", | |
Include: []string{cls}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("ValidateID", func(t *testing.T) { | |
_, err := s.Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "A*:", | |
Include: []string{cls}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("IncludeExclude", func(t *testing.T) { | |
_, err := s.Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "1234", | |
Include: []string{cls}, | |
Exclude: []string{cls}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("RequestIncludeHasDuplicate", func(t *testing.T) { | |
_, err := s.Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "1234", | |
Include: []string{"C2", "C2", "C1"}, | |
Exclude: []string{}, | |
}) | |
assert.NotNil(t, err) | |
assert.ErrorContains(t, err, "C2") | |
}) | |
t.Run("ResultingClassListIsEmpty", func(t *testing.T) { | |
// return one class and exclude it in the request | |
fs := newFakeScheduler(nil) | |
fs.selector.On("ListClasses", ctx).Return([]string{cls}) | |
_, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "1234", | |
Include: []string{}, | |
Exclude: []string{cls}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("ClassNotBackupable", func(t *testing.T) { | |
// return an error in case index doesn't exist or a shard has multiple nodes | |
fs := newFakeScheduler(nil) | |
fs.selector.On("ListClasses", ctx).Return([]string{cls}) | |
fs.selector.On("Backupable", ctx, []string{cls}).Return(ErrAny) | |
_, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: "1234", | |
Include: []string{}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("GetMetadataFails", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.selector.On("Backupable", ctx, []string{cls}).Return(nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(nil, errors.New("can not be read")) | |
fs.backend.On("GetObject", ctx, id, BackupFile).Return(nil, backup.ErrNotFound{}) | |
meta, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{cls}, | |
}) | |
assert.Nil(t, meta) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), fmt.Sprintf("check if backup %q exists", id)) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
t.Run("MetadataNotFound", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.selector.On("Backupable", ctx, []string{cls}).Return(nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
bytes := marshalMeta(backup.BackupDescriptor{ID: id}) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
meta, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{cls}, | |
}) | |
assert.Nil(t, meta) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), fmt.Sprintf("backup %q already exists", id)) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
} | |
func TestSchedulerBackupStatus(t *testing.T) { | |
t.Parallel() | |
var ( | |
backendName = "s3" | |
id = "1234" | |
ctx = context.Background() | |
starTime = time.Date(2022, 1, 1, 1, 0, 0, 0, time.UTC) | |
nodeHome = id + "/" + nodeName | |
path = "bucket/backups/" + nodeHome | |
want = &Status{ | |
Path: path, | |
StartedAt: starTime, | |
Status: backup.Transferring, | |
} | |
) | |
t.Run("ActiveState", func(t *testing.T) { | |
s := newFakeScheduler(nil).scheduler() | |
s.backupper.lastOp.reqStat = reqStat{ | |
Starttime: starTime, | |
ID: id, | |
Status: backup.Transferring, | |
Path: path, | |
} | |
st, err := s.BackupStatus(ctx, nil, backendName, id) | |
assert.Nil(t, err) | |
assert.Equal(t, want, st) | |
}) | |
t.Run("GetBackupProvider", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.backendErr = ErrAny | |
_, err := fs.scheduler().BackupStatus(ctx, nil, backendName, id) | |
assert.NotNil(t, err) | |
}) | |
t.Run("MetadataNotFound", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(nil, ErrAny) | |
fs.backend.On("GetObject", ctx, id, BackupFile).Return(nil, backup.ErrNotFound{}) | |
_, err := fs.scheduler().BackupStatus(ctx, nil, backendName, id) | |
assert.NotNil(t, err) | |
nerr := backup.ErrNotFound{} | |
if !errors.As(err, &nerr) { | |
t.Errorf("error want=%v got=%v", nerr, err) | |
} | |
}) | |
t.Run("ReadFromMetadata", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
completedAt := starTime.Add(time.Hour) | |
bytes := marshalCoordinatorMeta( | |
backup.DistributedBackupDescriptor{ | |
StartedAt: starTime, CompletedAt: completedAt, | |
Nodes: map[string]*backup.NodeDescriptor{"N1": {Classes: []string{"C1"}}}, | |
Status: backup.Success, | |
}) | |
want := want | |
want.CompletedAt = completedAt | |
want.Status = backup.Success | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
got, err := fs.scheduler().BackupStatus(ctx, nil, backendName, id) | |
assert.Nil(t, err) | |
assert.Equal(t, want, got) | |
}) | |
t.Run("ReadFromOldMetadata", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
completedAt := starTime.Add(time.Hour) | |
bytes := marshalMeta(backup.BackupDescriptor{StartedAt: starTime, CompletedAt: completedAt, Status: string(backup.Success)}) | |
want := want | |
want.CompletedAt = completedAt | |
want.Status = backup.Success | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(nil, ErrAny) | |
fs.backend.On("GetObject", ctx, id, BackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
got, err := fs.scheduler().BackupStatus(ctx, nil, backendName, id) | |
assert.Nil(t, err) | |
assert.Equal(t, want, got) | |
}) | |
} | |
func TestSchedulerRestorationStatus(t *testing.T) { | |
t.Parallel() | |
var ( | |
backendName = "s3" | |
id = "1234" | |
ctx = context.Background() | |
starTime = time.Date(2022, 1, 1, 1, 0, 0, 0, time.UTC) | |
nodeHome = id + "/" + nodeName | |
path = "bucket/backups/" + nodeHome | |
want = &Status{ | |
Path: path, | |
StartedAt: starTime, | |
Status: backup.Transferring, | |
} | |
) | |
t.Run("ActiveState", func(t *testing.T) { | |
s := newFakeScheduler(nil).scheduler() | |
s.restorer.lastOp.reqStat = reqStat{ | |
Starttime: starTime, | |
ID: id, | |
Status: backup.Transferring, | |
Path: path, | |
} | |
st, err := s.RestorationStatus(ctx, nil, backendName, id) | |
assert.Nil(t, err) | |
assert.Equal(t, want, st) | |
}) | |
t.Run("GetBackupProvider", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.backendErr = ErrAny | |
_, err := fs.scheduler().RestorationStatus(ctx, nil, backendName, id) | |
assert.NotNil(t, err) | |
}) | |
t.Run("MetadataNotFound", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.backend.On("GetObject", ctx, id, GlobalRestoreFile).Return(nil, ErrAny) | |
_, err := fs.scheduler().RestorationStatus(ctx, nil, backendName, id) | |
assert.NotNil(t, err) | |
nerr := backup.ErrNotFound{} | |
if !errors.As(err, &nerr) { | |
t.Errorf("error want=%v got=%v", nerr, err) | |
} | |
}) | |
t.Run("ReadFromMetadata", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
completedAt := starTime.Add(time.Hour) | |
bytes := marshalMeta(backup.BackupDescriptor{StartedAt: starTime, CompletedAt: completedAt, Status: string(backup.Success)}) | |
want := want | |
want.CompletedAt = completedAt | |
want.Status = backup.Success | |
fs.backend.On("GetObject", ctx, id, GlobalRestoreFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
got, err := fs.scheduler().RestorationStatus(ctx, nil, backendName, id) | |
assert.Nil(t, err) | |
assert.Equal(t, want, got) | |
}) | |
} | |
func TestSchedulerCreateBackup(t *testing.T) { | |
t.Parallel() | |
var ( | |
cls = "Class-A" | |
node = "Node-A" | |
backendName = "gcs" | |
backupID = "1" | |
any = mock.Anything | |
ctx = context.Background() | |
path = "dst/path" | |
req = BackupRequest{ | |
ID: backupID, | |
Include: []string{cls}, | |
Backend: backendName, | |
} | |
cresp = &CanCommitResponse{Method: OpCreate, ID: backupID, Timeout: 1} | |
sReq = &StatusRequest{OpCreate, backupID, backendName} | |
sresp = &StatusResponse{Status: backup.Success, ID: backupID, Method: OpCreate} | |
) | |
t.Run("AnotherBackupIsInProgress", func(t *testing.T) { | |
req1 := BackupRequest{ | |
ID: backupID, | |
Include: []string{cls}, | |
Backend: backendName, | |
} | |
fs := newFakeScheduler(newFakeNodeResolver([]string{node})) | |
// first | |
fs.selector.On("Backupable", ctx, req1.Include).Return(nil) | |
fs.selector.On("Shards", ctx, cls).Return([]string{node}, nil) | |
fs.backend.On("GetObject", ctx, backupID, GlobalBackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("Initialize", ctx, mock.Anything).Return(nil) | |
fs.client.On("CanCommit", any, node, any).Return(cresp, nil) | |
fs.client.On("Commit", any, node, sReq).Return(nil) | |
fs.client.On("Status", any, node, sReq).Return(sresp, nil) | |
fs.backend.On("PutObject", any, backupID, GlobalBackupFile, any).Return(nil).Twice() | |
m := fs.scheduler() | |
resp1, err := m.Backup(ctx, nil, &req1) | |
assert.Nil(t, err) | |
status1 := string(backup.Started) | |
want1 := &models.BackupCreateResponse{ | |
Backend: backendName, | |
Classes: req1.Include, | |
ID: backupID, | |
Status: &status1, | |
Path: path, | |
} | |
assert.Equal(t, resp1, want1) | |
resp2, err := m.Backup(ctx, nil, &req1) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), "already in progress") | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
assert.Nil(t, resp2) | |
}) | |
t.Run("BackendUnregistered", func(t *testing.T) { | |
classes := []string{cls} | |
backendError := errors.New("I do not exist") | |
fs := newFakeScheduler(nil) | |
fs.backendErr = backendError | |
meta, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: backupID, | |
Include: classes, | |
}) | |
assert.Nil(t, meta) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), backendName) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
t.Run("InitMetadata", func(t *testing.T) { | |
classes := []string{cls} | |
fs := newFakeScheduler(nil) | |
fs.selector.On("Backupable", ctx, classes).Return(nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("GetObject", ctx, backupID, GlobalBackupFile).Return(nil, backup.NewErrNotFound(errors.New("not found"))) | |
fs.backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("Initialize", ctx, backupID).Return(errors.New("init meta failed")) | |
meta, err := fs.scheduler().Backup(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: backupID, | |
Include: classes, | |
}) | |
assert.Nil(t, meta) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), "init") | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
t.Run("Success", func(t *testing.T) { | |
fs := newFakeScheduler(newFakeNodeResolver([]string{node})) | |
fs.selector.On("Backupable", ctx, req.Include).Return(nil) | |
fs.selector.On("Shards", ctx, cls).Return([]string{node}, nil) | |
fs.backend.On("GetObject", ctx, backupID, GlobalBackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("Initialize", ctx, mock.Anything).Return(nil) | |
fs.client.On("CanCommit", any, node, any).Return(cresp, nil) | |
fs.client.On("Commit", any, node, sReq).Return(nil) | |
fs.client.On("Status", any, node, sReq).Return(sresp, nil) | |
fs.backend.On("PutObject", any, backupID, GlobalBackupFile, any).Return(nil).Twice() | |
s := fs.scheduler() | |
resp, err := s.Backup(ctx, nil, &req) | |
assert.Nil(t, err) | |
status1 := string(backup.Started) | |
want1 := &models.BackupCreateResponse{ | |
Backend: backendName, | |
Classes: req.Include, | |
ID: backupID, | |
Status: &status1, | |
Path: path, | |
} | |
assert.Equal(t, resp, want1) | |
for i := 0; i < 10; i++ { | |
time.Sleep(time.Millisecond * 50) | |
if i > 0 && s.backupper.lastOp.get().Status == "" { | |
break | |
} | |
} | |
assert.Equal(t, fs.backend.glMeta.Status, backup.Success) | |
assert.Equal(t, fs.backend.glMeta.Error, "") | |
}) | |
} | |
func TestSchedulerRestoration(t *testing.T) { | |
var ( | |
cls = "MyClass-A" | |
node = "Node-A" | |
any = mock.Anything | |
backendName = "gcs" | |
backupID = "1" | |
timePt = time.Now().UTC() | |
ctx = context.Background() | |
path = "bucket/backups/" + backupID | |
cresp = &CanCommitResponse{Method: OpRestore, ID: backupID, Timeout: 1} | |
sReq = &StatusRequest{OpRestore, backupID, backendName} | |
sresp = &StatusResponse{Status: backup.Success, ID: backupID, Method: OpRestore} | |
) | |
meta1 := backup.DistributedBackupDescriptor{ | |
ID: backupID, | |
StartedAt: timePt, | |
Version: "1", | |
ServerVersion: "1", | |
Status: backup.Success, | |
Nodes: map[string]*backup.NodeDescriptor{ | |
node: {Classes: []string{cls}}, | |
}, | |
} | |
t.Run("AnotherBackupIsInProgress", func(t *testing.T) { | |
req1 := BackupRequest{ | |
ID: backupID, | |
Include: []string{cls}, | |
Backend: backendName, | |
} | |
fs := newFakeScheduler(newFakeNodeResolver([]string{node})) | |
bytes := marshalCoordinatorMeta(meta1) | |
fs.backend.On("Initialize", ctx, mock.Anything).Return(nil) | |
fs.backend.On("GetObject", ctx, backupID, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("PutObject", mock.Anything, mock.Anything, GlobalRestoreFile, mock.AnythingOfType("[]uint8")).Return(nil) | |
fs.client.On("CanCommit", any, node, any).Return(cresp, nil) | |
fs.client.On("Commit", any, node, sReq).Return(nil) | |
fs.client.On("Status", any, node, sReq).Return(sresp, nil).After(time.Minute) | |
s := fs.scheduler() | |
resp, err := s.Restore(ctx, nil, &req1) | |
assert.Nil(t, err) | |
status1 := string(backup.Started) | |
want1 := &models.BackupRestoreResponse{ | |
Backend: backendName, | |
Classes: req1.Include, | |
ID: backupID, | |
Status: &status1, | |
Path: path, | |
} | |
assert.Equal(t, resp, want1) | |
resp, err = s.Restore(ctx, nil, &req1) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), "already in progress") | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
assert.Nil(t, resp) | |
}) | |
t.Run("Success", func(t *testing.T) { | |
req := BackupRequest{ | |
ID: backupID, | |
Include: []string{cls}, | |
Backend: backendName, | |
} | |
fs := newFakeScheduler(newFakeNodeResolver([]string{node})) | |
bytes := marshalCoordinatorMeta(meta1) | |
fs.backend.On("Initialize", ctx, mock.Anything).Return(nil) | |
fs.backend.On("GetObject", ctx, backupID, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
// first for initial "STARTED", second for updated participant status | |
fs.backend.On("PutObject", mock.Anything, mock.Anything, GlobalRestoreFile, mock.AnythingOfType("[]uint8")).Return(nil) | |
fs.backend.On("PutObject", mock.Anything, mock.Anything, GlobalRestoreFile, mock.AnythingOfType("[]uint8")).Return(nil) | |
fs.client.On("CanCommit", any, node, any).Return(cresp, nil) | |
fs.client.On("Commit", any, node, sReq).Return(nil) | |
fs.client.On("Status", any, node, sReq).Return(sresp, nil) | |
fs.backend.On("PutObject", any, backupID, GlobalRestoreFile, any).Return(nil).Twice() | |
s := fs.scheduler() | |
resp, err := s.Restore(ctx, nil, &req) | |
assert.Nil(t, err) | |
status1 := string(backup.Started) | |
want1 := &models.BackupRestoreResponse{ | |
Backend: backendName, | |
Classes: req.Include, | |
ID: backupID, | |
Status: &status1, | |
Path: path, | |
} | |
assert.Equal(t, resp, want1) | |
for i := 0; i < 10; i++ { | |
time.Sleep(time.Millisecond * 60) | |
if i > 0 && s.restorer.lastOp.get().Status == "" { | |
break | |
} | |
} | |
assert.Equal(t, fs.backend.glMeta.Status, backup.Success) | |
assert.Equal(t, fs.backend.glMeta.Error, "") | |
}) | |
} | |
func TestSchedulerRestoreRequestValidation(t *testing.T) { | |
var ( | |
cls = "MyClass" | |
backendName = "s3" | |
s = newFakeScheduler(nil).scheduler() | |
id = "1234" | |
timePt = time.Now().UTC() | |
ctx = context.Background() | |
path = "bucket/backups/" + id | |
req = &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{cls}, | |
Exclude: []string{}, | |
} | |
) | |
meta := backup.DistributedBackupDescriptor{ | |
ID: id, | |
StartedAt: timePt, | |
Version: "1", | |
ServerVersion: "1", | |
Status: backup.Success, | |
Nodes: map[string]*backup.NodeDescriptor{ | |
nodeName: {Classes: []string{cls}}, | |
}, | |
} | |
t.Run("NonEmptyIncludeAndExclude", func(t *testing.T) { | |
_, err := s.Restore(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{cls}, | |
Exclude: []string{cls}, | |
}) | |
assert.NotNil(t, err) | |
}) | |
t.Run("RequestIncludeHasDuplicates", func(t *testing.T) { | |
_, err := s.Restore(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{"C1", "C2", "C1"}, | |
Exclude: []string{}, | |
}) | |
assert.NotNil(t, err) | |
assert.ErrorContains(t, err, "C1") | |
}) | |
t.Run("BackendFailure", func(t *testing.T) { // backend provider fails | |
fs := newFakeScheduler(nil) | |
fs.backendErr = ErrAny | |
_, err := fs.scheduler().Restore(ctx, nil, &BackupRequest{ | |
Backend: backendName, | |
ID: id, | |
Include: []string{cls}, | |
Exclude: []string{}, | |
}) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), backendName) | |
}) | |
t.Run("GetMetadataFile", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(nil, ErrAny) | |
fs.backend.On("GetObject", ctx, id, BackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, req) | |
if err == nil || !strings.Contains(err.Error(), "find") { | |
t.Errorf("must return an error if it fails to get meta data: %v", err) | |
} | |
// meta data not found | |
fs = newFakeScheduler(nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(nil, backup.ErrNotFound{}) | |
fs.backend.On("GetObject", ctx, id, BackupFile).Return(nil, backup.ErrNotFound{}) | |
_, err = fs.scheduler().Restore(ctx, nil, req) | |
if _, ok := err.(backup.ErrNotFound); !ok { | |
t.Errorf("must return an error if meta data doesn't exist: %v", err) | |
} | |
}) | |
t.Run("FailedBackup", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
bytes := marshalMeta(backup.BackupDescriptor{ID: id, Status: string(backup.Failed)}) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, req) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), backup.Failed) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
t.Run("BackupWithHigherVersion", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
version := "3.0" | |
meta := backup.DistributedBackupDescriptor{ | |
ID: id, | |
StartedAt: timePt, | |
Version: version, | |
ServerVersion: "2", | |
Status: backup.Success, | |
Nodes: map[string]*backup.NodeDescriptor{ | |
nodeName: {Classes: []string{cls}}, | |
}, | |
} | |
bytes := marshalCoordinatorMeta(meta) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, req) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), errMsgHigherVersion) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
}) | |
t.Run("CorruptedBackupFile", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
bytes := marshalMeta(backup.BackupDescriptor{ID: id, Status: string(backup.Success)}) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, req) | |
assert.NotNil(t, err) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
assert.Contains(t, err.Error(), "corrupted") | |
}) | |
t.Run("WrongBackupFile", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
bytes := marshalMeta(backup.BackupDescriptor{ID: "123", Status: string(backup.Success)}) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, req) | |
assert.NotNil(t, err) | |
assert.IsType(t, backup.ErrUnprocessable{}, err) | |
assert.Contains(t, err.Error(), "wrong backup file") | |
}) | |
t.Run("UnknownClass", func(t *testing.T) { | |
fs := newFakeScheduler(nil) | |
bytes := marshalCoordinatorMeta(meta) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, &BackupRequest{ID: id, Include: []string{"unknown"}}) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), "unknown") | |
}) | |
t.Run("EmptyResultClassList", func(t *testing.T) { // backup was successful but class list is empty | |
fs := newFakeScheduler(&fakeNodeResolver{}) | |
bytes := marshalCoordinatorMeta(meta) | |
fs.backend.On("GetObject", ctx, id, GlobalBackupFile).Return(bytes, nil) | |
fs.backend.On("HomeDir", mock.Anything).Return(path) | |
_, err := fs.scheduler().Restore(ctx, nil, &BackupRequest{ID: id, Exclude: []string{cls}}) | |
assert.NotNil(t, err) | |
assert.Contains(t, err.Error(), cls) | |
}) | |
} | |
type fakeScheduler struct { | |
selector fakeSelector | |
client fakeClient | |
backend *fakeBackend | |
backendErr error | |
auth *fakeAuthorizer | |
nodeResolver nodeResolver | |
log logrus.FieldLogger | |
} | |
func newFakeScheduler(resolver nodeResolver) *fakeScheduler { | |
fc := fakeScheduler{} | |
fc.backend = newFakeBackend() | |
fc.backendErr = nil | |
logger, _ := test.NewNullLogger() | |
fc.auth = &fakeAuthorizer{} | |
fc.log = logger | |
if resolver == nil { | |
fc.nodeResolver = &fakeNodeResolver{} | |
} else { | |
fc.nodeResolver = resolver | |
} | |
return &fc | |
} | |
func (f *fakeScheduler) scheduler() *Scheduler { | |
provider := &fakeBackupBackendProvider{f.backend, f.backendErr} | |
c := NewScheduler(f.auth, &f.client, &f.selector, provider, | |
f.nodeResolver, f.log) | |
c.backupper.timeoutNextRound = time.Millisecond * 200 | |
c.restorer.timeoutNextRound = time.Millisecond * 200 | |
return c | |
} | |
func marshalCoordinatorMeta(m backup.DistributedBackupDescriptor) []byte { | |
bytes, _ := json.MarshalIndent(m, "", "") | |
return bytes | |
} | |
func TestFirstDuplicate(t *testing.T) { | |
tests := []struct { | |
in []string | |
want string | |
}{ | |
{}, | |
{[]string{"1"}, ""}, | |
{[]string{"1", "1"}, "1"}, | |
{[]string{"1", "2", "2", "1"}, "2"}, | |
{[]string{"1", "2", "3", "1"}, "1"}, | |
} | |
for _, test := range tests { | |
got := findDuplicate(test.in) | |
if got != test.want { | |
t.Errorf("firstDuplicate(%v) want=%s got=%s", test.in, test.want, got) | |
} | |
} | |
} | |