storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/metacache-server-pool.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "os" 25 "path" 26 pathutil "path" 27 "strings" 28 "sync" 29 "time" 30 31 "storj.io/minio/cmd/logger" 32 ) 33 34 func renameAllBucketMetacache(epPath string) error { 35 // Rename all previous `.minio.sys/buckets/<bucketname>/.metacache` to 36 // to `.minio.sys/tmp/` for deletion. 37 return readDirFn(pathJoin(epPath, minioMetaBucket, bucketMetaPrefix), func(name string, typ os.FileMode) error { 38 if typ == os.ModeDir { 39 tmpMetacacheOld := pathutil.Join(epPath, minioMetaTmpDeletedBucket, mustGetUUID()) 40 if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)), 41 tmpMetacacheOld); err != nil && err != errFileNotFound { 42 return fmt.Errorf("unable to rename (%s -> %s) %w", 43 pathJoin(epPath, minioMetaBucket+metacachePrefixForID(minioMetaBucket, slashSeparator)), 44 tmpMetacacheOld, 45 osErrToFileErr(err)) 46 } 47 } 48 return nil 49 }) 50 } 51 52 // listPath will return the requested entries. 53 // If no more entries are in the listing io.EOF is returned, 54 // otherwise nil or an unexpected error is returned. 55 // The listPathOptions given will be checked and modified internally. 56 // Required important fields are Bucket, Prefix, Separator. 57 // Other important fields are Limit, Marker. 58 // List ID always derived from the Marker. 59 func (z *erasureServerPools) listPath(ctx context.Context, o listPathOptions) (entries metaCacheEntriesSorted, err error) { 60 if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil { 61 return entries, err 62 } 63 64 // Marker is set validate pre-condition. 65 if o.Marker != "" && o.Prefix != "" { 66 // Marker not common with prefix is not implemented. Send an empty response 67 if !HasPrefix(o.Marker, o.Prefix) { 68 return entries, io.EOF 69 } 70 } 71 72 // With max keys of zero we have reached eof, return right here. 73 if o.Limit == 0 { 74 return entries, io.EOF 75 } 76 77 // For delimiter and prefix as '/' we do not list anything at all 78 // along // with the prefix. On a flat namespace with 'prefix' 79 // as '/' we don't have any entries, since all the keys are 80 // of form 'keyName/...' 81 if strings.HasPrefix(o.Prefix, SlashSeparator) { 82 return entries, io.EOF 83 } 84 85 // If delimiter is slashSeparator we must return directories of 86 // the non-recursive scan unless explicitly requested. 87 o.IncludeDirectories = o.Separator == slashSeparator 88 if (o.Separator == slashSeparator || o.Separator == "") && !o.Recursive { 89 o.Recursive = o.Separator != slashSeparator 90 o.Separator = slashSeparator 91 } else { 92 // Default is recursive, if delimiter is set then list non recursive. 93 o.Recursive = true 94 } 95 96 // Decode and get the optional list id from the marker. 97 o.Marker, o.ID = parseMarker(o.Marker) 98 o.Create = o.ID == "" 99 if o.ID == "" { 100 o.ID = mustGetUUID() 101 } 102 o.BaseDir = baseDirFromPrefix(o.Prefix) 103 if o.discardResult { 104 // Override for single object. 105 o.BaseDir = o.Prefix 106 } 107 108 // For very small recursive listings, don't same cache. 109 // Attempts to avoid expensive listings to run for a long 110 // while when clients aren't interested in results. 111 // If the client DOES resume the listing a full cache 112 // will be generated due to the marker without ID and this check failing. 113 if o.Limit < 10 && o.Marker == "" && o.Create && o.Recursive { 114 o.discardResult = true 115 o.Transient = true 116 } 117 118 var cache metacache 119 // If we don't have a list id we must ask the server if it has a cache or create a new. 120 if o.Create { 121 o.CurrentCycle = intDataUpdateTracker.current() 122 o.OldestCycle = GlobalNotificationSys.findEarliestCleanBloomFilter(ctx, path.Join(o.Bucket, o.BaseDir)) 123 var cache metacache 124 rpc := GlobalNotificationSys.restClientFromHash(o.Bucket) 125 if isReservedOrInvalidBucket(o.Bucket, false) { 126 rpc = nil 127 o.Transient = true 128 } 129 // Apply prefix filter if enabled. 130 o.SetFilter() 131 if rpc == nil || o.Transient { 132 // Local 133 cache = localMetacacheMgr.findCache(ctx, o) 134 } else { 135 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 136 defer cancel() 137 c, err := rpc.GetMetacacheListing(ctx, o) 138 if err != nil { 139 if errors.Is(err, context.Canceled) { 140 // Context is canceled, return at once. 141 // request canceled, no entries to return 142 return entries, io.EOF 143 } 144 if !errors.Is(err, context.DeadlineExceeded) { 145 logger.LogIf(ctx, err) 146 } 147 o.Transient = true 148 cache = localMetacacheMgr.findCache(ctx, o) 149 } else { 150 cache = *c 151 } 152 } 153 if cache.fileNotFound { 154 // No cache found, no entries found. 155 return entries, io.EOF 156 } 157 // Only create if we created a new. 158 o.Create = o.ID == cache.id 159 o.ID = cache.id 160 } 161 162 var mu sync.Mutex 163 var wg sync.WaitGroup 164 var errs []error 165 allAtEOF := true 166 mu.Lock() 167 // Ask all sets and merge entries. 168 for _, pool := range z.serverPools { 169 for _, set := range pool.sets { 170 wg.Add(1) 171 go func(i int, set *erasureObjects) { 172 defer wg.Done() 173 e, err := set.listPath(ctx, o) 174 mu.Lock() 175 defer mu.Unlock() 176 if err == nil { 177 allAtEOF = false 178 } 179 errs[i] = err 180 entries.merge(e, -1) 181 182 // Resolve non-trivial conflicts 183 entries.deduplicate(func(existing, other *metaCacheEntry) (replace bool) { 184 if existing.isDir() { 185 return false 186 } 187 eFIV, err := existing.fileInfo(o.Bucket) 188 if err != nil { 189 return true 190 } 191 oFIV, err := existing.fileInfo(o.Bucket) 192 if err != nil { 193 return false 194 } 195 return oFIV.ModTime.After(eFIV.ModTime) 196 }) 197 if entries.len() > o.Limit { 198 allAtEOF = false 199 entries.truncate(o.Limit) 200 } 201 }(len(errs), set) 202 errs = append(errs, nil) 203 } 204 } 205 mu.Unlock() 206 wg.Wait() 207 208 if isAllNotFound(errs) { 209 // All sets returned not found. 210 go func() { 211 // Update master cache with that information. 212 cache.status = scanStateSuccess 213 cache.fileNotFound = true 214 o.updateMetacacheListing(cache, GlobalNotificationSys.restClientFromHash(o.Bucket)) 215 }() 216 // cache returned not found, entries truncated. 217 return entries, io.EOF 218 } 219 220 for _, err := range errs { 221 if err == nil { 222 allAtEOF = false 223 continue 224 } 225 if err.Error() == io.EOF.Error() { 226 continue 227 } 228 logger.LogIf(ctx, err) 229 return entries, err 230 } 231 truncated := entries.len() > o.Limit || !allAtEOF 232 entries.truncate(o.Limit) 233 if !o.discardResult { 234 entries.listID = o.ID 235 } 236 if !truncated { 237 return entries, io.EOF 238 } 239 return entries, nil 240 }