2019-11-13 15:17:45 -05:00
|
|
|
/*
|
2020-02-03 22:40:01 -05:00
|
|
|
* MinIO Cloud Storage, (C) 2019,2020 MinIO, Inc.
|
2019-11-13 15:17:45 -05:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2018-03-28 17:14:06 -04:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2020-11-02 13:00:45 -05:00
|
|
|
"strconv"
|
2019-05-22 17:54:15 -04:00
|
|
|
"strings"
|
2019-08-09 20:09:08 -04:00
|
|
|
"sync"
|
2020-06-15 12:05:35 -04:00
|
|
|
"sync/atomic"
|
2019-05-22 17:54:15 -04:00
|
|
|
"time"
|
2018-03-28 17:14:06 -04:00
|
|
|
|
2019-10-04 13:35:33 -04:00
|
|
|
"github.com/minio/minio/cmd/config/cache"
|
2020-11-02 13:00:45 -05:00
|
|
|
xhttp "github.com/minio/minio/cmd/http"
|
2018-04-05 18:04:40 -04:00
|
|
|
"github.com/minio/minio/cmd/logger"
|
2020-01-27 17:12:34 -05:00
|
|
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
2019-10-04 13:35:33 -04:00
|
|
|
"github.com/minio/minio/pkg/color"
|
2020-11-02 13:00:45 -05:00
|
|
|
"github.com/minio/minio/pkg/hash"
|
2019-10-14 12:44:51 -04:00
|
|
|
"github.com/minio/minio/pkg/sync/errgroup"
|
2018-07-27 18:32:19 -04:00
|
|
|
"github.com/minio/minio/pkg/wildcard"
|
2018-03-28 17:14:06 -04:00
|
|
|
)
|
|
|
|
|
2019-05-22 17:54:15 -04:00
|
|
|
const (
|
2020-11-02 13:00:45 -05:00
|
|
|
cacheBlkSize = 1 << 20
|
|
|
|
cacheGCInterval = time.Minute * 30
|
|
|
|
writeBackStatusHeader = ReservedMetadataPrefixLower + "write-back-status"
|
|
|
|
writeBackRetryHeader = ReservedMetadataPrefixLower + "write-back-retry"
|
2019-05-22 17:54:15 -04:00
|
|
|
)
|
|
|
|
|
2020-11-02 13:00:45 -05:00
|
|
|
type cacheCommitStatus string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// CommitPending - cache writeback with backend is pending.
|
|
|
|
CommitPending cacheCommitStatus = "pending"
|
|
|
|
|
|
|
|
// CommitComplete - cache writeback completed ok.
|
|
|
|
CommitComplete cacheCommitStatus = "complete"
|
|
|
|
|
|
|
|
// CommitFailed - cache writeback needs a retry.
|
|
|
|
CommitFailed cacheCommitStatus = "failed"
|
|
|
|
)
|
|
|
|
|
|
|
|
// String returns string representation of status
|
|
|
|
func (s cacheCommitStatus) String() string {
|
|
|
|
return string(s)
|
|
|
|
}
|
|
|
|
|
2018-05-30 14:30:14 -04:00
|
|
|
// CacheStorageInfo - represents total, free capacity of
|
|
|
|
// underlying cache storage.
|
|
|
|
type CacheStorageInfo struct {
|
|
|
|
Total uint64 // Total cache disk space.
|
|
|
|
Free uint64 // Free cache available space.
|
|
|
|
}
|
|
|
|
|
2018-03-28 17:14:06 -04:00
|
|
|
// CacheObjectLayer implements primitives for cache object API layer.
|
|
|
|
type CacheObjectLayer interface {
|
|
|
|
// Object operations.
|
2018-09-27 06:06:45 -04:00
|
|
|
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
|
2018-09-10 12:42:43 -04:00
|
|
|
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
2020-06-12 23:04:01 -04:00
|
|
|
DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)
|
|
|
|
DeleteObjects(ctx context.Context, bucket string, objects []ObjectToDelete, opts ObjectOptions) ([]DeletedObject, []error)
|
2019-09-05 10:20:16 -04:00
|
|
|
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
2020-02-07 17:36:46 -05:00
|
|
|
CopyObject(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
|
2018-03-28 17:14:06 -04:00
|
|
|
// Storage operations.
|
2018-05-30 14:30:14 -04:00
|
|
|
StorageInfo(ctx context.Context) CacheStorageInfo
|
2019-12-06 02:16:06 -05:00
|
|
|
CacheStats() *CacheStats
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// Abstracts disk caching - used by the S3 layer
|
|
|
|
type cacheObjects struct {
|
|
|
|
// slice of cache drives
|
|
|
|
cache []*diskCache
|
|
|
|
// file path patterns to exclude from cache
|
|
|
|
exclude []string
|
2020-02-03 22:40:01 -05:00
|
|
|
// number of accesses after which to cache an object
|
|
|
|
after int
|
2020-11-02 13:00:45 -05:00
|
|
|
// commit objects in async manner
|
|
|
|
commitWriteback bool
|
2019-08-09 20:09:08 -04:00
|
|
|
// if true migration is in progress from v1 to v2
|
|
|
|
migrating bool
|
|
|
|
// mutex to protect migration bool
|
|
|
|
migMutex sync.Mutex
|
2020-11-02 13:00:45 -05:00
|
|
|
// retry queue for writeback cache mode to reattempt upload to backend
|
|
|
|
wbRetryCh chan ObjectInfo
|
2019-12-06 02:16:06 -05:00
|
|
|
// Cache stats
|
|
|
|
cacheStats *CacheStats
|
|
|
|
|
2020-10-26 15:30:40 -04:00
|
|
|
InnerGetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
|
2020-10-26 18:41:24 -04:00
|
|
|
InnerGetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
|
|
|
InnerDeleteObjectFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
|
|
|
InnerPutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
|
|
|
InnerCopyObjectFn func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:33:43 -04:00
|
|
|
func (c *cacheObjects) incHitsToMeta(ctx context.Context, dcache *diskCache, bucket, object string, size int64, eTag string, rs *HTTPRangeSpec) error {
|
2020-09-10 14:37:22 -04:00
|
|
|
metadata := map[string]string{"etag": eTag}
|
2020-05-18 21:33:43 -04:00
|
|
|
return dcache.SaveMetadata(ctx, bucket, object, metadata, size, rs, "", true)
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
|
|
|
|
2020-02-03 22:40:01 -05:00
|
|
|
// Backend metadata could have changed through server side copy - reset cache metadata if that is the case
|
2020-05-18 21:33:43 -04:00
|
|
|
func (c *cacheObjects) updateMetadataIfChanged(ctx context.Context, dcache *diskCache, bucket, object string, bkObjectInfo, cacheObjInfo ObjectInfo, rs *HTTPRangeSpec) error {
|
2019-05-22 17:54:15 -04:00
|
|
|
|
2020-09-10 14:37:22 -04:00
|
|
|
bkMeta := make(map[string]string, len(bkObjectInfo.UserDefined))
|
|
|
|
cacheMeta := make(map[string]string, len(cacheObjInfo.UserDefined))
|
2020-02-03 22:40:01 -05:00
|
|
|
for k, v := range bkObjectInfo.UserDefined {
|
2020-05-28 17:36:38 -04:00
|
|
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
2020-02-03 22:40:01 -05:00
|
|
|
// Do not need to send any internal metadata
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bkMeta[http.CanonicalHeaderKey(k)] = v
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2020-02-03 22:40:01 -05:00
|
|
|
for k, v := range cacheObjInfo.UserDefined {
|
2020-05-28 17:36:38 -04:00
|
|
|
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
2020-02-03 22:40:01 -05:00
|
|
|
// Do not need to send any internal metadata
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cacheMeta[http.CanonicalHeaderKey(k)] = v
|
2019-05-21 23:00:27 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
|
2020-02-03 22:40:01 -05:00
|
|
|
if !isMetadataSame(bkMeta, cacheMeta) ||
|
|
|
|
bkObjectInfo.ETag != cacheObjInfo.ETag ||
|
|
|
|
bkObjectInfo.ContentType != cacheObjInfo.ContentType ||
|
|
|
|
!bkObjectInfo.Expires.Equal(cacheObjInfo.Expires) {
|
|
|
|
return dcache.SaveMetadata(ctx, bucket, object, getMetadata(bkObjectInfo), bkObjectInfo.Size, nil, "", false)
|
2019-12-08 16:58:04 -05:00
|
|
|
}
|
2020-05-18 21:33:43 -04:00
|
|
|
return c.incHitsToMeta(ctx, dcache, bucket, object, cacheObjInfo.Size, cacheObjInfo.ETag, rs)
|
2019-12-08 16:58:04 -05:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// DeleteObject clears cache entry if backend delete operation succeeds
|
2020-06-12 23:04:01 -04:00
|
|
|
func (c *cacheObjects) DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
2020-10-26 18:41:24 -04:00
|
|
|
if objInfo, err = c.InnerDeleteObjectFn(ctx, bucket, object, opts); err != nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if c.isCacheExclude(bucket, object) || c.skipCache() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-15 21:35:41 -04:00
|
|
|
dcache, cerr := c.getCacheLoc(bucket, object)
|
2019-08-09 20:09:08 -04:00
|
|
|
if cerr != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
return objInfo, cerr
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
2020-02-03 22:40:01 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2019-08-09 20:09:08 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteObjects batch deletes objects in slice, and clears any cached entries
|
2020-06-12 23:04:01 -04:00
|
|
|
func (c *cacheObjects) DeleteObjects(ctx context.Context, bucket string, objects []ObjectToDelete, opts ObjectOptions) ([]DeletedObject, []error) {
|
2019-08-09 20:09:08 -04:00
|
|
|
errs := make([]error, len(objects))
|
2020-06-12 23:04:01 -04:00
|
|
|
objInfos := make([]ObjectInfo, len(objects))
|
2019-08-09 20:09:08 -04:00
|
|
|
for idx, object := range objects {
|
2020-06-12 23:04:01 -04:00
|
|
|
opts.VersionID = object.VersionID
|
|
|
|
objInfos[idx], errs[idx] = c.DeleteObject(ctx, bucket, object.ObjectName, opts)
|
|
|
|
}
|
|
|
|
deletedObjects := make([]DeletedObject, len(objInfos))
|
|
|
|
for idx := range errs {
|
|
|
|
if errs[idx] != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if objInfos[idx].DeleteMarker {
|
|
|
|
deletedObjects[idx] = DeletedObject{
|
|
|
|
DeleteMarker: objInfos[idx].DeleteMarker,
|
|
|
|
DeleteMarkerVersionID: objInfos[idx].VersionID,
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
deletedObjects[idx] = DeletedObject{
|
|
|
|
ObjectName: objInfos[idx].Name,
|
|
|
|
VersionID: objInfos[idx].VersionID,
|
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return deletedObjects, errs
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// construct a metadata k-v map
|
2019-08-09 20:09:08 -04:00
|
|
|
func getMetadata(objInfo ObjectInfo) map[string]string {
|
2020-09-10 14:37:22 -04:00
|
|
|
metadata := make(map[string]string, len(objInfo.UserDefined)+4)
|
2018-03-28 17:14:06 -04:00
|
|
|
metadata["etag"] = objInfo.ETag
|
|
|
|
metadata["content-type"] = objInfo.ContentType
|
2019-08-09 20:09:08 -04:00
|
|
|
if objInfo.ContentEncoding != "" {
|
|
|
|
metadata["content-encoding"] = objInfo.ContentEncoding
|
|
|
|
}
|
2020-08-24 15:11:20 -04:00
|
|
|
if !objInfo.Expires.Equal(timeSentinel) {
|
2019-08-09 20:09:08 -04:00
|
|
|
metadata["expires"] = objInfo.Expires.Format(http.TimeFormat)
|
|
|
|
}
|
|
|
|
for k, v := range objInfo.UserDefined {
|
|
|
|
metadata[k] = v
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
return metadata
|
|
|
|
}
|
|
|
|
|
2019-12-07 12:44:33 -05:00
|
|
|
// marks cache hit
|
|
|
|
func (c *cacheObjects) incCacheStats(size int64) {
|
|
|
|
c.cacheStats.incHit()
|
|
|
|
c.cacheStats.incBytesServed(size)
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
|
|
|
if c.isCacheExclude(bucket, object) || c.skipCache() {
|
2020-10-26 15:30:40 -04:00
|
|
|
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
2020-02-23 08:33:39 -05:00
|
|
|
var cc *cacheControl
|
2019-12-07 12:44:33 -05:00
|
|
|
var cacheObjSize int64
|
2019-08-09 20:09:08 -04:00
|
|
|
// fetch diskCache if object is currently cached or nearest available cache drive
|
|
|
|
dcache, err := c.getCacheToLoc(ctx, bucket, object)
|
2018-09-20 22:22:09 -04:00
|
|
|
if err != nil {
|
2020-10-26 15:30:40 -04:00
|
|
|
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
|
|
|
|
2020-02-03 22:40:01 -05:00
|
|
|
cacheReader, numCacheHits, cacheErr := dcache.Get(ctx, bucket, object, rs, h, opts)
|
2019-08-09 20:09:08 -04:00
|
|
|
if cacheErr == nil {
|
2019-12-07 12:44:33 -05:00
|
|
|
cacheObjSize = cacheReader.ObjInfo.Size
|
|
|
|
if rs != nil {
|
|
|
|
if _, len, err := rs.GetOffsetLength(cacheObjSize); err == nil {
|
|
|
|
cacheObjSize = len
|
|
|
|
}
|
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
cc = cacheControlOpts(cacheReader.ObjInfo)
|
2020-02-23 08:33:39 -05:00
|
|
|
if cc != nil && (!cc.isStale(cacheReader.ObjInfo.ModTime) ||
|
|
|
|
cc.onlyIfCached) {
|
2019-12-06 02:16:06 -05:00
|
|
|
// This is a cache hit, mark it so
|
2019-12-08 16:58:04 -05:00
|
|
|
bytesServed := cacheReader.ObjInfo.Size
|
|
|
|
if rs != nil {
|
|
|
|
if _, len, err := rs.GetOffsetLength(bytesServed); err == nil {
|
|
|
|
bytesServed = len
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.cacheStats.incHit()
|
|
|
|
c.cacheStats.incBytesServed(bytesServed)
|
2020-05-18 21:33:43 -04:00
|
|
|
c.incHitsToMeta(ctx, dcache, bucket, object, cacheReader.ObjInfo.Size, cacheReader.ObjInfo.ETag, rs)
|
2019-08-09 20:09:08 -04:00
|
|
|
return cacheReader, nil
|
|
|
|
}
|
2020-02-27 13:57:00 -05:00
|
|
|
if cc != nil && cc.noStore {
|
2020-04-22 15:13:57 -04:00
|
|
|
cacheReader.Close()
|
2019-12-06 21:19:36 -05:00
|
|
|
c.cacheStats.incMiss()
|
2020-10-26 15:30:40 -04:00
|
|
|
bReader, err := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2020-01-10 23:21:13 -05:00
|
|
|
bReader.ObjInfo.CacheLookupStatus = CacheHit
|
|
|
|
bReader.ObjInfo.CacheStatus = CacheMiss
|
|
|
|
return bReader, err
|
2019-12-06 21:19:36 -05:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
2019-05-21 23:00:27 -04:00
|
|
|
|
2020-10-26 18:41:24 -04:00
|
|
|
objInfo, err := c.InnerGetObjectInfoFn(ctx, bucket, object, opts)
|
2018-10-17 13:57:12 -04:00
|
|
|
if backendDownError(err) && cacheErr == nil {
|
2019-12-07 12:44:33 -05:00
|
|
|
c.incCacheStats(cacheObjSize)
|
2018-10-17 13:57:12 -04:00
|
|
|
return cacheReader, nil
|
|
|
|
} else if err != nil {
|
2020-04-22 15:13:57 -04:00
|
|
|
if cacheErr == nil {
|
|
|
|
cacheReader.Close()
|
|
|
|
}
|
2018-09-20 22:22:09 -04:00
|
|
|
if _, ok := err.(ObjectNotFound); ok {
|
2019-04-18 16:53:22 -04:00
|
|
|
if cacheErr == nil {
|
|
|
|
// Delete cached entry if backend object
|
|
|
|
// was deleted.
|
|
|
|
dcache.Delete(ctx, bucket, object)
|
|
|
|
}
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
2019-12-07 12:44:33 -05:00
|
|
|
c.cacheStats.incMiss()
|
2018-10-17 13:57:12 -04:00
|
|
|
return nil, err
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
if !objInfo.IsCacheable() {
|
2020-04-22 15:13:57 -04:00
|
|
|
if cacheErr == nil {
|
|
|
|
cacheReader.Close()
|
|
|
|
}
|
2019-12-07 12:44:33 -05:00
|
|
|
c.cacheStats.incMiss()
|
2020-10-26 15:30:40 -04:00
|
|
|
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
2020-01-16 18:41:56 -05:00
|
|
|
// skip cache for objects with locks
|
|
|
|
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
|
|
|
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
2020-04-06 16:44:16 -04:00
|
|
|
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
|
2020-04-22 15:13:57 -04:00
|
|
|
if cacheErr == nil {
|
|
|
|
cacheReader.Close()
|
|
|
|
}
|
2020-01-16 18:41:56 -05:00
|
|
|
c.cacheStats.incMiss()
|
2020-10-26 15:30:40 -04:00
|
|
|
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2020-01-16 18:41:56 -05:00
|
|
|
}
|
2018-10-17 13:57:12 -04:00
|
|
|
if cacheErr == nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
// if ETag matches for stale cache entry, serve from cache
|
|
|
|
if cacheReader.ObjInfo.ETag == objInfo.ETag {
|
|
|
|
// Update metadata in case server-side copy might have changed object metadata
|
2020-05-18 21:33:43 -04:00
|
|
|
c.updateMetadataIfChanged(ctx, dcache, bucket, object, objInfo, cacheReader.ObjInfo, rs)
|
2019-12-07 12:44:33 -05:00
|
|
|
c.incCacheStats(cacheObjSize)
|
2018-09-20 22:22:09 -04:00
|
|
|
return cacheReader, nil
|
|
|
|
}
|
2018-10-11 02:01:24 -04:00
|
|
|
cacheReader.Close()
|
2018-09-20 22:22:09 -04:00
|
|
|
// Object is stale, so delete from cache
|
2020-02-03 22:40:01 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
|
|
|
|
2019-12-07 12:44:33 -05:00
|
|
|
// Reaching here implies cache miss
|
|
|
|
c.cacheStats.incMiss()
|
2020-06-15 12:05:35 -04:00
|
|
|
|
2020-10-26 15:30:40 -04:00
|
|
|
bkReader, bkErr := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
2020-02-03 22:40:01 -05:00
|
|
|
|
2020-01-24 18:49:16 -05:00
|
|
|
if bkErr != nil {
|
|
|
|
return bkReader, bkErr
|
|
|
|
}
|
2020-02-03 22:40:01 -05:00
|
|
|
// If object has less hits than configured cache after, just increment the hit counter
|
|
|
|
// but do not cache it.
|
|
|
|
if numCacheHits < c.after {
|
2020-05-18 21:33:43 -04:00
|
|
|
c.incHitsToMeta(ctx, dcache, bucket, object, objInfo.Size, objInfo.ETag, rs)
|
2020-02-03 22:40:01 -05:00
|
|
|
return bkReader, bkErr
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:21:13 -05:00
|
|
|
// Record if cache has a hit that was invalidated by ETag verification
|
|
|
|
if cacheErr == nil {
|
|
|
|
bkReader.ObjInfo.CacheLookupStatus = CacheHit
|
|
|
|
}
|
2020-09-02 20:48:44 -04:00
|
|
|
|
|
|
|
// Check if we can add it without exceeding total cache size.
|
|
|
|
if !dcache.diskSpaceAvailable(objInfo.Size) {
|
2020-01-10 23:21:13 -05:00
|
|
|
return bkReader, bkErr
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
|
|
|
|
2020-10-27 02:04:48 -04:00
|
|
|
if rs != nil && !dcache.enableRange {
|
2019-08-09 20:09:08 -04:00
|
|
|
go func() {
|
2020-06-29 16:25:29 -04:00
|
|
|
// if range caching is disabled, download entire object.
|
2020-10-27 02:04:48 -04:00
|
|
|
rs = nil
|
2019-08-09 20:09:08 -04:00
|
|
|
// fill cache in the background for range GET requests
|
2020-10-26 15:30:40 -04:00
|
|
|
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, rs, h, lockType, opts)
|
2019-08-09 20:09:08 -04:00
|
|
|
if bErr != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer bReader.Close()
|
2020-08-20 17:23:24 -04:00
|
|
|
oi, _, _, err := dcache.statRange(GlobalContext, bucket, object, rs)
|
2019-08-09 20:09:08 -04:00
|
|
|
// avoid cache overwrite if another background routine filled cache
|
|
|
|
if err != nil || oi.ETag != bReader.ObjInfo.ETag {
|
2020-06-29 16:25:29 -04:00
|
|
|
// use a new context to avoid locker prematurely timing out operation when the GetObjectNInfo returns.
|
2020-08-13 12:16:01 -04:00
|
|
|
dcache.Put(GlobalContext, bucket, object, bReader, bReader.ObjInfo.Size, rs, ObjectOptions{
|
|
|
|
UserDefined: getMetadata(bReader.ObjInfo),
|
|
|
|
}, false)
|
2020-06-29 16:25:29 -04:00
|
|
|
return
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
|
|
|
}()
|
2020-01-10 23:21:13 -05:00
|
|
|
return bkReader, bkErr
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
2020-01-10 23:21:13 -05:00
|
|
|
|
2018-09-20 22:22:09 -04:00
|
|
|
// Initialize pipe.
|
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
|
|
teeReader := io.TeeReader(bkReader, pipeWriter)
|
2020-09-10 14:37:22 -04:00
|
|
|
userDefined := getMetadata(bkReader.ObjInfo)
|
2018-09-20 22:22:09 -04:00
|
|
|
go func() {
|
2020-11-02 13:00:45 -05:00
|
|
|
_, putErr := dcache.Put(ctx, bucket, object,
|
2020-08-13 12:16:01 -04:00
|
|
|
io.LimitReader(pipeReader, bkReader.ObjInfo.Size),
|
2020-10-27 02:04:48 -04:00
|
|
|
bkReader.ObjInfo.Size, rs, ObjectOptions{
|
2020-09-10 14:37:22 -04:00
|
|
|
UserDefined: userDefined,
|
2020-08-13 12:16:01 -04:00
|
|
|
}, false)
|
2018-09-20 22:22:09 -04:00
|
|
|
// close the write end of the pipe, so the error gets
|
|
|
|
// propagated to getObjReader
|
|
|
|
pipeWriter.CloseWithError(putErr)
|
|
|
|
}()
|
|
|
|
cleanupBackend := func() { bkReader.Close() }
|
2019-09-05 14:03:32 -04:00
|
|
|
cleanupPipe := func() { pipeWriter.Close() }
|
2020-04-21 01:01:59 -04:00
|
|
|
return NewGetObjectReaderFromReader(teeReader, bkReader.ObjInfo, opts, cleanupBackend, cleanupPipe)
|
2018-09-20 22:22:09 -04:00
|
|
|
}
|
|
|
|
|
2018-03-28 17:14:06 -04:00
|
|
|
// Returns ObjectInfo from cache if available.
|
2019-08-09 20:09:08 -04:00
|
|
|
func (c *cacheObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
2020-10-26 18:41:24 -04:00
|
|
|
getObjectInfoFn := c.InnerGetObjectInfoFn
|
2019-08-09 20:09:08 -04:00
|
|
|
|
|
|
|
if c.isCacheExclude(bucket, object) || c.skipCache() {
|
2018-09-10 12:42:43 -04:00
|
|
|
return getObjectInfoFn(ctx, bucket, object, opts)
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
|
|
|
|
// fetch diskCache if object is currently cached or nearest available cache drive
|
|
|
|
dcache, err := c.getCacheToLoc(ctx, bucket, object)
|
2018-03-28 17:14:06 -04:00
|
|
|
if err != nil {
|
2018-09-10 12:42:43 -04:00
|
|
|
return getObjectInfoFn(ctx, bucket, object, opts)
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2020-02-23 08:33:39 -05:00
|
|
|
var cc *cacheControl
|
2019-08-09 20:09:08 -04:00
|
|
|
// if cache control setting is valid, avoid HEAD operation to backend
|
2020-02-03 22:40:01 -05:00
|
|
|
cachedObjInfo, _, cerr := dcache.Stat(ctx, bucket, object)
|
2019-08-09 20:09:08 -04:00
|
|
|
if cerr == nil {
|
|
|
|
cc = cacheControlOpts(cachedObjInfo)
|
2020-02-23 08:33:39 -05:00
|
|
|
if cc == nil || (cc != nil && !cc.isStale(cachedObjInfo.ModTime)) {
|
2019-12-06 02:16:06 -05:00
|
|
|
// This is a cache hit, mark it so
|
|
|
|
c.cacheStats.incHit()
|
2019-08-09 20:09:08 -04:00
|
|
|
return cachedObjInfo, nil
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 02:16:06 -05:00
|
|
|
|
2018-09-10 12:42:43 -04:00
|
|
|
objInfo, err := getObjectInfoFn(ctx, bucket, object, opts)
|
2018-03-28 17:14:06 -04:00
|
|
|
if err != nil {
|
2018-04-10 12:36:37 -04:00
|
|
|
if _, ok := err.(ObjectNotFound); ok {
|
2018-03-28 17:14:06 -04:00
|
|
|
// Delete the cached entry if backend object was deleted.
|
2020-02-03 22:40:01 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2019-12-07 12:44:33 -05:00
|
|
|
c.cacheStats.incMiss()
|
2018-03-28 17:14:06 -04:00
|
|
|
return ObjectInfo{}, err
|
|
|
|
}
|
|
|
|
if !backendDownError(err) {
|
2019-12-07 12:44:33 -05:00
|
|
|
c.cacheStats.incMiss()
|
2018-03-28 17:14:06 -04:00
|
|
|
return ObjectInfo{}, err
|
|
|
|
}
|
|
|
|
if cerr == nil {
|
2019-12-07 12:44:33 -05:00
|
|
|
// This is a cache hit, mark it so
|
|
|
|
c.cacheStats.incHit()
|
2018-03-28 17:14:06 -04:00
|
|
|
return cachedObjInfo, nil
|
|
|
|
}
|
2019-12-07 12:44:33 -05:00
|
|
|
c.cacheStats.incMiss()
|
2018-03-28 17:14:06 -04:00
|
|
|
return ObjectInfo{}, BackendDown{}
|
|
|
|
}
|
2019-12-07 12:44:33 -05:00
|
|
|
// Reaching here implies cache miss
|
|
|
|
c.cacheStats.incMiss()
|
2018-03-28 17:14:06 -04:00
|
|
|
// when backend is up, do a sanity check on cached object
|
2019-08-09 20:09:08 -04:00
|
|
|
if cerr != nil {
|
2018-03-28 17:14:06 -04:00
|
|
|
return objInfo, nil
|
|
|
|
}
|
|
|
|
if cachedObjInfo.ETag != objInfo.ETag {
|
|
|
|
// Delete the cached entry if the backend object was replaced.
|
2020-02-03 22:40:01 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
return objInfo, nil
|
|
|
|
}
|
|
|
|
|
2020-02-07 17:36:46 -05:00
|
|
|
// CopyObject reverts to backend after evicting any stale cache entries
|
|
|
|
func (c *cacheObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
|
2020-10-26 18:41:24 -04:00
|
|
|
copyObjectFn := c.InnerCopyObjectFn
|
2020-02-07 17:36:46 -05:00
|
|
|
if c.isCacheExclude(srcBucket, srcObject) || c.skipCache() {
|
|
|
|
return copyObjectFn(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
|
|
|
}
|
|
|
|
if srcBucket != dstBucket || srcObject != dstObject {
|
|
|
|
return copyObjectFn(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
|
|
|
}
|
|
|
|
// fetch diskCache if object is currently cached or nearest available cache drive
|
|
|
|
dcache, err := c.getCacheToLoc(ctx, srcBucket, srcObject)
|
|
|
|
if err != nil {
|
|
|
|
return copyObjectFn(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
|
|
|
}
|
|
|
|
// if currently cached, evict old entry and revert to backend.
|
|
|
|
if cachedObjInfo, _, cerr := dcache.Stat(ctx, srcBucket, srcObject); cerr == nil {
|
|
|
|
cc := cacheControlOpts(cachedObjInfo)
|
2020-02-27 13:57:00 -05:00
|
|
|
if cc == nil || !cc.isStale(cachedObjInfo.ModTime) {
|
2020-02-07 17:36:46 -05:00
|
|
|
dcache.Delete(ctx, srcBucket, srcObject)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return copyObjectFn(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts)
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// StorageInfo - returns underlying storage statistics.
|
|
|
|
func (c *cacheObjects) StorageInfo(ctx context.Context) (cInfo CacheStorageInfo) {
|
|
|
|
var total, free uint64
|
|
|
|
for _, cache := range c.cache {
|
2018-04-26 01:09:05 -04:00
|
|
|
if cache == nil {
|
|
|
|
continue
|
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
info, err := getDiskInfo(cache.dir)
|
|
|
|
logger.GetReqInfo(ctx).AppendTags("cachePath", cache.dir)
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
total += info.Total
|
|
|
|
free += info.Free
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
return CacheStorageInfo{
|
|
|
|
Total: total,
|
|
|
|
Free: free,
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 02:16:06 -05:00
|
|
|
// CacheStats - returns underlying storage statistics.
|
|
|
|
func (c *cacheObjects) CacheStats() (cs *CacheStats) {
|
|
|
|
return c.cacheStats
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// skipCache() returns true if cache migration is in progress
|
|
|
|
func (c *cacheObjects) skipCache() bool {
|
|
|
|
c.migMutex.Lock()
|
|
|
|
defer c.migMutex.Unlock()
|
|
|
|
return c.migrating
|
2019-05-13 15:25:49 -04:00
|
|
|
}
|
|
|
|
|
2018-03-28 17:14:06 -04:00
|
|
|
// Returns true if object should be excluded from cache
|
2019-08-09 20:09:08 -04:00
|
|
|
func (c *cacheObjects) isCacheExclude(bucket, object string) bool {
|
|
|
|
// exclude directories from cache
|
|
|
|
if strings.HasSuffix(object, SlashSeparator) {
|
|
|
|
return true
|
|
|
|
}
|
2018-03-28 17:14:06 -04:00
|
|
|
for _, pattern := range c.exclude {
|
|
|
|
matchStr := fmt.Sprintf("%s/%s", bucket, object)
|
|
|
|
if ok := wildcard.MatchSimple(pattern, matchStr); ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// choose a cache deterministically based on hash of bucket,object. The hash index is treated as
|
|
|
|
// a hint. In the event that the cache drive at hash index is offline, treat the list of cache drives
|
|
|
|
// as a circular buffer and walk through them starting at hash index until an online drive is found.
|
2019-10-15 21:35:41 -04:00
|
|
|
func (c *cacheObjects) getCacheLoc(bucket, object string) (*diskCache, error) {
|
2019-08-09 20:09:08 -04:00
|
|
|
index := c.hashIndex(bucket, object)
|
|
|
|
numDisks := len(c.cache)
|
|
|
|
for k := 0; k < numDisks; k++ {
|
|
|
|
i := (index + k) % numDisks
|
|
|
|
if c.cache[i] == nil {
|
|
|
|
continue
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
if c.cache[i].IsOnline() {
|
|
|
|
return c.cache[i], nil
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
return nil, errDiskNotFound
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// get cache disk where object is currently cached for a GET operation. If object does not exist at that location,
|
|
|
|
// treat the list of cache drives as a circular buffer and walk through them starting at hash index
|
|
|
|
// until an online drive is found.If object is not found, fall back to the first online cache drive
|
|
|
|
// closest to the hash index, so that object can be re-cached.
|
|
|
|
func (c *cacheObjects) getCacheToLoc(ctx context.Context, bucket, object string) (*diskCache, error) {
|
|
|
|
index := c.hashIndex(bucket, object)
|
2019-05-22 17:54:15 -04:00
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
numDisks := len(c.cache)
|
|
|
|
// save first online cache disk closest to the hint index
|
|
|
|
var firstOnlineDisk *diskCache
|
|
|
|
for k := 0; k < numDisks; k++ {
|
|
|
|
i := (index + k) % numDisks
|
|
|
|
if c.cache[i] == nil {
|
|
|
|
continue
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
if c.cache[i].IsOnline() {
|
|
|
|
if firstOnlineDisk == nil {
|
|
|
|
firstOnlineDisk = c.cache[i]
|
|
|
|
}
|
|
|
|
if c.cache[i].Exists(ctx, bucket, object) {
|
|
|
|
return c.cache[i], nil
|
|
|
|
}
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
if firstOnlineDisk != nil {
|
|
|
|
return firstOnlineDisk, nil
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
return nil, errDiskNotFound
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// Compute a unique hash sum for bucket and object
|
|
|
|
func (c *cacheObjects) hashIndex(bucket, object string) int {
|
|
|
|
return crcHashMod(pathJoin(bucket, object), len(c.cache))
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// newCache initializes the cacheFSObjects for the "drives" specified in config.json
|
|
|
|
// or the global env overrides.
|
2019-10-04 13:35:33 -04:00
|
|
|
func newCache(config cache.Config) ([]*diskCache, bool, error) {
|
2019-08-09 20:09:08 -04:00
|
|
|
var caches []*diskCache
|
2020-04-09 12:30:02 -04:00
|
|
|
ctx := logger.SetReqInfo(GlobalContext, &logger.ReqInfo{})
|
2019-08-09 20:09:08 -04:00
|
|
|
formats, migrating, err := loadAndValidateCacheFormat(ctx, config.Drives)
|
2018-03-28 17:14:06 -04:00
|
|
|
if err != nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
return nil, false, err
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2018-03-29 17:38:26 -04:00
|
|
|
for i, dir := range config.Drives {
|
2019-08-09 20:09:08 -04:00
|
|
|
// skip diskCache creation for cache drives missing a format.json
|
2018-03-28 17:14:06 -04:00
|
|
|
if formats[i] == nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
caches = append(caches, nil)
|
2018-03-28 17:14:06 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := checkAtimeSupport(dir); err != nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
return nil, false, errors.New("Atime support required for disk caching")
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
|
2020-06-29 16:25:29 -04:00
|
|
|
cache, err := newDiskCache(ctx, dir, config)
|
2018-03-29 17:38:26 -04:00
|
|
|
if err != nil {
|
2019-08-09 20:09:08 -04:00
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
caches = append(caches, cache)
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
return caches, migrating, nil
|
2018-03-28 17:14:06 -04:00
|
|
|
}
|
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
func (c *cacheObjects) migrateCacheFromV1toV2(ctx context.Context) {
|
2019-10-04 13:35:33 -04:00
|
|
|
logStartupMessage(color.Blue("Cache migration initiated ...."))
|
2019-08-23 13:13:22 -04:00
|
|
|
|
2019-10-14 12:44:51 -04:00
|
|
|
g := errgroup.WithNErrs(len(c.cache))
|
|
|
|
for index, dc := range c.cache {
|
2019-08-09 20:09:08 -04:00
|
|
|
if dc == nil {
|
|
|
|
continue
|
|
|
|
}
|
2019-10-14 12:44:51 -04:00
|
|
|
index := index
|
|
|
|
g.Go(func() error {
|
|
|
|
// start migration from V1 to V2
|
|
|
|
return migrateOldCache(ctx, c.cache[index])
|
|
|
|
}, index)
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
errCnt := 0
|
2020-02-23 08:33:39 -05:00
|
|
|
for _, err := range g.Wait() {
|
2019-08-09 20:09:08 -04:00
|
|
|
if err != nil {
|
|
|
|
errCnt++
|
2019-10-14 12:44:51 -04:00
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
continue
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
|
|
|
}
|
2019-10-14 12:44:51 -04:00
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
if errCnt > 0 {
|
|
|
|
return
|
|
|
|
}
|
2019-10-14 12:44:51 -04:00
|
|
|
|
2019-08-09 20:09:08 -04:00
|
|
|
// update migration status
|
|
|
|
c.migMutex.Lock()
|
|
|
|
defer c.migMutex.Unlock()
|
|
|
|
c.migrating = false
|
2019-10-04 13:35:33 -04:00
|
|
|
logStartupMessage(color.Blue("Cache migration completed successfully."))
|
2019-08-09 20:09:08 -04:00
|
|
|
}
|
2018-03-28 17:14:06 -04:00
|
|
|
|
2019-09-05 10:20:16 -04:00
|
|
|
// PutObject - caches the uploaded object for single Put operations
|
|
|
|
func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
2020-10-26 18:41:24 -04:00
|
|
|
putObjectFn := c.InnerPutObjectFn
|
2019-09-05 10:20:16 -04:00
|
|
|
dcache, err := c.getCacheToLoc(ctx, bucket, object)
|
|
|
|
if err != nil {
|
|
|
|
// disk cache could not be located,execute backend call.
|
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
|
|
|
size := r.Size()
|
|
|
|
if c.skipCache() {
|
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetch from backend if there is no space on cache drive
|
2020-09-02 20:48:44 -04:00
|
|
|
if !dcache.diskSpaceAvailable(size) {
|
2019-09-05 10:20:16 -04:00
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
2020-09-02 20:48:44 -04:00
|
|
|
|
2019-09-05 10:20:16 -04:00
|
|
|
if opts.ServerSideEncryption != nil {
|
2019-12-12 18:11:27 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2019-09-05 10:20:16 -04:00
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
2019-11-20 16:18:09 -05:00
|
|
|
|
|
|
|
// skip cache for objects with locks
|
2020-01-16 18:41:56 -05:00
|
|
|
objRetention := objectlock.GetObjectRetentionMeta(opts.UserDefined)
|
|
|
|
legalHold := objectlock.GetObjectLegalHoldMeta(opts.UserDefined)
|
2020-04-06 16:44:16 -04:00
|
|
|
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
|
2019-12-12 18:11:27 -05:00
|
|
|
dcache.Delete(ctx, bucket, object)
|
2019-11-20 16:18:09 -05:00
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
|
|
|
|
2019-09-05 10:20:16 -04:00
|
|
|
// fetch from backend if cache exclude pattern or cache-control
|
|
|
|
// directive set to exclude
|
|
|
|
if c.isCacheExclude(bucket, object) {
|
|
|
|
dcache.Delete(ctx, bucket, object)
|
|
|
|
return putObjectFn(ctx, bucket, object, r, opts)
|
|
|
|
}
|
2020-11-02 13:00:45 -05:00
|
|
|
if c.commitWriteback {
|
|
|
|
oi, err := dcache.Put(ctx, bucket, object, r, r.Size(), nil, opts, false)
|
|
|
|
if err != nil {
|
|
|
|
return ObjectInfo{}, err
|
|
|
|
}
|
|
|
|
go c.uploadObject(GlobalContext, oi)
|
|
|
|
return oi, nil
|
|
|
|
}
|
2019-09-16 17:24:04 -04:00
|
|
|
objInfo, err = putObjectFn(ctx, bucket, object, r, opts)
|
2019-09-05 10:20:16 -04:00
|
|
|
|
2019-09-16 17:24:04 -04:00
|
|
|
if err == nil {
|
|
|
|
go func() {
|
|
|
|
// fill cache in the background
|
2020-10-26 15:30:40 -04:00
|
|
|
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
|
2019-09-16 17:24:04 -04:00
|
|
|
if bErr != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer bReader.Close()
|
2020-08-20 17:23:24 -04:00
|
|
|
oi, _, err := dcache.Stat(GlobalContext, bucket, object)
|
2019-09-16 17:24:04 -04:00
|
|
|
// avoid cache overwrite if another background routine filled cache
|
|
|
|
if err != nil || oi.ETag != bReader.ObjInfo.ETag {
|
2020-08-20 17:23:24 -04:00
|
|
|
dcache.Put(GlobalContext, bucket, object, bReader, bReader.ObjInfo.Size, nil, ObjectOptions{UserDefined: getMetadata(bReader.ObjInfo)}, false)
|
2019-09-16 17:24:04 -04:00
|
|
|
}
|
|
|
|
}()
|
2019-09-05 10:20:16 -04:00
|
|
|
}
|
|
|
|
return objInfo, err
|
2020-11-02 13:00:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// upload cached object to backend in async commit mode.
|
|
|
|
func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
|
|
|
|
dcache, err := c.getCacheToLoc(ctx, oi.Bucket, oi.Name)
|
|
|
|
if err != nil {
|
|
|
|
// disk cache could not be located.
|
|
|
|
logger.LogIf(ctx, fmt.Errorf("Could not upload %s/%s to backend: %w", oi.Bucket, oi.Name, err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cReader, _, bErr := dcache.Get(ctx, oi.Bucket, oi.Name, nil, http.Header{}, ObjectOptions{})
|
|
|
|
if bErr != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer cReader.Close()
|
2019-09-05 10:20:16 -04:00
|
|
|
|
2020-11-02 13:00:45 -05:00
|
|
|
if cReader.ObjInfo.ETag != oi.ETag {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
st := cacheCommitStatus(oi.UserDefined[writeBackStatusHeader])
|
|
|
|
if st == CommitComplete || st.String() == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size, globalCLIContext.StrictS3Compat)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var opts ObjectOptions
|
|
|
|
opts.UserDefined = make(map[string]string)
|
|
|
|
opts.UserDefined[xhttp.ContentMD5] = oi.UserDefined["content-md5"]
|
|
|
|
objInfo, err := c.InnerPutObjectFn(ctx, oi.Bucket, oi.Name, NewPutObjReader(hashReader, nil, nil), opts)
|
|
|
|
wbCommitStatus := CommitComplete
|
|
|
|
if err != nil {
|
|
|
|
wbCommitStatus = CommitFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
meta := cloneMSS(cReader.ObjInfo.UserDefined)
|
|
|
|
retryCnt := 0
|
|
|
|
if wbCommitStatus == CommitFailed {
|
|
|
|
retryCnt, _ = strconv.Atoi(meta[writeBackRetryHeader])
|
|
|
|
retryCnt++
|
|
|
|
meta[writeBackRetryHeader] = strconv.Itoa(retryCnt)
|
|
|
|
} else {
|
|
|
|
delete(meta, writeBackRetryHeader)
|
|
|
|
}
|
|
|
|
meta[writeBackStatusHeader] = wbCommitStatus.String()
|
|
|
|
meta["etag"] = oi.ETag
|
|
|
|
dcache.SaveMetadata(ctx, oi.Bucket, oi.Name, meta, objInfo.Size, nil, "", false)
|
|
|
|
if retryCnt > 0 {
|
|
|
|
// slow down retries
|
|
|
|
time.Sleep(time.Second * time.Duration(retryCnt%10+1))
|
|
|
|
c.queueWritebackRetry(oi)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cacheObjects) queueWritebackRetry(oi ObjectInfo) {
|
|
|
|
select {
|
|
|
|
case c.wbRetryCh <- oi:
|
|
|
|
c.uploadObject(GlobalContext, oi)
|
|
|
|
default:
|
|
|
|
}
|
2019-09-05 10:20:16 -04:00
|
|
|
}
|
|
|
|
|
2018-03-28 17:14:06 -04:00
|
|
|
// Returns cacheObjects for use by Server.
|
2019-10-04 13:35:33 -04:00
|
|
|
func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjectLayer, error) {
|
2018-03-28 17:14:06 -04:00
|
|
|
// list of disk caches for cache "drives" specified in config.json or MINIO_CACHE_DRIVES env var.
|
2019-08-09 20:09:08 -04:00
|
|
|
cache, migrateSw, err := newCache(config)
|
2018-03-28 17:14:06 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
c := &cacheObjects{
|
2020-11-02 13:00:45 -05:00
|
|
|
cache: cache,
|
|
|
|
exclude: config.Exclude,
|
|
|
|
after: config.After,
|
|
|
|
migrating: migrateSw,
|
|
|
|
migMutex: sync.Mutex{},
|
|
|
|
commitWriteback: config.CommitWriteback,
|
|
|
|
cacheStats: newCacheStats(),
|
2020-10-26 18:41:24 -04:00
|
|
|
InnerGetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
2019-11-09 12:27:23 -05:00
|
|
|
return newObjectLayerFn().GetObjectInfo(ctx, bucket, object, opts)
|
2018-03-28 17:14:06 -04:00
|
|
|
},
|
2020-10-26 15:30:40 -04:00
|
|
|
InnerGetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
2019-11-09 12:27:23 -05:00
|
|
|
return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
2018-10-11 02:01:24 -04:00
|
|
|
},
|
2020-10-26 18:41:24 -04:00
|
|
|
InnerDeleteObjectFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return newObjectLayerFn().DeleteObject(ctx, bucket, object, opts)
|
2019-05-13 15:25:49 -04:00
|
|
|
},
|
2020-10-26 18:41:24 -04:00
|
|
|
InnerPutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
|
2019-11-09 12:27:23 -05:00
|
|
|
return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
|
2019-09-05 10:20:16 -04:00
|
|
|
},
|
2020-10-26 18:41:24 -04:00
|
|
|
InnerCopyObjectFn: func(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) {
|
2020-02-07 17:36:46 -05:00
|
|
|
return newObjectLayerFn().CopyObject(ctx, srcBucket, srcObject, destBucket, destObject, srcInfo, srcOpts, dstOpts)
|
|
|
|
},
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2020-06-15 12:05:35 -04:00
|
|
|
c.cacheStats.GetDiskStats = func() []CacheDiskStats {
|
|
|
|
cacheDiskStats := make([]CacheDiskStats, len(c.cache))
|
|
|
|
for i := range c.cache {
|
2020-07-28 13:06:52 -04:00
|
|
|
dcache := c.cache[i]
|
|
|
|
cacheDiskStats[i] = CacheDiskStats{}
|
|
|
|
if dcache != nil {
|
|
|
|
cacheDiskStats[i].Dir = dcache.stats.Dir
|
|
|
|
atomic.StoreInt32(&cacheDiskStats[i].UsageState, atomic.LoadInt32(&dcache.stats.UsageState))
|
|
|
|
atomic.StoreUint64(&cacheDiskStats[i].UsagePercent, atomic.LoadUint64(&dcache.stats.UsagePercent))
|
2020-06-15 12:05:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return cacheDiskStats
|
|
|
|
}
|
2019-08-09 20:09:08 -04:00
|
|
|
if migrateSw {
|
|
|
|
go c.migrateCacheFromV1toV2(ctx)
|
2019-05-22 17:54:15 -04:00
|
|
|
}
|
2020-03-22 15:16:36 -04:00
|
|
|
go c.gc(ctx)
|
2020-11-02 13:00:45 -05:00
|
|
|
if c.commitWriteback {
|
|
|
|
c.wbRetryCh = make(chan ObjectInfo, 10000)
|
|
|
|
go func() {
|
|
|
|
<-GlobalContext.Done()
|
|
|
|
close(c.wbRetryCh)
|
|
|
|
}()
|
|
|
|
go c.queuePendingWriteback(ctx)
|
|
|
|
}
|
|
|
|
|
2019-05-22 17:54:15 -04:00
|
|
|
return c, nil
|
|
|
|
}
|
2020-02-23 08:33:39 -05:00
|
|
|
|
2020-03-22 15:16:36 -04:00
|
|
|
func (c *cacheObjects) gc(ctx context.Context) {
|
2020-02-23 08:33:39 -05:00
|
|
|
ticker := time.NewTicker(cacheGCInterval)
|
|
|
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
|
|
select {
|
2020-03-22 15:16:36 -04:00
|
|
|
case <-ctx.Done():
|
2020-02-23 08:33:39 -05:00
|
|
|
return
|
|
|
|
case <-ticker.C:
|
|
|
|
if c.migrating {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, dcache := range c.cache {
|
2020-07-28 13:06:52 -04:00
|
|
|
if dcache != nil {
|
2020-09-02 20:48:44 -04:00
|
|
|
// Check if there is disk.
|
|
|
|
// Will queue a GC scan if at high watermark.
|
|
|
|
dcache.diskSpaceAvailable(0)
|
2020-07-28 13:06:52 -04:00
|
|
|
}
|
2020-02-23 08:33:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-02 13:00:45 -05:00
|
|
|
|
|
|
|
// queues any pending or failed async commits when server restarts
|
|
|
|
func (c *cacheObjects) queuePendingWriteback(ctx context.Context) {
|
|
|
|
for _, dcache := range c.cache {
|
|
|
|
if dcache != nil {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case oi, ok := <-dcache.retryWritebackCh:
|
|
|
|
if !ok {
|
|
|
|
goto next
|
|
|
|
}
|
|
|
|
c.queueWritebackRetry(oi)
|
|
|
|
default:
|
|
|
|
time.Sleep(time.Second * 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
next:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|