github.com/letsencrypt/boulder@v0.20251208.0/redis/lookup_test.go (about)

     1  package redis
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/letsencrypt/boulder/cmd"
     9  	blog "github.com/letsencrypt/boulder/log"
    10  	"github.com/letsencrypt/boulder/metrics"
    11  	"github.com/letsencrypt/boulder/test"
    12  
    13  	"github.com/redis/go-redis/v9"
    14  )
    15  
    16  func newTestRedisRing() *redis.Ring {
    17  	CACertFile := "../test/certs/ipki/minica.pem"
    18  	CertFile := "../test/certs/ipki/localhost/cert.pem"
    19  	KeyFile := "../test/certs/ipki/localhost/key.pem"
    20  	tlsConfig := cmd.TLSConfig{
    21  		CACertFile: CACertFile,
    22  		CertFile:   CertFile,
    23  		KeyFile:    KeyFile,
    24  	}
    25  	tlsConfig2, err := tlsConfig.Load(metrics.NoopRegisterer)
    26  	if err != nil {
    27  		panic(err)
    28  	}
    29  
    30  	client := redis.NewRing(&redis.RingOptions{
    31  		Username:  "boulder",
    32  		Password:  "824968fa490f4ecec1e52d5e34916bdb60d45f8d",
    33  		TLSConfig: tlsConfig2,
    34  	})
    35  	return client
    36  }
    37  
    38  func TestNewLookup(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	logger := blog.NewMock()
    42  	ring := newTestRedisRing()
    43  
    44  	_, err := newLookup([]cmd.ServiceDomain{
    45  		{
    46  			Service: "redisratelimits",
    47  			Domain:  "service.consul",
    48  		},
    49  	},
    50  		"consul.service.consul",
    51  		250*time.Millisecond,
    52  		ring,
    53  		logger,
    54  		metrics.NoopRegisterer,
    55  	)
    56  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
    57  }
    58  
    59  func TestStart(t *testing.T) {
    60  	t.Parallel()
    61  
    62  	logger := blog.NewMock()
    63  	ring := newTestRedisRing()
    64  
    65  	lookup, err := newLookup([]cmd.ServiceDomain{
    66  		{
    67  			Service: "redisratelimits",
    68  			Domain:  "service.consul",
    69  		},
    70  	},
    71  		"consul.service.consul",
    72  		250*time.Millisecond,
    73  		ring,
    74  		logger,
    75  		metrics.NoopRegisterer,
    76  	)
    77  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
    78  
    79  	lookup.start()
    80  	lookup.stop()
    81  }
    82  
    83  func TestNewLookupWithOneFailingSRV(t *testing.T) {
    84  	t.Parallel()
    85  
    86  	logger := blog.NewMock()
    87  	ring := newTestRedisRing()
    88  
    89  	_, err := newLookup([]cmd.ServiceDomain{
    90  		{
    91  			Service: "doesnotexist",
    92  			Domain:  "service.consuls",
    93  		},
    94  		{
    95  			Service: "redisratelimits",
    96  			Domain:  "service.consul",
    97  		},
    98  	},
    99  		"consul.service.consul",
   100  		250*time.Millisecond,
   101  		ring,
   102  		logger,
   103  		metrics.NoopRegisterer,
   104  	)
   105  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
   106  }
   107  
   108  func TestNewLookupWithAllFailingSRV(t *testing.T) {
   109  	t.Parallel()
   110  
   111  	logger := blog.NewMock()
   112  	ring := newTestRedisRing()
   113  
   114  	_, err := newLookup([]cmd.ServiceDomain{
   115  		{
   116  			Service: "doesnotexist",
   117  			Domain:  "service.consuls",
   118  		},
   119  		{
   120  			Service: "doesnotexist2",
   121  			Domain:  "service.consuls",
   122  		},
   123  	},
   124  		"consul.service.consul",
   125  		250*time.Millisecond,
   126  		ring,
   127  		logger,
   128  		metrics.NoopRegisterer,
   129  	)
   130  	test.AssertError(t, err, "Expected newLookup construction to fail")
   131  }
   132  
   133  func TestUpdateNowWithAllFailingSRV(t *testing.T) {
   134  	t.Parallel()
   135  
   136  	logger := blog.NewMock()
   137  	ring := newTestRedisRing()
   138  
   139  	lookup, err := newLookup([]cmd.ServiceDomain{
   140  		{
   141  			Service: "redisratelimits",
   142  			Domain:  "service.consul",
   143  		},
   144  	},
   145  		"consul.service.consul",
   146  		250*time.Millisecond,
   147  		ring,
   148  		logger,
   149  		metrics.NoopRegisterer,
   150  	)
   151  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
   152  
   153  	lookup.srvLookups = []cmd.ServiceDomain{
   154  		{
   155  			Service: "doesnotexist1",
   156  			Domain:  "service.consul",
   157  		},
   158  		{
   159  			Service: "doesnotexist2",
   160  			Domain:  "service.consul",
   161  		},
   162  	}
   163  
   164  	tempErr, nonTempErr := lookup.updateNow(t.Context())
   165  	test.AssertNotError(t, tempErr, "Expected no temporary errors")
   166  	test.AssertError(t, nonTempErr, "Expected non-temporary errors to have occurred")
   167  }
   168  
   169  func TestUpdateNowWithAllFailingSRVs(t *testing.T) {
   170  	t.Parallel()
   171  
   172  	logger := blog.NewMock()
   173  	ring := newTestRedisRing()
   174  
   175  	lookup, err := newLookup([]cmd.ServiceDomain{
   176  		{
   177  			Service: "redisratelimits",
   178  			Domain:  "service.consul",
   179  		},
   180  	},
   181  		"consul.service.consul",
   182  		250*time.Millisecond,
   183  		ring,
   184  		logger,
   185  		metrics.NoopRegisterer,
   186  	)
   187  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
   188  
   189  	// Replace the dnsAuthority with a non-existent DNS server, this will cause
   190  	// a timeout error, which is technically a temporary error, but will
   191  	// eventually result in a non-temporary error when no shards are resolved.
   192  	lookup.dnsAuthority = "consuls.services.consuls:53"
   193  
   194  	tempErr, nonTempErr := lookup.updateNow(t.Context())
   195  	test.AssertError(t, tempErr, "Expected temporary errors")
   196  	test.AssertError(t, nonTempErr, "Expected a non-temporary error")
   197  	test.AssertErrorIs(t, nonTempErr, ErrNoShardsResolved)
   198  }
   199  
   200  func TestUpdateNowWithOneFailingSRV(t *testing.T) {
   201  	t.Parallel()
   202  
   203  	logger := blog.NewMock()
   204  	ring := newTestRedisRing()
   205  
   206  	lookup, err := newLookup([]cmd.ServiceDomain{
   207  		{
   208  			Service: "doesnotexist",
   209  			Domain:  "service.consuls",
   210  		},
   211  		{
   212  			Service: "redisratelimits",
   213  			Domain:  "service.consul",
   214  		},
   215  	},
   216  		"consul.service.consul",
   217  		250*time.Millisecond,
   218  		ring,
   219  		logger,
   220  		metrics.NoopRegisterer,
   221  	)
   222  	test.AssertNotError(t, err, "Expected newLookup construction to succeed")
   223  
   224  	// The Consul service entry for 'redisratelimits' is configured to return
   225  	// two SRV targets. We should only have two shards in the ring.
   226  	test.Assert(t, ring.Len() == 2, "Expected 2 shards in the ring")
   227  
   228  	testCtx := t.Context()
   229  
   230  	// Ensure we can reach both shards using the PING command.
   231  	err = ring.ForEachShard(testCtx, func(ctx context.Context, shard *redis.Client) error {
   232  		return shard.Ping(ctx).Err()
   233  	})
   234  	test.AssertNotError(t, err, "Expected PING to succeed for both shards")
   235  
   236  	// Drop both Shards from the ring.
   237  	ring.SetAddrs(map[string]string{})
   238  	test.Assert(t, ring.Len() == 0, "Expected 0 shards in the ring")
   239  
   240  	// Force a lookup to occur.
   241  	tempErr, nonTempErr := lookup.updateNow(testCtx)
   242  	test.AssertNotError(t, tempErr, "Expected no temporary errors")
   243  	test.AssertNotError(t, nonTempErr, "Expected no non-temporary errors")
   244  
   245  	// The ring should now have two shards again.
   246  	test.Assert(t, ring.Len() == 2, "Expected 2 shards in the ring")
   247  }