254 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| package redis
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/letsencrypt/boulder/cmd"
 | |
| 	blog "github.com/letsencrypt/boulder/log"
 | |
| 	"github.com/letsencrypt/boulder/metrics"
 | |
| 	"github.com/letsencrypt/boulder/test"
 | |
| 
 | |
| 	"github.com/redis/go-redis/v9"
 | |
| )
 | |
| 
 | |
| func newTestRedisRing() *redis.Ring {
 | |
| 	CACertFile := "../test/certs/ipki/minica.pem"
 | |
| 	CertFile := "../test/certs/ipki/localhost/cert.pem"
 | |
| 	KeyFile := "../test/certs/ipki/localhost/key.pem"
 | |
| 	tlsConfig := cmd.TLSConfig{
 | |
| 		CACertFile: CACertFile,
 | |
| 		CertFile:   CertFile,
 | |
| 		KeyFile:    KeyFile,
 | |
| 	}
 | |
| 	tlsConfig2, err := tlsConfig.Load(metrics.NoopRegisterer)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	client := redis.NewRing(&redis.RingOptions{
 | |
| 		Username:  "unittest-rw",
 | |
| 		Password:  "824968fa490f4ecec1e52d5e34916bdb60d45f8d",
 | |
| 		TLSConfig: tlsConfig2,
 | |
| 	})
 | |
| 	return client
 | |
| }
 | |
| 
 | |
| func TestNewLookup(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	_, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| }
 | |
| 
 | |
| func TestStart(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	lookup, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| 
 | |
| 	lookup.start()
 | |
| 	lookup.stop()
 | |
| }
 | |
| 
 | |
| func TestNewLookupWithOneFailingSRV(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	_, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "doesnotexist",
 | |
| 			Domain:  "service.consuls",
 | |
| 		},
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| }
 | |
| 
 | |
| func TestNewLookupWithAllFailingSRV(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	_, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "doesnotexist",
 | |
| 			Domain:  "service.consuls",
 | |
| 		},
 | |
| 		{
 | |
| 			Service: "doesnotexist2",
 | |
| 			Domain:  "service.consuls",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertError(t, err, "Expected newLookup construction to fail")
 | |
| }
 | |
| 
 | |
| func TestUpdateNowWithAllFailingSRV(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	lookup, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| 
 | |
| 	lookup.srvLookups = []cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "doesnotexist1",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 		{
 | |
| 			Service: "doesnotexist2",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	testCtx, cancel := context.WithCancel(context.Background())
 | |
| 	defer cancel()
 | |
| 
 | |
| 	tempErr, nonTempErr := lookup.updateNow(testCtx)
 | |
| 	test.AssertNotError(t, tempErr, "Expected no temporary errors")
 | |
| 	test.AssertError(t, nonTempErr, "Expected non-temporary errors to have occurred")
 | |
| }
 | |
| 
 | |
| func TestUpdateNowWithAllFailingSRVs(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	lookup, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| 
 | |
| 	// Replace the dnsAuthority with a non-existent DNS server, this will cause
 | |
| 	// a timeout error, which is technically a temporary error, but will
 | |
| 	// eventually result in a non-temporary error when no shards are resolved.
 | |
| 	lookup.dnsAuthority = "consuls.services.consuls:53"
 | |
| 
 | |
| 	testCtx, cancel := context.WithCancel(context.Background())
 | |
| 	defer cancel()
 | |
| 	tempErr, nonTempErr := lookup.updateNow(testCtx)
 | |
| 	test.AssertError(t, tempErr, "Expected temporary errors")
 | |
| 	test.AssertError(t, nonTempErr, "Expected a non-temporary error")
 | |
| 	test.AssertErrorIs(t, nonTempErr, ErrNoShardsResolved)
 | |
| }
 | |
| 
 | |
| func TestUpdateNowWithOneFailingSRV(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	logger := blog.NewMock()
 | |
| 	ring := newTestRedisRing()
 | |
| 
 | |
| 	lookup, err := newLookup([]cmd.ServiceDomain{
 | |
| 		{
 | |
| 			Service: "doesnotexist",
 | |
| 			Domain:  "service.consuls",
 | |
| 		},
 | |
| 		{
 | |
| 			Service: "redisratelimits",
 | |
| 			Domain:  "service.consul",
 | |
| 		},
 | |
| 	},
 | |
| 		"consul.service.consul",
 | |
| 		250*time.Millisecond,
 | |
| 		ring,
 | |
| 		logger,
 | |
| 		metrics.NoopRegisterer,
 | |
| 	)
 | |
| 	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
 | |
| 
 | |
| 	// The Consul service entry for 'redisratelimits' is configured to return
 | |
| 	// two SRV targets. We should only have two shards in the ring.
 | |
| 	test.Assert(t, ring.Len() == 2, "Expected 2 shards in the ring")
 | |
| 
 | |
| 	testCtx, cancel := context.WithCancel(context.Background())
 | |
| 	defer cancel()
 | |
| 
 | |
| 	// Ensure we can reach both shards using the PING command.
 | |
| 	err = ring.ForEachShard(testCtx, func(ctx context.Context, shard *redis.Client) error {
 | |
| 		return shard.Ping(ctx).Err()
 | |
| 	})
 | |
| 	test.AssertNotError(t, err, "Expected PING to succeed for both shards")
 | |
| 
 | |
| 	// Drop both Shards from the ring.
 | |
| 	ring.SetAddrs(map[string]string{})
 | |
| 	test.Assert(t, ring.Len() == 0, "Expected 0 shards in the ring")
 | |
| 
 | |
| 	// Force a lookup to occur.
 | |
| 	tempErr, nonTempErr := lookup.updateNow(testCtx)
 | |
| 	test.AssertNotError(t, tempErr, "Expected no temporary errors")
 | |
| 	test.AssertNotError(t, nonTempErr, "Expected no non-temporary errors")
 | |
| 
 | |
| 	// The ring should now have two shards again.
 | |
| 	test.Assert(t, ring.Len() == 2, "Expected 2 shards in the ring")
 | |
| }
 |