github.com/argoproj/argo-cd/v3@v3.2.1/util/cache/redis_test.go (about) 1 package cache 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "io" 7 "strconv" 8 "testing" 9 "time" 10 11 "github.com/alicebob/miniredis/v2" 12 "github.com/prometheus/client_golang/prometheus" 13 promcm "github.com/prometheus/client_model/go" 14 "github.com/redis/go-redis/v9" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 var ( 20 redisRequestCounter = prometheus.NewCounterVec( 21 prometheus.CounterOpts{ 22 Name: "argocd_redis_request_total", 23 }, 24 []string{"initiator", "failed"}, 25 ) 26 redisRequestHistogram = prometheus.NewHistogramVec( 27 prometheus.HistogramOpts{ 28 Name: "argocd_redis_request_duration", 29 Buckets: []float64{0.1, 0.25, .5, 1, 2}, 30 }, 31 []string{"initiator"}, 32 ) 33 ) 34 35 type MockMetricsServer struct { 36 redisRequestCounter *prometheus.CounterVec 37 redisRequestHistogram *prometheus.HistogramVec 38 } 39 40 func NewMockMetricsServer() *MockMetricsServer { 41 registry := prometheus.NewRegistry() 42 registry.MustRegister(redisRequestCounter) 43 registry.MustRegister(redisRequestHistogram) 44 return &MockMetricsServer{ 45 redisRequestCounter: redisRequestCounter, 46 redisRequestHistogram: redisRequestHistogram, 47 } 48 } 49 50 func (m *MockMetricsServer) IncRedisRequest(failed bool) { 51 m.redisRequestCounter.WithLabelValues("mock", strconv.FormatBool(failed)).Inc() 52 } 53 54 func (m *MockMetricsServer) ObserveRedisRequestDuration(duration time.Duration) { 55 m.redisRequestHistogram.WithLabelValues("mock").Observe(duration.Seconds()) 56 } 57 58 func TestRedisSetCache(t *testing.T) { 59 mr, err := miniredis.Run() 60 if err != nil { 61 panic(err) 62 } 63 defer mr.Close() 64 assert.NotNil(t, mr) 65 66 t.Run("Successful set", func(t *testing.T) { 67 client := NewRedisCache(redis.NewClient(&redis.Options{Addr: mr.Addr()}), 60*time.Second, RedisCompressionNone) 68 err = client.Set(&Item{Key: "foo", Object: "bar"}) 69 require.NoError(t, err) 70 }) 71 72 t.Run("Successful get", func(t *testing.T) { 73 var res string 74 client := NewRedisCache(redis.NewClient(&redis.Options{Addr: mr.Addr()}), 10*time.Second, RedisCompressionNone) 75 err = client.Get("foo", &res) 76 require.NoError(t, err) 77 assert.Equal(t, "bar", res) 78 }) 79 80 t.Run("Successful delete", func(t *testing.T) { 81 client := NewRedisCache(redis.NewClient(&redis.Options{Addr: mr.Addr()}), 10*time.Second, RedisCompressionNone) 82 err = client.Delete("foo") 83 require.NoError(t, err) 84 }) 85 86 t.Run("Cache miss", func(t *testing.T) { 87 var res string 88 client := NewRedisCache(redis.NewClient(&redis.Options{Addr: mr.Addr()}), 10*time.Second, RedisCompressionNone) 89 err = client.Get("foo", &res) 90 assert.ErrorContains(t, err, "cache: key is missing") 91 }) 92 } 93 94 func TestRedisSetCacheCompressed(t *testing.T) { 95 mr, err := miniredis.Run() 96 if err != nil { 97 panic(err) 98 } 99 defer mr.Close() 100 assert.NotNil(t, mr) 101 102 redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()}) 103 104 client := NewRedisCache(redisClient, 10*time.Second, RedisCompressionGZip) 105 testValue := "my-value" 106 require.NoError(t, client.Set(&Item{Key: "my-key", Object: testValue})) 107 108 compressedData, err := redisClient.Get(t.Context(), "my-key.gz").Bytes() 109 require.NoError(t, err) 110 111 assert.Greater(t, len(compressedData), len([]byte(testValue)), "compressed data is bigger than uncompressed") 112 113 // trying to unzip compressed data 114 gzipReader, err := gzip.NewReader(bytes.NewBuffer(compressedData)) 115 require.NoError(t, err) 116 _, err = io.ReadAll(gzipReader) 117 require.NoError(t, err) 118 119 var result string 120 require.NoError(t, client.Get("my-key", &result)) 121 122 assert.Equal(t, testValue, result) 123 } 124 125 func TestRedisMetrics(t *testing.T) { 126 mr, err := miniredis.Run() 127 if err != nil { 128 panic(err) 129 } 130 defer mr.Close() 131 132 metric := &promcm.Metric{} 133 ms := NewMockMetricsServer() 134 redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()}) 135 faultyRedisClient := redis.NewClient(&redis.Options{Addr: "invalidredishost.invalid:12345"}) 136 CollectMetrics(redisClient, ms, nil) 137 CollectMetrics(faultyRedisClient, ms, nil) 138 139 client := NewRedisCache(redisClient, 60*time.Second, RedisCompressionNone) 140 faultyClient := NewRedisCache(faultyRedisClient, 60*time.Second, RedisCompressionNone) 141 var res string 142 143 // client successful request 144 err = client.Set(&Item{Key: "foo", Object: "bar"}) 145 require.NoError(t, err) 146 err = client.Get("foo", &res) 147 require.NoError(t, err) 148 149 c, err := ms.redisRequestCounter.GetMetricWithLabelValues("mock", "false") 150 require.NoError(t, err) 151 err = c.Write(metric) 152 require.NoError(t, err) 153 assert.InEpsilon(t, float64(2), metric.Counter.GetValue(), 0.0001) 154 155 // faulty client failed request 156 err = faultyClient.Get("foo", &res) 157 require.Error(t, err) 158 c, err = ms.redisRequestCounter.GetMetricWithLabelValues("mock", "true") 159 require.NoError(t, err) 160 err = c.Write(metric) 161 require.NoError(t, err) 162 assert.InEpsilon(t, float64(1), metric.Counter.GetValue(), 0.0001) 163 164 // both clients histogram count 165 o, err := ms.redisRequestHistogram.GetMetricWithLabelValues("mock") 166 require.NoError(t, err) 167 err = o.(prometheus.Metric).Write(metric) 168 require.NoError(t, err) 169 assert.Equal(t, 3, int(metric.Histogram.GetSampleCount())) 170 }