github.com/weaviate/weaviate@v1.24.6/usecases/backup/handler.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 "fmt" 17 "regexp" 18 "time" 19 20 "github.com/sirupsen/logrus" 21 "github.com/weaviate/weaviate/entities/backup" 22 "github.com/weaviate/weaviate/entities/models" 23 "github.com/weaviate/weaviate/entities/modulecapabilities" 24 ) 25 26 // Version of backup structure 27 const ( 28 // Version > version1 support compression 29 Version = "2.0" 30 // version1 store plain files without compression 31 version1 = "1.0" 32 ) 33 34 // TODO error handling need to be implemented properly. 35 // Current error handling is not idiomatic and relays on string comparisons which makes testing very brittle. 36 37 var regExpID = regexp.MustCompile("^[a-z0-9_-]+$") 38 39 type BackupBackendProvider interface { 40 BackupBackend(backend string) (modulecapabilities.BackupBackend, error) 41 } 42 43 type authorizer interface { 44 Authorize(principal *models.Principal, verb, resource string) error 45 } 46 47 type schemaManger interface { 48 RestoreClass(ctx context.Context, d *backup.ClassDescriptor, nodeMapping map[string]string) error 49 NodeName() string 50 } 51 52 type nodeResolver interface { 53 NodeHostname(nodeName string) (string, bool) 54 AllNames() []string 55 NodeCount() int 56 } 57 58 type Status struct { 59 Path string 60 StartedAt time.Time 61 CompletedAt time.Time 62 Status backup.Status 63 Err string 64 } 65 66 type Handler struct { 67 node string 68 // deps 69 logger logrus.FieldLogger 70 authorizer authorizer 71 backupper *backupper 72 restorer *restorer 73 backends BackupBackendProvider 74 } 75 76 func NewHandler( 77 logger logrus.FieldLogger, 78 authorizer authorizer, 79 schema schemaManger, 80 sourcer Sourcer, 81 backends BackupBackendProvider, 82 ) *Handler { 83 node := schema.NodeName() 84 m := &Handler{ 85 node: node, 86 logger: logger, 87 authorizer: authorizer, 88 backends: backends, 89 backupper: newBackupper(node, logger, 90 sourcer, 91 backends), 92 restorer: newRestorer(node, logger, 93 sourcer, 94 backends, 95 schema, 96 ), 97 } 98 return m 99 } 100 101 // Compression is the compression configuration. 102 type Compression struct { 103 // Level is one of DefaultCompression, BestSpeed, BestCompression 104 Level CompressionLevel 105 106 // ChunkSize represents the desired size for chunks between 1 - 512 MB 107 // However, during compression, the chunk size might 108 // slightly deviate from this value, being either slightly 109 // below or above the specified size 110 ChunkSize int 111 112 // CPUPercentage desired CPU core utilization (1%-80%), default: 50% 113 CPUPercentage int 114 } 115 116 // BackupRequest a transition request from API to Backend. 117 type BackupRequest struct { 118 // Compression is the compression configuration. 119 Compression 120 121 // ID is the backup ID 122 ID string 123 // Backend specify on which backend to store backups (gcs, s3, ..) 124 Backend string 125 126 // Include is list of class which need to be backed up 127 // The same class cannot appear in both Include and Exclude in the same request 128 Include []string 129 // Exclude means include all classes but those specified in Exclude 130 // The same class cannot appear in both Include and Exclude in the same request 131 Exclude []string 132 133 // NodeMapping is a map of node name replacement where key is the old name and value is the new name 134 // No effect if the map is empty 135 NodeMapping map[string]string 136 } 137 138 // OnCanCommit will be triggered when coordinator asks the node to participate 139 // in a distributed backup operation 140 func (m *Handler) OnCanCommit(ctx context.Context, req *Request) *CanCommitResponse { 141 ret := &CanCommitResponse{Method: req.Method, ID: req.ID} 142 143 nodeName := m.node 144 // If we are doing a restore and have a nodeMapping specified, ensure we use the "old" node name from the backup to retrieve/store the 145 // backup information. 146 if req.Method == OpRestore { 147 for oldNodeName, newNodeName := range req.NodeMapping { 148 if nodeName == newNodeName { 149 nodeName = oldNodeName 150 break 151 } 152 } 153 } 154 store, err := nodeBackend(nodeName, m.backends, req.Backend, req.ID) 155 if err != nil { 156 ret.Err = fmt.Sprintf("no backup backend %q, did you enable the right module?", req.Backend) 157 return ret 158 } 159 160 switch req.Method { 161 case OpCreate: 162 if err := m.backupper.sourcer.Backupable(ctx, req.Classes); err != nil { 163 ret.Err = err.Error() 164 return ret 165 } 166 if err = store.Initialize(ctx); err != nil { 167 ret.Err = fmt.Sprintf("init uploader: %v", err) 168 return ret 169 } 170 res, err := m.backupper.backup(ctx, store, req) 171 if err != nil { 172 ret.Err = err.Error() 173 return ret 174 } 175 ret.Timeout = res.Timeout 176 case OpRestore: 177 meta, _, err := m.restorer.validate(ctx, &store, req) 178 if err != nil { 179 ret.Err = err.Error() 180 return ret 181 } 182 res, err := m.restorer.restore(ctx, req, meta, store) 183 if err != nil { 184 ret.Err = err.Error() 185 return ret 186 } 187 ret.Timeout = res.Timeout 188 default: 189 ret.Err = fmt.Sprintf("unknown backup operation: %s", req.Method) 190 return ret 191 } 192 193 return ret 194 } 195 196 // OnCommit will be triggered when the coordinator confirms the execution of a previous operation 197 func (m *Handler) OnCommit(ctx context.Context, req *StatusRequest) (err error) { 198 switch req.Method { 199 case OpCreate: 200 return m.backupper.OnCommit(ctx, req) 201 case OpRestore: 202 return m.restorer.OnCommit(ctx, req) 203 default: 204 return fmt.Errorf("%w: %s", errUnknownOp, req.Method) 205 } 206 } 207 208 // OnAbort will be triggered when the coordinator abort the execution of a previous operation 209 func (m *Handler) OnAbort(ctx context.Context, req *AbortRequest) error { 210 switch req.Method { 211 case OpCreate: 212 return m.backupper.OnAbort(ctx, req) 213 case OpRestore: 214 return m.restorer.OnAbort(ctx, req) 215 default: 216 return fmt.Errorf("%w: %s", errUnknownOp, req.Method) 217 218 } 219 } 220 221 func (m *Handler) OnStatus(ctx context.Context, req *StatusRequest) *StatusResponse { 222 ret := StatusResponse{ 223 Method: req.Method, 224 ID: req.ID, 225 } 226 switch req.Method { 227 case OpCreate: 228 st, err := m.backupper.OnStatus(ctx, req) 229 ret.Status = st.Status 230 if err != nil { 231 ret.Status = backup.Failed 232 ret.Err = err.Error() 233 } 234 case OpRestore: 235 st, err := m.restorer.status(req.Backend, req.ID) 236 ret.Status = st.Status 237 ret.Err = st.Err 238 if err != nil { 239 ret.Status = backup.Failed 240 ret.Err = err.Error() 241 } else if st.Err != "" { 242 ret.Err = st.Err 243 } 244 default: 245 ret.Status = backup.Failed 246 ret.Err = fmt.Sprintf("%v: %s", errUnknownOp, req.Method) 247 } 248 249 return &ret 250 } 251 252 func validateID(backupID string) error { 253 if !regExpID.MatchString(backupID) { 254 return fmt.Errorf("invalid backup id: allowed characters are lowercase, 0-9, _, -") 255 } 256 return nil 257 } 258 259 func nodeBackend(node string, provider BackupBackendProvider, backend, id string) (nodeStore, error) { 260 caps, err := provider.BackupBackend(backend) 261 if err != nil { 262 return nodeStore{}, err 263 } 264 return nodeStore{objStore{b: caps, BasePath: fmt.Sprintf("%s/%s", id, node)}}, nil 265 } 266 267 // basePath of the backup 268 func basePath(backendType, backupID string) string { 269 return fmt.Sprintf("%s/%s", backendType, backupID) 270 } 271 272 func filterClasses(classes, excludes []string) []string { 273 if len(excludes) == 0 { 274 return classes 275 } 276 m := make(map[string]struct{}, len(classes)) 277 for _, c := range classes { 278 m[c] = struct{}{} 279 } 280 for _, x := range excludes { 281 delete(m, x) 282 } 283 if len(classes) != len(m) { 284 classes = classes[:len(m)] 285 i := 0 286 for k := range m { 287 classes[i] = k 288 i++ 289 } 290 } 291 292 return classes 293 }