github.com/cilium/cilium@v1.16.2/pkg/status/status_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package status
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/cilium/cilium/pkg/lock"
    18  	"github.com/cilium/cilium/pkg/testutils"
    19  )
    20  
    21  type StatusTestSuite struct {
    22  	config Config
    23  	mutex  lock.Mutex
    24  }
    25  
    26  func setUpTest(_ testing.TB) *StatusTestSuite {
    27  	s := &StatusTestSuite{}
    28  	s.mutex.Lock()
    29  	s.config = Config{
    30  		Interval:         10 * time.Millisecond,
    31  		WarningThreshold: 20 * time.Millisecond,
    32  		FailureThreshold: 80 * time.Millisecond,
    33  	}
    34  	s.mutex.Unlock()
    35  
    36  	return s
    37  }
    38  
    39  func (s *StatusTestSuite) Config() (c Config) {
    40  	s.mutex.Lock()
    41  	c = s.config
    42  	s.mutex.Unlock()
    43  	return
    44  }
    45  
    46  func TestVariableProbeInterval(t *testing.T) {
    47  	s := setUpTest(t)
    48  
    49  	var runs, ok atomic.Uint64
    50  
    51  	p := []Probe{
    52  		{
    53  			Interval: func(failures int) time.Duration {
    54  				// While failing, retry every millisecond
    55  				if failures > 0 {
    56  					return time.Millisecond
    57  				}
    58  
    59  				// Ensure that the regular interval would never retry
    60  				return time.Minute
    61  			},
    62  			Probe: func(ctx context.Context) (interface{}, error) {
    63  				// Let 5 runs fail and then succeed
    64  				if runs.Add(1) < 5 {
    65  					return nil, fmt.Errorf("still failing")
    66  				}
    67  
    68  				return nil, nil
    69  			},
    70  			OnStatusUpdate: func(status Status) {
    71  				if status.Data == nil && status.Err == nil {
    72  					ok.Add(1)
    73  				}
    74  			},
    75  		},
    76  	}
    77  
    78  	collector := NewCollector(p, s.Config())
    79  	defer collector.Close()
    80  
    81  	// wait for 5 probe intervals to occur with 1 millisecond interval
    82  	// until we reach success
    83  	require.NoError(t, testutils.WaitUntil(func() bool {
    84  		return ok.Load() >= 1
    85  	}, 1*time.Second))
    86  }
    87  
    88  func TestCollectorFailureTimeout(t *testing.T) {
    89  	s := setUpTest(t)
    90  
    91  	var ok atomic.Uint64
    92  
    93  	p := []Probe{
    94  		{
    95  			Probe: func(ctx context.Context) (interface{}, error) {
    96  				time.Sleep(s.Config().FailureThreshold * 2)
    97  				return nil, nil
    98  			},
    99  			OnStatusUpdate: func(status Status) {
   100  				if status.StaleWarning && status.Data == nil && status.Err != nil {
   101  					if strings.Contains(status.Err.Error(),
   102  						fmt.Sprintf("within %v seconds", s.Config().FailureThreshold.Seconds())) {
   103  
   104  						ok.Add(1)
   105  					}
   106  				}
   107  			},
   108  		},
   109  	}
   110  
   111  	collector := NewCollector(p, s.Config())
   112  	defer collector.Close()
   113  
   114  	// wait for the failure timeout to kick in
   115  	require.NoError(t, testutils.WaitUntil(func() bool {
   116  		return ok.Load() >= 1
   117  	}, 1*time.Second))
   118  	require.Len(t, collector.GetStaleProbes(), 1)
   119  }
   120  
   121  func TestCollectorSuccess(t *testing.T) {
   122  	s := setUpTest(t)
   123  
   124  	var ok, errs atomic.Uint64
   125  	err := fmt.Errorf("error")
   126  
   127  	p := []Probe{
   128  		{
   129  			Probe: func(ctx context.Context) (interface{}, error) {
   130  				if ok.Load() > 3 {
   131  					return nil, err
   132  				}
   133  				return "testData", nil
   134  			},
   135  			OnStatusUpdate: func(status Status) {
   136  				if !errors.Is(status.Err, err) {
   137  					errs.Add(1)
   138  				}
   139  				if !status.StaleWarning && status.Data != nil && status.Err == nil {
   140  					if s, isString := status.Data.(string); isString && s == "testData" {
   141  						ok.Add(1)
   142  					}
   143  				}
   144  			},
   145  		},
   146  	}
   147  
   148  	collector := NewCollector(p, s.Config())
   149  	defer collector.Close()
   150  
   151  	// wait for the probe to succeed 3 times and to return the error 3 times
   152  	require.NoError(t, testutils.WaitUntil(func() bool {
   153  		return ok.Load() >= 3 && errs.Load() >= 3
   154  	}, 1*time.Second))
   155  	require.Len(t, collector.GetStaleProbes(), 0)
   156  }
   157  
   158  func TestCollectorSuccessAfterTimeout(t *testing.T) {
   159  	s := setUpTest(t)
   160  
   161  	var ok, timeout atomic.Uint64
   162  
   163  	p := []Probe{
   164  		{
   165  			Probe: func(ctx context.Context) (interface{}, error) {
   166  				if timeout.Load() == 0 {
   167  					time.Sleep(2 * s.Config().FailureThreshold)
   168  				}
   169  				return nil, nil
   170  			},
   171  			OnStatusUpdate: func(status Status) {
   172  				if status.StaleWarning {
   173  					timeout.Add(1)
   174  				} else {
   175  					ok.Add(1)
   176  				}
   177  
   178  			},
   179  		},
   180  	}
   181  
   182  	collector := NewCollector(p, s.Config())
   183  	defer collector.Close()
   184  
   185  	// wait for the probe to timeout (warning and failure) and then to succeed
   186  	require.NoError(t, testutils.WaitUntil(func() bool {
   187  		return timeout.Load() == 1 && ok.Load() > 0
   188  	}, 1*time.Second))
   189  	require.Len(t, collector.GetStaleProbes(), 0)
   190  }