github.com/weaviate/weaviate@v1.24.6/usecases/backup/backupper.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 "fmt" 18 "time" 19 20 enterrors "github.com/weaviate/weaviate/entities/errors" 21 22 "github.com/sirupsen/logrus" 23 "github.com/weaviate/weaviate/entities/backup" 24 "github.com/weaviate/weaviate/entities/models" 25 "github.com/weaviate/weaviate/usecases/config" 26 ) 27 28 type backupper struct { 29 node string 30 logger logrus.FieldLogger 31 sourcer Sourcer 32 backends BackupBackendProvider 33 // shardCoordinationChan is sync and coordinate operations 34 shardSyncChan 35 } 36 37 func newBackupper(node string, logger logrus.FieldLogger, sourcer Sourcer, backends BackupBackendProvider, 38 ) *backupper { 39 return &backupper{ 40 node: node, 41 logger: logger, 42 sourcer: sourcer, 43 backends: backends, 44 shardSyncChan: shardSyncChan{coordChan: make(chan interface{}, 5)}, 45 } 46 } 47 48 // Backup is called by the User 49 func (b *backupper) Backup(ctx context.Context, 50 store nodeStore, id string, classes []string, 51 ) (*backup.CreateMeta, error) { 52 // make sure there is no active backup 53 req := Request{ 54 Method: OpCreate, 55 ID: id, 56 Classes: classes, 57 } 58 if _, err := b.backup(ctx, store, &req); err != nil { 59 return nil, backup.NewErrUnprocessable(err) 60 } 61 62 return &backup.CreateMeta{ 63 Path: store.HomeDir(), 64 Status: backup.Started, 65 }, nil 66 } 67 68 // Status returns status of a backup 69 // If the backup is still active the status is immediately returned 70 // If not it fetches the metadata file to get the status 71 func (b *backupper) Status(ctx context.Context, backend, bakID string, 72 ) (*models.BackupCreateStatusResponse, error) { 73 st, err := b.OnStatus(ctx, &StatusRequest{OpCreate, bakID, backend}) 74 if err != nil { 75 if errors.Is(err, errMetaNotFound) { 76 err = backup.NewErrNotFound(err) 77 } else { 78 err = backup.NewErrUnprocessable(err) 79 } 80 return nil, err 81 } 82 // check if backup is still active 83 status := string(st.Status) 84 return &models.BackupCreateStatusResponse{ 85 ID: bakID, 86 Path: st.Path, 87 Status: &status, 88 Backend: backend, 89 }, nil 90 } 91 92 func (b *backupper) OnStatus(ctx context.Context, req *StatusRequest) (reqStat, error) { 93 // check if backup is still active 94 st := b.lastOp.get() 95 if st.ID == req.ID { 96 return st, nil 97 } 98 99 // The backup might have been already created. 100 store, err := nodeBackend(b.node, b.backends, req.Backend, req.ID) 101 if err != nil { 102 return reqStat{}, fmt.Errorf("no backup provider %q, did you enable the right module?", req.Backend) 103 } 104 105 meta, err := store.Meta(ctx, req.ID, false) 106 if err != nil { 107 path := fmt.Sprintf("%s/%s", req.ID, BackupFile) 108 return reqStat{}, fmt.Errorf("cannot get status while backing up: %w: %q: %v", errMetaNotFound, path, err) 109 } 110 if err != nil || meta.Error != "" { 111 return reqStat{}, errors.New(meta.Error) 112 } 113 114 return reqStat{ 115 Starttime: meta.StartedAt, 116 ID: req.ID, 117 Path: store.HomeDir(), 118 Status: backup.Status(meta.Status), 119 }, nil 120 } 121 122 // backup checks if the node is ready to back up (can commit phase) 123 // 124 // Moreover it starts a goroutine in the background which waits for the 125 // next instruction from the coordinator (second phase). 126 // It will start the backup as soon as it receives an ack, or abort otherwise 127 func (b *backupper) backup(ctx context.Context, 128 store nodeStore, req *Request, 129 ) (CanCommitResponse, error) { 130 id := req.ID 131 expiration := req.Duration 132 if expiration > _TimeoutShardCommit { 133 expiration = _TimeoutShardCommit 134 } 135 ret := CanCommitResponse{ 136 Method: OpCreate, 137 ID: req.ID, 138 Timeout: expiration, 139 } 140 // make sure there is no active backup 141 if prevID := b.lastOp.renew(id, store.HomeDir()); prevID != "" { 142 return ret, fmt.Errorf("backup %s already in progress", prevID) 143 } 144 b.waitingForCoordinatorToCommit.Store(true) // is set to false by wait() 145 // waits for ack from coordinator in order to processed with the backup 146 f := func() { 147 defer b.lastOp.reset() 148 if err := b.waitForCoordinator(expiration, id); err != nil { 149 b.logger.WithField("action", "create_backup"). 150 Error(err) 151 b.lastAsyncError = err 152 return 153 154 } 155 provider := newUploader(b.sourcer, store, req.ID, b.lastOp.set, b.logger). 156 withCompression(newZipConfig(req.Compression)) 157 158 result := backup.BackupDescriptor{ 159 StartedAt: time.Now().UTC(), 160 ID: id, 161 Classes: make([]backup.ClassDescriptor, 0, len(req.Classes)), 162 Version: Version, 163 ServerVersion: config.ServerVersion, 164 } 165 166 // the coordinator might want to abort the backup 167 done := make(chan struct{}) 168 ctx := b.withCancellation(context.Background(), id, done, b.logger) 169 defer close(done) 170 171 logFields := logrus.Fields{"action": "create_backup", "backup_id": req.ID} 172 if err := provider.all(ctx, req.Classes, &result); err != nil { 173 b.logger.WithFields(logFields).Error(err) 174 b.lastAsyncError = err 175 176 } else { 177 b.logger.WithFields(logFields).Info("backup completed successfully") 178 } 179 result.CompletedAt = time.Now().UTC() 180 } 181 enterrors.GoWrapper(f, b.logger) 182 183 return ret, nil 184 }