| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * MinIO Cloud Storage, (C) 2020 MinIO, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"runtime/debug" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // localMetacacheMgr is the *local* manager for this peer.
 | 
					
						
							|  |  |  | // It should never be used directly since buckets are
 | 
					
						
							|  |  |  | // distributed deterministically.
 | 
					
						
							|  |  |  | // Therefore no cluster locks are required.
 | 
					
						
							|  |  |  | var localMetacacheMgr = &metacacheManager{ | 
					
						
							|  |  |  | 	buckets: make(map[string]*bucketMetacache), | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	trash:   make(map[string]metacache), | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type metacacheManager struct { | 
					
						
							|  |  |  | 	mu      sync.RWMutex | 
					
						
							|  |  |  | 	init    sync.Once | 
					
						
							|  |  |  | 	buckets map[string]*bucketMetacache | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	trash   map[string]metacache // Recently deleted lists.
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const metacacheManagerTransientBucket = "**transient**" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // initManager will start async saving the cache.
 | 
					
						
							|  |  |  | func (m *metacacheManager) initManager() { | 
					
						
							|  |  |  | 	// Add a transient bucket.
 | 
					
						
							| 
									
										
										
										
											2020-11-01 01:26:16 +08:00
										 |  |  | 	tb := newBucketMetacache(metacacheManagerTransientBucket, false) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	tb.transient = true | 
					
						
							|  |  |  | 	m.buckets[metacacheManagerTransientBucket] = tb | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start saver when object layer is ready.
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		objAPI := newObjectLayerFn() | 
					
						
							|  |  |  | 		for objAPI == nil { | 
					
						
							|  |  |  | 			time.Sleep(time.Second) | 
					
						
							|  |  |  | 			objAPI = newObjectLayerFn() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !globalIsErasure { | 
					
						
							|  |  |  | 			logger.Info("metacacheManager was initialized in non-erasure mode, skipping save") | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t := time.NewTicker(time.Minute) | 
					
						
							|  |  |  | 		var exit bool | 
					
						
							|  |  |  | 		bg := context.Background() | 
					
						
							|  |  |  | 		for !exit { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-t.C: | 
					
						
							|  |  |  | 			case <-GlobalContext.Done(): | 
					
						
							|  |  |  | 				exit = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			m.mu.RLock() | 
					
						
							|  |  |  | 			for _, v := range m.buckets { | 
					
						
							|  |  |  | 				if !exit { | 
					
						
							|  |  |  | 					v.cleanup() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				logger.LogIf(bg, v.save(bg)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			m.mu.RUnlock() | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 			m.mu.Lock() | 
					
						
							|  |  |  | 			for k, v := range m.trash { | 
					
						
							|  |  |  | 				if time.Since(v.lastUpdate) > metacacheMaxRunningAge { | 
					
						
							|  |  |  | 					v.delete(context.Background()) | 
					
						
							|  |  |  | 					delete(m.trash, k) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			m.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		m.getTransient().deleteAll() | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | // findCache will get a metacache.
 | 
					
						
							|  |  |  | func (m *metacacheManager) findCache(ctx context.Context, o listPathOptions) metacache { | 
					
						
							|  |  |  | 	if o.Transient || isReservedOrInvalidBucket(o.Bucket, false) { | 
					
						
							|  |  |  | 		return m.getTransient().findCache(o) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.mu.RLock() | 
					
						
							|  |  |  | 	b, ok := m.buckets[o.Bucket] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		m.mu.RUnlock() | 
					
						
							|  |  |  | 		return b.findCache(o) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if meta, ok := m.trash[o.ID]; ok { | 
					
						
							|  |  |  | 		m.mu.RUnlock() | 
					
						
							|  |  |  | 		return meta | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.mu.RUnlock() | 
					
						
							|  |  |  | 	return m.getBucket(ctx, o.Bucket).findCache(o) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // updateCacheEntry will update non-transient state.
 | 
					
						
							|  |  |  | func (m *metacacheManager) updateCacheEntry(update metacache) (metacache, error) { | 
					
						
							|  |  |  | 	m.mu.RLock() | 
					
						
							|  |  |  | 	if meta, ok := m.trash[update.id]; ok { | 
					
						
							|  |  |  | 		m.mu.RUnlock() | 
					
						
							|  |  |  | 		return meta, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	b, ok := m.buckets[update.bucket] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		m.mu.RUnlock() | 
					
						
							|  |  |  | 		return b.updateCacheEntry(update) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.mu.RUnlock() | 
					
						
							|  |  |  | 	// We should have either a trashed bucket or this
 | 
					
						
							|  |  |  | 	return metacache{}, errVolumeNotFound | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | // getBucket will get a bucket metacache or load it from disk if needed.
 | 
					
						
							|  |  |  | func (m *metacacheManager) getBucket(ctx context.Context, bucket string) *bucketMetacache { | 
					
						
							|  |  |  | 	m.init.Do(m.initManager) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return a transient bucket for invalid or system buckets.
 | 
					
						
							|  |  |  | 	if isReservedOrInvalidBucket(bucket, false) { | 
					
						
							|  |  |  | 		return m.getTransient() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.mu.RLock() | 
					
						
							|  |  |  | 	b, ok := m.buckets[bucket] | 
					
						
							|  |  |  | 	m.mu.RUnlock() | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		if b.bucket != bucket { | 
					
						
							|  |  |  | 			logger.Info("getBucket: cached bucket %s does not match this bucket %s", b.bucket, bucket) | 
					
						
							|  |  |  | 			debug.PrintStack() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return b | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.mu.Lock() | 
					
						
							|  |  |  | 	// See if someone else fetched it while we waited for the lock.
 | 
					
						
							|  |  |  | 	b, ok = m.buckets[bucket] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		m.mu.Unlock() | 
					
						
							|  |  |  | 		if b.bucket != bucket { | 
					
						
							|  |  |  | 			logger.Info("getBucket: newly cached bucket %s does not match this bucket %s", b.bucket, bucket) | 
					
						
							|  |  |  | 			debug.PrintStack() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return b | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Load bucket. If we fail return the transient bucket.
 | 
					
						
							|  |  |  | 	b, err := loadBucketMetaCache(ctx, bucket) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		m.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 		logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		return m.getTransient() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if b.bucket != bucket { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, fmt.Errorf("getBucket: loaded bucket %s does not match this bucket %s", b.bucket, bucket)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.buckets[bucket] = b | 
					
						
							|  |  |  | 	m.mu.Unlock() | 
					
						
							|  |  |  | 	return b | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 00:46:18 +08:00
										 |  |  | // deleteBucketCache will delete the bucket cache if it exists.
 | 
					
						
							|  |  |  | func (m *metacacheManager) deleteBucketCache(bucket string) { | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	m.init.Do(m.initManager) | 
					
						
							| 
									
										
										
										
											2020-11-01 00:46:18 +08:00
										 |  |  | 	m.mu.Lock() | 
					
						
							|  |  |  | 	b, ok := m.buckets[bucket] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 		m.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2020-11-01 00:46:18 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	delete(m.buckets, bucket) | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	m.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Since deletes may take some time we try to do it without
 | 
					
						
							|  |  |  | 	// holding lock to m all the time.
 | 
					
						
							|  |  |  | 	b.mu.Lock() | 
					
						
							|  |  |  | 	defer b.mu.Unlock() | 
					
						
							|  |  |  | 	for k, v := range b.caches { | 
					
						
							|  |  |  | 		if time.Since(v.lastUpdate) > metacacheMaxRunningAge { | 
					
						
							|  |  |  | 			v.delete(context.Background()) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		v.error = "Bucket deleted" | 
					
						
							|  |  |  | 		v.status = scanStateError | 
					
						
							|  |  |  | 		m.mu.Lock() | 
					
						
							|  |  |  | 		m.trash[k] = v | 
					
						
							|  |  |  | 		m.mu.Unlock() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-01 00:46:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | // deleteAll will delete all caches.
 | 
					
						
							|  |  |  | func (m *metacacheManager) deleteAll() { | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	m.init.Do(m.initManager) | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 	m.mu.Lock() | 
					
						
							|  |  |  | 	defer m.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 	for bucket, b := range m.buckets { | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 		b.deleteAll() | 
					
						
							| 
									
										
										
										
											2020-11-04 04:47:52 +08:00
										 |  |  | 		if !b.transient { | 
					
						
							|  |  |  | 			delete(m.buckets, bucket) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | // getTransient will return a transient bucket.
 | 
					
						
							|  |  |  | func (m *metacacheManager) getTransient() *bucketMetacache { | 
					
						
							|  |  |  | 	m.init.Do(m.initManager) | 
					
						
							|  |  |  | 	m.mu.RLock() | 
					
						
							|  |  |  | 	bmc := m.buckets[metacacheManagerTransientBucket] | 
					
						
							|  |  |  | 	m.mu.RUnlock() | 
					
						
							|  |  |  | 	return bmc | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // checkMetacacheState should be used if data is not updating.
 | 
					
						
							|  |  |  | // Should only be called if a failure occurred.
 | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | func (o listPathOptions) checkMetacacheState(ctx context.Context, rpc *peerRESTClient) error { | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	// We operate on a copy...
 | 
					
						
							|  |  |  | 	o.Create = false | 
					
						
							|  |  |  | 	var cache metacache | 
					
						
							| 
									
										
										
										
											2020-11-07 00:54:09 +08:00
										 |  |  | 	if rpc == nil || o.Transient { | 
					
						
							|  |  |  | 		cache = localMetacacheMgr.findCache(ctx, o) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		c, err := rpc.GetMetacacheListing(ctx, o) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-07 00:54:09 +08:00
										 |  |  | 		cache = *c | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 	if cache.status == scanStateNone || cache.fileNotFound { | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		return errFileNotFound | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-03 09:20:27 +08:00
										 |  |  | 	if cache.status == scanStateSuccess || cache.status == scanStateStarted { | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 		if time.Since(cache.lastUpdate) > metacacheMaxRunningAge { | 
					
						
							| 
									
										
										
										
											2020-11-07 00:54:09 +08:00
										 |  |  | 			// We got a stale entry, mark error on handling server.
 | 
					
						
							|  |  |  | 			err := fmt.Errorf("timeout: list %s not updated", cache.id) | 
					
						
							|  |  |  | 			cache.error = err.Error() | 
					
						
							|  |  |  | 			cache.status = scanStateError | 
					
						
							|  |  |  | 			if rpc == nil || o.Transient { | 
					
						
							|  |  |  | 				localMetacacheMgr.updateCacheEntry(cache) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				rpc.UpdateMetacacheListing(ctx, cache) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cache.error != "" { | 
					
						
							| 
									
										
										
										
											2020-10-31 00:33:16 +08:00
										 |  |  | 		return fmt.Errorf("async cache listing failed with: %s", cache.error) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |