github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/cache/cache.go (about)

     1  // Copyright (c) 2015-2023 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cache
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/dustin/go-humanize"
    30  	"github.com/minio/minio/internal/config"
    31  	xhttp "github.com/minio/minio/internal/http"
    32  	"github.com/minio/pkg/v2/env"
    33  	"github.com/tinylib/msgp/msgp"
    34  )
    35  
    36  // Cache related keys
    37  const (
    38  	Enable    = "enable"
    39  	Endpoint  = "endpoint"
    40  	BlockSize = "block_size"
    41  
    42  	EnvEnable    = "MINIO_CACHE_ENABLE"
    43  	EnvEndpoint  = "MINIO_CACHE_ENDPOINT"
    44  	EnvBlockSize = "MINIO_CACHE_BLOCK_SIZE"
    45  )
    46  
    47  // DefaultKVS - default KV config for cache settings
    48  var DefaultKVS = config.KVS{
    49  	config.KV{
    50  		Key:   Enable,
    51  		Value: "off",
    52  	},
    53  	config.KV{
    54  		Key:   Endpoint,
    55  		Value: "",
    56  	},
    57  	config.KV{
    58  		Key:   BlockSize,
    59  		Value: "",
    60  	},
    61  }
    62  
    63  // Config represents the subnet related configuration
    64  type Config struct {
    65  	// Flag indicating whether cache is enabled.
    66  	Enable bool `json:"enable"`
    67  
    68  	// Endpoint for caching uses remote mcache server to
    69  	// store and retrieve pre-condition check entities such as
    70  	// Etag and ModTime of an object + version
    71  	Endpoint string `json:"endpoint"`
    72  
    73  	// BlockSize indicates the maximum object size below which
    74  	// data is cached and fetched remotely from DRAM.
    75  	BlockSize int64
    76  
    77  	// Is the HTTP client used for communicating with mcache server
    78  	clnt *http.Client
    79  }
    80  
    81  var configLock sync.RWMutex
    82  
    83  // Enabled - indicates if cache is enabled or not
    84  func (c *Config) Enabled() bool {
    85  	return c.Enable && c.Endpoint != ""
    86  }
    87  
    88  // MatchesSize verifies if input 'size' falls under cacheable threshold
    89  func (c Config) MatchesSize(size int64) bool {
    90  	configLock.RLock()
    91  	defer configLock.RUnlock()
    92  
    93  	return c.Enable && c.BlockSize > 0 && size <= c.BlockSize
    94  }
    95  
    96  // Update updates new cache frequency
    97  func (c *Config) Update(ncfg Config) {
    98  	configLock.Lock()
    99  	defer configLock.Unlock()
   100  
   101  	c.Enable = ncfg.Enable
   102  	c.Endpoint = ncfg.Endpoint
   103  	c.BlockSize = ncfg.BlockSize
   104  	c.clnt = ncfg.clnt
   105  }
   106  
   107  // cache related errors
   108  var (
   109  	ErrInvalidArgument = errors.New("invalid argument")
   110  	ErrKeyMissing      = errors.New("key is missing")
   111  )
   112  
   113  const (
   114  	mcacheV1Check  = "/_mcache/v1/check"
   115  	mcacheV1Update = "/_mcache/v1/update"
   116  	mcacheV1Delete = "/_mcache/v1/delete"
   117  )
   118  
   119  // Get performs conditional check and returns the cached object info if any.
   120  func (c Config) Get(r *CondCheck) (*ObjectInfo, error) {
   121  	configLock.RLock()
   122  	defer configLock.RUnlock()
   123  
   124  	if !c.Enable {
   125  		return nil, nil
   126  	}
   127  
   128  	if c.Endpoint == "" {
   129  		// Endpoint not set, make this a no-op
   130  		return nil, nil
   131  	}
   132  
   133  	buf, err := r.MarshalMsg(nil)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	// We do not want Gets to take so much time, anything
   139  	// beyond 250ms we should cut it, remote cache is too
   140  	// busy already.
   141  	ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
   142  	defer cancel()
   143  
   144  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.Endpoint+mcacheV1Check, bytes.NewReader(buf))
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	resp, err := c.clnt.Do(req)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	defer xhttp.DrainBody(resp.Body)
   154  
   155  	switch resp.StatusCode {
   156  	case http.StatusNotFound:
   157  		return nil, ErrKeyMissing
   158  	case http.StatusOK:
   159  		co := &ObjectInfo{}
   160  		return co, co.DecodeMsg(msgp.NewReader(resp.Body))
   161  	default:
   162  		return nil, ErrInvalidArgument
   163  	}
   164  }
   165  
   166  // Set sets the cache object info
   167  func (c Config) Set(ci *ObjectInfo) {
   168  	configLock.RLock()
   169  	defer configLock.RUnlock()
   170  
   171  	if !c.Enable {
   172  		return
   173  	}
   174  
   175  	if c.Endpoint == "" {
   176  		// Endpoint not set, make this a no-op
   177  		return
   178  	}
   179  
   180  	buf, err := ci.MarshalMsg(nil)
   181  	if err != nil {
   182  		return
   183  	}
   184  
   185  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, c.Endpoint+mcacheV1Update, bytes.NewReader(buf))
   186  	if err != nil {
   187  		return
   188  	}
   189  
   190  	resp, err := c.clnt.Do(req)
   191  	if err != nil {
   192  		return
   193  	}
   194  	defer xhttp.DrainBody(resp.Body)
   195  }
   196  
   197  // Delete deletes remote cached content for object and its version.
   198  func (c Config) Delete(bucket, key string) {
   199  	configLock.RLock()
   200  	defer configLock.RUnlock()
   201  
   202  	if !c.Enable {
   203  		return
   204  	}
   205  
   206  	if c.Endpoint == "" {
   207  		return
   208  	}
   209  
   210  	req, err := http.NewRequestWithContext(context.Background(), http.MethodDelete, c.Endpoint+fmt.Sprintf("%s?bucket=%s&key=%s", mcacheV1Delete, bucket, key), nil)
   211  	if err != nil {
   212  		return
   213  	}
   214  
   215  	resp, err := c.clnt.Do(req)
   216  	if err != nil {
   217  		return
   218  	}
   219  	defer xhttp.DrainBody(resp.Body)
   220  }
   221  
   222  // LookupConfig - lookup config and override with valid environment settings if any.
   223  func LookupConfig(kvs config.KVS, transport http.RoundTripper) (cfg Config, err error) {
   224  	cfg.Enable = env.Get(EnvEnable, kvs.GetWithDefault(Enable, DefaultKVS)) == config.EnableOn
   225  
   226  	if d := env.Get(EnvBlockSize, kvs.GetWithDefault(BlockSize, DefaultKVS)); d != "" {
   227  		objectSize, err := humanize.ParseBytes(d)
   228  		if err != nil {
   229  			return cfg, err
   230  		}
   231  		cfg.BlockSize = int64(objectSize)
   232  	}
   233  
   234  	cfg.Endpoint = env.Get(EnvEndpoint, kvs.GetWithDefault(Endpoint, DefaultKVS))
   235  	cfg.clnt = &http.Client{Transport: transport}
   236  
   237  	return cfg, nil
   238  }