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  }