Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package backup | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"testing" | |
"time" | |
"github.com/stretchr/testify/assert" | |
"github.com/weaviate/weaviate/entities/backup" | |
"github.com/weaviate/weaviate/entities/models" | |
) | |
// helper methods | |
func (m *Handler) Backup(ctx context.Context, pr *models.Principal, req *BackupRequest, | |
) (*models.BackupCreateResponse, error) { | |
store, err := nodeBackend(m.node, m.backends, req.Backend, req.ID) | |
if err != nil { | |
err = fmt.Errorf("no backup backend %q, did you enable the right module?", req.Backend) | |
return nil, backup.NewErrUnprocessable(err) | |
} | |
classes := req.Include | |
if err := store.Initialize(ctx); err != nil { | |
return nil, backup.NewErrUnprocessable(fmt.Errorf("init uploader: %w", err)) | |
} | |
if meta, err := m.backupper.Backup(ctx, store, req.ID, classes); err != nil { | |
return nil, err | |
} else { | |
status := string(meta.Status) | |
return &models.BackupCreateResponse{ | |
Classes: classes, | |
ID: req.ID, | |
Backend: req.Backend, | |
Status: &status, | |
Path: meta.Path, | |
}, nil | |
} | |
} | |
func (m *Handler) Restore(ctx context.Context, pr *models.Principal, | |
req *BackupRequest, | |
) (*models.BackupRestoreResponse, error) { | |
store, err := nodeBackend(m.node, m.backends, req.Backend, req.ID) | |
if err != nil { | |
err = fmt.Errorf("no backup backend %q, did you enable the right module?", req.Backend) | |
return nil, backup.NewErrUnprocessable(err) | |
} | |
meta, err := m.validateRestoreRequest(ctx, store, req) | |
if err != nil { | |
return nil, err | |
} | |
cs := meta.List() | |
// if cls := m.restorer.AnyExists(cs); cls != "" { | |
// err := fmt.Errorf("cannot restore class %q because it already exists", cls) | |
// return nil, backup.NewErrUnprocessable(err) | |
// } | |
rreq := Request{ | |
Method: OpRestore, | |
ID: meta.ID, | |
Backend: req.Backend, | |
Classes: cs, | |
NodeMapping: req.NodeMapping, | |
} | |
data, err := m.restorer.Restore(ctx, &rreq, meta, store) | |
if err != nil { | |
return nil, backup.NewErrUnprocessable(err) | |
} | |
return data, nil | |
} | |
func (m *Handler) validateRestoreRequest(ctx context.Context, store nodeStore, req *BackupRequest) (*backup.BackupDescriptor, error) { | |
meta, cs, err := m.restorer.validate(ctx, &store, &Request{ID: req.ID, Classes: req.Include}) | |
if err != nil { | |
if errors.Is(err, errMetaNotFound) { | |
return nil, backup.NewErrNotFound(err) | |
} | |
return nil, backup.NewErrUnprocessable(err) | |
} | |
meta.Exclude(req.Exclude) | |
if len(meta.Classes) == 0 { | |
err = fmt.Errorf("empty class list: please choose from : %v", cs) | |
return nil, backup.NewErrUnprocessable(err) | |
} | |
return meta, nil | |
} | |
type fakeSchemaManger struct { | |
errRestoreClass error | |
nodeName string | |
} | |
func (f *fakeSchemaManger) RestoreClass(context.Context, *backup.ClassDescriptor, map[string]string, | |
) error { | |
return f.errRestoreClass | |
} | |
func (f *fakeSchemaManger) NodeName() string { | |
return f.nodeName | |
} | |
type fakeAuthorizer struct{} | |
func (f *fakeAuthorizer) Authorize(principal *models.Principal, verb, resource string) error { | |
return nil | |
} | |
func TestFilterClasses(t *testing.T) { | |
tests := []struct { | |
in []string | |
xs []string | |
out []string | |
}{ | |
{in: []string{}, xs: []string{}, out: []string{}}, | |
{in: []string{"a"}, xs: []string{}, out: []string{"a"}}, | |
{in: []string{"a"}, xs: []string{"a"}, out: []string{}}, | |
{in: []string{"1", "2", "3", "4"}, xs: []string{"2", "3"}, out: []string{"1", "4"}}, | |
{in: []string{"1", "2", "3"}, xs: []string{"1", "3"}, out: []string{"2"}}, | |
{in: []string{"1", "2", "1", "3", "1", "3"}, xs: []string{"2"}, out: []string{"1", "3"}}, | |
} | |
for _, tc := range tests { | |
got := filterClasses(tc.in, tc.xs) | |
assert.ElementsMatch(t, tc.out, got) | |
} | |
} | |
func TestHandlerValidateCoordinationOperation(t *testing.T) { | |
var ( | |
ctx = context.Background() | |
bm = createManager(nil, nil, nil, nil) | |
) | |
{ // OnCanCommit | |
req := Request{ | |
Method: "Unknown", | |
ID: "1", | |
Classes: []string{"class1"}, | |
Backend: "s3", | |
Duration: time.Millisecond * 20, | |
} | |
resp := bm.OnCanCommit(ctx, &req) | |
assert.Contains(t, resp.Err, "unknown backup operation") | |
assert.Equal(t, resp.Timeout, time.Duration(0)) | |
} | |
{ // OnCommit | |
req := StatusRequest{ | |
Method: "Unknown", | |
ID: "1", | |
Backend: "s3", | |
} | |
err := bm.OnCommit(ctx, &req) | |
assert.NotNil(t, err) | |
assert.ErrorIs(t, err, errUnknownOp) | |
} | |
{ // OnAbort | |
req := AbortRequest{ | |
Method: "Unknown", | |
ID: "1", | |
} | |
err := bm.OnAbort(ctx, &req) | |
assert.NotNil(t, err) | |
assert.ErrorIs(t, err, errUnknownOp) | |
} | |
{ // OnStatus | |
req := StatusRequest{ | |
Method: "Unknown", | |
ID: "1", | |
} | |
ret := bm.OnStatus(ctx, &req) | |
assert.Contains(t, ret.Err, errUnknownOp.Error()) | |
} | |
} | |