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 }