github.com/weaviate/weaviate@v1.24.6/usecases/backup/backupper_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package backup
    13  
    14  import (
    15  	"context"
    16  	"errors"
    17  	"os"
    18  	"path/filepath"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/sirupsen/logrus/hooks/test"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/mock"
    25  	"github.com/weaviate/weaviate/entities/backup"
    26  	"github.com/weaviate/weaviate/entities/models"
    27  	"github.com/weaviate/weaviate/entities/modulecapabilities"
    28  )
    29  
    30  const (
    31  	nodeName = "Node-1"
    32  )
    33  
    34  var errNotFound = backup.NewErrNotFound(errors.New("not found"))
    35  
    36  func (r *backupper) waitForCompletion(n, ms int) backup.Status {
    37  	for i := 0; i < n; i++ {
    38  		time.Sleep(time.Millisecond * time.Duration(ms))
    39  		if i < 1 {
    40  			continue
    41  		}
    42  		if x := r.lastOp.get(); x.Status != "" {
    43  			return x.Status
    44  		}
    45  	}
    46  	return ""
    47  }
    48  
    49  func TestBackupStatus(t *testing.T) {
    50  	t.Parallel()
    51  	var (
    52  		backendName = "s3"
    53  		id          = "1234"
    54  		ctx         = context.Background()
    55  		starTime    = time.Date(2022, 1, 1, 1, 0, 0, 0, time.UTC)
    56  		nodeHome    = id + "/" + nodeName
    57  		path        = "bucket/backups/" + nodeHome
    58  		rawstatus   = string(backup.Transferring)
    59  		want        = &models.BackupCreateStatusResponse{
    60  			ID:      id,
    61  			Path:    path,
    62  			Status:  &rawstatus,
    63  			Backend: backendName,
    64  		}
    65  	)
    66  
    67  	t.Run("ActiveState", func(t *testing.T) {
    68  		m := createManager(nil, nil, nil, nil)
    69  		m.backupper.lastOp.reqStat = reqStat{
    70  			Starttime: starTime,
    71  			ID:        id,
    72  			Status:    backup.Transferring,
    73  			Path:      path,
    74  		}
    75  		st, err := m.backupper.Status(ctx, backendName, id)
    76  		assert.Nil(t, err)
    77  		assert.Equal(t, want, st)
    78  	})
    79  
    80  	t.Run("GetBackupProvider", func(t *testing.T) {
    81  		m := createManager(nil, nil, nil, ErrAny)
    82  		_, err := m.backupper.Status(ctx, backendName, id)
    83  		assert.NotNil(t, err)
    84  	})
    85  
    86  	t.Run("MetadataNotFound", func(t *testing.T) {
    87  		backend := &fakeBackend{}
    88  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, ErrAny)
    89  		backend.On("GetObject", ctx, id, BackupFile).Return(nil, ErrAny)
    90  		m := createManager(nil, nil, backend, nil)
    91  		_, err := m.backupper.Status(ctx, backendName, id)
    92  		assert.NotNil(t, err)
    93  		nerr := backup.ErrNotFound{}
    94  		if !errors.As(err, &nerr) {
    95  			t.Errorf("error want=%v got=%v", nerr, err)
    96  		}
    97  	})
    98  
    99  	t.Run("ReadFromMetadata", func(t *testing.T) {
   100  		backend := &fakeBackend{}
   101  		bytes := marshalMeta(backup.BackupDescriptor{Status: string(backup.Transferring)})
   102  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(bytes, nil)
   103  		backend.On("HomeDir", mock.Anything).Return(path)
   104  		m := createManager(nil, nil, backend, nil)
   105  		got, err := m.backupper.Status(ctx, backendName, id)
   106  		assert.Nil(t, err)
   107  		assert.Equal(t, want, got)
   108  	})
   109  
   110  	t.Run("ReadFromMetadataError", func(t *testing.T) {
   111  		backend := &fakeBackend{}
   112  		st := string(backup.Failed)
   113  		bytes := marshalMeta(backup.BackupDescriptor{Status: st, Error: "error1"})
   114  		want = &models.BackupCreateStatusResponse{
   115  			ID:      id,
   116  			Path:    path,
   117  			Status:  &st,
   118  			Backend: backendName,
   119  		}
   120  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(bytes, nil)
   121  		backend.On("HomeDir", mock.Anything).Return(path)
   122  		m := createManager(nil, nil, backend, nil)
   123  		_, err := m.backupper.Status(ctx, backendName, id)
   124  		assert.NotNil(t, err)
   125  		assert.ErrorContains(t, err, "error1")
   126  	})
   127  }
   128  
   129  func TestBackupOnStatus(t *testing.T) {
   130  	t.Parallel()
   131  	var (
   132  		backendName = "s3"
   133  		id          = "1234"
   134  		ctx         = context.Background()
   135  		starTime    = time.Date(2022, 1, 1, 1, 0, 0, 0, time.UTC)
   136  		nodeHome    = id + "/" + nodeName
   137  		path        = "bucket/backups/" + nodeHome
   138  		req         = StatusRequest{
   139  			Method:  OpCreate,
   140  			ID:      id,
   141  			Backend: backendName,
   142  		}
   143  	)
   144  
   145  	t.Run("ActiveState", func(t *testing.T) {
   146  		m := createManager(nil, nil, nil, nil)
   147  		m.backupper.lastOp.reqStat = reqStat{
   148  			Starttime: starTime,
   149  			ID:        id,
   150  			Status:    backup.Transferring,
   151  			Path:      path,
   152  		}
   153  		want := &StatusResponse{
   154  			Method: OpCreate,
   155  			ID:     id,
   156  			Status: backup.Transferring,
   157  		}
   158  		st := m.OnStatus(ctx, &req)
   159  		assert.Equal(t, want, st)
   160  	})
   161  
   162  	t.Run("GetBackupProvider", func(t *testing.T) {
   163  		want := &StatusResponse{
   164  			Method: OpCreate,
   165  			ID:     id,
   166  			Status: backup.Failed,
   167  		}
   168  		m := createManager(nil, nil, nil, ErrAny)
   169  		got := m.OnStatus(ctx, &req)
   170  		assert.Contains(t, got.Err, req.Backend)
   171  		want.Err = got.Err
   172  		assert.Equal(t, want, got)
   173  	})
   174  
   175  	t.Run("MetadataNotFound", func(t *testing.T) {
   176  		want := &StatusResponse{
   177  			Method: OpCreate,
   178  			ID:     id,
   179  			Status: backup.Failed,
   180  		}
   181  		backend := &fakeBackend{}
   182  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, ErrAny)
   183  		backend.On("GetObject", ctx, id, BackupFile).Return(nil, ErrAny)
   184  
   185  		m := createManager(nil, nil, backend, nil)
   186  		got := m.OnStatus(ctx, &req)
   187  		assert.Contains(t, got.Err, errMetaNotFound.Error())
   188  		want.Err = got.Err
   189  		assert.Equal(t, want, got)
   190  	})
   191  
   192  	t.Run("ReadFromMetadata", func(t *testing.T) {
   193  		want := &StatusResponse{
   194  			Method: OpCreate,
   195  			ID:     id,
   196  			Status: backup.Success,
   197  		}
   198  		backend := &fakeBackend{}
   199  		bytes := marshalMeta(backup.BackupDescriptor{Status: string(backup.Success)})
   200  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(bytes, nil)
   201  		backend.On("HomeDir", mock.Anything).Return(path)
   202  		m := createManager(nil, nil, backend, nil)
   203  		got := m.OnStatus(ctx, &req)
   204  		assert.Equal(t, want, got)
   205  	})
   206  }
   207  
   208  func TestManagerCreateBackup(t *testing.T) {
   209  	t.Parallel()
   210  	var (
   211  		cls         = "Class-A"
   212  		cls2        = "Class-B"
   213  		backendName = "gcs"
   214  		backupID    = "1"
   215  		ctx         = context.Background()
   216  		nodeHome    = backupID + "/" + nodeName
   217  		path        = "bucket/backups/" + nodeHome
   218  		req         = BackupRequest{
   219  			ID:      backupID,
   220  			Include: []string{cls},
   221  			Backend: backendName,
   222  		}
   223  	)
   224  
   225  	t.Run("AnotherBackupIsInProgress", func(t *testing.T) {
   226  		req1 := BackupRequest{
   227  			ID:      backupID,
   228  			Include: []string{cls},
   229  			Backend: backendName,
   230  		}
   231  
   232  		sourcer := &fakeSourcer{}
   233  		// first
   234  		sourcer.On("Backupable", ctx, req1.Include).Return(nil)
   235  		sourcer.On("CreateBackup", ctx, any).Return(nil, nil)
   236  		sourcer.On("ReleaseBackup", ctx, any).Return(nil)
   237  		var ch <-chan backup.ClassDescriptor
   238  		sourcer.On("BackupDescriptors", any, any, any).Return(ch) // just block
   239  
   240  		backend := &fakeBackend{}
   241  		// second
   242  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, backup.ErrNotFound{})
   243  		backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, backup.ErrNotFound{})
   244  		backend.On("HomeDir", any).Return(path)
   245  		sourcer.On("Backupable", any, req1.Include).Return(nil)
   246  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   247  		sourcer.On("CreateBackup", ctx, any).Return(nil, ErrAny)
   248  		sourcer.On("ReleaseBackup", ctx, any).Return(nil)
   249  		m := createManager(sourcer, nil, backend, nil)
   250  		resp1, err := m.Backup(ctx, nil, &req1)
   251  		assert.Nil(t, err)
   252  		status1 := string(backup.Started)
   253  		want1 := &models.BackupCreateResponse{
   254  			Backend: backendName,
   255  			Classes: req1.Include,
   256  			ID:      backupID,
   257  			Status:  &status1,
   258  			Path:    path,
   259  		}
   260  		assert.Equal(t, resp1, want1)
   261  		resp2, err := m.Backup(ctx, nil, &req1)
   262  		assert.NotNil(t, err)
   263  		assert.Contains(t, err.Error(), "already in progress")
   264  		assert.IsType(t, backup.ErrUnprocessable{}, err)
   265  		assert.Nil(t, resp2)
   266  	})
   267  
   268  	t.Run("InitMetadata", func(t *testing.T) {
   269  		classes := []string{cls}
   270  
   271  		sourcer := &fakeSourcer{}
   272  		sourcer.On("Backupable", ctx, classes).Return(nil)
   273  		backend := &fakeBackend{}
   274  		backend.On("HomeDir", mock.Anything).Return(path)
   275  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   276  		backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, errNotFound)
   277  
   278  		backend.On("Initialize", ctx, nodeHome).Return(errors.New("init meta failed"))
   279  		bm := createManager(sourcer, nil, backend, nil)
   280  
   281  		meta, err := bm.Backup(ctx, nil, &BackupRequest{
   282  			Backend: backendName,
   283  			ID:      backupID,
   284  			Include: classes,
   285  		})
   286  
   287  		assert.Nil(t, meta)
   288  		assert.NotNil(t, err)
   289  		assert.Contains(t, err.Error(), "init")
   290  		assert.IsType(t, backup.ErrUnprocessable{}, err)
   291  	})
   292  
   293  	t.Run("Success", func(t *testing.T) {
   294  		var (
   295  			classes    = []string{cls}
   296  			sourcePath = t.TempDir()
   297  			sourcer    = &fakeSourcer{}
   298  			backend    = newFakeBackend()
   299  		)
   300  		sourcer.On("Backupable", ctx, classes).Return(nil)
   301  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   302  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   303  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   304  		backend.On("HomeDir", mock.Anything).Return(path)
   305  		backend.On("SourceDataPath").Return(sourcePath)
   306  
   307  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   308  		backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, errNotFound)
   309  
   310  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   311  		backend.On("PutObject", any, nodeHome, BackupFile, mock.Anything).Return(nil).Twice()
   312  		backend.On("Write", any, nodeHome, any, any).Return(any, nil)
   313  		m := createManager(sourcer, nil, backend, nil)
   314  
   315  		resp, err := m.Backup(ctx, nil, &req)
   316  
   317  		assert.Nil(t, err)
   318  		status1 := string(backup.Started)
   319  		want1 := &models.BackupCreateResponse{
   320  			Backend: backendName,
   321  			Classes: req.Include,
   322  			ID:      backupID,
   323  			Status:  &status1,
   324  			Path:    path,
   325  		}
   326  		assert.Equal(t, resp, want1)
   327  		m.backupper.waitForCompletion(10, 50)
   328  		assert.Equal(t, string(backup.Success), backend.meta.Status)
   329  		assert.Equal(t, backend.meta.Error, "")
   330  	})
   331  
   332  	t.Run("PutFile", func(t *testing.T) {
   333  		var (
   334  			classes    = []string{cls}
   335  			sourcePath = t.TempDir()
   336  			sourcer    = &fakeSourcer{}
   337  			backend    = newFakeBackend()
   338  		)
   339  		sourcer.On("Backupable", ctx, classes).Return(nil)
   340  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   341  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   342  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   343  
   344  		backend.On("HomeDir", mock.Anything).Return(path)
   345  		backend.On("SourceDataPath").Return(sourcePath)
   346  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   347  		backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, errNotFound)
   348  
   349  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   350  
   351  		backend.On("Write", any, nodeHome, any, any).Return(any, ErrAny).Once()
   352  		backend.On("PutObject", any, nodeHome, BackupFile, any).Return(nil).Once()
   353  		m := createManager(sourcer, nil, backend, nil)
   354  
   355  		resp, err := m.Backup(ctx, nil, &req)
   356  
   357  		assert.Nil(t, err)
   358  		status1 := string(backup.Started)
   359  		want1 := &models.BackupCreateResponse{
   360  			Backend: backendName,
   361  			Classes: req.Include,
   362  			ID:      backupID,
   363  			Status:  &status1,
   364  			Path:    path,
   365  		}
   366  		assert.Equal(t, resp, want1)
   367  		m.backupper.waitForCompletion(10, 50)
   368  
   369  		assert.Equal(t, string(backup.Transferring), backend.meta.Status)
   370  		assert.Contains(t, backend.meta.Error, "pipe")
   371  	})
   372  
   373  	t.Run("ClassDescriptor", func(t *testing.T) {
   374  		var (
   375  			classes     = []string{cls}
   376  			sourcer     = &fakeSourcer{}
   377  			sourcePath  = t.TempDir()
   378  			errNotFound = errNotFound
   379  			backend     = newFakeBackend()
   380  		)
   381  
   382  		sourcer.On("Backupable", ctx, classes).Return(nil)
   383  		cs := genClassDescriptions(t, sourcePath, cls, cls2)
   384  		cs[1].Error = ErrAny
   385  		ch := fakeBackupDescriptor(cs...)
   386  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   387  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   388  
   389  		backend.On("HomeDir", mock.Anything).Return(path)
   390  		backend.On("SourceDataPath").Return(sourcePath)
   391  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   392  		backend.On("GetObject", ctx, backupID, BackupFile).Return(nil, errNotFound)
   393  
   394  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   395  		backend.On("Write", mock.Anything, nodeHome, mock.Anything, mock.Anything).Return(any, nil)
   396  		backend.On("PutObject", mock.Anything, nodeHome, BackupFile, mock.Anything).Return(nil).Once()
   397  		m := createManager(sourcer, nil, backend, nil)
   398  
   399  		resp, err := m.Backup(ctx, nil, &req)
   400  
   401  		assert.Nil(t, err)
   402  		status1 := string(backup.Started)
   403  		want1 := &models.BackupCreateResponse{
   404  			Backend: backendName,
   405  			Classes: req.Include,
   406  			ID:      backupID,
   407  			Status:  &status1,
   408  			Path:    path,
   409  		}
   410  		assert.Equal(t, resp, want1)
   411  		m.backupper.waitForCompletion(10, 50)
   412  		assert.Nil(t, err)
   413  		assert.Equal(t, backend.meta.Status, string(backup.Transferring))
   414  		assert.Equal(t, backend.meta.Error, ErrAny.Error())
   415  	})
   416  }
   417  
   418  func TestManagerCoordinatedBackup(t *testing.T) {
   419  	t.Parallel()
   420  	var (
   421  		cls         = "Class-A"
   422  		cls2        = "Class-B"
   423  		backendName = "gcs"
   424  		backupID    = "1"
   425  		ctx         = context.Background()
   426  		nodeHome    = backupID + "/" + nodeName
   427  		path        = "bucket/backups/" + nodeHome
   428  		req         = Request{
   429  			Method:   OpCreate,
   430  			ID:       backupID,
   431  			Classes:  []string{cls, cls2},
   432  			Backend:  backendName,
   433  			Duration: time.Millisecond * 20,
   434  		}
   435  		any = mock.Anything
   436  	)
   437  
   438  	t.Run("BackendUnregistered", func(t *testing.T) {
   439  		backendError := errors.New("I do not exist")
   440  		bm := createManager(nil, nil, nil, backendError)
   441  		ret := bm.OnCanCommit(ctx, &req)
   442  		assert.Contains(t, ret.Err, backendName)
   443  	})
   444  
   445  	t.Run("ClassNotBackupable", func(t *testing.T) {
   446  		backend := &fakeBackend{}
   447  		backend.On("HomeDir", mock.Anything).Return(path)
   448  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   449  		sourcer := &fakeSourcer{}
   450  		sourcer.On("Backupable", ctx, req.Classes).Return(ErrAny)
   451  		bm := createManager(sourcer, nil, backend, nil)
   452  
   453  		resp := bm.OnCanCommit(ctx, &req)
   454  		assert.Contains(t, resp.Err, ErrAny.Error())
   455  		assert.Equal(t, resp.Timeout, time.Duration(0))
   456  	})
   457  
   458  	t.Run("InitializeBackend", func(t *testing.T) {
   459  		backend := &fakeBackend{}
   460  		backend.On("HomeDir", mock.Anything).Return(path)
   461  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   462  		sourcer := &fakeSourcer{}
   463  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   464  		backend.On("Initialize", ctx, nodeHome).Return(errors.New("init meta failed"))
   465  		bm := createManager(sourcer, nil, backend, nil)
   466  
   467  		resp := bm.OnCanCommit(ctx, &req)
   468  		assert.Contains(t, resp.Err, "init")
   469  		assert.Equal(t, resp.Timeout, time.Duration(0))
   470  	})
   471  
   472  	t.Run("AnotherBackupIsInProgress", func(t *testing.T) {
   473  		// first
   474  		sourcer := &fakeSourcer{}
   475  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   476  		sourcer.On("CreateBackup", mock.Anything, mock.Anything).Return(nil, nil)
   477  		sourcer.On("ReleaseBackup", mock.Anything, mock.Anything).Return(nil)
   478  		var ch <-chan backup.ClassDescriptor
   479  		sourcer.On("BackupDescriptors", any, any, any).Return(ch)
   480  
   481  		backend := &fakeBackend{}
   482  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, backup.ErrNotFound{})
   483  		backend.On("HomeDir", mock.Anything).Return(path)
   484  		backend.On("Initialize", ctx, mock.Anything).Return(nil)
   485  		m := createManager(sourcer, nil, backend, nil)
   486  		// second
   487  		resp1 := m.OnCanCommit(ctx, &req)
   488  		want1 := &CanCommitResponse{
   489  			Method:  OpCreate,
   490  			ID:      req.ID,
   491  			Timeout: req.Duration,
   492  		}
   493  		assert.Equal(t, resp1, want1)
   494  		resp := m.OnCanCommit(ctx, &req)
   495  		assert.Contains(t, resp.Err, "already in progress")
   496  		assert.Equal(t, resp.Timeout, time.Duration(0))
   497  	})
   498  
   499  	t.Run("Success", func(t *testing.T) {
   500  		var (
   501  			sourcePath = t.TempDir()
   502  			sourcer    = &fakeSourcer{}
   503  			backend    = newFakeBackend()
   504  		)
   505  
   506  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   507  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   508  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   509  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   510  
   511  		backend.On("HomeDir", mock.Anything).Return(path)
   512  		backend.On("SourceDataPath").Return(sourcePath)
   513  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   514  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   515  		backend.On("PutObject", mock.Anything, nodeHome, BackupFile, mock.Anything).Return(nil).Once()
   516  		backend.On("Write", mock.Anything, nodeHome, mock.Anything, mock.Anything).Return(any, nil)
   517  		m := createManager(sourcer, nil, backend, nil)
   518  
   519  		req := req
   520  		req.Duration = time.Hour
   521  		got := m.OnCanCommit(ctx, &req)
   522  		want := &CanCommitResponse{OpCreate, req.ID, _TimeoutShardCommit, ""}
   523  		assert.Equal(t, got, want)
   524  
   525  		err := m.OnCommit(ctx, &StatusRequest{OpCreate, req.ID, backendName})
   526  		assert.Nil(t, err)
   527  		m.backupper.waitForCompletion(20, 50)
   528  		assert.Equal(t, string(backup.Success), backend.meta.Status)
   529  		assert.Equal(t, "", backend.meta.Error)
   530  	})
   531  
   532  	t.Run("AbortBeforeCommit", func(t *testing.T) {
   533  		var (
   534  			sourcePath = t.TempDir()
   535  			sourcer    = &fakeSourcer{}
   536  			backend    = newFakeBackend()
   537  		)
   538  
   539  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   540  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   541  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   542  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   543  
   544  		backend.On("HomeDir", mock.Anything).Return(path)
   545  		backend.On("SourceDataPath").Return(sourcePath)
   546  
   547  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   548  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   549  		backend.On("PutObject", mock.Anything, nodeHome, BackupFile, mock.Anything).Return(nil).Once()
   550  		backend.On("Write", mock.Anything, nodeHome, mock.Anything, mock.Anything).Return(any, nil)
   551  		m := createManager(sourcer, nil, backend, nil)
   552  
   553  		req := req
   554  		req.Duration = time.Hour
   555  		got := m.OnCanCommit(ctx, &req)
   556  		want := &CanCommitResponse{OpCreate, req.ID, _TimeoutShardCommit, ""}
   557  		assert.Equal(t, got, want)
   558  
   559  		err := m.OnAbort(ctx, &AbortRequest{OpCreate, req.ID, backendName})
   560  		assert.Nil(t, err)
   561  		m.backupper.waitForCompletion(20, 50)
   562  		assert.Contains(t, m.backupper.lastAsyncError.Error(), "abort")
   563  	})
   564  
   565  	t.Run("AbortCommit", func(t *testing.T) {
   566  		var (
   567  			sourcePath = t.TempDir()
   568  			sourcer    = &fakeSourcer{}
   569  			backend    = newFakeBackend()
   570  			m          = createManager(sourcer, nil, backend, nil)
   571  		)
   572  
   573  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   574  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   575  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch).RunFn = func(a mock.Arguments) {
   576  			m.OnAbort(ctx, &AbortRequest{OpCreate, req.ID, backendName})
   577  			// give the abort request time to propagate
   578  			time.Sleep(10 * time.Millisecond)
   579  		}
   580  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   581  		// backend
   582  		backend.On("HomeDir", mock.Anything).Return(path)
   583  		backend.On("SourceDataPath").Return(sourcePath)
   584  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   585  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   586  		backend.On("PutObject", mock.Anything, nodeHome, BackupFile, mock.Anything).Return(nil).Once()
   587  		backend.On("Write", mock.Anything, nodeHome, mock.Anything, mock.Anything).Return(any, nil)
   588  
   589  		req := req
   590  		req.Duration = time.Hour
   591  		got := m.OnCanCommit(ctx, &req)
   592  		want := &CanCommitResponse{OpCreate, req.ID, _TimeoutShardCommit, ""}
   593  		assert.Equal(t, got, want)
   594  
   595  		err := m.OnCommit(ctx, &StatusRequest{OpCreate, req.ID, backendName})
   596  		assert.Nil(t, err)
   597  		m.backupper.waitForCompletion(20, 50)
   598  		errMsg := context.Canceled.Error()
   599  		assert.Equal(t, string(backup.Transferring), backend.meta.Status)
   600  		assert.Equal(t, errMsg, backend.meta.Error)
   601  		assert.Contains(t, m.backupper.lastAsyncError.Error(), errMsg)
   602  	})
   603  
   604  	t.Run("ExpirationTimeout", func(t *testing.T) {
   605  		var (
   606  			sourcePath = t.TempDir()
   607  			sourcer    = &fakeSourcer{}
   608  			backend    = newFakeBackend()
   609  		)
   610  
   611  		sourcer.On("Backupable", ctx, req.Classes).Return(nil)
   612  		ch := fakeBackupDescriptor(genClassDescriptions(t, sourcePath, cls, cls2)...)
   613  		sourcer.On("BackupDescriptors", any, backupID, mock.Anything).Return(ch)
   614  		sourcer.On("ReleaseBackup", ctx, backupID, mock.Anything).Return(nil)
   615  
   616  		backend.On("HomeDir", mock.Anything).Return(path)
   617  		backend.On("SourceDataPath").Return(sourcePath)
   618  		backend.On("GetObject", ctx, nodeHome, BackupFile).Return(nil, errNotFound)
   619  		backend.On("Initialize", ctx, nodeHome).Return(nil)
   620  		backend.On("PutObject", mock.Anything, nodeHome, BackupFile, mock.Anything).Return(nil).Once()
   621  		backend.On("Write", mock.Anything, backupID, mock.Anything, mock.Anything).Return(any, nil)
   622  		m := createManager(sourcer, nil, backend, nil)
   623  
   624  		req := req
   625  		req.Duration = time.Millisecond * 10
   626  		got := m.OnCanCommit(ctx, &req)
   627  		want := &CanCommitResponse{OpCreate, req.ID, req.Duration, ""}
   628  		assert.Equal(t, got, want)
   629  
   630  		m.backupper.waitForCompletion(20, 50)
   631  		assert.Contains(t, m.backupper.lastAsyncError.Error(), "timed out")
   632  	})
   633  }
   634  
   635  func genClassDescriptions(t *testing.T, sourcePath string, classes ...string) []backup.ClassDescriptor {
   636  	ret := make([]backup.ClassDescriptor, len(classes))
   637  	rawbytes := []byte("raw")
   638  	subDir := filepath.Join(sourcePath, "dir1")
   639  	if err := os.MkdirAll(subDir, os.ModePerm); err != nil {
   640  		t.Fatalf("create test subdirectory %s: %v", subDir, err)
   641  	}
   642  	files := []string{"dir1/file1", "dir1/file2", "counter.txt", "version.txt", "prop.txt"}
   643  	for _, p := range files {
   644  		p = filepath.Join(sourcePath, p)
   645  		if err := os.WriteFile(p, rawbytes, os.ModePerm); err != nil {
   646  			t.Fatalf("create test file %s: %v", p, err)
   647  		}
   648  	}
   649  
   650  	for i, cls := range classes {
   651  		ret[i] = backup.ClassDescriptor{
   652  			Name: cls, Schema: rawbytes, ShardingState: rawbytes,
   653  			Shards: []*backup.ShardDescriptor{
   654  				{
   655  					Name: "Shard1", Node: "Node-1",
   656  					Files:                 files[0:2],
   657  					DocIDCounterPath:      files[2],
   658  					ShardVersionPath:      files[3],
   659  					PropLengthTrackerPath: files[4],
   660  					DocIDCounter:          rawbytes,
   661  					Version:               rawbytes,
   662  					PropLengthTracker:     rawbytes,
   663  				},
   664  			},
   665  		}
   666  	}
   667  	return ret
   668  }
   669  
   670  func fakeBackupDescriptor(descs ...backup.ClassDescriptor) <-chan backup.ClassDescriptor {
   671  	ch := make(chan backup.ClassDescriptor, len(descs))
   672  	go func() {
   673  		for _, cls := range descs {
   674  			ch <- cls
   675  		}
   676  		close(ch)
   677  	}()
   678  
   679  	return ch
   680  }
   681  
   682  func createManager(sourcer Sourcer, schema schemaManger, backend modulecapabilities.BackupBackend, backendErr error) *Handler {
   683  	backends := &fakeBackupBackendProvider{backend, backendErr}
   684  	if sourcer == nil {
   685  		sourcer = &fakeSourcer{}
   686  	}
   687  	if schema == nil {
   688  		schema = &fakeSchemaManger{nodeName: nodeName}
   689  	}
   690  
   691  	logger, _ := test.NewNullLogger()
   692  	return NewHandler(logger, &fakeAuthorizer{}, schema, sourcer, backends)
   693  }