github.com/cilium/statedb@v0.3.2/reconciler/retries_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconciler
     5  
     6  import (
     7  	"errors"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/cilium/statedb/index"
    15  )
    16  
    17  func TestRetries(t *testing.T) {
    18  	objectToKey := func(o any) index.Key {
    19  		return index.Uint64(o.(uint64))
    20  	}
    21  	rq := newRetries(time.Millisecond, 100*time.Millisecond, objectToKey)
    22  
    23  	obj1, obj2, obj3 := uint64(1), uint64(2), uint64(3)
    24  
    25  	// Add objects to be retried in order. We assume here that 'time.Time' has
    26  	// enough granularity for these to be added with rising retryAt times.
    27  	err := errors.New("some error")
    28  	rq.Add(obj1, 1, false, err)
    29  	rq.Add(obj2, 2, false, err)
    30  	rq.Add(obj3, 3, false, err)
    31  
    32  	errs := rq.errors()
    33  	assert.Len(t, errs, 3)
    34  	assert.Equal(t, err, errs[0])
    35  
    36  	// Adding an item a second time will increment the number of retries and
    37  	// recalculate when it should be retried.
    38  	rq.Add(obj3, 3, false, err)
    39  
    40  	<-rq.Wait()
    41  	item1, ok := rq.Top()
    42  	if assert.True(t, ok) {
    43  		assert.True(t, item1.retryAt.Before(time.Now()), "expected item to be expired")
    44  		assert.Equal(t, item1.object, obj1)
    45  
    46  		rq.Pop()
    47  		rq.Clear(item1.object)
    48  	}
    49  
    50  	<-rq.Wait()
    51  	item2, ok := rq.Top()
    52  	if assert.True(t, ok) {
    53  		assert.True(t, item2.retryAt.Before(time.Now()), "expected item to be expired")
    54  		assert.True(t, item2.retryAt.After(item1.retryAt), "expected item to expire later than previous")
    55  		assert.Equal(t, item2.object, obj2)
    56  
    57  		rq.Pop()
    58  		rq.Clear(item2.object)
    59  	}
    60  
    61  	// Pop the last object. But don't clear its retry count.
    62  	<-rq.Wait()
    63  	item3, ok := rq.Top()
    64  	if assert.True(t, ok) {
    65  		assert.True(t, item3.retryAt.Before(time.Now()), "expected item to be expired")
    66  		assert.True(t, item3.retryAt.After(item2.retryAt), "expected item to expire later than previous")
    67  		assert.Equal(t, item3.object, obj3)
    68  
    69  		rq.Pop()
    70  	}
    71  
    72  	// Queue should be empty now.
    73  	item, ok := rq.Top()
    74  	assert.False(t, ok)
    75  
    76  	// Retry 'obj3' and since it was added back without clearing it'll be retried
    77  	// later. Add obj1 and check that 'obj3' has later retry time.
    78  	rq.Add(obj3, 4, false, err)
    79  	rq.Add(obj1, 5, false, err)
    80  
    81  	<-rq.Wait()
    82  	item4, ok := rq.Top()
    83  	if assert.True(t, ok) {
    84  		assert.True(t, item4.retryAt.Before(time.Now()), "expected item to be expired")
    85  		assert.Equal(t, item4.object, obj1)
    86  
    87  		rq.Pop()
    88  		rq.Clear(item4.object)
    89  	}
    90  
    91  	<-rq.Wait()
    92  	item5, ok := rq.Top()
    93  	if assert.True(t, ok) {
    94  		assert.True(t, item5.retryAt.Before(time.Now()), "expected item to be expired")
    95  		assert.True(t, item5.retryAt.After(item4.retryAt), "expected obj1 before obj3")
    96  		assert.Equal(t, obj3, item5.object)
    97  
    98  		// numRetries is 3 since 'obj3' was added to the queue 3 times and it has not
    99  		// been cleared.
   100  		assert.Equal(t, 3, item5.numRetries)
   101  
   102  		rq.Pop()
   103  		rq.Clear(item5.object)
   104  	}
   105  
   106  	_, ok = rq.Top()
   107  	assert.False(t, ok)
   108  
   109  	// Test that object can be cleared from the queue without popping it.
   110  	rq.Add(obj1, 6, false, err)
   111  	rq.Add(obj2, 7, false, err)
   112  	rq.Add(obj3, 8, false, err)
   113  
   114  	rq.Clear(obj1) // Remove obj1, testing that it'll fix the queue correctly.
   115  	rq.Pop()       // Pop and remove obj2 and clear it to test that Clear doesn't mess with queue
   116  	rq.Clear(obj2)
   117  	item, ok = rq.Top()
   118  	if assert.True(t, ok) {
   119  		rq.Pop()
   120  		rq.Clear(item.object)
   121  		assert.Equal(t, item.object, obj3)
   122  	}
   123  	_, ok = rq.Top()
   124  	assert.False(t, ok)
   125  }
   126  
   127  func TestExponentialBackoff(t *testing.T) {
   128  	backoff := exponentialBackoff{
   129  		min: time.Millisecond,
   130  		max: time.Second,
   131  	}
   132  
   133  	for i := 0; i < 1000; i++ {
   134  		dur := backoff.Duration(i)
   135  		require.GreaterOrEqual(t, dur, backoff.min)
   136  		require.LessOrEqual(t, dur, backoff.max)
   137  	}
   138  	require.Equal(t, backoff.Duration(0)*2, backoff.Duration(1))
   139  }