2015-09-27 15:59:51 +02:00
// This is the main file that sets up integration tests using go-check.
2017-07-06 16:28:13 +02:00
package integration
2015-09-27 15:59:51 +02:00
import (
2017-05-17 15:22:44 +02:00
"bytes"
2021-11-25 10:10:06 +00:00
"context"
2022-07-13 18:32:08 +02:00
"errors"
2017-10-13 11:08:03 +02:00
"flag"
2015-09-27 15:59:51 +02:00
"fmt"
2022-07-13 18:32:08 +02:00
"io/fs"
2015-09-28 22:37:19 +02:00
"os"
"os/exec"
"path/filepath"
2019-06-17 11:48:05 +02:00
"strings"
2015-09-27 15:59:51 +02:00
"testing"
2015-09-28 22:37:19 +02:00
"text/template"
2020-10-09 09:32:03 +02:00
"time"
2015-09-27 15:59:51 +02:00
2021-11-25 10:10:06 +00:00
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/compose/v2/cmd/formatter"
composeapi "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
2019-07-15 10:22:03 +02:00
"github.com/fatih/structs"
2016-04-02 12:40:21 +02:00
"github.com/go-check/check"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/log"
2015-09-27 15:59:51 +02:00
checker "github.com/vdemeester/shakers"
)
2020-07-07 14:42:03 +02:00
var (
integration = flag . Bool ( "integration" , false , "run integration tests" )
showLog = flag . Bool ( "tlog" , false , "always show Traefik logs" )
)
2017-10-13 11:08:03 +02:00
2015-09-27 15:59:51 +02:00
func Test ( t * testing . T ) {
2017-10-13 11:08:03 +02:00
if ! * integration {
2019-09-13 19:28:04 +02:00
log . WithoutContext ( ) . Info ( "Integration tests disabled." )
2017-10-13 11:08:03 +02:00
return
}
2022-07-13 18:32:08 +02:00
// TODO(mpl): very niche optimization: do not start tailscale if none of the
// wanted tests actually need it (e.g. KeepAliveSuite does not).
var (
vpn * tailscaleNotSuite
useVPN bool
)
if os . Getenv ( "IN_DOCKER" ) != "true" {
if vpn = setupVPN ( nil , "tailscale.secret" ) ; vpn != nil {
defer vpn . TearDownSuite ( nil )
useVPN = true
}
}
2021-11-25 10:10:06 +00:00
check . Suite ( & AccessLogSuite { } )
2022-07-13 18:32:08 +02:00
if ! useVPN {
check . Suite ( & AcmeSuite { } )
}
2021-11-25 10:10:06 +00:00
check . Suite ( & ConsulCatalogSuite { } )
check . Suite ( & ConsulSuite { } )
check . Suite ( & DockerComposeSuite { } )
check . Suite ( & DockerSuite { } )
check . Suite ( & ErrorPagesSuite { } )
check . Suite ( & EtcdSuite { } )
check . Suite ( & FileSuite { } )
check . Suite ( & GRPCSuite { } )
check . Suite ( & HeadersSuite { } )
check . Suite ( & HealthCheckSuite { } )
check . Suite ( & HostResolverSuite { } )
check . Suite ( & HTTPSSuite { } )
check . Suite ( & HTTPSuite { } )
2022-07-13 18:32:08 +02:00
if ! useVPN {
check . Suite ( & K8sSuite { } )
}
2021-11-25 10:10:06 +00:00
check . Suite ( & KeepAliveSuite { } )
check . Suite ( & LogRotationSuite { } )
check . Suite ( & MarathonSuite { } )
2022-07-13 18:32:08 +02:00
check . Suite ( & MarathonSuite15 { } )
if ! useVPN {
check . Suite ( & ProxyProtocolSuite { } )
}
2021-11-25 10:10:06 +00:00
check . Suite ( & RateLimitSuite { } )
check . Suite ( & RedisSuite { } )
check . Suite ( & RestSuite { } )
check . Suite ( & RetrySuite { } )
check . Suite ( & SimpleSuite { } )
check . Suite ( & TCPSuite { } )
check . Suite ( & TimeoutSuite { } )
2022-02-07 11:58:04 +01:00
check . Suite ( & ThrottlingSuite { } )
2021-11-25 10:10:06 +00:00
check . Suite ( & TLSClientHeadersSuite { } )
check . Suite ( & TracingSuite { } )
check . Suite ( & UDPSuite { } )
check . Suite ( & WebsocketSuite { } )
check . Suite ( & ZookeeperSuite { } )
2019-08-26 15:06:05 +02:00
check . TestingT ( t )
2015-09-27 15:59:51 +02:00
}
var traefikBinary = "../dist/traefik"
type BaseSuite struct {
2021-11-25 10:10:06 +00:00
composeProject * types . Project
dockerComposeService composeapi . Service
dockerClient * client . Client
2015-09-27 15:59:51 +02:00
}
func ( s * BaseSuite ) TearDownSuite ( c * check . C ) {
2021-11-25 10:10:06 +00:00
if s . composeProject != nil && s . dockerComposeService != nil {
s . composeDown ( c )
2015-09-27 15:59:51 +02:00
}
}
2021-11-25 10:10:06 +00:00
// createComposeProject creates the docker compose project stored as a field in the BaseSuite.
// This method should be called before starting and/or stopping compose services.
2015-09-27 15:59:51 +02:00
func ( s * BaseSuite ) createComposeProject ( c * check . C , name string ) {
2021-11-25 10:10:06 +00:00
projectName := fmt . Sprintf ( "traefik-integration-test-%s" , name )
2016-03-27 19:58:08 +02:00
composeFile := fmt . Sprintf ( "resources/compose/%s.yml" , name )
2016-12-12 18:30:31 +01:00
2021-11-25 10:10:06 +00:00
var err error
s . dockerClient , err = client . NewClientWithOpts ( )
2016-12-12 18:30:31 +01:00
c . Assert ( err , checker . IsNil )
2021-11-25 10:10:06 +00:00
s . dockerComposeService = compose . NewComposeService ( s . dockerClient , & configfile . ConfigFile { } )
ops , err := cli . NewProjectOptions ( [ ] string { composeFile } , cli . WithName ( projectName ) )
c . Assert ( err , checker . IsNil )
s . composeProject , err = cli . ProjectFromOptions ( ops )
c . Assert ( err , checker . IsNil )
2015-09-27 15:59:51 +02:00
}
2015-09-28 22:37:19 +02:00
2021-11-25 10:10:06 +00:00
// composeUp starts the given services of the current docker compose project, if they are not already started.
// Already running services are not affected (i.e. not stopped).
func ( s * BaseSuite ) composeUp ( c * check . C , services ... string ) {
c . Assert ( s . composeProject , check . NotNil )
c . Assert ( s . dockerComposeService , check . NotNil )
// We use Create and Restart instead of Up, because the only option that actually works to control which containers
// are started is within the RestartOptions.
err := s . dockerComposeService . Create ( context . Background ( ) , s . composeProject , composeapi . CreateOptions { } )
c . Assert ( err , checker . IsNil )
err = s . dockerComposeService . Restart ( context . Background ( ) , s . composeProject , composeapi . RestartOptions { Services : services } )
c . Assert ( err , checker . IsNil )
}
2022-07-13 18:32:08 +02:00
// composeExec runs the command in the given args in the given compose service container.
// Already running services are not affected (i.e. not stopped).
func ( s * BaseSuite ) composeExec ( c * check . C , service string , args ... string ) {
c . Assert ( s . composeProject , check . NotNil )
c . Assert ( s . dockerComposeService , check . NotNil )
_ , err := s . dockerComposeService . Exec ( context . Background ( ) , s . composeProject . Name , composeapi . RunOptions {
Service : service ,
Stdin : os . Stdin ,
Stdout : os . Stdout ,
Stderr : os . Stderr ,
Command : args ,
Tty : false ,
Index : 1 ,
} )
c . Assert ( err , checker . IsNil )
}
2021-11-25 10:10:06 +00:00
// composeStop stops the given services of the current docker compose project and removes the corresponding containers.
func ( s * BaseSuite ) composeStop ( c * check . C , services ... string ) {
c . Assert ( s . dockerComposeService , check . NotNil )
c . Assert ( s . composeProject , check . NotNil )
err := s . dockerComposeService . Stop ( context . Background ( ) , s . composeProject , composeapi . StopOptions { Services : services } )
c . Assert ( err , checker . IsNil )
err = s . dockerComposeService . Remove ( context . Background ( ) , s . composeProject , composeapi . RemoveOptions {
Services : services ,
Force : true ,
} )
c . Assert ( err , checker . IsNil )
}
// composeDown stops all compose project services and removes the corresponding containers.
func ( s * BaseSuite ) composeDown ( c * check . C ) {
c . Assert ( s . dockerComposeService , check . NotNil )
c . Assert ( s . composeProject , check . NotNil )
err := s . dockerComposeService . Down ( context . Background ( ) , s . composeProject . Name , composeapi . DownOptions { } )
c . Assert ( err , checker . IsNil )
2017-05-17 15:22:44 +02:00
}
func ( s * BaseSuite ) cmdTraefik ( args ... string ) ( * exec . Cmd , * bytes . Buffer ) {
cmd := exec . Command ( traefikBinary , args ... )
var out bytes . Buffer
cmd . Stdout = & out
cmd . Stderr = & out
return cmd , & out
}
2020-10-09 09:32:03 +02:00
func ( s * BaseSuite ) killCmd ( cmd * exec . Cmd ) {
err := cmd . Process . Kill ( )
if err != nil {
log . WithoutContext ( ) . Errorf ( "Kill: %v" , err )
}
time . Sleep ( 100 * time . Millisecond )
}
2017-09-13 10:34:04 +02:00
func ( s * BaseSuite ) traefikCmd ( args ... string ) ( * exec . Cmd , func ( * check . C ) ) {
cmd , out := s . cmdTraefik ( args ... )
return cmd , func ( c * check . C ) {
2018-06-27 15:08:05 +02:00
if c . Failed ( ) || * showLog {
2022-08-31 08:24:08 +02:00
s . displayLogK3S ( )
2021-11-25 10:10:06 +00:00
s . displayLogCompose ( c )
2017-09-13 10:34:04 +02:00
s . displayTraefikLog ( c , out )
}
}
}
2022-08-31 08:24:08 +02:00
func ( s * BaseSuite ) displayLogK3S ( ) {
2019-08-11 12:22:14 +02:00
filePath := "./fixtures/k8s/config.skip/k3s.log"
if _ , err := os . Stat ( filePath ) ; err == nil {
2021-03-04 20:08:03 +01:00
content , errR := os . ReadFile ( filePath )
2019-08-11 12:22:14 +02:00
if errR != nil {
log . WithoutContext ( ) . Error ( errR )
}
log . WithoutContext ( ) . Println ( string ( content ) )
}
log . WithoutContext ( ) . Println ( )
log . WithoutContext ( ) . Println ( "################################" )
log . WithoutContext ( ) . Println ( )
}
2021-11-25 10:10:06 +00:00
func ( s * BaseSuite ) displayLogCompose ( c * check . C ) {
if s . dockerComposeService == nil || s . composeProject == nil {
log . WithoutContext ( ) . Infof ( "%s: No docker compose logs." , c . TestName ( ) )
return
}
log . WithoutContext ( ) . Infof ( "%s: docker compose logs: " , c . TestName ( ) )
logWriter := log . WithoutContext ( ) . WriterLevel ( log . GetLevel ( ) )
logConsumer := formatter . NewLogConsumer ( context . Background ( ) , logWriter , false , true )
err := s . dockerComposeService . Logs ( context . Background ( ) , s . composeProject . Name , logConsumer , composeapi . LogOptions { } )
c . Assert ( err , checker . IsNil )
log . WithoutContext ( ) . Println ( )
log . WithoutContext ( ) . Println ( "################################" )
log . WithoutContext ( ) . Println ( )
}
2017-05-17 15:22:44 +02:00
func ( s * BaseSuite ) displayTraefikLog ( c * check . C , output * bytes . Buffer ) {
if output == nil || output . Len ( ) == 0 {
2019-08-11 12:22:14 +02:00
log . WithoutContext ( ) . Infof ( "%s: No Traefik logs." , c . TestName ( ) )
2017-05-17 15:22:44 +02:00
} else {
2019-08-11 12:22:14 +02:00
log . WithoutContext ( ) . Infof ( "%s: Traefik logs: " , c . TestName ( ) )
log . WithoutContext ( ) . Infof ( output . String ( ) )
2017-05-17 15:22:44 +02:00
}
}
2019-01-21 19:06:02 +01:00
func ( s * BaseSuite ) getDockerHost ( ) string {
2015-09-28 22:37:19 +02:00
dockerHost := os . Getenv ( "DOCKER_HOST" )
if dockerHost == "" {
// Default docker socket
dockerHost = "unix:///var/run/docker.sock"
}
2021-11-25 10:10:06 +00:00
2019-01-21 19:06:02 +01:00
return dockerHost
2016-04-28 01:43:43 +02:00
}
2015-09-28 22:37:19 +02:00
2016-04-28 01:43:43 +02:00
func ( s * BaseSuite ) adaptFile ( c * check . C , path string , tempObjects interface { } ) string {
2015-09-28 22:37:19 +02:00
// Load file
tmpl , err := template . ParseFiles ( path )
c . Assert ( err , checker . IsNil )
folder , prefix := filepath . Split ( path )
2021-03-04 20:08:03 +01:00
tmpFile , err := os . CreateTemp ( folder , strings . TrimSuffix ( prefix , filepath . Ext ( prefix ) ) + "_*" + filepath . Ext ( prefix ) )
2015-09-28 22:37:19 +02:00
c . Assert ( err , checker . IsNil )
defer tmpFile . Close ( )
2019-07-15 10:22:03 +02:00
model := structs . Map ( tempObjects )
model [ "SelfFilename" ] = tmpFile . Name ( )
err = tmpl . ExecuteTemplate ( tmpFile , prefix , model )
2015-09-28 22:37:19 +02:00
c . Assert ( err , checker . IsNil )
err = tmpFile . Sync ( )
c . Assert ( err , checker . IsNil )
return tmpFile . Name ( )
}
2021-11-25 10:10:06 +00:00
func ( s * BaseSuite ) getComposeServiceIP ( c * check . C , name string ) string {
filter := filters . NewArgs (
filters . Arg ( "label" , fmt . Sprintf ( "%s=%s" , composeapi . ProjectLabel , s . composeProject . Name ) ) ,
filters . Arg ( "label" , fmt . Sprintf ( "%s=%s" , composeapi . ServiceLabel , name ) ) ,
)
containers , err := s . dockerClient . ContainerList ( context . Background ( ) , dockertypes . ContainerListOptions { Filters : filter } )
c . Assert ( err , checker . IsNil )
c . Assert ( containers , checker . HasLen , 1 )
networkNames := s . composeProject . NetworkNames ( )
c . Assert ( networkNames , checker . HasLen , 1 )
network := s . composeProject . Networks [ networkNames [ 0 ] ]
return containers [ 0 ] . NetworkSettings . Networks [ network . Name ] . IPAddress
}
func ( s * BaseSuite ) getContainerIP ( c * check . C , name string ) string {
container , err := s . dockerClient . ContainerInspect ( context . Background ( ) , name )
c . Assert ( err , checker . IsNil )
c . Assert ( container . NetworkSettings . Networks , check . NotNil )
for _ , network := range container . NetworkSettings . Networks {
return network . IPAddress
}
// Should never happen.
c . Error ( "No network found" )
return ""
}
func withConfigFile ( file string ) string {
return "--configFile=" + file
}
2022-07-13 18:32:08 +02:00
// tailscaleNotSuite includes a BaseSuite out of convenience, so we can benefit
// from composeUp et co., but it is not meant to function as a TestSuite per se.
type tailscaleNotSuite struct { BaseSuite }
// setupVPN starts Tailscale on the corresponding container, and makes it a subnet
// router, for all the other containers (whoamis, etc) subsequently started for the
// integration tests.
// It only does so if the file provided as argument exists, and contains a
// Tailscale auth key (an ephemeral, but reusable, one is recommended).
//
// Add this section to your tailscale ACLs to auto-approve the routes for the
// containers in the docker subnet:
//
2022-07-19 18:38:09 +02:00
// "autoApprovers": {
// // Allow myself to automatically advertize routes for docker networks
// "routes": {
// "172.0.0.0/8": ["your_tailscale_identity"],
// },
// },
2022-07-13 18:32:08 +02:00
//
// TODO(mpl): we could maybe even move this setup to the Makefile, to start it
// and let it run (forever, or until voluntarily stopped).
func setupVPN ( c * check . C , keyFile string ) * tailscaleNotSuite {
2022-08-09 17:36:08 +02:00
data , err := os . ReadFile ( keyFile )
2022-07-13 18:32:08 +02:00
if err != nil {
if ! errors . Is ( err , fs . ErrNotExist ) {
log . Fatal ( err )
}
return nil
}
authKey := strings . TrimSpace ( string ( data ) )
// TODO: copy and create versions that don't need a check.C?
vpn := & tailscaleNotSuite { }
vpn . createComposeProject ( c , "tailscale" )
vpn . composeUp ( c )
time . Sleep ( 5 * time . Second )
// If we ever change the docker subnet in the Makefile,
// we need to change this one below correspondingly.
vpn . composeExec ( c , "tailscaled" , "tailscale" , "up" , "--authkey=" + authKey , "--advertise-routes=172.31.42.0/24" )
return vpn
}