Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: hello@weaviate.io | |
| // | |
| 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()) | |
| } | |
| } | |