github.com/thanos-io/thanos@v0.32.5/pkg/store/cache/caching_bucket_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  //nolint:unparam
     5  package storecache
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/pkg/errors"
    19  	promtest "github.com/prometheus/client_golang/prometheus/testutil"
    20  
    21  	"github.com/thanos-io/objstore"
    22  
    23  	"github.com/efficientgo/core/testutil"
    24  	thanoscache "github.com/thanos-io/thanos/pkg/cache"
    25  	"github.com/thanos-io/thanos/pkg/runutil"
    26  	"github.com/thanos-io/thanos/pkg/store/cache/cachekey"
    27  )
    28  
    29  const testFilename = "/random_object"
    30  
    31  func TestChunksCaching(t *testing.T) {
    32  	length := int64(1024 * 1024)
    33  	subrangeSize := int64(16000) // All tests are based on this value.
    34  
    35  	data := make([]byte, length)
    36  	for ix := 0; ix < len(data); ix++ {
    37  		data[ix] = byte(ix)
    38  	}
    39  
    40  	name := "/test/chunks/000001"
    41  
    42  	inmem := objstore.NewInMemBucket()
    43  	testutil.Ok(t, inmem.Upload(context.Background(), name, bytes.NewReader(data)))
    44  
    45  	// We reuse cache between tests (!)
    46  	cache := newMockCache()
    47  
    48  	// Warning, these tests must be run in order, they depend cache state from previous test.
    49  	for _, tc := range []struct {
    50  		name                   string
    51  		init                   func()
    52  		offset                 int64
    53  		length                 int64
    54  		maxGetRangeRequests    int
    55  		expectedLength         int64
    56  		expectedFetchedBytes   int64
    57  		expectedCachedBytes    int64
    58  		expectedRefetchedBytes int64
    59  	}{
    60  		{
    61  			name:                 "basic test",
    62  			offset:               555555,
    63  			length:               55555,
    64  			expectedLength:       55555,
    65  			expectedFetchedBytes: 5 * subrangeSize,
    66  		},
    67  
    68  		{
    69  			name:                "same request will hit all subranges in the cache",
    70  			offset:              555555,
    71  			length:              55555,
    72  			expectedLength:      55555,
    73  			expectedCachedBytes: 5 * subrangeSize,
    74  		},
    75  
    76  		{
    77  			name:                 "request data close to the end of object",
    78  			offset:               length - 10,
    79  			length:               3000,
    80  			expectedLength:       10,
    81  			expectedFetchedBytes: 8576, // Last (incomplete) subrange is fetched.
    82  		},
    83  
    84  		{
    85  			name:                "another request data close to the end of object, cached by previous test",
    86  			offset:              1040100,
    87  			length:              subrangeSize,
    88  			expectedLength:      8476,
    89  			expectedCachedBytes: 8576,
    90  		},
    91  
    92  		{
    93  			name:                 "entire object, combination of cached and uncached subranges",
    94  			offset:               0,
    95  			length:               length,
    96  			expectedLength:       length,
    97  			expectedCachedBytes:  5*subrangeSize + 8576, // 5 subrange cached from first test, plus last incomplete subrange.
    98  			expectedFetchedBytes: 60 * subrangeSize,
    99  		},
   100  
   101  		{
   102  			name:                "entire object again, everything is cached",
   103  			offset:              0,
   104  			length:              length,
   105  			expectedLength:      length,
   106  			expectedCachedBytes: length, // Entire file is now cached.
   107  		},
   108  
   109  		{
   110  			name:                 "entire object again, nothing is cached",
   111  			offset:               0,
   112  			length:               length,
   113  			expectedLength:       length,
   114  			expectedFetchedBytes: length,
   115  			expectedCachedBytes:  0, // Cache is flushed.
   116  			init: func() {
   117  				cache.flush()
   118  			},
   119  		},
   120  
   121  		{
   122  			name:                 "missing first subranges",
   123  			offset:               0,
   124  			length:               10 * subrangeSize,
   125  			expectedLength:       10 * subrangeSize,
   126  			expectedFetchedBytes: 3 * subrangeSize,
   127  			expectedCachedBytes:  7 * subrangeSize,
   128  			init: func() {
   129  				// Delete first 3 subranges.
   130  				objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 0 * subrangeSize, End: 1 * subrangeSize}
   131  				delete(cache.cache, objectSubrange.String())
   132  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 1 * subrangeSize, End: 2 * subrangeSize}
   133  				delete(cache.cache, objectSubrange.String())
   134  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 2 * subrangeSize, End: 3 * subrangeSize}
   135  				delete(cache.cache, objectSubrange.String())
   136  			},
   137  		},
   138  
   139  		{
   140  			name:                 "missing last subranges",
   141  			offset:               0,
   142  			length:               10 * subrangeSize,
   143  			expectedLength:       10 * subrangeSize,
   144  			expectedFetchedBytes: 3 * subrangeSize,
   145  			expectedCachedBytes:  7 * subrangeSize,
   146  			init: func() {
   147  				// Delete last 3 subranges.
   148  				objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 7 * subrangeSize, End: 8 * subrangeSize}
   149  				delete(cache.cache, objectSubrange.String())
   150  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 8 * subrangeSize, End: 9 * subrangeSize}
   151  				delete(cache.cache, objectSubrange.String())
   152  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 9 * subrangeSize, End: 10 * subrangeSize}
   153  				delete(cache.cache, objectSubrange.String())
   154  			},
   155  		},
   156  
   157  		{
   158  			name:                 "missing middle subranges",
   159  			offset:               0,
   160  			length:               10 * subrangeSize,
   161  			expectedLength:       10 * subrangeSize,
   162  			expectedFetchedBytes: 3 * subrangeSize,
   163  			expectedCachedBytes:  7 * subrangeSize,
   164  			init: func() {
   165  				// Delete 3 subranges in the middle.
   166  				objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 3 * subrangeSize, End: 4 * subrangeSize}
   167  				delete(cache.cache, objectSubrange.String())
   168  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 4 * subrangeSize, End: 5 * subrangeSize}
   169  				delete(cache.cache, objectSubrange.String())
   170  				objectSubrange = cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: 5 * subrangeSize, End: 6 * subrangeSize}
   171  				delete(cache.cache, objectSubrange.String())
   172  			},
   173  		},
   174  
   175  		{
   176  			name:                 "missing everything except middle subranges",
   177  			offset:               0,
   178  			length:               10 * subrangeSize,
   179  			expectedLength:       10 * subrangeSize,
   180  			expectedFetchedBytes: 7 * subrangeSize,
   181  			expectedCachedBytes:  3 * subrangeSize,
   182  			init: func() {
   183  				// Delete all but 3 subranges in the middle, and keep unlimited number of ranged subrequests.
   184  				for i := int64(0); i < 10; i++ {
   185  					if i > 0 && i%3 == 0 {
   186  						continue
   187  					}
   188  					objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: i * subrangeSize, End: (i + 1) * subrangeSize}
   189  					delete(cache.cache, objectSubrange.String())
   190  				}
   191  			},
   192  		},
   193  
   194  		{
   195  			name:                   "missing everything except middle subranges, one subrequest only",
   196  			offset:                 0,
   197  			length:                 10 * subrangeSize,
   198  			expectedLength:         10 * subrangeSize,
   199  			expectedFetchedBytes:   7 * subrangeSize,
   200  			expectedCachedBytes:    3 * subrangeSize,
   201  			expectedRefetchedBytes: 3 * subrangeSize, // Entire object fetched, 3 subranges are "refetched".
   202  			maxGetRangeRequests:    1,
   203  			init: func() {
   204  				// Delete all but 3 subranges in the middle, but only allow 1 subrequest.
   205  				for i := int64(0); i < 10; i++ {
   206  					if i == 3 || i == 5 || i == 7 {
   207  						continue
   208  					}
   209  					objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: i * subrangeSize, End: (i + 1) * subrangeSize}
   210  					delete(cache.cache, objectSubrange.String())
   211  				}
   212  			},
   213  		},
   214  
   215  		{
   216  			name:                 "missing everything except middle subranges, two subrequests",
   217  			offset:               0,
   218  			length:               10 * subrangeSize,
   219  			expectedLength:       10 * subrangeSize,
   220  			expectedFetchedBytes: 7 * subrangeSize,
   221  			expectedCachedBytes:  3 * subrangeSize,
   222  			maxGetRangeRequests:  2,
   223  			init: func() {
   224  				// Delete all but one subranges in the middle, and allow 2 subrequests. They will be: 0-80000, 128000-160000.
   225  				for i := int64(0); i < 10; i++ {
   226  					if i == 5 || i == 6 || i == 7 {
   227  						continue
   228  					}
   229  					objectSubrange := cachekey.BucketCacheKey{Verb: cachekey.SubrangeVerb, Name: name, Start: i * subrangeSize, End: (i + 1) * subrangeSize}
   230  					delete(cache.cache, objectSubrange.String())
   231  				}
   232  			},
   233  		},
   234  	} {
   235  		t.Run(tc.name, func(t *testing.T) {
   236  			if tc.init != nil {
   237  				tc.init()
   238  			}
   239  
   240  			cfg := thanoscache.NewCachingBucketConfig()
   241  			cfg.CacheGetRange("chunks", cache, isTSDBChunkFile, subrangeSize, time.Hour, time.Hour, tc.maxGetRangeRequests)
   242  
   243  			cachingBucket, err := NewCachingBucket(inmem, cfg, nil, nil)
   244  			testutil.Ok(t, err)
   245  
   246  			verifyGetRange(t, cachingBucket, name, tc.offset, tc.length, tc.expectedLength)
   247  			testutil.Equals(t, tc.expectedCachedBytes, int64(promtest.ToFloat64(cachingBucket.fetchedGetRangeBytes.WithLabelValues(originCache, "chunks"))))
   248  			testutil.Equals(t, tc.expectedFetchedBytes, int64(promtest.ToFloat64(cachingBucket.fetchedGetRangeBytes.WithLabelValues(originBucket, "chunks"))))
   249  			testutil.Equals(t, tc.expectedRefetchedBytes, int64(promtest.ToFloat64(cachingBucket.refetchedGetRangeBytes.WithLabelValues(originCache, "chunks"))))
   250  		})
   251  	}
   252  }
   253  
   254  func verifyGetRange(t *testing.T, cachingBucket *CachingBucket, name string, offset, length, expectedLength int64) {
   255  	r, err := cachingBucket.GetRange(context.Background(), name, offset, length)
   256  	testutil.Ok(t, err)
   257  
   258  	read, err := io.ReadAll(r)
   259  	testutil.Ok(t, err)
   260  	testutil.Equals(t, expectedLength, int64(len(read)))
   261  
   262  	for ix := 0; ix < len(read); ix++ {
   263  		if byte(ix)+byte(offset) != read[ix] {
   264  			t.Fatalf("bytes differ at position %d", ix)
   265  		}
   266  	}
   267  }
   268  
   269  type cacheItem struct {
   270  	data []byte
   271  	exp  time.Time
   272  }
   273  
   274  type mockCache struct {
   275  	mu    sync.Mutex
   276  	cache map[string]cacheItem
   277  }
   278  
   279  func newMockCache() *mockCache {
   280  	c := &mockCache{}
   281  	c.flush()
   282  	return c
   283  }
   284  
   285  func (m *mockCache) Store(data map[string][]byte, ttl time.Duration) {
   286  	m.mu.Lock()
   287  	defer m.mu.Unlock()
   288  
   289  	exp := time.Now().Add(ttl)
   290  	for key, val := range data {
   291  		m.cache[key] = cacheItem{data: val, exp: exp}
   292  	}
   293  }
   294  
   295  func (m *mockCache) Fetch(_ context.Context, keys []string) map[string][]byte {
   296  	m.mu.Lock()
   297  	defer m.mu.Unlock()
   298  
   299  	found := make(map[string][]byte, len(keys))
   300  
   301  	now := time.Now()
   302  	for _, k := range keys {
   303  		v, ok := m.cache[k]
   304  		if ok && now.Before(v.exp) {
   305  			found[k] = v.data
   306  		}
   307  	}
   308  
   309  	return found
   310  }
   311  
   312  func (m *mockCache) Name() string {
   313  	return "mockCache"
   314  }
   315  
   316  func (m *mockCache) flush() {
   317  	m.cache = map[string]cacheItem{}
   318  }
   319  
   320  func TestMergeRanges(t *testing.T) {
   321  	for ix, tc := range []struct {
   322  		input    []rng
   323  		limit    int64
   324  		expected []rng
   325  	}{
   326  		{
   327  			input:    nil,
   328  			limit:    0,
   329  			expected: nil,
   330  		},
   331  
   332  		{
   333  			input:    []rng{{start: 0, end: 100}, {start: 100, end: 200}, {start: 500, end: 1000}},
   334  			limit:    0,
   335  			expected: []rng{{start: 0, end: 200}, {start: 500, end: 1000}},
   336  		},
   337  
   338  		{
   339  			input:    []rng{{start: 0, end: 100}, {start: 500, end: 1000}},
   340  			limit:    300,
   341  			expected: []rng{{start: 0, end: 100}, {start: 500, end: 1000}},
   342  		},
   343  		{
   344  			input:    []rng{{start: 0, end: 100}, {start: 500, end: 1000}},
   345  			limit:    400,
   346  			expected: []rng{{start: 0, end: 1000}},
   347  		},
   348  	} {
   349  		t.Run(fmt.Sprintf("%d", ix), func(t *testing.T) {
   350  			testutil.Equals(t, tc.expected, mergeRanges(tc.input, tc.limit))
   351  		})
   352  	}
   353  }
   354  
   355  func TestInvalidOffsetAndLength(t *testing.T) {
   356  	b := &testBucket{objstore.NewInMemBucket()}
   357  
   358  	cfg := thanoscache.NewCachingBucketConfig()
   359  	cfg.CacheGetRange("chunks", newMockCache(), func(string) bool { return true }, 10000, time.Hour, time.Hour, 3)
   360  
   361  	c, err := NewCachingBucket(b, cfg, nil, nil)
   362  	testutil.Ok(t, err)
   363  
   364  	r, err := c.GetRange(context.Background(), "test", -1, 1000)
   365  	testutil.Equals(t, nil, r)
   366  	testutil.NotOk(t, err)
   367  
   368  	r, err = c.GetRange(context.Background(), "test", 100, -1)
   369  	testutil.Equals(t, nil, r)
   370  	testutil.NotOk(t, err)
   371  }
   372  
   373  type testBucket struct {
   374  	*objstore.InMemBucket
   375  }
   376  
   377  func (b *testBucket) GetRange(ctx context.Context, name string, off, length int64) (io.ReadCloser, error) {
   378  	if off < 0 {
   379  		return nil, errors.Errorf("invalid offset: %d", off)
   380  	}
   381  
   382  	if length <= 0 {
   383  		return nil, errors.Errorf("invalid length: %d", length)
   384  	}
   385  
   386  	return b.InMemBucket.GetRange(ctx, name, off, length)
   387  }
   388  
   389  func TestCachedIter(t *testing.T) {
   390  	inmem := objstore.NewInMemBucket()
   391  	testutil.Ok(t, inmem.Upload(context.Background(), "/file-1", strings.NewReader("hej")))
   392  	testutil.Ok(t, inmem.Upload(context.Background(), "/file-2", strings.NewReader("ahoj")))
   393  	testutil.Ok(t, inmem.Upload(context.Background(), "/file-3", strings.NewReader("hello")))
   394  	testutil.Ok(t, inmem.Upload(context.Background(), "/file-4", strings.NewReader("ciao")))
   395  
   396  	allFiles := []string{"/file-1", "/file-2", "/file-3", "/file-4"}
   397  
   398  	// We reuse cache between tests (!)
   399  	cache := newMockCache()
   400  
   401  	const cfgName = "dirs"
   402  	cfg := thanoscache.NewCachingBucketConfig()
   403  	cfg.CacheIter(cfgName, cache, func(string) bool { return true }, 5*time.Minute, JSONIterCodec{})
   404  
   405  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   406  	testutil.Ok(t, err)
   407  
   408  	verifyIter(t, cb, allFiles, false, cfgName)
   409  
   410  	testutil.Ok(t, inmem.Upload(context.Background(), "/file-5", strings.NewReader("nazdar")))
   411  	verifyIter(t, cb, allFiles, true, cfgName) // Iter returns old response.
   412  
   413  	cache.flush()
   414  	allFiles = append(allFiles, "/file-5")
   415  	verifyIter(t, cb, allFiles, false, cfgName)
   416  
   417  	cache.flush()
   418  
   419  	e := errors.Errorf("test error")
   420  
   421  	// This iteration returns false. Result will not be cached.
   422  	testutil.Equals(t, e, cb.Iter(context.Background(), "/", func(_ string) error {
   423  		return e
   424  	}))
   425  
   426  	// Nothing cached now.
   427  	verifyIter(t, cb, allFiles, false, cfgName)
   428  }
   429  
   430  func verifyIter(t *testing.T, cb *CachingBucket, expectedFiles []string, expectedCache bool, cfgName string) {
   431  	hitsBefore := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpIter, cfgName)))
   432  
   433  	col := iterCollector{}
   434  	testutil.Ok(t, cb.Iter(context.Background(), "/", col.collect))
   435  
   436  	hitsAfter := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpIter, cfgName)))
   437  
   438  	sort.Strings(col.items)
   439  	testutil.Equals(t, expectedFiles, col.items)
   440  
   441  	expectedHitsDiff := 0
   442  	if expectedCache {
   443  		expectedHitsDiff = 1
   444  	}
   445  
   446  	testutil.Equals(t, expectedHitsDiff, hitsAfter-hitsBefore)
   447  }
   448  
   449  type iterCollector struct {
   450  	items []string
   451  }
   452  
   453  func (it *iterCollector) collect(s string) error {
   454  	it.items = append(it.items, s)
   455  	return nil
   456  }
   457  
   458  func TestExists(t *testing.T) {
   459  	inmem := objstore.NewInMemBucket()
   460  
   461  	// We reuse cache between tests (!)
   462  	cache := newMockCache()
   463  
   464  	cfg := thanoscache.NewCachingBucketConfig()
   465  	const cfgName = "test"
   466  	cfg.CacheExists(cfgName, cache, matchAll, 10*time.Minute, 2*time.Minute)
   467  
   468  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   469  	testutil.Ok(t, err)
   470  
   471  	verifyExists(t, cb, testFilename, false, false, cfgName)
   472  
   473  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, strings.NewReader("hej")))
   474  	verifyExists(t, cb, testFilename, false, true, cfgName) // Reused cache result.
   475  	cache.flush()
   476  	verifyExists(t, cb, testFilename, true, false, cfgName)
   477  
   478  	testutil.Ok(t, inmem.Delete(context.Background(), testFilename))
   479  	verifyExists(t, cb, testFilename, true, true, cfgName) // Reused cache result.
   480  	cache.flush()
   481  	verifyExists(t, cb, testFilename, false, false, cfgName)
   482  }
   483  
   484  func TestExistsCachingDisabled(t *testing.T) {
   485  	inmem := objstore.NewInMemBucket()
   486  
   487  	// We reuse cache between tests (!)
   488  	cache := newMockCache()
   489  
   490  	cfg := thanoscache.NewCachingBucketConfig()
   491  	const cfgName = "test"
   492  	cfg.CacheExists(cfgName, cache, func(string) bool { return false }, 10*time.Minute, 2*time.Minute)
   493  
   494  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   495  	testutil.Ok(t, err)
   496  
   497  	verifyExists(t, cb, testFilename, false, false, cfgName)
   498  
   499  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, strings.NewReader("hej")))
   500  	verifyExists(t, cb, testFilename, true, false, cfgName)
   501  
   502  	testutil.Ok(t, inmem.Delete(context.Background(), testFilename))
   503  	verifyExists(t, cb, testFilename, false, false, cfgName)
   504  }
   505  
   506  func verifyExists(t *testing.T, cb *CachingBucket, file string, exists, fromCache bool, cfgName string) {
   507  	t.Helper()
   508  	hitsBefore := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpExists, cfgName)))
   509  	ok, err := cb.Exists(context.Background(), file)
   510  	testutil.Ok(t, err)
   511  	testutil.Equals(t, exists, ok)
   512  	hitsAfter := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpExists, cfgName)))
   513  
   514  	if fromCache {
   515  		testutil.Equals(t, 1, hitsAfter-hitsBefore)
   516  	} else {
   517  		testutil.Equals(t, 0, hitsAfter-hitsBefore)
   518  	}
   519  }
   520  
   521  func TestGet(t *testing.T) {
   522  	inmem := objstore.NewInMemBucket()
   523  
   524  	// We reuse cache between tests (!)
   525  	cache := newMockCache()
   526  
   527  	cfg := thanoscache.NewCachingBucketConfig()
   528  	const cfgName = "metafile"
   529  	cfg.CacheGet(cfgName, cache, matchAll, 1024, 10*time.Minute, 10*time.Minute, 2*time.Minute)
   530  	cfg.CacheExists(cfgName, cache, matchAll, 10*time.Minute, 2*time.Minute)
   531  
   532  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   533  	testutil.Ok(t, err)
   534  
   535  	verifyGet(t, cb, testFilename, nil, false, cfgName)
   536  	verifyExists(t, cb, testFilename, false, true, cfgName)
   537  
   538  	data := []byte("hello world")
   539  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, bytes.NewBuffer(data)))
   540  
   541  	// Even if file is now uploaded, old data is served from cache.
   542  	verifyGet(t, cb, testFilename, nil, true, cfgName)
   543  	verifyExists(t, cb, testFilename, false, true, cfgName)
   544  
   545  	cache.flush()
   546  
   547  	verifyGet(t, cb, testFilename, data, false, cfgName)
   548  	verifyGet(t, cb, testFilename, data, true, cfgName)
   549  	verifyExists(t, cb, testFilename, true, true, cfgName)
   550  }
   551  
   552  func TestGetTooBigObject(t *testing.T) {
   553  	inmem := objstore.NewInMemBucket()
   554  
   555  	// We reuse cache between tests (!)
   556  	cache := newMockCache()
   557  
   558  	cfg := thanoscache.NewCachingBucketConfig()
   559  	const cfgName = "metafile"
   560  	// Only allow 5 bytes to be cached.
   561  	cfg.CacheGet(cfgName, cache, matchAll, 5, 10*time.Minute, 10*time.Minute, 2*time.Minute)
   562  	cfg.CacheExists(cfgName, cache, matchAll, 10*time.Minute, 2*time.Minute)
   563  
   564  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   565  	testutil.Ok(t, err)
   566  
   567  	data := []byte("hello world")
   568  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, bytes.NewBuffer(data)))
   569  
   570  	// Object is too big, so it will not be stored to cache on first read.
   571  	verifyGet(t, cb, testFilename, data, false, cfgName)
   572  	verifyGet(t, cb, testFilename, data, false, cfgName)
   573  	verifyExists(t, cb, testFilename, true, true, cfgName)
   574  }
   575  
   576  func TestGetPartialRead(t *testing.T) {
   577  	inmem := objstore.NewInMemBucket()
   578  
   579  	cache := newMockCache()
   580  
   581  	cfg := thanoscache.NewCachingBucketConfig()
   582  	const cfgName = "metafile"
   583  	cfg.CacheGet(cfgName, cache, matchAll, 1024, 10*time.Minute, 10*time.Minute, 2*time.Minute)
   584  	cfg.CacheExists(cfgName, cache, matchAll, 10*time.Minute, 2*time.Minute)
   585  
   586  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   587  	testutil.Ok(t, err)
   588  
   589  	data := []byte("hello world")
   590  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, bytes.NewBuffer(data)))
   591  
   592  	// Read only few bytes from data.
   593  	r, err := cb.Get(context.Background(), testFilename)
   594  	testutil.Ok(t, err)
   595  	_, err = r.Read(make([]byte, 1))
   596  	testutil.Ok(t, err)
   597  	testutil.Ok(t, r.Close())
   598  
   599  	// Object wasn't cached as it wasn't fully read.
   600  	verifyGet(t, cb, testFilename, data, false, cfgName)
   601  	// VerifyGet read object, so now it's cached.
   602  	verifyGet(t, cb, testFilename, data, true, cfgName)
   603  }
   604  
   605  func verifyGet(t *testing.T, cb *CachingBucket, file string, expectedData []byte, cacheUsed bool, cfgName string) {
   606  	hitsBefore := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpGet, cfgName)))
   607  
   608  	r, err := cb.Get(context.Background(), file)
   609  	if expectedData == nil {
   610  		testutil.Assert(t, cb.IsObjNotFoundErr(err))
   611  
   612  		hitsAfter := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpGet, cfgName)))
   613  		if cacheUsed {
   614  			testutil.Equals(t, 1, hitsAfter-hitsBefore)
   615  		} else {
   616  			testutil.Equals(t, 0, hitsAfter-hitsBefore)
   617  		}
   618  	} else {
   619  		testutil.Ok(t, err)
   620  		defer runutil.CloseWithLogOnErr(nil, r, "verifyGet")
   621  		data, err := io.ReadAll(r)
   622  		testutil.Ok(t, err)
   623  		testutil.Equals(t, expectedData, data)
   624  
   625  		hitsAfter := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpGet, cfgName)))
   626  		if cacheUsed {
   627  			testutil.Equals(t, 1, hitsAfter-hitsBefore)
   628  		} else {
   629  			testutil.Equals(t, 0, hitsAfter-hitsBefore)
   630  		}
   631  	}
   632  }
   633  
   634  func TestAttributes(t *testing.T) {
   635  	inmem := objstore.NewInMemBucket()
   636  
   637  	// We reuse cache between tests (!)
   638  	cache := newMockCache()
   639  
   640  	cfg := thanoscache.NewCachingBucketConfig()
   641  	const cfgName = "test"
   642  	cfg.CacheAttributes(cfgName, cache, matchAll, time.Minute)
   643  
   644  	cb, err := NewCachingBucket(inmem, cfg, nil, nil)
   645  	testutil.Ok(t, err)
   646  
   647  	verifyObjectAttrs(t, cb, testFilename, -1, false, cfgName)
   648  	verifyObjectAttrs(t, cb, testFilename, -1, false, cfgName) // Attributes doesn't cache non-existent files.
   649  
   650  	data := []byte("hello world")
   651  	testutil.Ok(t, inmem.Upload(context.Background(), testFilename, bytes.NewBuffer(data)))
   652  
   653  	verifyObjectAttrs(t, cb, testFilename, len(data), false, cfgName)
   654  	verifyObjectAttrs(t, cb, testFilename, len(data), true, cfgName)
   655  }
   656  
   657  func verifyObjectAttrs(t *testing.T, cb *CachingBucket, file string, expectedLength int, cacheUsed bool, cfgName string) {
   658  	t.Helper()
   659  	hitsBefore := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpAttributes, cfgName)))
   660  
   661  	attrs, err := cb.Attributes(context.Background(), file)
   662  	if expectedLength < 0 {
   663  		testutil.Assert(t, cb.IsObjNotFoundErr(err))
   664  	} else {
   665  		testutil.Ok(t, err)
   666  		testutil.Equals(t, int64(expectedLength), attrs.Size)
   667  
   668  		hitsAfter := int(promtest.ToFloat64(cb.operationHits.WithLabelValues(objstore.OpAttributes, cfgName)))
   669  		if cacheUsed {
   670  			testutil.Equals(t, 1, hitsAfter-hitsBefore)
   671  		} else {
   672  			testutil.Equals(t, 0, hitsAfter-hitsBefore)
   673  		}
   674  	}
   675  }
   676  
   677  func matchAll(string) bool { return true }