github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/externalresource/internal/bucket/file_manager.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package bucket 15 16 import ( 17 "context" 18 "fmt" 19 "strings" 20 "sync" 21 22 "github.com/pingcap/log" 23 brStorage "github.com/pingcap/tidb/br/pkg/storage" 24 "github.com/pingcap/tiflow/engine/model" 25 "github.com/pingcap/tiflow/engine/pkg/externalresource/internal" 26 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 27 "github.com/pingcap/tiflow/pkg/errors" 28 "go.uber.org/zap" 29 ) 30 31 const ( 32 // TODO: use the contents of placeholder to indicate persistent status 33 placeholderFileName = ".keep" 34 ) 35 36 type persistedResources map[resModel.ResourceName]struct{} 37 38 func newPersistedResources() persistedResources { 39 return make(map[resModel.ResourceName]struct{}) 40 } 41 42 func (f persistedResources) SetPersisted(ident internal.ResourceIdent) bool { 43 if _, ok := f[ident.Name]; ok { 44 return false 45 } 46 f[ident.Name] = struct{}{} 47 return true 48 } 49 50 func (f persistedResources) UnsetPersisted(ident internal.ResourceIdent) bool { 51 if _, ok := f[ident.Name]; !ok { 52 return false 53 } 54 delete(f, ident.Name) 55 return true 56 } 57 58 // FileManager manages resource files stored on s3. 59 type FileManager struct { 60 executorID model.ExecutorID 61 storageCreator Creator 62 63 mu sync.RWMutex 64 persistedResMap map[resModel.WorkerID]persistedResources 65 } 66 67 // NewFileManagerWithConfig returns a new bucket FileManager. 68 // Note that the lifetime of the returned object should span the whole 69 // lifetime of the executor. 70 func NewFileManagerWithConfig( 71 executorID resModel.ExecutorID, config *resModel.Config, 72 ) *FileManager { 73 creator := NewCreator(config) 74 return NewFileManager(executorID, creator) 75 } 76 77 // NewFileManager creates a new bucket FileManager. 78 func NewFileManager( 79 executorID resModel.ExecutorID, 80 creator Creator, 81 ) *FileManager { 82 return &FileManager{ 83 executorID: executorID, 84 storageCreator: creator, 85 persistedResMap: make(map[string]persistedResources), 86 } 87 } 88 89 // CreateResource creates a new resource on s3. 90 // It can only be used to create resources that belong to the current executor. 91 func (m *FileManager) CreateResource( 92 ctx context.Context, ident internal.ResourceIdent, 93 ) (internal.ResourceDescriptor, error) { 94 m.validateExecutor(ident.Executor, ident) 95 desc := newResourceDescriptor(ident, m.storageCreator) 96 storage, err := desc.ExternalStorage(ctx) 97 if err != nil { 98 return nil, err 99 } 100 101 if err := createPlaceholderFile(ctx, storage); err != nil { 102 return nil, err 103 } 104 return desc, nil 105 } 106 107 // GetPersistedResource returns the descriptor of a resource that has already 108 // been marked as persisted. 109 // Note that GetPersistedResource will work on any executor for any persisted resource. 110 func (m *FileManager) GetPersistedResource( 111 ctx context.Context, ident internal.ResourceIdent, 112 ) (internal.ResourceDescriptor, error) { 113 desc := newResourceDescriptor(ident, m.storageCreator) 114 storage, err := desc.ExternalStorage(ctx) 115 if err != nil { 116 return nil, err 117 } 118 119 ok, err := storage.FileExists(ctx, placeholderFileName) 120 if err != nil { 121 return nil, errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("check placeholder file") 122 } 123 if !ok { 124 return nil, errors.ErrResourceFilesNotFound.GenWithStackByArgs() 125 } 126 127 return desc, nil 128 } 129 130 // CleanOrRecreatePersistedResource cleans the bucket directory or recreates placeholder 131 // file of the given resource. 132 // Note that CleanOrRecreatePersistedResource will work on any executor for any persisted resource. 133 func (m *FileManager) CleanOrRecreatePersistedResource( 134 ctx context.Context, ident internal.ResourceIdent, 135 ) (internal.ResourceDescriptor, error) { 136 desc, err := m.GetPersistedResource(ctx, ident) 137 if errors.Is(err, errors.ErrResourceFilesNotFound) { 138 desc := newResourceDescriptor(ident, m.storageCreator) 139 storage, err := desc.ExternalStorage(ctx) 140 if err != nil { 141 return nil, err 142 } 143 144 if err := createPlaceholderFile(ctx, storage); err != nil { 145 return nil, err 146 } 147 return desc, nil 148 } 149 if err != nil { 150 return nil, err 151 } 152 153 err = m.removeFilesIf(ctx, ident.Scope(), getPathPredByName(ident.Name, true)) 154 if err != nil { 155 return nil, err 156 } 157 158 return desc, nil 159 } 160 161 // RemoveTemporaryFiles removes all temporary resources (those that are not persisted). 162 // It can only be used to clean up resources created by the local executor. 163 func (m *FileManager) RemoveTemporaryFiles( 164 ctx context.Context, scope internal.ResourceScope, 165 ) error { 166 m.validateExecutor(scope.Executor, scope) 167 if scope.WorkerID == "" { 168 return m.removeTemporaryFilesForExecutor(ctx, scope) 169 } 170 return m.removeTemporaryFilesForWorker(ctx, scope) 171 } 172 173 func (m *FileManager) removeTemporaryFilesForWorker( 174 ctx context.Context, scope internal.ResourceScope, 175 ) error { 176 m.mu.RLock() 177 resources, ok := m.persistedResMap[scope.WorkerID] 178 // unlock here is safe because `resources` will not be changed after worker exits. 179 m.mu.RUnlock() 180 181 log.Info("Removing temporary resources for single worker", zap.Any("scope", scope)) 182 if !ok { 183 return m.removeFilesIf(ctx, scope, getPathPredAlwaysTrue()) 184 } 185 186 return m.removeFilesIf(ctx, scope, getPathPredByPersistedResources(resources, 1)) 187 } 188 189 func (m *FileManager) removeTemporaryFilesForExecutor( 190 ctx context.Context, scope internal.ResourceScope, 191 ) error { 192 // Get all persisted files which is created by current executor. 193 persistedResSet := make(map[string]struct{}) 194 195 m.mu.RLock() 196 for workerID, resources := range m.persistedResMap { 197 for resName := range resources { 198 resPath := fmt.Sprintf("%s/%s", workerID, resName) 199 persistedResSet[resPath] = struct{}{} 200 } 201 } 202 m.mu.RUnlock() 203 204 return m.removeAllTemporaryFilesByMeta(ctx, scope, persistedResSet) 205 } 206 207 // removeAllTemporaryFilesByMeta removes all temporary resources located in the given scope. 208 // Note that this function could be called from executor and master. 209 func (m *FileManager) removeAllTemporaryFilesByMeta( 210 ctx context.Context, 211 scope internal.ResourceScope, 212 persistedResSet map[string]struct{}, 213 ) error { 214 log.Info("Removing temporary resources for executor", zap.Any("scope", scope)) 215 216 return m.removeFilesIf(ctx, scope, getPathPredByPersistedResources(persistedResSet, 2)) 217 } 218 219 // RemoveResource removes a resource from s3. 220 // It can be called on any executor node. 221 func (m *FileManager) RemoveResource( 222 ctx context.Context, ident internal.ResourceIdent, 223 ) error { 224 log.Info("Removing resource", 225 zap.Any("ident", ident)) 226 227 err := m.removeFilesIf(ctx, ident.Scope(), getPathPredByName(ident.Name, false)) 228 if err != nil { 229 return err 230 } 231 232 if m.executorID == ident.Executor { 233 // Remove from persistedResMap 234 m.mu.Lock() 235 defer m.mu.Unlock() 236 if resources, ok := m.persistedResMap[ident.WorkerID]; ok { 237 resources.UnsetPersisted(ident) 238 } 239 } 240 return nil 241 } 242 243 // SetPersisted marks a resource as persisted. It can only be called 244 // on the creator of the resource. 245 func (m *FileManager) SetPersisted( 246 ctx context.Context, ident internal.ResourceIdent, 247 ) error { 248 m.validateExecutor(ident.Executor, ident) 249 250 m.mu.Lock() 251 defer m.mu.Unlock() 252 resources, ok := m.persistedResMap[ident.WorkerID] 253 if !ok { 254 resources = newPersistedResources() 255 m.persistedResMap[ident.WorkerID] = resources 256 } 257 258 ok = resources.SetPersisted(ident) 259 if !ok { 260 log.Warn("resource is already persisted", 261 zap.Any("ident", ident)) 262 } 263 return nil 264 } 265 266 func (m *FileManager) validateExecutor(creator model.ExecutorID, res interface{}) { 267 if creator != m.executorID { 268 log.Panic("inconsistent executor ID of bucket file", 269 zap.Any("resource", res), 270 zap.Any("creator", creator), 271 zap.String("currentExecutor", string(m.executorID))) 272 } 273 } 274 275 func (m *FileManager) removeFilesIf( 276 ctx context.Context, 277 scope internal.ResourceScope, 278 pred func(path string) bool, 279 ) error { 280 // TODO: add a cache here to reuse storage. 281 storage, err := m.storageCreator.newBucketForScope(ctx, scope) 282 if err != nil { 283 return err 284 } 285 286 var toRemoveFiles []string 287 err = storage.WalkDir(ctx, &brStorage.WalkOption{}, func(path string, _ int64) error { 288 path = strings.TrimPrefix(path, "/") 289 if pred(path) { 290 toRemoveFiles = append(toRemoveFiles, path) 291 } 292 return nil 293 }) 294 if err != nil { 295 return errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("RemoveTemporaryFiles") 296 } 297 298 log.Info("Removing resources", 299 zap.Any("scope", scope), 300 zap.Any("numOfToRemoveFiles", len(toRemoveFiles))) 301 log.Debug("Removing files", zap.Any("toRemoveFiles", toRemoveFiles)) 302 303 for _, path := range toRemoveFiles { 304 if err := storage.DeleteFile(ctx, path); err != nil { 305 return errors.ErrExternalStorageAPI.Wrap(err) 306 } 307 } 308 return nil 309 } 310 311 func createPlaceholderFile(ctx context.Context, storage brStorage.ExternalStorage) error { 312 exists, err := storage.FileExists(ctx, placeholderFileName) 313 if err != nil { 314 return errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("checking placeholder file") 315 } 316 if exists { 317 // This should not happen in production. Unless the caller of the FileManager has a bug. 318 return errors.ErrExternalStorageAPI.GenWithStackByArgs("resource already exists") 319 } 320 321 writer, err := storage.Create(ctx, placeholderFileName, nil) 322 if err != nil { 323 return errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("creating placeholder file") 324 } 325 326 _, err = writer.Write(ctx, []byte("placeholder")) 327 if err != nil { 328 return errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("writing placeholder file") 329 } 330 331 if err := writer.Close(ctx); err != nil { 332 return errors.ErrExternalStorageAPI.Wrap(err).GenWithStackByArgs("closing placeholder file") 333 } 334 return nil 335 } 336 337 // PreCheckConfig does a preflight check on the executor's storage configurations. 338 func PreCheckConfig(config *resModel.Config) error { 339 // TODO: use customized retry policy. 340 log.Debug("pre-checking s3Storage config", zap.Any("config", config)) 341 creator := NewCreator(config) 342 _, err := creator.newBucketForScope(context.Background(), internal.ResourceScope{}) 343 if err != nil { 344 return err 345 } 346 return nil 347 }