...

Source file src/k8s.io/client-go/discovery/cached/disk/round_tripper_test.go

Documentation: k8s.io/client-go/discovery/cached/disk

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package disk
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/sha256"
    22  	"io"
    23  	"net/http"
    24  	"net/url"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	"github.com/peterbourgon/diskv"
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  // copied from k8s.io/client-go/transport/round_trippers_test.go
    34  type testRoundTripper struct {
    35  	Request  *http.Request
    36  	Response *http.Response
    37  	Err      error
    38  }
    39  
    40  func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    41  	rt.Request = req
    42  	return rt.Response, rt.Err
    43  }
    44  
    45  func BenchmarkDiskCache(b *testing.B) {
    46  	cacheDir, err := os.MkdirTemp("", "cache-rt")
    47  	if err != nil {
    48  		b.Fatal(err)
    49  	}
    50  	defer os.RemoveAll(cacheDir)
    51  
    52  	d := diskv.New(diskv.Options{
    53  		PathPerm: os.FileMode(0750),
    54  		FilePerm: os.FileMode(0660),
    55  		BasePath: cacheDir,
    56  		TempDir:  filepath.Join(cacheDir, ".diskv-temp"),
    57  	})
    58  
    59  	k := "localhost:8080/apis/batch/v1.json"
    60  	v, err := os.ReadFile("../../testdata/apis/batch/v1.json")
    61  	if err != nil {
    62  		b.Fatal(err)
    63  	}
    64  
    65  	c := sumDiskCache{disk: d}
    66  
    67  	for n := 0; n < b.N; n++ {
    68  		c.Set(k, v)
    69  		c.Get(k)
    70  		c.Delete(k)
    71  	}
    72  }
    73  
    74  func TestCacheRoundTripper(t *testing.T) {
    75  	rt := &testRoundTripper{}
    76  	cacheDir, err := os.MkdirTemp("", "cache-rt")
    77  	defer os.RemoveAll(cacheDir)
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	cache := newCacheRoundTripper(cacheDir, rt)
    82  
    83  	// First call, caches the response
    84  	req := &http.Request{
    85  		Method: http.MethodGet,
    86  		URL:    &url.URL{Host: "localhost"},
    87  	}
    88  	rt.Response = &http.Response{
    89  		Header:     http.Header{"ETag": []string{`"123456"`}},
    90  		Body:       io.NopCloser(bytes.NewReader([]byte("Content"))),
    91  		StatusCode: http.StatusOK,
    92  	}
    93  	resp, err := cache.RoundTrip(req)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	content, err := io.ReadAll(resp.Body)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	if string(content) != "Content" {
   102  		t.Errorf(`Expected Body to be "Content", got %q`, string(content))
   103  	}
   104  
   105  	// Second call, returns cached response
   106  	req = &http.Request{
   107  		Method: http.MethodGet,
   108  		URL:    &url.URL{Host: "localhost"},
   109  	}
   110  	rt.Response = &http.Response{
   111  		StatusCode: http.StatusNotModified,
   112  		Body:       io.NopCloser(bytes.NewReader([]byte("Other Content"))),
   113  	}
   114  
   115  	resp, err = cache.RoundTrip(req)
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  
   120  	// Read body and make sure we have the initial content
   121  	content, err = io.ReadAll(resp.Body)
   122  	resp.Body.Close()
   123  	if err != nil {
   124  		t.Fatal(err)
   125  	}
   126  	if string(content) != "Content" {
   127  		t.Errorf("Invalid content read from cache %q", string(content))
   128  	}
   129  }
   130  
   131  func TestCacheRoundTripperPathPerm(t *testing.T) {
   132  	assert := assert.New(t)
   133  
   134  	rt := &testRoundTripper{}
   135  	cacheDir, err := os.MkdirTemp("", "cache-rt")
   136  	os.RemoveAll(cacheDir)
   137  	defer os.RemoveAll(cacheDir)
   138  
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	cache := newCacheRoundTripper(cacheDir, rt)
   143  
   144  	// First call, caches the response
   145  	req := &http.Request{
   146  		Method: http.MethodGet,
   147  		URL:    &url.URL{Host: "localhost"},
   148  	}
   149  	rt.Response = &http.Response{
   150  		Header:     http.Header{"ETag": []string{`"123456"`}},
   151  		Body:       io.NopCloser(bytes.NewReader([]byte("Content"))),
   152  		StatusCode: http.StatusOK,
   153  	}
   154  	resp, err := cache.RoundTrip(req)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	content, err := io.ReadAll(resp.Body)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	if string(content) != "Content" {
   163  		t.Errorf(`Expected Body to be "Content", got %q`, string(content))
   164  	}
   165  
   166  	err = filepath.Walk(cacheDir, func(path string, info os.FileInfo, err error) error {
   167  		if err != nil {
   168  			return err
   169  		}
   170  		if info.IsDir() {
   171  			assert.Equal(os.FileMode(0750), info.Mode().Perm())
   172  		} else {
   173  			assert.Equal(os.FileMode(0660), info.Mode().Perm())
   174  		}
   175  		return nil
   176  	})
   177  	assert.NoError(err)
   178  }
   179  
   180  func TestSumDiskCache(t *testing.T) {
   181  	assert := assert.New(t)
   182  
   183  	// Ensure that we'll return a cache miss if the backing file doesn't exist.
   184  	t.Run("NoSuchKey", func(t *testing.T) {
   185  		cacheDir, err := os.MkdirTemp("", "cache-test")
   186  		if err != nil {
   187  			t.Fatal(err)
   188  		}
   189  		defer os.RemoveAll(cacheDir)
   190  		d := diskv.New(diskv.Options{BasePath: cacheDir, TempDir: filepath.Join(cacheDir, ".diskv-temp")})
   191  		c := &sumDiskCache{disk: d}
   192  
   193  		key := "testing"
   194  
   195  		got, ok := c.Get(key)
   196  		assert.False(ok)
   197  		assert.Equal([]byte{}, got)
   198  	})
   199  
   200  	// Ensure that we'll return a cache miss if the backing file is empty.
   201  	t.Run("EmptyFile", func(t *testing.T) {
   202  		cacheDir, err := os.MkdirTemp("", "cache-test")
   203  		if err != nil {
   204  			t.Fatal(err)
   205  		}
   206  		defer os.RemoveAll(cacheDir)
   207  		d := diskv.New(diskv.Options{BasePath: cacheDir, TempDir: filepath.Join(cacheDir, ".diskv-temp")})
   208  		c := &sumDiskCache{disk: d}
   209  
   210  		key := "testing"
   211  
   212  		f, err := os.Create(filepath.Join(cacheDir, sanitize(key)))
   213  		if err != nil {
   214  			t.Fatal(err)
   215  		}
   216  		f.Close()
   217  
   218  		got, ok := c.Get(key)
   219  		assert.False(ok)
   220  		assert.Equal([]byte{}, got)
   221  	})
   222  
   223  	// Ensure that we'll return a cache miss if the backing has an invalid
   224  	// checksum.
   225  	t.Run("InvalidChecksum", func(t *testing.T) {
   226  		cacheDir, err := os.MkdirTemp("", "cache-test")
   227  		if err != nil {
   228  			t.Fatal(err)
   229  		}
   230  		defer os.RemoveAll(cacheDir)
   231  		d := diskv.New(diskv.Options{BasePath: cacheDir, TempDir: filepath.Join(cacheDir, ".diskv-temp")})
   232  		c := &sumDiskCache{disk: d}
   233  
   234  		key := "testing"
   235  		value := []byte("testing")
   236  		mismatchedValue := []byte("testink")
   237  		sum := sha256.Sum256(value)
   238  
   239  		// Create a file with the sum of 'value' followed by the bytes of
   240  		// 'mismatchedValue'.
   241  		f, err := os.Create(filepath.Join(cacheDir, sanitize(key)))
   242  		if err != nil {
   243  			t.Fatal(err)
   244  		}
   245  		f.Write(sum[:])
   246  		f.Write(mismatchedValue)
   247  		f.Close()
   248  
   249  		// The mismatched checksum should result in a cache miss.
   250  		got, ok := c.Get(key)
   251  		assert.False(ok)
   252  		assert.Equal([]byte{}, got)
   253  	})
   254  
   255  	// Ensure that our disk cache will happily cache over the top of an existing
   256  	// value. We depend on this behaviour to recover from corrupted cache
   257  	// entries. When Get detects a bad checksum it will return a cache miss.
   258  	// This should cause httpcache to fall back to its underlying transport and
   259  	// to subsequently cache the new value, overwriting the corrupt one.
   260  	t.Run("OverwriteExistingKey", func(t *testing.T) {
   261  		cacheDir, err := os.MkdirTemp("", "cache-test")
   262  		if err != nil {
   263  			t.Fatal(err)
   264  		}
   265  		defer os.RemoveAll(cacheDir)
   266  		d := diskv.New(diskv.Options{BasePath: cacheDir, TempDir: filepath.Join(cacheDir, ".diskv-temp")})
   267  		c := &sumDiskCache{disk: d}
   268  
   269  		key := "testing"
   270  		value := []byte("cool value!")
   271  
   272  		// Write a value.
   273  		c.Set(key, value)
   274  		got, ok := c.Get(key)
   275  
   276  		// Ensure we can read back what we wrote.
   277  		assert.True(ok)
   278  		assert.Equal(value, got)
   279  
   280  		differentValue := []byte("I'm different!")
   281  
   282  		// Write a different value.
   283  		c.Set(key, differentValue)
   284  		got, ok = c.Get(key)
   285  
   286  		// Ensure we can read back the different value.
   287  		assert.True(ok)
   288  		assert.Equal(differentValue, got)
   289  	})
   290  
   291  	// Ensure that deleting a key does in fact delete it.
   292  	t.Run("DeleteKey", func(t *testing.T) {
   293  		cacheDir, err := os.MkdirTemp("", "cache-test")
   294  		if err != nil {
   295  			t.Fatal(err)
   296  		}
   297  		defer os.RemoveAll(cacheDir)
   298  		d := diskv.New(diskv.Options{BasePath: cacheDir, TempDir: filepath.Join(cacheDir, ".diskv-temp")})
   299  		c := &sumDiskCache{disk: d}
   300  
   301  		key := "testing"
   302  		value := []byte("coolValue")
   303  
   304  		c.Set(key, value)
   305  
   306  		// Ensure we successfully set the value.
   307  		got, ok := c.Get(key)
   308  		assert.True(ok)
   309  		assert.Equal(value, got)
   310  
   311  		c.Delete(key)
   312  
   313  		// Ensure the value is gone.
   314  		got, ok = c.Get(key)
   315  		assert.False(ok)
   316  		assert.Equal([]byte{}, got)
   317  
   318  		// Ensure that deleting a non-existent value is a no-op.
   319  		c.Delete(key)
   320  	})
   321  }
   322  

View as plain text