github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/storage/utils.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 storage 15 16 import ( 17 "context" 18 "os" 19 "path" 20 "path/filepath" 21 "strings" 22 23 gstorage "cloud.google.com/go/storage" 24 "github.com/aws/aws-sdk-go/aws/awserr" 25 "github.com/aws/aws-sdk-go/service/s3" 26 "github.com/pingcap/errors" 27 bstorage "github.com/pingcap/tidb/br/pkg/storage" 28 ) 29 30 // AdjustPath adjust rawURL, add uniqueId as path suffix, returns a new path. 31 // This function supports both local dir or s3 path. It can be used like the following: 32 // 1. adjust subtask's `LoaderConfig.Dir`, uniqueID like `.test-mysql01`. 33 // 2. add Lightning checkpoint's fileName to rawURL, uniqueID like `/tidb_lightning_checkpoint.pb`. 34 func AdjustPath(rawURL string, uniqueID string) (string, error) { 35 if rawURL == "" || uniqueID == "" { 36 return rawURL, nil 37 } 38 u, err := bstorage.ParseRawURL(rawURL) 39 if err != nil { 40 return "", errors.Trace(err) 41 } 42 // not url format, we don't use url library to avoid being escaped or unescaped 43 if u.Scheme == "" { 44 // avoid duplicate add uniqueID, and trim suffix '/' like './dump_data/' 45 trimPath := strings.TrimRight(rawURL, string(filepath.Separator)) 46 if !strings.HasSuffix(trimPath, uniqueID) { 47 return trimPath + uniqueID, nil 48 } 49 return rawURL, nil 50 } 51 // u.Path is an unescaped string and can be used as normal 52 trimPath := strings.TrimRight(u.Path, string(filepath.Separator)) 53 if !strings.HasSuffix(trimPath, uniqueID) { 54 u.Path = trimPath + uniqueID 55 // u.String will return escaped url and can be used safely in other steps 56 return u.String(), err 57 } 58 return rawURL, nil 59 } 60 61 // TrimPath trims rawURL suffix which is uniqueID, supports local and s3. 62 func TrimPath(rawURL string, uniqueID string) (string, error) { 63 if rawURL == "" || uniqueID == "" { 64 return rawURL, nil 65 } 66 u, err := bstorage.ParseRawURL(rawURL) 67 if err != nil { 68 return "", errors.Trace(err) 69 } 70 // not url format, we don't use url library to avoid being escaped or unescaped 71 if u.Scheme == "" { 72 return strings.TrimSuffix(rawURL, uniqueID), nil 73 } 74 // u.Path is an unescaped string and can be used as normal 75 u.Path = strings.TrimSuffix(u.Path, uniqueID) 76 // u.String will return escaped url and can be used safely in other steps 77 return u.String(), err 78 } 79 80 // IsS3Path judges if rawURL is s3 path. 81 func IsS3Path(rawURL string) bool { 82 if rawURL == "" { 83 return false 84 } 85 u, err := bstorage.ParseRawURL(rawURL) 86 if err != nil { 87 return false 88 } 89 return u.Scheme == "s3" 90 } 91 92 // IsLocalDiskPath judges if path is local disk path. 93 func IsLocalDiskPath(rawURL string) bool { 94 if rawURL == "" { 95 return false 96 } 97 u, err := bstorage.ParseRawURL(rawURL) 98 if err != nil { 99 return false 100 } 101 return u.Scheme == "" || u.Scheme == "file" 102 } 103 104 // CreateStorage creates ExternalStore. 105 func CreateStorage(ctx context.Context, path string) (bstorage.ExternalStorage, error) { 106 backend, err := bstorage.ParseBackend(path, nil) 107 if err != nil { 108 return nil, err 109 } 110 return bstorage.New(ctx, backend, &bstorage.ExternalStorageOptions{}) 111 } 112 113 // CollectDirFiles gets files in dir. 114 func CollectDirFiles(ctx context.Context, dir string, storage bstorage.ExternalStorage) (map[string]struct{}, error) { 115 var err error 116 if storage == nil { 117 storage, err = CreateStorage(ctx, dir) 118 if err != nil { 119 return nil, err 120 } 121 } 122 files := make(map[string]struct{}) 123 124 err = storage.WalkDir(ctx, &bstorage.WalkOption{}, func(filePath string, size int64) error { 125 name := path.Base(filePath) 126 files[name] = struct{}{} 127 return nil 128 }) 129 130 return files, err 131 } 132 133 // RemoveAll remove files in dir. 134 func RemoveAll(ctx context.Context, dir string, storage bstorage.ExternalStorage) error { 135 var err error 136 if storage == nil { 137 storage, err = CreateStorage(ctx, dir) 138 if err != nil { 139 return err 140 } 141 } 142 143 err = storage.WalkDir(ctx, &bstorage.WalkOption{}, func(filePath string, size int64) error { 144 err2 := storage.DeleteFile(ctx, filePath) 145 if errors.Cause(err2) == gstorage.ErrObjectNotExist { 146 // ignore not exist error when we delete files 147 return nil 148 } 149 return err2 150 }) 151 if err == nil { 152 err = storage.DeleteFile(ctx, "") 153 if errors.Cause(err) == gstorage.ErrObjectNotExist { 154 // ignore not exist error when we delete files 155 return nil 156 } 157 } 158 return err 159 } 160 161 func ReadFile(ctx context.Context, dir, fileName string, storage bstorage.ExternalStorage) ([]byte, error) { 162 var err error 163 if storage == nil { 164 storage, err = CreateStorage(ctx, dir) 165 if err != nil { 166 return nil, err 167 } 168 } 169 return storage.ReadFile(ctx, fileName) 170 } 171 172 func OpenFile(ctx context.Context, dir, fileName string, storage bstorage.ExternalStorage) (bstorage.ExternalFileReader, error) { 173 var err error 174 if storage == nil { 175 storage, err = CreateStorage(ctx, dir) 176 if err != nil { 177 return nil, err 178 } 179 } 180 return storage.Open(ctx, fileName, nil) 181 } 182 183 func IsNotExistError(err error) bool { 184 if err == nil { 185 return false 186 } 187 err = errors.Cause(err) 188 if os.IsNotExist(err) { 189 return true 190 } 191 if aerr, ok := err.(awserr.Error); ok { 192 switch aerr.Code() { 193 case s3.ErrCodeNoSuchBucket, s3.ErrCodeNoSuchKey, "NotFound": 194 return true 195 } 196 } 197 return false 198 }