tunnel_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package supervisor
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/quic-go/quic-go"
  6. "github.com/rs/zerolog"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/cloudflare/cloudflared/connection"
  9. "github.com/cloudflare/cloudflared/edgediscovery"
  10. "github.com/cloudflare/cloudflared/retry"
  11. )
  12. type dynamicMockFetcher struct {
  13. protocolPercents edgediscovery.ProtocolPercents
  14. err error
  15. }
  16. func (dmf *dynamicMockFetcher) fetch() edgediscovery.PercentageFetcher {
  17. return func() (edgediscovery.ProtocolPercents, error) {
  18. return dmf.protocolPercents, dmf.err
  19. }
  20. }
  21. func immediateTimeAfter(time.Duration) <-chan time.Time {
  22. c := make(chan time.Time, 1)
  23. c <- time.Now()
  24. return c
  25. }
  26. func TestWaitForBackoffFallback(t *testing.T) {
  27. maxRetries := uint(3)
  28. backoff := retry.NewBackoff(maxRetries, 40*time.Millisecond, false)
  29. backoff.Clock.After = immediateTimeAfter
  30. log := zerolog.Nop()
  31. resolveTTL := 10 * time.Second
  32. mockFetcher := dynamicMockFetcher{
  33. protocolPercents: edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}},
  34. }
  35. protocolSelector, err := connection.NewProtocolSelector(
  36. "auto",
  37. "",
  38. false,
  39. false,
  40. mockFetcher.fetch(),
  41. resolveTTL,
  42. &log,
  43. )
  44. assert.NoError(t, err)
  45. initProtocol := protocolSelector.Current()
  46. assert.Equal(t, connection.QUIC, initProtocol)
  47. protoFallback := &protocolFallback{
  48. backoff,
  49. initProtocol,
  50. false,
  51. }
  52. // Retry #0 and #1. At retry #2, we switch protocol, so the fallback loop has one more retry than this
  53. for i := 0; i < int(maxRetries-1); i++ {
  54. protoFallback.BackoffTimer() // simulate retry
  55. ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
  56. assert.True(t, ok)
  57. assert.Equal(t, initProtocol, protoFallback.protocol)
  58. }
  59. // Retry fallback protocol
  60. protoFallback.BackoffTimer() // simulate retry
  61. ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
  62. assert.True(t, ok)
  63. fallback, ok := protocolSelector.Fallback()
  64. assert.True(t, ok)
  65. assert.Equal(t, fallback, protoFallback.protocol)
  66. assert.Equal(t, connection.HTTP2, protoFallback.protocol)
  67. currentGlobalProtocol := protocolSelector.Current()
  68. assert.Equal(t, initProtocol, currentGlobalProtocol)
  69. // Simulate max retries again (retries reset after protocol switch)
  70. for i := 0; i < int(maxRetries); i++ {
  71. protoFallback.BackoffTimer()
  72. }
  73. // No protocol to fallback, return error
  74. ok = selectNextProtocol(&log, protoFallback, protocolSelector, nil)
  75. assert.False(t, ok)
  76. protoFallback.reset()
  77. protoFallback.BackoffTimer() // simulate retry
  78. ok = selectNextProtocol(&log, protoFallback, protocolSelector, nil)
  79. assert.True(t, ok)
  80. assert.Equal(t, initProtocol, protoFallback.protocol)
  81. protoFallback.reset()
  82. protoFallback.BackoffTimer() // simulate retry
  83. ok = selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
  84. // Check that we get a true after the first try itself when this flag is true. This allows us to immediately
  85. // switch protocols when there is a fallback.
  86. assert.True(t, ok)
  87. // But if there is no fallback available, then we exhaust the retries despite the type of error.
  88. // The reason why there's no fallback available is because we pick a specific protocol instead of letting it be auto.
  89. protocolSelector, err = connection.NewProtocolSelector(
  90. "quic",
  91. "",
  92. false,
  93. false,
  94. mockFetcher.fetch(),
  95. resolveTTL,
  96. &log,
  97. )
  98. assert.NoError(t, err)
  99. protoFallback = &protocolFallback{backoff, protocolSelector.Current(), false}
  100. for i := 0; i < int(maxRetries-1); i++ {
  101. protoFallback.BackoffTimer() // simulate retry
  102. ok := selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
  103. assert.True(t, ok)
  104. assert.Equal(t, connection.QUIC, protoFallback.protocol)
  105. }
  106. // And finally it fails as it should, with no fallback.
  107. protoFallback.BackoffTimer()
  108. ok = selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
  109. assert.False(t, ok)
  110. }