traefik/integration/keepalive_test.go
2024-01-10 14:08:30 +01:00

109 lines
3 KiB
Go

package integration
import (
"math"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try"
)
type KeepAliveSuite struct {
BaseSuite
}
func TestKeepAliveSuite(t *testing.T) {
suite.Run(t, new(KeepAliveSuite))
}
type KeepAliveConfig struct {
KeepAliveServer string
IdleConnTimeout string
}
type connStateChangeEvent struct {
key string
state http.ConnState
}
func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime() {
idleTimeout := time.Duration(75) * time.Millisecond
connStateChanges := make(chan connStateChangeEvent)
noMoreRequests := make(chan bool, 1)
completed := make(chan bool, 1)
// keep track of HTTP connections and their status changes and measure their idle period
go func() {
connCount := 0
idlePeriodStartMap := make(map[string]time.Time)
idlePeriodLengthMap := make(map[string]time.Duration)
maxWaitDuration := 5 * time.Second
maxWaitTimeExceeded := time.After(maxWaitDuration)
moreRequestsExpected := true
// Ensure that all idle HTTP connections are closed before verification phase
for moreRequestsExpected || len(idlePeriodLengthMap) < connCount {
select {
case event := <-connStateChanges:
switch event.state {
case http.StateNew:
connCount++
case http.StateIdle:
idlePeriodStartMap[event.key] = time.Now()
case http.StateClosed:
idlePeriodLengthMap[event.key] = time.Since(idlePeriodStartMap[event.key])
}
case <-noMoreRequests:
moreRequestsExpected = false
case <-maxWaitTimeExceeded:
log.Info().Msgf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout)
s.T().Fail()
close(completed)
return
}
}
require.Equal(s.T(), 1, connCount)
for _, idlePeriod := range idlePeriodLengthMap {
// Our method of measuring the actual idle period is not precise, so allow some sub-ms deviation
require.LessOrEqual(s.T(), math.Round(idlePeriod.Seconds()), idleTimeout.Seconds())
}
close(completed)
}()
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
server.Config.ConnState = func(conn net.Conn, state http.ConnState) {
connStateChanges <- connStateChangeEvent{key: conn.RemoteAddr().String(), state: state}
}
server.Start()
defer server.Close()
config := KeepAliveConfig{KeepAliveServer: server.URL, IdleConnTimeout: idleTimeout.String()}
file := s.adaptFile("fixtures/timeout/keepalive.toml", config)
s.traefikCmd(withConfigFile(file))
// Wait for Traefik
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Duration(1)*time.Second, try.StatusCodeIs(200), try.BodyContains("PathPrefix(`/keepalive`)"))
require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200))
require.NoError(s.T(), err)
close(noMoreRequests)
<-completed
}