github.com/cilium/cilium@v1.16.2/pkg/k8s/synced/resources_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package synced
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"github.com/cilium/cilium/pkg/lock"
    14  )
    15  
    16  // waitForCacheTest is a table test case for testing WaitForCacheSyncWithTimeout.
    17  // Each test case will similate waiting for sync of every key in resourceNamesToSyncDuration
    18  // with their respective timeout duration.
    19  type waitForCacheTest struct {
    20  	timeout time.Duration
    21  	// This maps resource names (ex. "core/v1::Pods") onto the duration the simulated
    22  	// cache sync will take to complete.
    23  	resourceNamesToSyncDuration map[string]time.Duration
    24  	// Maps resource names to a duration to wait after init upon which an "event" will
    25  	// be emitted. Used to test cases where events cause pushing out of timeout.
    26  	resourceNamesToEvent map[string]time.Duration
    27  	// Array which is passed to WaitForCacheSync... function.
    28  	// Only resources in this array should cause return of timeout error when doing test.
    29  	resourceNames []string
    30  	expectErr     error
    31  	// Used to test edge cases where controller doesn't actually invoke BlockWaitGroupToSyncResources.
    32  	dontStartBlockWaitGroupToSyncResources bool
    33  }
    34  
    35  func TestWaitForCacheSyncWithTimeout(t *testing.T) {
    36  	unit := func(d int) time.Duration { return syncedPollPeriod * time.Duration(d) }
    37  	for msg, test := range map[string]waitForCacheTest{
    38  		"Should complete due to event causing timeout to be extended past initial timeout": {
    39  			timeout: unit(5),
    40  			resourceNamesToSyncDuration: map[string]time.Duration{
    41  				"foo": unit(7),
    42  				"bar": unit(7),
    43  			},
    44  			resourceNamesToEvent: map[string]time.Duration{
    45  				"foo": unit(4),
    46  			},
    47  			resourceNames: []string{"foo"},
    48  		},
    49  		"Should timeout due to watched resource exceeding timeout": {
    50  			timeout: unit(1),
    51  			resourceNamesToSyncDuration: map[string]time.Duration{
    52  				"foo": unit(3),
    53  			},
    54  			resourceNamesToEvent: map[string]time.Duration{},
    55  			resourceNames:        []string{"foo"},
    56  			expectErr:            fmt.Errorf("timed out after 100ms, never received event for resource \"foo\""),
    57  		},
    58  		"Any one timeout should cause error": {
    59  			timeout: unit(5),
    60  			resourceNamesToSyncDuration: map[string]time.Duration{
    61  				"foo": unit(3),
    62  				"bar": unit(7),
    63  			},
    64  			resourceNamesToEvent: map[string]time.Duration{
    65  				"foo": unit(4),
    66  			},
    67  			resourceNames: []string{"foo", "bar"},
    68  			expectErr:     fmt.Errorf("timed out after 500ms, never received event for resource \"bar\""),
    69  		},
    70  		"Waiting for no resources should always sync": {
    71  			timeout: unit(5),
    72  			resourceNamesToSyncDuration: map[string]time.Duration{
    73  				"foo": unit(10),
    74  				"bar": unit(10),
    75  			},
    76  		},
    77  		// One expectation of the BlockWaitGroupToSyncResources is that waits without starting
    78  		// the controller sync will complete without waiting.
    79  		"Not invoking BlockWaitGroupToSyncResources should cause wait to succeed immediately": {
    80  			timeout: unit(60),
    81  			resourceNamesToSyncDuration: map[string]time.Duration{
    82  				"foo": unit(360),
    83  				"bar": unit(360),
    84  			},
    85  			resourceNames:                          []string{"foo", "bar"},
    86  			dontStartBlockWaitGroupToSyncResources: true,
    87  		},
    88  	} {
    89  		func(test waitForCacheTest) {
    90  			t.Run(msg, func(t *testing.T) {
    91  				t.Parallel()
    92  
    93  				assert := assert.New(t)
    94  				r := &Resources{CacheStatus: make(CacheStatus)}
    95  				stop := make(chan struct{})
    96  				swg := lock.NewStoppableWaitGroup()
    97  				start := time.Now()
    98  				// Create synced functions that will begin to return true after the resource timeout duration.
    99  				for resourceName, syncDurations := range test.resourceNamesToSyncDuration {
   100  					hasSyncedFn := func(d time.Duration) func() bool {
   101  						return func() bool {
   102  							return time.Now().After(start.Add(d))
   103  						}
   104  					}(syncDurations)
   105  					if test.dontStartBlockWaitGroupToSyncResources {
   106  						continue
   107  					}
   108  					r.BlockWaitGroupToSyncResources(
   109  						stop,
   110  						swg,
   111  						hasSyncedFn,
   112  						resourceName,
   113  					)
   114  				}
   115  
   116  				// Schedule resource events to happen after specified duration.
   117  				for resourceName, waitForEvent := range test.resourceNamesToEvent {
   118  					// schedule an event.
   119  					time.AfterFunc(waitForEvent, func() {
   120  						r.SetEventTimestamp(resourceName)
   121  					})
   122  				}
   123  
   124  				err := r.WaitForCacheSyncWithTimeout(test.timeout, test.resourceNames...)
   125  				if test.expectErr == nil {
   126  					assert.NoError(err)
   127  				} else {
   128  					assert.EqualError(err, test.expectErr.Error())
   129  				}
   130  			})
   131  		}(test)
   132  	}
   133  }