Merge v2.11 into v3.0

This commit is contained in:
mmatur 2024-01-10 10:47:44 +01:00
commit 3bbc560283
No known key found for this signature in database
GPG key ID: 2FFE42FC256CFF8E
85 changed files with 3456 additions and 5204 deletions

View file

@ -8,7 +8,6 @@ on:
env: env:
GO_VERSION: '1.21' GO_VERSION: '1.21'
CGO_ENABLED: 0 CGO_ENABLED: 0
IN_DOCKER: ""
jobs: jobs:
@ -17,7 +16,7 @@ jobs:
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
@ -39,38 +38,22 @@ jobs:
os: [ ubuntu-20.04, macos-latest, windows-latest ] os: [ ubuntu-20.04, macos-latest, windows-latest ]
needs: needs:
- build-webui - build-webui
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
steps: steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: go/src/github.com/traefik/traefik
fetch-depth: 0 fetch-depth: 0
- name: Cache Go modules - name: Set up Go ${{ env.GO_VERSION }}
uses: actions/cache@v3 uses: actions/setup-go@v5
with: with:
path: | go-version: ${{ env.GO_VERSION }}
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
'%LocalAppData%\go-build'
key: ${{ runner.os }}-build-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-go-
- name: Artifact webui - name: Artifact webui
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: webui.tar.gz name: webui.tar.gz
path: ${{ github.workspace }}/go/src/github.com/traefik/traefik
- name: Untar webui - name: Untar webui
run: tar xvf webui.tar.gz run: tar xvf webui.tar.gz

View file

@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

View file

@ -28,7 +28,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View file

@ -19,7 +19,7 @@ jobs:
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

View file

@ -17,7 +17,7 @@ jobs:
# https://github.com/marketplace/actions/checkout # https://github.com/marketplace/actions/checkout
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

74
.github/workflows/test-integration.yaml vendored Normal file
View file

@ -0,0 +1,74 @@
name: Test Integration
on:
pull_request:
branches:
- '*'
push:
branches:
- 'gh-actions'
env:
GO_VERSION: '1.21'
CGO_ENABLED: 0
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Avoid generating webui
run: touch webui/static/index.html
- name: Build binary
run: make binary
test-integration:
runs-on: ubuntu-20.04
needs:
- build
strategy:
fail-fast: true
matrix:
parallel: [12]
index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11]
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Avoid generating webui
run: touch webui/static/index.html
- name: Build binary
run: make binary
- name: Generate go test Slice
id: test_split
uses: hashicorp-forge/go-test-split-action@v1
with:
packages: ./integration
total: ${{ matrix.parallel }}
index: ${{ matrix.index }}
- name: Run Integration tests
run: |
go test ./integration -test.timeout=20m -failfast -v -run "${{ steps.test_split.outputs.run}}"

View file

@ -7,37 +7,22 @@ on:
env: env:
GO_VERSION: '1.21' GO_VERSION: '1.21'
IN_DOCKER: ""
jobs: jobs:
test-unit: test-unit:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
steps: steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: go/src/github.com/traefik/traefik
fetch-depth: 0 fetch-depth: 0
- name: Cache Go modules - name: Set up Go ${{ env.GO_VERSION }}
uses: actions/cache@v3 uses: actions/setup-go@v5
with: with:
path: | go-version: ${{ env.GO_VERSION }}
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-test-unit-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-test-unit-go-
- name: Avoid generating webui - name: Avoid generating webui
run: touch webui/static/index.html run: touch webui/static/index.html

View file

@ -8,38 +8,23 @@ on:
env: env:
GO_VERSION: '1.21' GO_VERSION: '1.21'
GOLANGCI_LINT_VERSION: v1.55.2 GOLANGCI_LINT_VERSION: v1.55.2
MISSSPELL_VERSION: v0.4.0 MISSSPELL_VERSION: v0.4.1
IN_DOCKER: ""
jobs: jobs:
validate: validate:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
steps: steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: go/src/github.com/traefik/traefik
fetch-depth: 0 fetch-depth: 0
- name: Cache Go modules - name: Set up Go ${{ env.GO_VERSION }}
uses: actions/cache@v3 uses: actions/setup-go@v5
with: with:
path: | go-version: ${{ env.GO_VERSION }}
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-validate-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-validate-go-
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
@ -56,30 +41,16 @@ jobs:
validate-generate: validate-generate:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
defaults:
run:
working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik
steps: steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: go/src/github.com/traefik/traefik
fetch-depth: 0 fetch-depth: 0
- name: Cache Go modules - name: Set up Go ${{ env.GO_VERSION }}
uses: actions/cache@v3 uses: actions/setup-go@v5
with: with:
path: | go-version: ${{ env.GO_VERSION }}
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-validate-generate-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-validate-generate-go-
- name: go generate - name: go generate
run: | run: |

View file

@ -144,11 +144,11 @@ linters-settings:
gomoddirectives: gomoddirectives:
replace-allow-list: replace-allow-list:
- github.com/abbot/go-http-auth - github.com/abbot/go-http-auth
- github.com/go-check/check
- github.com/gorilla/mux - github.com/gorilla/mux
- github.com/mailgun/minheap - github.com/mailgun/minheap
- github.com/mailgun/multibuf - github.com/mailgun/multibuf
- github.com/jaguilar/vt100 - github.com/jaguilar/vt100
- github.com/cucumber/godog
testifylint: testifylint:
enable: enable:
- bool-compare - bool-compare
@ -159,7 +159,6 @@ linters-settings:
- expected-actual - expected-actual
- float-compare - float-compare
- len - len
- suite-dont-use-pkg
- suite-extra-assert-call - suite-extra-assert-call
- suite-thelper - suite-thelper

View file

@ -31,24 +31,6 @@ global_job_config:
- cache restore traefik-$(checksum go.sum) - cache restore traefik-$(checksum go.sum)
blocks: blocks:
- name: Test Integration
dependencies: []
run:
when: "branch =~ '.*' OR pull_request =~'.*'"
task:
jobs:
- name: Test Integration
commands:
- make pull-images
- touch webui/static/index.html # Avoid generating webui
- IN_DOCKER="" make binary
- make test-integration
- df -h
epilogue:
always:
commands:
- cache store traefik-$(checksum go.sum) $HOME/go/pkg/mod
- name: Release - name: Release
dependencies: [] dependencies: []
run: run:
@ -65,8 +47,6 @@ blocks:
value: 2.32.1 value: 2.32.1
- name: CODENAME - name: CODENAME
value: "beaufort" value: "beaufort"
- name: IN_DOCKER
value: ""
prologue: prologue:
commands: commands:
- export VERSION=${SEMAPHORE_GIT_TAG_NAME} - export VERSION=${SEMAPHORE_GIT_TAG_NAME}

103
Makefile
View file

@ -6,34 +6,6 @@ VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT)) VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT))
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)) GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"traefik/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)",-v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
# only used when running in docker
TRAEFIK_ENVS := \
-e OS_ARCH_ARG \
-e OS_PLATFORM_ARG \
-e TESTFLAGS \
-e VERBOSE \
-e VERSION \
-e CODENAME \
-e TESTDIRS \
-e CI \
-e IN_DOCKER=true # Indicator for integration tests that we are running inside a container.
TRAEFIK_MOUNT := -v "$(CURDIR)/dist:/go/src/github.com/traefik/traefik/dist"
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
DOCKER_NON_INTERACTIVE ?= false
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -it) $(DOCKER_RUN_OPTS)
DOCKER_RUN_TRAEFIK_TEST := docker run --add-host=host.docker.internal:127.0.0.1 --rm --name=traefik --network traefik-test-network -v $(PWD):$(PWD) -w $(PWD) $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -it) $(DOCKER_RUN_OPTS)
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -i) $(DOCKER_RUN_OPTS)
IN_DOCKER ?= true
.PHONY: default .PHONY: default
default: binary default: binary
@ -42,20 +14,6 @@ default: binary
dist: dist:
mkdir -p dist mkdir -p dist
## Build Dev Docker image
.PHONY: build-dev-image
build-dev-image: dist
ifneq ("$(IN_DOCKER)", "")
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" --build-arg HOST_PWD="$(PWD)" -f build.Dockerfile .
endif
## Build Dev Docker image without cache
.PHONY: build-dev-image-no-cache
build-dev-image-no-cache: dist
ifneq ("$(IN_DOCKER)", "")
docker build $(DOCKER_BUILD_ARGS) --no-cache -t "$(TRAEFIK_DEV_IMAGE)" --build-arg HOST_PWD="$(PWD)" -f build.Dockerfile .
endif
## Build WebUI Docker image ## Build WebUI Docker image
.PHONY: build-webui-image .PHONY: build-webui-image
build-webui-image: build-webui-image:
@ -79,8 +37,8 @@ generate-webui: webui/static/index.html
## Build the binary ## Build the binary
.PHONY: binary .PHONY: binary
binary: generate-webui build-dev-image binary: generate-webui
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate binary ./script/make.sh generate binary
## Build the linux binary locally ## Build the linux binary locally
.PHONY: binary-debug .PHONY: binary-debug
@ -89,35 +47,29 @@ binary-debug: generate-webui
## Build the binary for the standard platforms (linux, darwin, windows) ## Build the binary for the standard platforms (linux, darwin, windows)
.PHONY: crossbinary-default .PHONY: crossbinary-default
crossbinary-default: generate-webui build-dev-image crossbinary-default: generate-webui
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default ./script/make.sh generate crossbinary-default
## Build the binary for the standard platforms (linux, darwin, windows) in parallel ## Build the binary for the standard platforms (linux, darwin, windows) in parallel
.PHONY: crossbinary-default-parallel .PHONY: crossbinary-default-parallel
crossbinary-default-parallel: crossbinary-default-parallel:
$(MAKE) generate-webui $(MAKE) generate-webui
$(MAKE) build-dev-image crossbinary-default $(MAKE) crossbinary-default
## Run the unit and integration tests ## Run the unit and integration tests
.PHONY: test .PHONY: test
test: build-dev-image test:
-docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 ./script/make.sh generate test-unit binary test-integration
trap 'docker network rm traefik-test-network' EXIT; \
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate test-unit binary test-integration
## Run the unit tests ## Run the unit tests
.PHONY: test-unit .PHONY: test-unit
test-unit: build-dev-image test-unit:
-docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 ./script/make.sh generate test-unit
trap 'docker network rm traefik-test-network' EXIT; \
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate test-unit
## Run the integration tests ## Run the integration tests
.PHONY: test-integration .PHONY: test-integration
test-integration: build-dev-image test-integration:
-docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 ./script/make.sh generate binary test-integration
trap 'docker network rm traefik-test-network' EXIT; \
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate binary test-integration
## Pull all images for integration tests ## Pull all images for integration tests
.PHONY: pull-images .PHONY: pull-images
@ -128,16 +80,22 @@ pull-images:
| uniq \ | uniq \
| xargs -P 6 -n 1 docker pull | xargs -P 6 -n 1 docker pull
EXECUTABLES = misspell shellcheck
## Validate code and docs ## Validate code and docs
.PHONY: validate-files .PHONY: validate-files
validate-files: build-dev-image validate-files:
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),,$(error "No $(exec) in PATH")))
./script/make.sh generate validate-lint validate-misspell
bash $(CURDIR)/script/validate-shell-script.sh bash $(CURDIR)/script/validate-shell-script.sh
## Validate code, docs, and vendor ## Validate code, docs, and vendor
.PHONY: validate .PHONY: validate
validate: build-dev-image validate:
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell validate-vendor $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),,$(error "No $(exec) in PATH")))
./script/make.sh generate validate-lint validate-misspell validate-vendor
bash $(CURDIR)/script/validate-shell-script.sh bash $(CURDIR)/script/validate-shell-script.sh
## Clean up static directory and build a Docker Traefik image ## Clean up static directory and build a Docker Traefik image
@ -155,11 +113,6 @@ build-image-dirty: binary
build-image-debug: binary-debug build-image-debug: binary-debug
docker build -t $(TRAEFIK_IMAGE) -f debug.Dockerfile . docker build -t $(TRAEFIK_IMAGE) -f debug.Dockerfile .
## Start a shell inside the build env
.PHONY: shell
shell: build-dev-image
$(DOCKER_RUN_TRAEFIK) /bin/bash
## Build documentation site ## Build documentation site
.PHONY: docs .PHONY: docs
docs: docs:
@ -187,23 +140,23 @@ generate-genconf:
## Create packages for the release ## Create packages for the release
.PHONY: release-packages .PHONY: release-packages
release-packages: generate-webui build-dev-image release-packages: generate-webui
rm -rf dist rm -rf dist
@- $(foreach os, linux darwin windows freebsd openbsd, \ @- $(foreach os, linux darwin windows freebsd openbsd, \
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) goreleaser release --skip-publish -p 2 --timeout="90m" --config $(shell go run ./internal/release $(os)); \ goreleaser release --skip-publish -p 2 --timeout="90m" --config $(shell go run ./internal/release $(os)); \
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) go clean -cache; \ go clean -cache; \
) )
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) cat dist/**/*_checksums.txt >> dist/traefik_${VERSION}_checksums.txt cat dist/**/*_checksums.txt >> dist/traefik_${VERSION}_checksums.txt
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) rm dist/**/*_checksums.txt rm dist/**/*_checksums.txt
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) tar cfz dist/traefik-${VERSION}.src.tar.gz \ tar cfz dist/traefik-${VERSION}.src.tar.gz \
--exclude-vcs \ --exclude-vcs \
--exclude .idea \ --exclude .idea \
--exclude .travis \ --exclude .travis \
--exclude .semaphoreci \ --exclude .semaphoreci \
--exclude .github \ --exclude .github \
--exclude dist . --exclude dist .
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) chown -R $(shell id -u):$(shell id -g) dist/ chown -R $(shell id -u):$(shell id -g) dist/
## Format the Code ## Format the Code
.PHONY: fmt .PHONY: fmt

View file

@ -13,67 +13,13 @@ Let's see how.
## Building ## Building
You need either [Docker](https://github.com/docker/docker "Link to website of Docker") and `make` (Method 1), or [Go](https://go.dev/ "Link to website of Go") (Method 2) in order to build Traefik. You need:
For changes to its dependencies, the `dep` dependency management tool is required. - [Docker](https://github.com/docker/docker "Link to website of Docker")
- `make`
### Method 1: Using `Docker` and `Makefile` - [Go](https://go.dev/ "Link to website of Go")
- [misspell](https://github.com/golangci/misspell)
Run make with the `binary` target. - [shellcheck](https://github.com/koalaman/shellcheck)
- [Tailscale](https://tailscale.com/) if you are using Docker Desktop
```bash
make binary
```
This will create binaries for the Linux platform in the `dist` folder.
In case when you run build on CI, you may probably want to run docker in non-interactive mode. To achieve that define `DOCKER_NON_INTERACTIVE=true` environment variable.
```bash
$ make binary
docker build -t traefik-webui -f webui/Dockerfile webui
Sending build context to Docker daemon 2.686MB
Step 1/11 : FROM node:8.15.0
---> 1f6c34f7921c
[...]
Successfully built ce4ff439c06a
Successfully tagged traefik-webui:latest
[...]
docker build -t "traefik-dev:4475--feature-documentation" -f build.Dockerfile .
Sending build context to Docker daemon 279MB
Step 1/10 : FROM golang:1.16-alpine
---> f4bfb3d22bda
[...]
Successfully built 5c3c1a911277
Successfully tagged traefik-dev:4475--feature-documentation
docker run -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -e VERBOSE -e VERSION -e CODENAME -e TESTDIRS -e CI -e CONTAINER=DOCKER -v "/home/ldez/sources/go/src/github.com/traefik/traefik/"dist":/go/src/github.com/traefik/traefik/"dist"" "traefik-dev:4475--feature-documentation" ./script/make.sh generate binary
---> Making bundle: generate (in .)
removed 'autogen/genstatic/gen.go'
---> Making bundle: binary (in .)
$ ls dist/
traefik*
```
The following targets can be executed outside Docker by setting the variable `IN_DOCKER` to an empty string (although be aware that some of the tests might fail in that context):
- `test-unit`
- `test-integration`
- `validate`
- `binary` (the webUI is still generated by using Docker)
ex:
```bash
IN_DOCKER= make test-unit
```
### Method 2: Using `go`
Requirements:
- `go` v1.16+
- environment variable `GO111MODULE=on`
!!! tip "Source Directory" !!! tip "Source Directory"
@ -106,41 +52,33 @@ Requirements:
## ... and the list goes on ## ... and the list goes on
``` ```
#### Build Traefik ### Build Traefik
Once you've set up your go environment and cloned the source repository, you can build Traefik. Once you've set up your go environment and cloned the source repository, you can build Traefik.
```bash ```bash
# Generate UI static files $ make binary
make clean-webui generate-webui ./script/make.sh generate binary
---> Making bundle: generate (in .)
# required to merge non-code components into the final binary, ---> Making bundle: binary (in .)
# such as the web dashboard/UI
go generate $ ls dist/
traefik*
``` ```
```bash You will find the Traefik executable (`traefik`) in the `./dist` directory.
# Standard go build
go build ./cmd/traefik
```
You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/traefik/traefik` directory.
## Testing ## Testing
### Method 1: `Docker` and `make`
Run unit tests using the `test-unit` target. Run unit tests using the `test-unit` target.
Run integration tests using the `test-integration` target. Run integration tests using the `test-integration` target.
Run all tests (unit and integration) using the `test` target. Run all tests (unit and integration) using the `test` target.
```bash ```bash
$ make test-unit $ make test-unit
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile . ./script/make.sh generate test-unit
# […]
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github/traefik/traefik/dist:/go/src/github.com/traefik/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
---> Making bundle: generate (in .) ---> Making bundle: generate (in .)
removed 'gen.go'
---> Making bundle: test-unit (in .) ---> Making bundle: test-unit (in .)
+ go test -cover -coverprofile=cover.out . + go test -cover -coverprofile=cover.out .
@ -151,28 +89,30 @@ Test success
For development purposes, you can specify which tests to run by using (only works the `test-integration` target): For development purposes, you can specify which tests to run by using (only works the `test-integration` target):
??? note "Configuring Tailscale for Docker Desktop user"
Create `tailscale.secret` file in `integration` directory.
This file need to contains a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys)
(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:
```json
"autoApprovers": {
// Allow myself to automatically
// advertize routes for docker networks
"routes": {
"172.31.42.0/24": ["your_tailscale_identity"],
},
},
```
```bash ```bash
# Run every tests in the MyTest suite # Run every tests in the MyTest suite
TESTFLAGS="-check.f MyTestSuite" make test-integration TESTFLAGS="-test.run TestAccessLogSuite" make test-integration
# Run the test "MyTest" in the MyTest suite # Run the test "MyTest" in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration TESTFLAGS="-test.run TestAccessLogSuite -testify.m ^TestAccessLog$" make test-integration
# Run every tests starting with "My", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
# Run every tests ending with "Test", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
``` ```
Check [gocheck](https://labix.org/gocheck "Link to website of gocheck") for more information.
### Method 2: `go`
Unit tests can be run from the cloned directory using `$ go test ./...` which should return `ok`, similar to:
```test
ok _/home/user/go/src/github/traefik/traefik 0.004s
```
Integration tests must be run from the `integration/` directory and require the `-integration` switch: `$ cd integration && go test -integration ./...`.

View file

@ -8,21 +8,7 @@ description: "Learn how to use IPAllowList in HTTP middleware for limiting clien
Limiting Clients to Specific IPs Limiting Clients to Specific IPs
{: .subtitle } {: .subtitle }
<<<<<<<< HEAD:docs/content/middlewares/http/ipallowlist.md
IPAllowList accepts / refuses requests based on the client IP. IPAllowList accepts / refuses requests based on the client IP.
|||||||| dae0491b6:docs/content/middlewares/http/ipwhitelist.md
![IpWhiteList](../../assets/img/middleware/ipwhitelist.png)
IPWhitelist accepts / refuses requests based on the client IP.
========
![IPWhiteList](../../assets/img/middleware/ipwhitelist.png)
IPWhiteList accepts / refuses requests based on the client IP.
!!! warning
This middleware is deprecated, please use the [IPAllowList](./ipallowlist.md) middleware instead.
>>>>>>>> upstream/v2.11:docs/content/middlewares/http/ipwhitelist.md
## Configuration Examples ## Configuration Examples
@ -207,45 +193,3 @@ http:
[http.middlewares.test-ipallowlist.ipAllowList.ipStrategy] [http.middlewares.test-ipallowlist.ipAllowList.ipStrategy]
excludedIPs = ["127.0.0.1/32", "192.168.1.7"] excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
``` ```
### `rejectStatusCode`
The `rejectStatusCode` option sets HTTP status code for refused requests. If not set, the default is 403 (Forbidden).
```yaml tab="Docker & Swarm"
# Reject requests with a 404 rather than a 403
labels:
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.rejectstatuscode=404"
```
```yaml tab="Kubernetes"
# Reject requests with a 404 rather than a 403
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-ipallowlist
spec:
ipAllowList:
rejectStatusCode: 404
```
```yaml tab="Consul Catalog"
# Reject requests with a 404 rather than a 403
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.rejectstatuscode=404"
```
```yaml tab="File (YAML)"
# Reject requests with a 404 rather than a 403
http:
middlewares:
test-ipallowlist:
ipAllowList:
rejectStatusCode: 404
```
```toml tab="File (TOML)"
# Reject requests with a 404 rather than a 403
[http.middlewares]
[http.middlewares.test-ipallowlist.ipAllowList]
rejectStatusCode = 404
```

102
go.mod
View file

@ -9,18 +9,15 @@ require (
github.com/andybalholm/brotli v1.0.6 github.com/andybalholm/brotli v1.0.6
github.com/aws/aws-sdk-go v1.44.327 github.com/aws/aws-sdk-go v1.44.327
github.com/cenkalti/backoff/v4 v4.2.1 github.com/cenkalti/backoff/v4 v4.2.1
github.com/compose-spec/compose-go v1.0.3
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/docker/cli v20.10.11+incompatible github.com/docker/cli v24.0.7+incompatible
github.com/docker/compose/v2 v2.0.1 github.com/docker/docker v24.0.7+incompatible
github.com/docker/docker v20.10.21+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/fatih/structs v1.1.0 github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-acme/lego/v4 v4.14.0 github.com/go-acme/lego/v4 v4.14.0
github.com/go-check/check v0.0.0-00010101000000-000000000000
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
github.com/golang/protobuf v1.5.3 github.com/golang/protobuf v1.5.3
github.com/google/go-github/v28 v28.1.1 github.com/google/go-github/v28 v28.1.1
@ -35,7 +32,7 @@ require (
github.com/http-wasm/http-wasm-host-go v0.5.2 github.com/http-wasm/http-wasm-host-go v0.5.2
github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
github.com/klauspost/compress v1.17.1 github.com/klauspost/compress v1.17.2
github.com/kvtools/consul v1.0.2 github.com/kvtools/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2 github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.1.0 github.com/kvtools/redis v1.1.0
@ -59,6 +56,7 @@ require (
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
github.com/testcontainers/testcontainers-go v0.27.0
github.com/tetratelabs/wazero v1.5.0 github.com/tetratelabs/wazero v1.5.0
github.com/tidwall/gjson v1.17.0 github.com/tidwall/gjson v1.17.0
github.com/traefik/grpc-web v0.16.0 github.com/traefik/grpc-web v0.16.0
@ -66,7 +64,6 @@ require (
github.com/traefik/yaegi v0.15.1 github.com/traefik/yaegi v0.15.1
github.com/unrolled/render v1.0.2 github.com/unrolled/render v1.0.2
github.com/unrolled/secure v1.0.9 github.com/unrolled/secure v1.0.9
github.com/vdemeester/shakers v0.1.0
github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c
github.com/vulcand/predicate v1.2.0 github.com/vulcand/predicate v1.2.0
go.opentelemetry.io/collector/pdata v0.66.0 go.opentelemetry.io/collector/pdata v0.66.0
@ -100,8 +97,8 @@ require (
require ( require (
cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/AlecAivazis/survey/v2 v2.2.3 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
@ -121,12 +118,11 @@ require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.8.25 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
@ -147,72 +143,59 @@ require (
github.com/aws/smithy-go v1.14.2 // indirect github.com/aws/smithy-go v1.14.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/buger/goterm v1.0.0 // indirect github.com/bytedance/sonic v1.10.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.70.0 // indirect github.com/cloudflare/cloudflare-go v0.70.0 // indirect
github.com/compose-spec/godotenv v1.0.0 // indirect github.com/containerd/containerd v1.7.11 // indirect
github.com/containerd/cgroups v1.0.3 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/containerd/containerd v1.5.17 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect
github.com/dnsimple/dnsimple-go v1.2.0 // indirect github.com/dnsimple/dnsimple-go v1.2.0 // indirect
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/exoscale/egoscale v0.100.1 // indirect github.com/exoscale/egoscale v0.100.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.15.0 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-gonic/gin v1.7.7 // indirect github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-zookeeper/zk v1.0.3 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect
github.com/gofrs/flock v0.8.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/googleapis v1.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
github.com/google/s2a-go v0.1.5 // indirect github.com/google/s2a-go v0.1.5 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/cronexpr v1.1.1 // indirect github.com/hashicorp/cronexpr v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@ -223,16 +206,14 @@ require (
github.com/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
@ -241,26 +222,22 @@ require (
github.com/liquidweb/go-lwApi v0.0.5 // indirect github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.3 // indirect github.com/liquidweb/liquidweb-go v1.6.3 // indirect
github.com/magiconair/properties v1.8.6 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect
github.com/mailgun/multibuf v0.1.2 // indirect github.com/mailgun/multibuf v0.1.2 // indirect
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/mount v0.2.0 // indirect github.com/moby/term v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
@ -279,13 +256,14 @@ require (
github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/onsi/gomega v1.27.10 // indirect github.com/onsi/gomega v1.27.10 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opencontainers/runc v1.1.5 // indirect github.com/opencontainers/runc v1.1.5 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
github.com/ovh/go-ovh v1.4.1 // indirect github.com/ovh/go-ovh v1.4.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
@ -297,33 +275,30 @@ require (
github.com/sacloud/go-http v0.1.6 // indirect github.com/sacloud/go-http v0.1.6 // indirect
github.com/sacloud/iaas-api-go v1.11.1 // indirect github.com/sacloud/iaas-api-go v1.11.1 // indirect
github.com/sacloud/packages-go v0.0.9 // indirect github.com/sacloud/packages-go v0.0.9 // indirect
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
github.com/shirou/gopsutil/v3 v3.23.11 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.2 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.1 // indirect github.com/stretchr/objx v0.5.1 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/transip/gotransip/v6 v6.20.0 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zeebo/errs v1.2.2 // indirect github.com/zeebo/errs v1.2.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.9 // indirect go.etcd.io/etcd/api/v3 v3.5.9 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
@ -335,10 +310,10 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.14.0 // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect
google.golang.org/api v0.128.0 // indirect google.golang.org/api v0.128.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
@ -363,7 +338,7 @@ require (
// Containous forks // Containous forks
replace ( replace (
github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e
github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f
github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595
) )
@ -373,3 +348,6 @@ replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402
// ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules // ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules
// tencentcloud uses monorepo with multimodule but the go.mod files are incomplete. // tencentcloud uses monorepo with multimodule but the go.mod files are incomplete.
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible
// https://github.com/docker/compose/blob/v2.19.0/go.mod#L12
replace github.com/cucumber/godog => github.com/cucumber/godog v0.13.0

1159
go.sum

File diff suppressed because it is too large Load diff

View file

@ -11,13 +11,15 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
checker "github.com/vdemeester/shakers"
) )
const ( const (
@ -28,6 +30,10 @@ const (
// AccessLogSuite tests suite. // AccessLogSuite tests suite.
type AccessLogSuite struct{ BaseSuite } type AccessLogSuite struct{ BaseSuite }
func TestAccessLogSuite(t *testing.T) {
suite.Run(t, new(AccessLogSuite))
}
type accessLogValue struct { type accessLogValue struct {
formatOnly bool formatOnly bool
code string code string
@ -36,67 +42,67 @@ type accessLogValue struct {
serviceURL string serviceURL string
} }
func (s *AccessLogSuite) SetUpSuite(c *check.C) { func (s *AccessLogSuite) SetupSuite() {
s.createComposeProject(c, "access_log") s.BaseSuite.SetupSuite()
s.composeUp(c) s.createComposeProject("access_log")
s.composeUp()
} }
func (s *AccessLogSuite) TearDownTest(c *check.C) { func (s *AccessLogSuite) TearDownSuite() {
displayTraefikLogFile(c, traefikTestLogFile) s.BaseSuite.TearDownSuite()
}
func (s *AccessLogSuite) TearDownTest() {
s.displayTraefikLogFile(traefikTestLogFile)
_ = os.Remove(traefikTestAccessLogFile) _ = os.Remove(traefikTestAccessLogFile)
} }
func (s *AccessLogSuite) TestAccessLog(c *check.C) { func (s *AccessLogSuite) TestAccessLog() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
defer func() { defer func() {
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
log.Info().Msg(string(traefikLog)) log.Info().Msg(string(traefikLog))
}() }()
err := cmd.Start() s.waitForTraefik("server1")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
waitForTraefik(c, "server1") s.checkStatsForLogFile()
checkStatsForLogFile(c)
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Make some requests // Make some requests
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend1.docker.local" req.Host = "frontend1.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend2.docker.local" req.Host = "frontend2.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogOutput(c) count := s.checkAccessLogOutput()
c.Assert(count, checker.GreaterOrEqualThan, 3) assert.Equal(s.T(), 3, count)
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { func (s *AccessLogSuite) TestAccessLogAuthFrontend() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -124,48 +130,43 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("authFrontend")
waitForTraefik(c, "authFrontend")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test auth entrypoint // Test auth entrypoint
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend.auth.docker.local" req.Host = "frontend.auth.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.SetBasicAuth("test", "") req.SetBasicAuth("test", "")
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.SetBasicAuth("test", "test") req.SetBasicAuth("test", "test")
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) { func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -193,27 +194,22 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("digestAuthMiddleware")
waitForTraefik(c, "digestAuthMiddleware")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test auth entrypoint // Test auth entrypoint
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8008/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8008/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "entrypoint.digest.auth.docker.local" req.Host = "entrypoint.digest.auth.docker.local"
resp, err := try.ResponseUntilStatusCode(req, 500*time.Millisecond, http.StatusUnauthorized) resp, err := try.ResponseUntilStatusCode(req, 500*time.Millisecond, http.StatusUnauthorized)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
digest := digestParts(resp) digest := digestParts(resp)
digest["uri"] = "/" digest["uri"] = "/"
@ -225,22 +221,22 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
digest["password"] = "test" digest["password"] = "test"
req.Header.Set("Authorization", getDigestAuthorization(digest)) req.Header.Set("Authorization", getDigestAuthorization(digest))
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
// Thanks to mvndaai for digest authentication // Thanks to mvndaai for digest authentication
@ -291,7 +287,7 @@ func getDigestAuthorization(digestParts map[string]string) string {
return authorization return authorization
} }
func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { func (s *AccessLogSuite) TestAccessLogFrontendRedirect() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -308,38 +304,33 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("frontendRedirect")
waitForTraefik(c, "frontendRedirect")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test frontend redirect // Test frontend redirect
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "" req.Host = ""
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect(c *check.C) { func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
type logLine struct { type logLine struct {
@ -365,30 +356,25 @@ func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_json_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_json_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("frontendRedirect")
waitForTraefik(c, "frontendRedirect")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test frontend redirect // Test frontend redirect
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "" req.Host = ""
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
lines := extractLines(c) lines := s.extractLines()
c.Assert(len(lines), checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), len(lines), len(expected))
for i, line := range lines { for i, line := range lines {
if line == "" { if line == "" {
@ -396,15 +382,15 @@ func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect(c *check.C) {
} }
var logline logLine var logline logLine
err := json.Unmarshal([]byte(line), &logline) err := json.Unmarshal([]byte(line), &logline)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(logline.DownstreamStatus, checker.Equals, expected[i].DownstreamStatus) assert.Equal(s.T(), expected[i].DownstreamStatus, logline.DownstreamStatus)
c.Assert(logline.OriginStatus, checker.Equals, expected[i].OriginStatus) assert.Equal(s.T(), expected[i].OriginStatus, logline.OriginStatus)
c.Assert(logline.RouterName, checker.Equals, expected[i].RouterName) assert.Equal(s.T(), expected[i].RouterName, logline.RouterName)
c.Assert(logline.ServiceName, checker.Equals, expected[i].ServiceName) assert.Equal(s.T(), expected[i].ServiceName, logline.ServiceName)
} }
} }
func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { func (s *AccessLogSuite) TestAccessLogRateLimit() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -424,42 +410,37 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("rateLimit")
waitForTraefik(c, "rateLimit")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test rate limit // Test rate limit
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8007/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8007/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "ratelimit.docker.local" req.Host = "ratelimit.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { func (s *AccessLogSuite) TestAccessLogBackendNotFound() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -473,38 +454,33 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.waitForTraefik("server1")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
waitForTraefik(c, "server1") s.checkStatsForLogFile()
checkStatsForLogFile(c)
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test rate limit // Test rate limit
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "backendnotfound.docker.local" req.Host = "backendnotfound.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogFrontendAllowlist(c *check.C) { func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -518,38 +494,33 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("frontendAllowlist")
waitForTraefik(c, "frontendAllowlist")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test rate limit // Test rate limit
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend.allowlist.docker.local" req.Host = "frontend.allowlist.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusForbidden), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusForbidden), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) { func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -563,39 +534,34 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("authFrontend")
waitForTraefik(c, "authFrontend")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test auth entrypoint // Test auth entrypoint
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend.auth.docker.local" req.Host = "frontend.auth.docker.local"
req.SetBasicAuth("test", "test") req.SetBasicAuth("test", "test")
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware(c *check.C) { func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
expected := []accessLogValue{ expected := []accessLogValue{
@ -609,79 +575,74 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware(c *check.C) {
} }
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
defer display(c)
err := cmd.Start() s.checkStatsForLogFile()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
checkStatsForLogFile(c) s.waitForTraefik("preflightCORS")
waitForTraefik(c, "preflightCORS")
// Verify Traefik started OK // Verify Traefik started OK
checkTraefikStarted(c) s.checkTraefikStarted()
// Test preflight response // Test preflight response
req, err := http.NewRequest(http.MethodOptions, "http://127.0.0.1:8009/", nil) req, err := http.NewRequest(http.MethodOptions, "http://127.0.0.1:8009/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "preflight.docker.local" req.Host = "preflight.docker.local"
req.Header.Set("Origin", "whatever") req.Header.Set("Origin", "whatever")
req.Header.Set("Access-Control-Request-Method", "GET") req.Header.Set("Access-Control-Request-Method", "GET")
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log output as expected // Verify access.log output as expected
count := checkAccessLogExactValuesOutput(c, expected) count := s.checkAccessLogExactValuesOutput(expected)
c.Assert(count, checker.GreaterOrEqualThan, len(expected)) assert.GreaterOrEqual(s.T(), count, len(expected))
// Verify no other Traefik problems // Verify no other Traefik problems
checkNoOtherTraefikProblems(c) s.checkNoOtherTraefikProblems()
} }
func checkNoOtherTraefikProblems(c *check.C) { func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if len(traefikLog) > 0 { if len(traefikLog) > 0 {
fmt.Printf("%s\n", string(traefikLog)) fmt.Printf("%s\n", string(traefikLog))
} }
} }
func checkAccessLogOutput(c *check.C) int { func (s *AccessLogSuite) checkAccessLogOutput() int {
lines := extractLines(c) lines := s.extractLines()
count := 0 count := 0
for i, line := range lines { for i, line := range lines {
if len(line) > 0 { if len(line) > 0 {
count++ count++
CheckAccessLogFormat(c, line, i) s.CheckAccessLogFormat(line, i)
} }
} }
return count return count
} }
func checkAccessLogExactValuesOutput(c *check.C, values []accessLogValue) int { func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int {
lines := extractLines(c) lines := s.extractLines()
count := 0 count := 0
for i, line := range lines { for i, line := range lines {
fmt.Println(line) fmt.Println(line)
if len(line) > 0 { if len(line) > 0 {
count++ count++
if values[i].formatOnly { if values[i].formatOnly {
CheckAccessLogFormat(c, line, i) s.CheckAccessLogFormat(line, i)
} else { } else {
checkAccessLogExactValues(c, line, i, values[i]) s.checkAccessLogExactValues(line, i, values[i])
} }
} }
} }
return count return count
} }
func extractLines(c *check.C) []string { func (s *AccessLogSuite) extractLines() []string {
accessLog, err := os.ReadFile(traefikTestAccessLogFile) accessLog, err := os.ReadFile(traefikTestAccessLogFile)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
lines := strings.Split(string(accessLog), "\n") lines := strings.Split(string(accessLog), "\n")
@ -694,14 +655,14 @@ func extractLines(c *check.C) []string {
return clean return clean
} }
func checkStatsForLogFile(c *check.C) { func (s *AccessLogSuite) checkStatsForLogFile() {
err := try.Do(1*time.Second, func() error { err := try.Do(1*time.Second, func() error {
if _, errStat := os.Stat(traefikTestLogFile); errStat != nil { if _, errStat := os.Stat(traefikTestLogFile); errStat != nil {
return fmt.Errorf("could not get stats for log file: %w", errStat) return fmt.Errorf("could not get stats for log file: %w", errStat)
} }
return nil return nil
}) })
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func ensureWorkingDirectoryIsClean() { func ensureWorkingDirectoryIsClean() {
@ -709,69 +670,38 @@ func ensureWorkingDirectoryIsClean() {
os.Remove(traefikTestLogFile) os.Remove(traefikTestLogFile)
} }
func checkTraefikStarted(c *check.C) []byte { func (s *AccessLogSuite) checkTraefikStarted() []byte {
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if len(traefikLog) > 0 { if len(traefikLog) > 0 {
fmt.Printf("%s\n", string(traefikLog)) fmt.Printf("%s\n", string(traefikLog))
} }
return traefikLog return traefikLog
} }
func CheckAccessLogFormat(c *check.C, line string, i int) { func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
results, err := accesslog.ParseAccessLog(line) results, err := accesslog.ParseAccessLog(line)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(results, checker.HasLen, 14) assert.Len(s.T(), results, 14)
c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`) assert.Regexp(s.T(), `^(-|\d{3})$`, results[accesslog.OriginStatus])
count, _ := strconv.Atoi(results[accesslog.RequestCount]) count, _ := strconv.Atoi(results[accesslog.RequestCount])
c.Assert(count, checker.GreaterOrEqualThan, i+1) assert.GreaterOrEqual(s.T(), count, i+1)
c.Assert(results[accesslog.RouterName], checker.Matches, `"(rt-.+@docker|api@internal)"`) assert.Regexp(s.T(), `"(rt-.+@docker|api@internal)"`, results[accesslog.RouterName])
c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, `"http://`) assert.True(s.T(), strings.HasPrefix(results[accesslog.ServiceURL], `"http://`))
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration])
} }
func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) { func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) {
results, err := accesslog.ParseAccessLog(line) results, err := accesslog.ParseAccessLog(line)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(results, checker.HasLen, 14) assert.Len(s.T(), results, 14)
if len(v.user) > 0 { if len(v.user) > 0 {
c.Assert(results[accesslog.ClientUsername], checker.Equals, v.user) assert.Equal(s.T(), v.user, results[accesslog.ClientUsername])
} }
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code) assert.Equal(s.T(), v.code, results[accesslog.OriginStatus])
count, _ := strconv.Atoi(results[accesslog.RequestCount]) count, _ := strconv.Atoi(results[accesslog.RequestCount])
c.Assert(count, checker.GreaterOrEqualThan, i+1) assert.GreaterOrEqual(s.T(), count, i+1)
c.Assert(results[accesslog.RouterName], checker.Matches, `^"?`+v.routerName+`.*(@docker)?$`) assert.Regexp(s.T(), `^"?`+v.routerName+`.*(@docker)?$`, results[accesslog.RouterName])
c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`) assert.Regexp(s.T(), `^"?`+v.serviceURL+`.*$`, results[accesslog.ServiceURL])
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration])
}
func waitForTraefik(c *check.C, containerName string) {
time.Sleep(1 * time.Second)
// Wait for Traefik to turn ready.
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
c.Assert(err, checker.IsNil)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName))
c.Assert(err, checker.IsNil)
}
func displayTraefikLogFile(c *check.C, path string) {
if c.Failed() {
if _, err := os.Stat(path); !os.IsNotExist(err) {
content, errRead := os.ReadFile(path)
fmt.Printf("%s: Traefik logs: \n", c.TestName())
if errRead == nil {
fmt.Println(content)
} else {
fmt.Println(errRead)
}
} else {
fmt.Printf("%s: No Traefik logs.\n", c.TestName())
}
errRemove := os.Remove(path)
if errRemove != nil {
fmt.Println(errRemove)
}
}
} }

View file

@ -8,16 +8,19 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/config/static"
"github.com/traefik/traefik/v3/pkg/provider/acme" "github.com/traefik/traefik/v3/pkg/provider/acme"
"github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/testhelpers"
"github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/types"
checker "github.com/vdemeester/shakers"
) )
// ACME test suites. // ACME test suites.
@ -27,6 +30,10 @@ type AcmeSuite struct {
fakeDNSServer *dns.Server fakeDNSServer *dns.Server
} }
func TestAcmeSuite(t *testing.T) {
suite.Run(t, new(AcmeSuite))
}
type subCases struct { type subCases struct {
host string host string
expectedCommonName string expectedCommonName string
@ -87,17 +94,18 @@ func setupPebbleRootCA() (*http.Transport, error) {
}, nil }, nil
} }
func (s *AcmeSuite) SetUpSuite(c *check.C) { func (s *AcmeSuite) SetupSuite() {
s.createComposeProject(c, "pebble") s.BaseSuite.SetupSuite()
s.composeUp(c)
s.fakeDNSServer = startFakeDNSServer(s.getContainerIP(c, "traefik")) s.createComposeProject("pebble")
s.pebbleIP = s.getComposeServiceIP(c, "pebble") s.composeUp()
// Retrieving the Docker host ip.
s.fakeDNSServer = startFakeDNSServer(s.hostIP)
s.pebbleIP = s.getComposeServiceIP("pebble")
pebbleTransport, err := setupPebbleRootCA() pebbleTransport, err := setupPebbleRootCA()
if err != nil { require.NoError(s.T(), err)
c.Fatal(err)
}
// wait for pebble // wait for pebble
req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil) req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil)
@ -113,21 +121,24 @@ func (s *AcmeSuite) SetUpSuite(c *check.C) {
} }
return try.StatusCodeIs(http.StatusOK)(resp) return try.StatusCodeIs(http.StatusOK)(resp)
}) })
c.Assert(err, checker.IsNil)
require.NoError(s.T(), err)
} }
func (s *AcmeSuite) TearDownSuite(c *check.C) { func (s *AcmeSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
if s.fakeDNSServer != nil { if s.fakeDNSServer != nil {
err := s.fakeDNSServer.Shutdown() err := s.fakeDNSServer.Shutdown()
if err != nil { if err != nil {
c.Log(err) log.Info().Msg(err.Error())
} }
} }
s.composeDown(c) s.composeDown()
} }
func (s *AcmeSuite) TestHTTP01Domains(c *check.C) { func (s *AcmeSuite) TestHTTP01Domains() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_domains.toml", traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -147,10 +158,10 @@ func (s *AcmeSuite) TestHTTP01Domains(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) { func (s *AcmeSuite) TestHTTP01StoreDomains() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_store_domains.toml", traefikConfFilePath: "fixtures/acme/acme_store_domains.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -170,10 +181,10 @@ func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) { func (s *AcmeSuite) TestHTTP01DomainsInSAN() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_domains.toml", traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -194,10 +205,10 @@ func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) { func (s *AcmeSuite) TestHTTP01OnHostRule() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml", traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -214,10 +225,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestMultipleResolver(c *check.C) { func (s *AcmeSuite) TestMultipleResolver() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_multiple_resolvers.toml", traefikConfFilePath: "fixtures/acme/acme_multiple_resolvers.toml",
subCases: []subCases{ subCases: []subCases{
@ -245,10 +256,10 @@ func (s *AcmeSuite) TestMultipleResolver(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) { func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml", traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -266,10 +277,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo(c *check.C) { func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml", traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -287,10 +298,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard(c *check.C) { func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tls.toml", traefikConfFilePath: "fixtures/acme/acme_tls.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -307,10 +318,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard(c
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check.C) { func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tls_dynamic.toml", traefikConfFilePath: "fixtures/acme/acme_tls_dynamic.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -327,10 +338,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP(c *check.C) { func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tcp.toml", traefikConfFilePath: "fixtures/acme/acme_tcp.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -347,10 +358,10 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestTLSALPN01OnHostRule(c *check.C) { func (s *AcmeSuite) TestTLSALPN01OnHostRule() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml", traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -367,10 +378,10 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRule(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestTLSALPN01Domains(c *check.C) { func (s *AcmeSuite) TestTLSALPN01Domains() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_domains.toml", traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -390,10 +401,10 @@ func (s *AcmeSuite) TestTLSALPN01Domains(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
func (s *AcmeSuite) TestTLSALPN01DomainsInSAN(c *check.C) { func (s *AcmeSuite) TestTLSALPN01DomainsInSAN() {
testCase := acmeTestCase{ testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_domains.toml", traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{ subCases: []subCases{{
@ -414,12 +425,12 @@ func (s *AcmeSuite) TestTLSALPN01DomainsInSAN(c *check.C) {
}, },
} }
s.retrieveAcmeCertificate(c, testCase) s.retrieveAcmeCertificate(testCase)
} }
// Test Let's encrypt down. // Test Let's encrypt down.
func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) { func (s *AcmeSuite) TestNoValidLetsEncryptServer() {
file := s.adaptFile(c, "fixtures/acme/acme_base.toml", templateModel{ file := s.adaptFile("fixtures/acme/acme_base.toml", templateModel{
Acme: map[string]static.CertificateResolver{ Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{ "default": {ACME: &acme.Configuration{
CAServer: "http://wrongurl:4001/directory", CAServer: "http://wrongurl:4001/directory",
@ -427,21 +438,16 @@ func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) {
}}, }},
}, },
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected traefik works // Expected traefik works
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
// Doing an HTTPS request and test the response certificate. // Doing an HTTPS request and test the response certificate.
func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { func (s *AcmeSuite) retrieveAcmeCertificate(testCase acmeTestCase) {
if len(testCase.template.PortHTTP) == 0 { if len(testCase.template.PortHTTP) == 0 {
testCase.template.PortHTTP = ":5002" testCase.template.PortHTTP = ":5002"
} }
@ -456,14 +462,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
} }
} }
file := s.adaptFile(c, testCase.traefikConfFilePath, testCase.template) file := s.adaptFile(testCase.traefikConfFilePath, testCase.template)
defer os.Remove(file)
s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// A real file is needed to have the right mode on acme.json file // A real file is needed to have the right mode on acme.json file
defer os.Remove("/tmp/acme.json") defer os.Remove("/tmp/acme.json")
@ -477,11 +479,11 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
} }
// wait for traefik (generating acme account take some seconds) // wait for traefik (generating acme account take some seconds)
err = try.Do(60*time.Second, func() error { err := try.Do(60*time.Second, func() error {
_, errGet := client.Get("https://127.0.0.1:5001") _, errGet := client.Get("https://127.0.0.1:5001")
return errGet return errGet
}) })
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
for _, sub := range testCase.subCases { for _, sub := range testCase.subCases {
client = &http.Client{ client = &http.Client{
@ -503,7 +505,7 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
var resp *http.Response var resp *http.Response
// Retry to send a Request which uses the LE generated certificate // Retry to send a Request which uses the LE generated certificate
err = try.Do(60*time.Second, func() error { err := try.Do(60*time.Second, func() error {
resp, err = client.Do(req) resp, err = client.Do(req)
if err != nil { if err != nil {
return err return err
@ -517,10 +519,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
return nil return nil
}) })
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, resp.StatusCode)
// Check Domain into response certificate // Check Domain into response certificate
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, sub.expectedCommonName) assert.Equal(s.T(), sub.expectedCommonName, resp.TLS.PeerCertificates[0].Subject.CommonName)
c.Assert(resp.TLS.PeerCertificates[0].PublicKeyAlgorithm, checker.Equals, sub.expectedAlgorithm) assert.Equal(s.T(), sub.expectedAlgorithm, resp.TLS.PeerCertificates[0].PublicKeyAlgorithm)
} }
} }

View file

@ -8,36 +8,38 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strconv" "strconv"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
checker "github.com/vdemeester/shakers"
) )
type ThrottlingSuite struct{ BaseSuite } type ThrottlingSuite struct{ BaseSuite }
func (s *ThrottlingSuite) SetUpSuite(c *check.C) { func TestThrottlingSuite(t *testing.T) {
s.createComposeProject(c, "rest") suite.Run(t, new(ThrottlingSuite))
s.composeUp(c)
} }
func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) { func (s *ThrottlingSuite) SetupSuite() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/throttling/simple.toml")) s.BaseSuite.SetupSuite()
s.createComposeProject("rest")
s.composeUp()
}
defer display(c) func (s *ThrottlingSuite) TestThrottleConfReload() {
err := cmd.Start() s.traefikCmd(withConfigFile("fixtures/throttling/simple.toml"))
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Expected a 404 as we did not configure anything. // Expected a 404 as we did not configure anything.
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
config := &dynamic.Configuration{ config := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
@ -47,7 +49,7 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) {
LoadBalancer: &dynamic.ServersLoadBalancer{ LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{ Servers: []dynamic.Server{
{ {
URL: "http://" + s.getComposeServiceIP(c, "whoami1") + ":80", URL: "http://" + s.getComposeServiceIP("whoami1") + ":80",
}, },
}, },
}, },
@ -68,28 +70,28 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) {
for i := 0; i < confChanges; i++ { for i := 0; i < confChanges; i++ {
config.HTTP.Routers[fmt.Sprintf("routerHTTP%d", i)] = router config.HTTP.Routers[fmt.Sprintf("routerHTTP%d", i)] = router
data, err := json.Marshal(config) data, err := json.Marshal(config)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data)) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, response.StatusCode)
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
} }
reloadsRegexp := regexp.MustCompile(`traefik_config_reloads_total (\d*)\n`) reloadsRegexp := regexp.MustCompile(`traefik_config_reloads_total (\d*)\n`)
resp, err := http.Get("http://127.0.0.1:8080/metrics") resp, err := http.Get("http://127.0.0.1:8080/metrics")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
fields := reloadsRegexp.FindStringSubmatch(string(body)) fields := reloadsRegexp.FindStringSubmatch(string(body))
c.Assert(len(fields), checker.Equals, 2) assert.Len(s.T(), fields, 2)
reloads, err := strconv.Atoi(fields[1]) reloads, err := strconv.Atoi(fields[1])
if err != nil { if err != nil {
@ -101,5 +103,5 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) {
// Therefore the throttling (set at 400ms for this test) should only let // Therefore the throttling (set at 400ms for this test) should only let
// (2s / 400 ms =) 5 config reloads happen in theory. // (2s / 400 ms =) 5 config reloads happen in theory.
// In addition, we have to take into account the extra config reload from the internal provider (5 + 1). // In addition, we have to take into account the extra config reload from the internal provider (5 + 1).
c.Assert(reloads, checker.LessOrEqualThan, 6) assert.LessOrEqual(s.T(), reloads, 6)
} }

View file

@ -4,13 +4,14 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type ConsulCatalogSuite struct { type ConsulCatalogSuite struct {
@ -20,26 +21,36 @@ type ConsulCatalogSuite struct {
consulURL string consulURL string
} }
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { func TestConsulCatalogSuite(t *testing.T) {
s.createComposeProject(c, "consul_catalog") suite.Run(t, new(ConsulCatalogSuite))
s.composeUp(c) }
s.consulURL = "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "consul"), "8500") func (s *ConsulCatalogSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("consul_catalog")
s.composeUp()
s.consulURL = "http://" + net.JoinHostPort(s.getComposeServiceIP("consul"), "8500")
var err error var err error
s.consulClient, err = api.NewClient(&api.Config{ s.consulClient, err = api.NewClient(&api.Config{
Address: s.consulURL, Address: s.consulURL,
}) })
c.Check(err, check.IsNil) require.NoError(s.T(), err)
// Wait for consul to elect itself leader // Wait for consul to elect itself leader
err = s.waitToElectConsulLeader() err = s.waitToElectConsulLeader()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.consulAgentClient, err = api.NewClient(&api.Config{ s.consulAgentClient, err = api.NewClient(&api.Config{
Address: "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "consul-agent"), "8500"), Address: "http://" + net.JoinHostPort(s.getComposeServiceIP("consul-agent"), "8500"),
}) })
c.Check(err, check.IsNil) require.NoError(s.T(), err)
}
func (s *ConsulCatalogSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
} }
func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
@ -83,36 +94,36 @@ func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error {
return client.Agent().ServiceDeregister(id) return client.Agent().ServiceDeregister(id)
} }
func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) { func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings() {
reg1 := &api.AgentServiceRegistration{ reg1 := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
Tags: []string{"traefik.enable=true"}, Tags: []string{"traefik.enable=true"},
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg1, false) err := s.registerService(reg1, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
reg2 := &api.AgentServiceRegistration{ reg2 := &api.AgentServiceRegistration{
ID: "whoami2", ID: "whoami2",
Name: "whoami", Name: "whoami",
Tags: []string{"traefik.enable=true"}, Tags: []string{"traefik.enable=true"},
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami2"), Address: s.getComposeServiceIP("whoami2"),
} }
err = s.registerService(reg2, false) err = s.registerService(reg2, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
reg3 := &api.AgentServiceRegistration{ reg3 := &api.AgentServiceRegistration{
ID: "whoami3", ID: "whoami3",
Name: "whoami", Name: "whoami",
Tags: []string{"traefik.enable=true"}, Tags: []string{"traefik.enable=true"},
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami3"), Address: s.getComposeServiceIP("whoami3"),
} }
err = s.registerService(reg3, false) err = s.registerService(reg3, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
@ -120,23 +131,18 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "whoami" req.Host = "whoami"
err = try.Request(req, 2*time.Second, err = try.Request(req, 2*time.Second,
try.StatusCodeIs(200), try.StatusCodeIs(200),
try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.StatusCodeIs(200), try.StatusCodeIs(200),
@ -145,18 +151,18 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c
fmt.Sprintf(`"http://%s:80":"UP"`, reg2.Address), fmt.Sprintf(`"http://%s:80":"UP"`, reg2.Address),
fmt.Sprintf(`"http://%s:80":"UP"`, reg3.Address), fmt.Sprintf(`"http://%s:80":"UP"`, reg3.Address),
)) ))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami2", false) err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami3", false) err = s.deregisterService("whoami3", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { func (s *ConsulCatalogSuite) TestByLabels() {
containerIP := s.getComposeServiceIP(c, "whoami1") containerIP := s.getComposeServiceIP("whoami1")
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
@ -171,7 +177,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
Address: containerIP, Address: containerIP,
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
@ -179,23 +185,18 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) {
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) err = try.GetRequest("http://127.0.0.1:8000/whoami", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { func (s *ConsulCatalogSuite) TestSimpleConfiguration() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -204,37 +205,32 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
Tags: []string{"traefik.enable=true"}, Tags: []string{"traefik.enable=true"},
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "whoami.consul.localhost" req.Host = "whoami.consul.localhost"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) { func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -243,39 +239,34 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple_watch.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple_watch.toml", tempObjects)
defer os.Remove(file)
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
Tags: []string{"traefik.enable=true"}, Tags: []string{"traefik.enable=true"},
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "whoami.consul.localhost" req.Host = "whoami.consul.localhost"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoamiIP := s.getComposeServiceIP(c, "whoami1") whoamiIP := s.getComposeServiceIP("whoami1")
reg.Check = &api.AgentServiceCheck{ reg.Check = &api.AgentServiceCheck{
CheckID: "some-ok-check", CheckID: "some-ok-check",
TCP: whoamiIP + ":80", TCP: whoamiIP + ":80",
@ -285,10 +276,10 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) {
} }
err = s.registerService(reg, false) err = s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
reg.Check = &api.AgentServiceCheck{ reg.Check = &api.AgentServiceCheck{
CheckID: "some-failing-check", CheckID: "some-failing-check",
@ -299,16 +290,16 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) {
} }
err = s.registerService(reg, false) err = s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -317,8 +308,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
@ -328,25 +318,21 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) {
Address: "", Address: "",
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/http/services", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/http/services", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\"")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\""))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { func (s *ConsulCatalogSuite) TestDefaultConsulService() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -355,37 +341,32 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "whoami.consul.localhost" req.Host = "whoami.consul.localhost"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -394,8 +375,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some tags // Start a container with some tags
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
@ -407,32 +387,28 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) {
"traefik.tcp.Services.Super.Loadbalancer.server.port=8080", "traefik.tcp.Services.Super.Loadbalancer.server.port=8080",
}, },
Port: 8080, Port: 8080,
Address: s.getComposeServiceIP(c, "whoamitcp"), Address: s.getComposeServiceIP("whoamitcp"),
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
who, err := guessWho("127.0.0.1:8000", "my.super.host", true) who, err := guessWho("127.0.0.1:8000", "my.super.host", true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(who, checker.Contains, "whoamitcp") assert.Contains(s.T(), who, "whoamitcp")
err = s.deregisterService("whoamitcp", false) err = s.deregisterService("whoamitcp", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { func (s *ConsulCatalogSuite) TestConsulServiceWithLabels() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -441,8 +417,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some tags // Start a container with some tags
reg1 := &api.AgentServiceRegistration{ reg1 := &api.AgentServiceRegistration{
@ -452,11 +427,11 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
"traefik.http.Routers.Super.Rule=Host(`my.super.host`)", "traefik.http.Routers.Super.Rule=Host(`my.super.host`)",
}, },
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg1, false) err := s.registerService(reg1, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start another container by replacing a '.' by a '-' // Start another container by replacing a '.' by a '-'
reg2 := &api.AgentServiceRegistration{ reg2 := &api.AgentServiceRegistration{
@ -466,40 +441,36 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) {
"traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)", "traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)",
}, },
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami2"), Address: s.getComposeServiceIP("whoami2"),
} }
err = s.registerService(reg2, false) err = s.registerService(reg2, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my-super.host" req.Host = "my-super.host"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami2", false) err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) { func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -508,8 +479,7 @@ func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C)
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects)
defer os.Remove(file)
// Start a container with some tags // Start a container with some tags
tags := []string{ tags := []string{
@ -523,50 +493,46 @@ func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C)
Name: "whoami", Name: "whoami",
Tags: tags, Tags: tags,
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg1, false) err := s.registerService(reg1, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
reg2 := &api.AgentServiceRegistration{ reg2 := &api.AgentServiceRegistration{
ID: "whoami", ID: "whoami",
Name: "whoami", Name: "whoami",
Tags: tags, Tags: tags,
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami2"), Address: s.getComposeServiceIP("whoami2"),
} }
err = s.registerService(reg2, true) err = s.registerService(reg2, true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2")) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), err = try.Request(req, 2*time.Second, try.StatusCodeIs(200),
try.BodyContainsOr(s.getComposeServiceIP(c, "whoami1"), s.getComposeServiceIP(c, "whoami2"))) try.BodyContainsOr(s.getComposeServiceIP("whoami1"), s.getComposeServiceIP("whoami2")))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami", false) err = s.deregisterService("whoami", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami", true) err = s.deregisterService("whoami", true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels() {
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
DefaultRule string DefaultRule string
@ -575,8 +541,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)",
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some tags // Start a container with some tags
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
@ -586,32 +551,28 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) {
"traefik.random.value=my.super.host", "traefik.random.value=my.super.host",
}, },
Port: 80, Port: 80,
Address: s.getComposeServiceIP(c, "whoami1"), Address: s.getComposeServiceIP("whoami1"),
} }
err := s.registerService(reg, false) err := s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
// TODO validate : run on 80 // TODO validate : run on 80
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) { func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck() {
whoamiIP := s.getComposeServiceIP(c, "whoami1") whoamiIP := s.getComposeServiceIP("whoami1")
tags := []string{ tags := []string{
"traefik.enable=true", "traefik.enable=true",
"traefik.http.routers.router1.rule=Path(`/whoami`)", "traefik.http.routers.router1.rule=Path(`/whoami`)",
@ -635,7 +596,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) {
} }
err := s.registerService(reg1, false) err := s.registerService(reg1, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
@ -643,22 +604,17 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) {
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoami2IP := s.getComposeServiceIP(c, "whoami2") whoami2IP := s.getComposeServiceIP("whoami2")
reg2 := &api.AgentServiceRegistration{ reg2 := &api.AgentServiceRegistration{
ID: "whoami2", ID: "whoami2",
Name: "whoami", Name: "whoami",
@ -675,26 +631,26 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) {
} }
err = s.registerService(reg2, false) err = s.registerService(reg2, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "whoami" req.Host = "whoami"
// TODO Need to wait for up to 10 seconds (for consul discovery or traefik to boot up ?) // TODO Need to wait for up to 10 seconds (for consul discovery or traefik to boot up ?)
err = try.Request(req, 10*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) err = try.Request(req, 10*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami2", false) err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) { func (s *ConsulCatalogSuite) TestConsulConnect() {
// Wait for consul to fully initialize connect CA // Wait for consul to fully initialize connect CA
err := s.waitForConnectCA() err := s.waitForConnectCA()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
connectIP := s.getComposeServiceIP(c, "connect") connectIP := s.getComposeServiceIP("connect")
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "uuid-api1", ID: "uuid-api1",
Name: "uuid-api", Name: "uuid-api",
@ -712,9 +668,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) {
Address: connectIP, Address: connectIP,
} }
err = s.registerService(reg, false) err = s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoamiIP := s.getComposeServiceIP(c, "whoami1") whoamiIP := s.getComposeServiceIP("whoami1")
regWhoami := &api.AgentServiceRegistration{ regWhoami := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
@ -727,40 +683,35 @@ func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) {
Address: whoamiIP, Address: whoamiIP,
} }
err = s.registerService(regWhoami, false) err = s.registerService(regWhoami, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
}{ }{
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/connect.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/connect.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("uuid-api1", false) err = s.deregisterService("uuid-api1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) { func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault() {
// Wait for consul to fully initialize connect CA // Wait for consul to fully initialize connect CA
err := s.waitForConnectCA() err := s.waitForConnectCA()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
connectIP := s.getComposeServiceIP(c, "connect") connectIP := s.getComposeServiceIP("connect")
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "uuid-api1", ID: "uuid-api1",
Name: "uuid-api", Name: "uuid-api",
@ -777,9 +728,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) {
Address: connectIP, Address: connectIP,
} }
err = s.registerService(reg, false) err = s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoamiIP := s.getComposeServiceIP(c, "whoami1") whoamiIP := s.getComposeServiceIP("whoami1")
regWhoami := &api.AgentServiceRegistration{ regWhoami := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami1", Name: "whoami1",
@ -792,9 +743,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) {
Address: whoamiIP, Address: whoamiIP,
} }
err = s.registerService(regWhoami, false) err = s.registerService(regWhoami, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoami2IP := s.getComposeServiceIP(c, "whoami2") whoami2IP := s.getComposeServiceIP("whoami2")
regWhoami2 := &api.AgentServiceRegistration{ regWhoami2 := &api.AgentServiceRegistration{
ID: "whoami2", ID: "whoami2",
Name: "whoami2", Name: "whoami2",
@ -808,45 +759,40 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) {
Address: whoami2IP, Address: whoami2IP,
} }
err = s.registerService(regWhoami2, false) err = s.registerService(regWhoami2, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
}{ }{
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/connect_by_default.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/connect_by_default.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/whoami2", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami2", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("uuid-api1", false) err = s.deregisterService("uuid-api1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami2", false) err = s.deregisterService("whoami2", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) { func (s *ConsulCatalogSuite) TestConsulConnect_NotAware() {
// Wait for consul to fully initialize connect CA // Wait for consul to fully initialize connect CA
err := s.waitForConnectCA() err := s.waitForConnectCA()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
connectIP := s.getComposeServiceIP(c, "connect") connectIP := s.getComposeServiceIP("connect")
reg := &api.AgentServiceRegistration{ reg := &api.AgentServiceRegistration{
ID: "uuid-api1", ID: "uuid-api1",
Name: "uuid-api", Name: "uuid-api",
@ -864,9 +810,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) {
Address: connectIP, Address: connectIP,
} }
err = s.registerService(reg, false) err = s.registerService(reg, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
whoamiIP := s.getComposeServiceIP(c, "whoami1") whoamiIP := s.getComposeServiceIP("whoami1")
regWhoami := &api.AgentServiceRegistration{ regWhoami := &api.AgentServiceRegistration{
ID: "whoami1", ID: "whoami1",
Name: "whoami", Name: "whoami",
@ -879,30 +825,25 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) {
Address: whoamiIP, Address: whoamiIP,
} }
err = s.registerService(regWhoami, false) err = s.registerService(regWhoami, false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tempObjects := struct { tempObjects := struct {
ConsulAddress string ConsulAddress string
}{ }{
ConsulAddress: s.consulURL, ConsulAddress: s.consulURL,
} }
file := s.adaptFile(c, "fixtures/consul_catalog/connect_not_aware.toml", tempObjects) file := s.adaptFile("fixtures/consul_catalog/connect_not_aware.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("uuid-api1", false) err = s.deregisterService("uuid-api1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = s.deregisterService("whoami1", false) err = s.deregisterService("whoami1", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }

View file

@ -10,16 +10,17 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/kvtools/consul" "github.com/kvtools/consul"
"github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie"
"github.com/kvtools/valkeyrie/store" "github.com/kvtools/valkeyrie/store"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
checker "github.com/vdemeester/shakers"
) )
// Consul test suites. // Consul test suites.
@ -29,18 +30,16 @@ type ConsulSuite struct {
consulURL string consulURL string
} }
func (s *ConsulSuite) resetStore(c *check.C) { func TestConsulSuite(t *testing.T) {
err := s.kvClient.DeleteTree(context.Background(), "traefik") suite.Run(t, new(ConsulSuite))
if err != nil && !errors.Is(err, store.ErrKeyNotFound) {
c.Fatal(err)
}
} }
func (s *ConsulSuite) setupStore(c *check.C) { func (s *ConsulSuite) SetupSuite() {
s.createComposeProject(c, "consul") s.BaseSuite.SetupSuite()
s.composeUp(c) s.createComposeProject("consul")
s.composeUp()
consulAddr := net.JoinHostPort(s.getComposeServiceIP(c, "consul"), "8500") consulAddr := net.JoinHostPort(s.getComposeServiceIP("consul"), "8500")
s.consulURL = fmt.Sprintf("http://%s", consulAddr) s.consulURL = fmt.Sprintf("http://%s", consulAddr)
kv, err := valkeyrie.NewStore( kv, err := valkeyrie.NewStore(
@ -51,21 +50,27 @@ func (s *ConsulSuite) setupStore(c *check.C) {
ConnectionTimeout: 10 * time.Second, ConnectionTimeout: 10 * time.Second,
}, },
) )
if err != nil { require.NoError(s.T(), err, "Cannot create store consul")
c.Fatal("Cannot create store consul")
}
s.kvClient = kv s.kvClient = kv
// wait for consul // wait for consul
err = try.Do(60*time.Second, try.KVExists(kv, "test")) err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { func (s *ConsulSuite) TearDownSuite() {
s.setupStore(c) s.BaseSuite.TearDownSuite()
}
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) func (s *ConsulSuite) TearDownTest() {
defer os.Remove(file) err := s.kvClient.DeleteTree(context.Background(), "traefik")
if err != nil && !errors.Is(err, store.ErrKeyNotFound) {
require.ErrorIs(s.T(), err, store.ErrKeyNotFound)
}
}
func (s *ConsulSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL})
data := map[string]string{ data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web", "traefik/http/routers/Router0/entryPoints/0": "web",
@ -114,39 +119,35 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
for k, v := range data { for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil) err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@consul":`, `"compressor@consul":`, `"srvcA@consul":`, `"srvcB@consul":`), try.BodyContains(`"striper@consul":`, `"compressor@consul":`, `"srvcA@consul":`, `"srvcB@consul":`),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var obtained api.RunTimeRepresentation var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained) err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
got, err := json.MarshalIndent(obtained, "", " ") got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash("testdata/rawdata-consul.json") expectedJSON := filepath.FromSlash("testdata/rawdata-consul.json")
if *updateExpected { if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666) err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
expected, err := os.ReadFile(expectedJSON) expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if !bytes.Equal(expected, got) { if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{ diff := difflib.UnifiedDiff{
@ -158,33 +159,27 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
} }
text, err := difflib.GetUnifiedDiffString(diff) text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err, text)
c.Error(text)
} }
} }
func (s *ConsulSuite) assertWhoami(c *check.C, host string, expectedStatusCode int) { func (s *ConsulSuite) assertWhoami(host string, expectedStatusCode int) {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
if err != nil { require.NoError(s.T(), err)
c.Fatal(err)
}
req.Host = host req.Host = host
resp, err := try.ResponseUntilStatusCode(req, 15*time.Second, expectedStatusCode) resp, err := try.ResponseUntilStatusCode(req, 15*time.Second, expectedStatusCode)
require.NoError(s.T(), err)
resp.Body.Close() resp.Body.Close()
c.Assert(err, checker.IsNil)
} }
func (s *ConsulSuite) TestDeleteRootKey(c *check.C) { func (s *ConsulSuite) TestDeleteRootKey() {
// This test case reproduce the issue: https://github.com/traefik/traefik/issues/8092 // This test case reproduce the issue: https://github.com/traefik/traefik/issues/8092
s.setupStore(c)
s.resetStore(c)
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL})
defer os.Remove(file)
ctx := context.Background() ctx := context.Background()
svcaddr := net.JoinHostPort(s.getComposeServiceIP(c, "whoami"), "80") svcaddr := net.JoinHostPort(s.getComposeServiceIP("whoami"), "80")
data := map[string]string{ data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web", "traefik/http/routers/Router0/entryPoints/0": "web",
@ -201,32 +196,28 @@ func (s *ConsulSuite) TestDeleteRootKey(c *check.C) {
for k, v := range data { for k, v := range data {
err := s.kvClient.Put(ctx, k, []byte(v), nil) err := s.kvClient.Put(ctx, k, []byte(v), nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"Router0@consul":`, `"Router1@consul":`, `"simplesvc0@consul":`, `"simplesvc1@consul":`), try.BodyContains(`"Router0@consul":`, `"Router1@consul":`, `"simplesvc0@consul":`, `"simplesvc1@consul":`),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.assertWhoami(c, "kv1.localhost", http.StatusOK) s.assertWhoami("kv1.localhost", http.StatusOK)
s.assertWhoami(c, "kv2.localhost", http.StatusOK) s.assertWhoami("kv2.localhost", http.StatusOK)
// delete router1 // delete router1
err = s.kvClient.DeleteTree(ctx, "traefik/http/routers/Router1") err = s.kvClient.DeleteTree(ctx, "traefik/http/routers/Router1")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.assertWhoami(c, "kv1.localhost", http.StatusOK) s.assertWhoami("kv1.localhost", http.StatusOK)
s.assertWhoami(c, "kv2.localhost", http.StatusNotFound) s.assertWhoami("kv2.localhost", http.StatusNotFound)
// delete simple services and router0 // delete simple services and router0
err = s.kvClient.DeleteTree(ctx, "traefik") err = s.kvClient.DeleteTree(ctx, "traefik")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.assertWhoami(c, "kv1.localhost", http.StatusNotFound) s.assertWhoami("kv1.localhost", http.StatusNotFound)
s.assertWhoami(c, "kv2.localhost", http.StatusNotFound) s.assertWhoami("kv2.localhost", http.StatusNotFound)
} }

View file

@ -3,15 +3,16 @@ package integration
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
"github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/testhelpers"
checker "github.com/vdemeester/shakers"
) )
// Docker tests suite. // Docker tests suite.
@ -19,12 +20,21 @@ type DockerComposeSuite struct {
BaseSuite BaseSuite
} }
func (s *DockerComposeSuite) SetUpSuite(c *check.C) { func TestDockerComposeSuite(t *testing.T) {
s.createComposeProject(c, "minimal") suite.Run(t, new(DockerComposeSuite))
s.composeUp(c)
} }
func (s *DockerComposeSuite) TestComposeScale(c *check.C) { func (s *DockerComposeSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("minimal")
s.composeUp()
}
func (s *DockerComposeSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *DockerComposeSuite) TestComposeScale() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -32,41 +42,36 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
DockerHost: s.getDockerHost(), DockerHost: s.getDockerHost(),
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/minimal.toml", tempObjects) file := s.adaptFile("fixtures/docker/minimal.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req := testhelpers.MustNewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
req.Host = "my.super.host" req.Host = "my.super.host"
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) _, err := try.ResponseUntilStatusCode(req, 5*time.Second, http.StatusOK)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer resp.Body.Close() defer resp.Body.Close()
var rtconf api.RunTimeRepresentation var rtconf api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&rtconf) err = json.NewDecoder(resp.Body).Decode(&rtconf)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// check that we have only three routers (the one from this test + 2 unrelated internal ones) // check that we have only three routers (the one from this test + 2 unrelated internal ones)
c.Assert(rtconf.Routers, checker.HasLen, 3) assert.Len(s.T(), rtconf.Routers, 3)
// check that we have only one service (not counting the internal ones) with n servers // check that we have only one service (not counting the internal ones) with n servers
services := rtconf.Services services := rtconf.Services
c.Assert(services, checker.HasLen, 4) assert.Len(s.T(), services, 4)
for name, service := range services { for name, service := range services {
if strings.HasSuffix(name, "@internal") { if strings.HasSuffix(name, "@internal") {
continue continue
} }
c.Assert(name, checker.Equals, "whoami1-"+s.composeProject.Name+"@docker") assert.Equal(s.T(), "service-mini@docker", name)
c.Assert(service.LoadBalancer.Servers, checker.HasLen, 2) assert.Len(s.T(), service.LoadBalancer.Servers, 2)
// We could break here, but we don't just to keep us honest. // We could break here, but we don't just to keep us honest.
} }
} }

View file

@ -2,15 +2,15 @@ package integration
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
// Docker tests suite. // Docker tests suite.
@ -18,15 +18,24 @@ type DockerSuite struct {
BaseSuite BaseSuite
} }
func (s *DockerSuite) SetUpTest(c *check.C) { func TestDockerSuite(t *testing.T) {
s.createComposeProject(c, "docker") suite.Run(t, new(DockerSuite))
} }
func (s *DockerSuite) TearDownTest(c *check.C) { func (s *DockerSuite) SetupSuite() {
s.composeDown(c) s.BaseSuite.SetupSuite()
s.createComposeProject("docker")
} }
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { func (s *DockerSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *DockerSuite) TearDownTest() {
s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow")
}
func (s *DockerSuite) TestSimpleConfiguration() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -35,24 +44,18 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c) s.composeUp()
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
err = try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { func (s *DockerSuite) TestDefaultDockerContainers() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -61,37 +64,30 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c, "simple") s.composeUp("simple")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = fmt.Sprintf("simple-%s.docker.localhost", s.composeProject.Name) req.Host = "simple.docker.localhost"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) require.NoError(s.T(), err)
c.Assert(err, checker.IsNil)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var version map[string]interface{} var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil) assert.NoError(s.T(), json.Unmarshal(body, &version))
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
} }
func (s *DockerSuite) TestDockerContainersWithTCPLabels(c *check.C) { func (s *DockerSuite) TestDockerContainersWithTCPLabels() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -100,29 +96,23 @@ func (s *DockerSuite) TestDockerContainersWithTCPLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c, "withtcplabels") s.composeUp("withtcplabels")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)"))
c.Assert(err, checker.IsNil)
who, err := guessWho("127.0.0.1:8000", "my.super.host", true) who, err := guessWho("127.0.0.1:8000", "my.super.host", true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(who, checker.Contains, "my.super.host") assert.Contains(s.T(), who, "my.super.host")
} }
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { func (s *DockerSuite) TestDockerContainersWithLabels() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -131,44 +121,37 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c, "withlabels1", "withlabels2") s.composeUp("withlabels1", "withlabels2")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my-super.host" req.Host = "my-super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) _, err = try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) require.NoError(s.T(), err)
c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) require.NoError(s.T(), err)
c.Assert(err, checker.IsNil)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var version map[string]interface{} var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil) assert.NoError(s.T(), json.Unmarshal(body, &version))
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
} }
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { func (s *DockerSuite) TestDockerContainersWithOneMissingLabels() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -177,31 +160,23 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c, "withonelabelmissing") s.composeUp("withonelabelmissing")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
// TODO validate : run on 80
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(req, 3*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { func (s *DockerSuite) TestRestartDockerContainers() {
tempObjects := struct { tempObjects := struct {
DockerHost string DockerHost string
DefaultRule string DefaultRule string
@ -210,46 +185,40 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
} }
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
s.composeUp(c, "powpow") s.composeUp("powpow")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "my.super.host" req.Host = "my.super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var version map[string]interface{} var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil) assert.NoError(s.T(), json.Unmarshal(body, &version))
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.composeStop(c, "powpow") s.composeStop("powpow")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
s.composeUp(c, "powpow") s.composeUp("powpow")
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }

View file

@ -3,12 +3,12 @@ package integration
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
// ErrorPagesSuite test suites. // ErrorPagesSuite test suites.
@ -18,83 +18,78 @@ type ErrorPagesSuite struct {
BackendIP string BackendIP string
} }
func (s *ErrorPagesSuite) SetUpSuite(c *check.C) { func TestErrorPagesSuite(t *testing.T) {
s.createComposeProject(c, "error_pages") suite.Run(t, new(ErrorPagesSuite))
s.composeUp(c)
s.ErrorPageIP = s.getComposeServiceIP(c, "nginx2")
s.BackendIP = s.getComposeServiceIP(c, "nginx1")
} }
func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) { func (s *ErrorPagesSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { s.BaseSuite.SetupSuite()
s.createComposeProject("error_pages")
s.composeUp()
s.ErrorPageIP = s.getComposeServiceIP("nginx2")
s.BackendIP = s.getComposeServiceIP("nginx1")
}
func (s *ErrorPagesSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *ErrorPagesSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/error_pages/simple.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{"http://" + s.BackendIP + ":80", s.ErrorPageIP}) }{"http://" + s.BackendIP + ":80", s.ErrorPageIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendReq.Host = "test.local" frontendReq.Host = "test.local"
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("nginx")) err = try.Request(frontendReq, 2*time.Second, try.BodyContains("nginx"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ErrorPagesSuite) TestErrorPage(c *check.C) { func (s *ErrorPagesSuite) TestErrorPage() {
// error.toml contains a mis-configuration of the backend host // error.toml contains a mis-configuration of the backend host
file := s.adaptFile(c, "fixtures/error_pages/error.toml", struct { file := s.adaptFile("fixtures/error_pages/error.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{s.BackendIP, s.ErrorPageIP}) }{s.BackendIP, s.ErrorPageIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendReq.Host = "test.local" frontendReq.Host = "test.local"
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred.")) err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ErrorPagesSuite) TestErrorPageFlush(c *check.C) { func (s *ErrorPagesSuite) TestErrorPageFlush() {
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Transfer-Encoding", "chunked") rw.Header().Add("Transfer-Encoding", "chunked")
rw.WriteHeader(http.StatusInternalServerError) rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte("KO")) _, _ = rw.Write([]byte("KO"))
})) }))
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { file := s.adaptFile("fixtures/error_pages/simple.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{srv.URL, s.ErrorPageIP}) }{srv.URL, s.ErrorPageIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendReq.Host = "test.local" frontendReq.Host = "test.local"
err = try.Request(frontendReq, 2*time.Second, err = try.Request(frontendReq, 2*time.Second,
try.BodyContains("An error occurred."), try.BodyContains("An error occurred."),
try.HasHeaderValue("Content-Type", "text/html", true), try.HasHeaderValue("Content-Type", "text/html", true),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }

View file

@ -8,16 +8,17 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/kvtools/etcdv3" "github.com/kvtools/etcdv3"
"github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie"
"github.com/kvtools/valkeyrie/store" "github.com/kvtools/valkeyrie/store"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
checker "github.com/vdemeester/shakers"
) )
// etcd test suites. // etcd test suites.
@ -27,12 +28,18 @@ type EtcdSuite struct {
etcdAddr string etcdAddr string
} }
func (s *EtcdSuite) SetUpSuite(c *check.C) { func TestEtcdSuite(t *testing.T) {
s.createComposeProject(c, "etcd") suite.Run(t, new(EtcdSuite))
s.composeUp(c) }
func (s *EtcdSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("etcd")
s.composeUp()
var err error var err error
s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP(c, "etcd"), "2379") s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP("etcd"), "2379")
s.kvClient, err = valkeyrie.NewStore( s.kvClient, err = valkeyrie.NewStore(
context.Background(), context.Background(),
etcdv3.StoreName, etcdv3.StoreName,
@ -41,18 +48,19 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) {
ConnectionTimeout: 10 * time.Second, ConnectionTimeout: 10 * time.Second,
}, },
) )
if err != nil { require.NoError(s.T(), err)
c.Fatal("Cannot create store etcd")
}
// wait for etcd // wait for etcd
err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test")) err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { func (s *EtcdSuite) TearDownSuite() {
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdAddress string }{s.etcdAddr}) s.BaseSuite.TearDownSuite()
defer os.Remove(file) }
func (s *EtcdSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/etcd/simple.toml", struct{ EtcdAddress string }{s.etcdAddr})
data := map[string]string{ data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web", "traefik/http/routers/Router0/entryPoints/0": "web",
@ -101,39 +109,35 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
for k, v := range data { for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil) err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@etcd":`, `"compressor@etcd":`, `"srvcA@etcd":`, `"srvcB@etcd":`), try.BodyContains(`"striper@etcd":`, `"compressor@etcd":`, `"srvcA@etcd":`, `"srvcB@etcd":`),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var obtained api.RunTimeRepresentation var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained) err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
got, err := json.MarshalIndent(obtained, "", " ") got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash("testdata/rawdata-etcd.json") expectedJSON := filepath.FromSlash("testdata/rawdata-etcd.json")
if *updateExpected { if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666) err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
expected, err := os.ReadFile(expectedJSON) expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if !bytes.Equal(expected, got) { if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{ diff := difflib.UnifiedDiff{
@ -145,7 +149,6 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
} }
text, err := difflib.GetUnifiedDiffString(diff) text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err, text)
c.Error(text)
} }
} }

View file

@ -2,61 +2,58 @@ package integration
import ( import (
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
// File tests suite. // File tests suite.
type FileSuite struct{ BaseSuite } type FileSuite struct{ BaseSuite }
func (s *FileSuite) SetUpSuite(c *check.C) { func TestFileSuite(t *testing.T) {
s.createComposeProject(c, "file") suite.Run(t, new(FileSuite))
s.composeUp(c)
} }
func (s *FileSuite) TestSimpleConfiguration(c *check.C) { func (s *FileSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/file/simple.toml", struct{}{}) s.BaseSuite.SetupSuite()
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.createComposeProject("file")
defer display(c) s.composeUp()
err := cmd.Start() }
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd) func (s *FileSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *FileSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/file/simple.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
// #56 regression test, make sure it does not fail? // #56 regression test, make sure it does not fail?
func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) { func (s *FileSuite) TestSimpleConfigurationNoPanic() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/file/56-simple-panic.toml")) s.traefikCmd(withConfigFile("fixtures/file/56-simple-panic.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *FileSuite) TestDirectoryConfiguration(c *check.C) { func (s *FileSuite) TestDirectoryConfiguration() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/file/directory.toml")) s.traefikCmd(withConfigFile("fixtures/file/directory.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected a 404 as we did not configure anything at /test // Expected a 404 as we did not configure anything at /test
err = try.GetRequest("http://127.0.0.1:8000/test", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/test", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Expected a 502 as there is no backend server // Expected a 502 as there is no backend server
err = try.GetRequest("http://127.0.0.1:8000/test2", 1000*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) err = try.GetRequest("http://127.0.0.1:8000/test2", 1000*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }

View file

@ -7,10 +7,14 @@
noColor = true noColor = true
[entryPoints] [entryPoints]
[entryPoints.web] [entryPoints.trust]
address = ":8000" address = ":8000"
[entryPoints.web.proxyProtocol] [entryPoints.trust.proxyProtocol]
trustedIPs = ["{{.HaproxyIP}}"] trustedIPs = ["127.0.0.1"]
[entryPoints.nottrust]
address = ":9000"
[entryPoints.nottrust.proxyProtocol]
trustedIPs = ["1.2.3.4"]
[api] [api]
insecure = true insecure = true

View file

@ -1,33 +0,0 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":8000"
[entryPoints.web.proxyProtocol]
trustedIPs = ["1.2.3.4"]
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
service = "service1"
rule = "Path(`/whoami`)"
[http.services]
[http.services.service1]
[http.services.service1.loadBalancer]
[[http.services.service1.loadBalancer.servers]]
url = "http://{{.WhoamiIP}}"

View file

@ -5,6 +5,7 @@
[log] [log]
level = "DEBUG" level = "DEBUG"
[entryPoints] [entryPoints]
[entryPoints.tcp] [entryPoints.tcp]
address = ":8093" address = ":8093"
@ -44,7 +45,7 @@
[[tcp.services.whoami-b.loadBalancer.servers]] [[tcp.services.whoami-b.loadBalancer.servers]]
address = "{{ .WhoamiB }}" address = "{{ .WhoamiB }}"
[tcp.middlewares] [tcp.middlewares]
[tcp.middlewares.allowing-ipallowlist.ipAllowList] [tcp.middlewares.allowing-ipallowlist.ipAllowList]
sourceRange = ["127.0.0.1/32"] sourceRange = ["127.0.0.1/32"]
[tcp.middlewares.blocking-ipallowlist.ipAllowList] [tcp.middlewares.blocking-ipallowlist.ipAllowList]

View file

@ -8,10 +8,12 @@ import (
"math/rand" "math/rand"
"net" "net"
"os" "os"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/helloworld" "github.com/traefik/traefik/v3/integration/helloworld"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -29,16 +31,20 @@ const randCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567
// GRPCSuite tests suite. // GRPCSuite tests suite.
type GRPCSuite struct{ BaseSuite } type GRPCSuite struct{ BaseSuite }
func TestGRPCSuite(t *testing.T) {
suite.Run(t, new(GRPCSuite))
}
type myserver struct { type myserver struct {
stopStreamExample chan bool stopStreamExample chan bool
} }
func (s *GRPCSuite) SetUpSuite(c *check.C) { func (s *GRPCSuite) SetupSuite() {
var err error var err error
LocalhostCert, err = os.ReadFile("./resources/tls/local.cert") LocalhostCert, err = os.ReadFile("./resources/tls/local.cert")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
LocalhostKey, err = os.ReadFile("./resources/tls/local.key") LocalhostKey, err = os.ReadFile("./resources/tls/local.key")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
} }
func (s *myserver) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) { func (s *myserver) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
@ -137,19 +143,18 @@ func callStreamExampleClientGRPC() (helloworld.Greeter_StreamExampleClient, func
return t, closer, nil return t, closer, nil
} }
func (s *GRPCSuite) TestGRPC(c *check.C) { func (s *GRPCSuite) TestGRPC() {
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := startGRPCServer(lis, &myserver{}) err := startGRPCServer(lis, &myserver{})
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { file := s.adaptFile("fixtures/grpc/config.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -159,79 +164,65 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var response string var response string
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World", true) response, err = callHelloClientGRPC("World", true)
return err return err
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(response, check.Equals, "Hello World") assert.Equal(s.T(), "Hello World", response)
} }
func (s *GRPCSuite) TestGRPCh2c(c *check.C) { func (s *GRPCSuite) TestGRPCh2c() {
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := starth2cGRPCServer(lis, &myserver{}) err := starth2cGRPCServer(lis, &myserver{})
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config_h2c.toml", struct { file := s.adaptFile("fixtures/grpc/config_h2c.toml", struct {
GRPCServerPort string GRPCServerPort string
}{ }{
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var response string var response string
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World", false) response, err = callHelloClientGRPC("World", false)
return err return err
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(response, check.Equals, "Hello World") assert.Equal(s.T(), "Hello World", response)
} }
func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) { func (s *GRPCSuite) TestGRPCh2cTermination() {
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := starth2cGRPCServer(lis, &myserver{}) err := starth2cGRPCServer(lis, &myserver{})
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config_h2c_termination.toml", struct { file := s.adaptFile("fixtures/grpc/config_h2c_termination.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -241,40 +232,33 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) {
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var response string var response string
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World", true) response, err = callHelloClientGRPC("World", true)
return err return err
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(response, check.Equals, "Hello World") assert.Equal(s.T(), "Hello World", response)
} }
func (s *GRPCSuite) TestGRPCInsecure(c *check.C) { func (s *GRPCSuite) TestGRPCInsecure() {
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := startGRPCServer(lis, &myserver{}) err := startGRPCServer(lis, &myserver{})
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config_insecure.toml", struct { file := s.adaptFile("fixtures/grpc/config_insecure.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -284,44 +268,37 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var response string var response string
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World", true) response, err = callHelloClientGRPC("World", true)
return err return err
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(response, check.Equals, "Hello World") assert.Equal(s.T(), "Hello World", response)
} }
func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { func (s *GRPCSuite) TestGRPCBuffer() {
stopStreamExample := make(chan bool) stopStreamExample := make(chan bool)
defer func() { stopStreamExample <- true }() defer func() { stopStreamExample <- true }()
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := startGRPCServer(lis, &myserver{ err := startGRPCServer(lis, &myserver{
stopStreamExample: stopStreamExample, stopStreamExample: stopStreamExample,
}) })
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { file := s.adaptFile("fixtures/grpc/config.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -331,27 +308,21 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
client, closer, err := callStreamExampleClientGRPC() client, closer, err := callStreamExampleClientGRPC()
defer func() { _ = closer() }() defer func() { _ = closer() }()
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
received := make(chan bool) received := make(chan bool)
go func() { go func() {
tr, err := client.Recv() tr, err := client.Recv()
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(len(tr.GetData()), check.Equals, 512) assert.Len(s.T(), tr.GetData(), 512)
received <- true received <- true
}() }()
@ -363,25 +334,24 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
return errors.New("failed to receive stream data") return errors.New("failed to receive stream data")
} }
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
} }
func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { func (s *GRPCSuite) TestGRPCBufferWithFlushInterval() {
stopStreamExample := make(chan bool) stopStreamExample := make(chan bool)
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := startGRPCServer(lis, &myserver{ err := startGRPCServer(lis, &myserver{
stopStreamExample: stopStreamExample, stopStreamExample: stopStreamExample,
}) })
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { file := s.adaptFile("fixtures/grpc/config.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -390,17 +360,11 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
KeyContent: string(LocalhostKey), KeyContent: string(LocalhostKey),
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
s.traefikCmd(withConfigFile(file))
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
client, closer, err := callStreamExampleClientGRPC() client, closer, err := callStreamExampleClientGRPC()
@ -408,13 +372,13 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
_ = closer() _ = closer()
stopStreamExample <- true stopStreamExample <- true
}() }()
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
received := make(chan bool) received := make(chan bool)
go func() { go func() {
tr, err := client.Recv() tr, err := client.Recv()
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(len(tr.GetData()), check.Equals, 512) assert.Len(s.T(), tr.GetData(), 512)
received <- true received <- true
}() }()
@ -426,22 +390,21 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
return errors.New("failed to receive stream data") return errors.New("failed to receive stream data")
} }
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
} }
func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) { func (s *GRPCSuite) TestGRPCWithRetry() {
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
go func() { go func() {
err := startGRPCServer(lis, &myserver{}) err := startGRPCServer(lis, &myserver{})
c.Log(err) assert.NoError(s.T(), err)
c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config_retry.toml", struct { file := s.adaptFile("fixtures/grpc/config_retry.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -451,23 +414,17 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) {
GRPCServerPort: port, GRPCServerPort: port,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
var response string var response string
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World", true) response, err = callHelloClientGRPC("World", true)
return err return err
}) })
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
c.Assert(response, check.Equals, "Hello World") assert.Equal(s.T(), "Hello World", response)
} }

View file

@ -4,50 +4,45 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
// Headers tests suite. // Headers tests suite.
type HeadersSuite struct{ BaseSuite } type HeadersSuite struct{ BaseSuite }
func (s *HeadersSuite) TestSimpleConfiguration(c *check.C) { func TestHeadersSuite(t *testing.T) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/headers/basic.toml")) suite.Run(t, new(HeadersSuite))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected a 404 as we did not configure anything
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
} }
func (s *HeadersSuite) TestReverseProxyHeaderRemoved(c *check.C) { func (s *HeadersSuite) TestSimpleConfiguration() {
file := s.adaptFile(c, "fixtures/headers/remove_reverseproxy_headers.toml", struct{}{}) s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() // Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil) err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
defer s.killCmd(cmd) require.NoError(s.T(), err)
}
func (s *HeadersSuite) TestReverseProxyHeaderRemoved() {
file := s.adaptFile("fixtures/headers/remove_reverseproxy_headers.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, found := r.Header["X-Forwarded-Host"] _, found := r.Header["X-Forwarded-Host"]
c.Assert(found, checker.True) assert.True(s.T(), found)
_, found = r.Header["Foo"] _, found = r.Header["Foo"]
c.Assert(found, checker.False) assert.False(s.T(), found)
_, found = r.Header["X-Forwarded-For"] _, found = r.Header["X-Forwarded-For"]
c.Assert(found, checker.False) assert.False(s.T(), found)
}) })
listener, err := net.Listen("tcp", "127.0.0.1:9000") listener, err := net.Listen("tcp", "127.0.0.1:9000")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
ts := &httptest.Server{ ts := &httptest.Server{
Listener: listener, Listener: listener,
@ -57,31 +52,25 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved(c *check.C) {
defer ts.Close() defer ts.Close()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "test.localhost" req.Host = "test.localhost"
req.Header = http.Header{ req.Header = http.Header{
"Foo": {"bar"}, "Foo": {"bar"},
} }
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *HeadersSuite) TestCorsResponses(c *check.C) { func (s *HeadersSuite) TestCorsResponses() {
file := s.adaptFile(c, "fixtures/headers/cors.toml", struct{}{}) file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
backend := startTestServer("9000", http.StatusOK, "") backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
testCase := []struct { testCase := []struct {
desc string desc string
@ -147,30 +136,24 @@ func (s *HeadersSuite) TestCorsResponses(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
req, err := http.NewRequest(test.method, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(test.method, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = test.reqHost req.Host = test.reqHost
req.Header = test.requestHeaders req.Header = test.requestHeaders
err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected)) err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }
func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) { func (s *HeadersSuite) TestSecureHeadersResponses() {
file := s.adaptFile(c, "fixtures/headers/secure.toml", struct{}{}) file := s.adaptFile("fixtures/headers/secure.toml", struct{}{})
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
backend := startTestServer("9000", http.StatusOK, "") backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
testCase := []struct { testCase := []struct {
desc string desc string
@ -190,36 +173,30 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = test.reqHost req.Host = test.reqHost
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected)) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/api/rawdata", nil) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/api/rawdata", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = test.internalReqHost req.Host = test.internalReqHost
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected)) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }
func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) { func (s *HeadersSuite) TestMultipleSecureHeadersResponses() {
file := s.adaptFile(c, "fixtures/headers/secure_multiple.toml", struct{}{}) file := s.adaptFile("fixtures/headers/secure_multiple.toml", struct{}{})
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
backend := startTestServer("9000", http.StatusOK, "") backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
testCase := []struct { testCase := []struct {
desc string desc string
@ -238,10 +215,10 @@ func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = test.reqHost req.Host = test.reqHost
err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected)) err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }

View file

@ -7,11 +7,13 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
// HealthCheck test suites. // HealthCheck test suites.
@ -23,287 +25,272 @@ type HealthCheckSuite struct {
whoami4IP string whoami4IP string
} }
func (s *HealthCheckSuite) SetUpSuite(c *check.C) { func TestHealthCheckSuite(t *testing.T) {
s.createComposeProject(c, "healthcheck") suite.Run(t, new(HealthCheckSuite))
s.composeUp(c)
s.whoami1IP = s.getComposeServiceIP(c, "whoami1")
s.whoami2IP = s.getComposeServiceIP(c, "whoami2")
s.whoami3IP = s.getComposeServiceIP(c, "whoami3")
s.whoami4IP = s.getComposeServiceIP(c, "whoami4")
} }
func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) { func (s *HealthCheckSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/healthcheck/simple.toml", struct { s.BaseSuite.SetupSuite()
s.createComposeProject("healthcheck")
s.composeUp()
s.whoami1IP = s.getComposeServiceIP("whoami1")
s.whoami2IP = s.getComposeServiceIP("whoami2")
s.whoami3IP = s.getComposeServiceIP("whoami3")
s.whoami4IP = s.getComposeServiceIP("whoami4")
}
func (s *HealthCheckSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *HealthCheckSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/healthcheck/simple.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{s.whoami1IP, s.whoami2IP}) }{s.whoami1IP, s.whoami2IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq.Host = "test.localhost" frontendHealthReq.Host = "test.localhost"
err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Fix all whoami health to 500 // Fix all whoami health to 500
client := &http.Client{} client := &http.Client{}
whoamiHosts := []string{s.whoami1IP, s.whoami2IP} whoamiHosts := []string{s.whoami1IP, s.whoami2IP}
for _, whoami := range whoamiHosts { for _, whoami := range whoamiHosts {
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
// Verify no backend service is available due to failing health checks // Verify no backend service is available due to failing health checks
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Change one whoami health to 200 // Change one whoami health to 200
statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusOKReq1) _, err = client.Do(statusOKReq1)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify frontend health : after // Verify frontend health : after
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendReq.Host = "test.localhost" frontendReq.Host = "test.localhost"
// Check if whoami1 responds // Check if whoami1 responds
err = try.Request(frontendReq, 500*time.Millisecond, try.BodyContains(s.whoami1IP)) err = try.Request(frontendReq, 500*time.Millisecond, try.BodyContains(s.whoami1IP))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Check if the service with bad health check (whoami2) never respond. // Check if the service with bad health check (whoami2) never respond.
err = try.Request(frontendReq, 2*time.Second, try.BodyContains(s.whoami2IP)) err = try.Request(frontendReq, 2*time.Second, try.BodyContains(s.whoami2IP))
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
// TODO validate : run on 80 // TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/") resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not configure anything // Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) assert.Equal(s.T(), http.StatusNotFound, resp.StatusCode)
} }
func (s *HealthCheckSuite) TestMultipleEntrypoints(c *check.C) { func (s *HealthCheckSuite) TestMultipleEntrypoints() {
file := s.adaptFile(c, "fixtures/healthcheck/multiple-entrypoints.toml", struct { file := s.adaptFile("fixtures/healthcheck/multiple-entrypoints.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{s.whoami1IP, s.whoami2IP}) }{s.whoami1IP, s.whoami2IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Wait for traefik // Wait for traefik
err = try.GetRequest("http://localhost:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Check entrypoint http1 // Check entrypoint http1
frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq.Host = "test.localhost" frontendHealthReq.Host = "test.localhost"
err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Check entrypoint http2 // Check entrypoint http2
frontendHealthReq, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil) frontendHealthReq, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq.Host = "test.localhost" frontendHealthReq.Host = "test.localhost"
err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Set the both whoami health to 500 // Set the both whoami health to 500
client := &http.Client{} client := &http.Client{}
whoamiHosts := []string{s.whoami1IP, s.whoami2IP} whoamiHosts := []string{s.whoami1IP, s.whoami2IP}
for _, whoami := range whoamiHosts { for _, whoami := range whoamiHosts {
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
// Verify no backend service is available due to failing health checks // Verify no backend service is available due to failing health checks
err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// reactivate the whoami2 // reactivate the whoami2
statusInternalServerOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("200")) statusInternalServerOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerOkReq) _, err = client.Do(statusInternalServerOkReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontend1Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) frontend1Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontend1Req.Host = "test.localhost" frontend1Req.Host = "test.localhost"
frontend2Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/", nil) frontend2Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontend2Req.Host = "test.localhost" frontend2Req.Host = "test.localhost"
// Check if whoami1 never responds // Check if whoami1 never responds
err = try.Request(frontend2Req, 2*time.Second, try.BodyContains(s.whoami1IP)) err = try.Request(frontend2Req, 2*time.Second, try.BodyContains(s.whoami1IP))
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
// Check if whoami1 never responds // Check if whoami1 never responds
err = try.Request(frontend1Req, 2*time.Second, try.BodyContains(s.whoami1IP)) err = try.Request(frontend1Req, 2*time.Second, try.BodyContains(s.whoami1IP))
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
} }
func (s *HealthCheckSuite) TestPortOverload(c *check.C) { func (s *HealthCheckSuite) TestPortOverload() {
// Set one whoami health to 200 // Set one whoami health to 200
client := &http.Client{} client := &http.Client{}
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
file := s.adaptFile(c, "fixtures/healthcheck/port_overload.toml", struct { file := s.adaptFile("fixtures/healthcheck/port_overload.toml", struct {
Server1 string Server1 string
}{s.whoami1IP}) }{s.whoami1IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("Host(`test.localhost`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
frontendHealthReq.Host = "test.localhost" frontendHealthReq.Host = "test.localhost"
// We test bad gateway because we use an invalid port for the backend // We test bad gateway because we use an invalid port for the backend
err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Set one whoami health to 500 // Set one whoami health to 500
statusInternalServerErrorReq, err = http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err = http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify no backend service is available due to failing health checks // Verify no backend service is available due to failing health checks
err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
// Checks if all the loadbalancers created will correctly update the server status. // Checks if all the loadbalancers created will correctly update the server status.
func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) { func (s *HealthCheckSuite) TestMultipleRoutersOnSameService() {
file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.toml", struct { file := s.adaptFile("fixtures/healthcheck/multiple-routers-one-same-service.toml", struct {
Server1 string Server1 string
}{s.whoami1IP}) }{s.whoami1IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Set whoami health to 200 to be sure to start with the wanted status // Set whoami health to 200 to be sure to start with the wanted status
client := &http.Client{} client := &http.Client{}
statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusOkReq) _, err = client.Do(statusOkReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// check healthcheck on web1 entrypoint // check healthcheck on web1 entrypoint
healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
healthReqWeb1.Host = "test.localhost" healthReqWeb1.Host = "test.localhost"
err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// check healthcheck on web2 entrypoint // check healthcheck on web2 entrypoint
healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil) healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
healthReqWeb2.Host = "test.localhost" healthReqWeb2.Host = "test.localhost"
err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Set whoami health to 500 // Set whoami health to 500
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify no backend service is available due to failing health checks // Verify no backend service is available due to failing health checks
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Change one whoami health to 200 // Change one whoami health to 200
statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusOKReq1) _, err = client.Do(statusOKReq1)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify health check // Verify health check
err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *HealthCheckSuite) TestPropagate(c *check.C) { func (s *HealthCheckSuite) TestPropagate() {
file := s.adaptFile(c, "fixtures/healthcheck/propagate.toml", struct { file := s.adaptFile("fixtures/healthcheck/propagate.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
Server3 string Server3 string
Server4 string Server4 string
}{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP}) }{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq.Host = "root.localhost" rootReq.Host = "root.localhost"
err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Bring whoami1 and whoami3 down // Bring whoami1 and whoami3 down
client := http.Client{ client := http.Client{
@ -313,9 +300,9 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
whoamiHosts := []string{s.whoami1IP, s.whoami3IP} whoamiHosts := []string{s.whoami1IP, s.whoami3IP}
for _, whoami := range whoamiHosts { for _, whoami := range whoamiHosts {
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
try.Sleep(time.Second) try.Sleep(time.Second)
@ -327,19 +314,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
reachedServers := make(map[string]int) reachedServers := make(map[string]int)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(rootReq) resp, err := client.Do(rootReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if reachedServers[s.whoami4IP] > reachedServers[s.whoami2IP] { if reachedServers[s.whoami4IP] > reachedServers[s.whoami2IP] {
c.Assert(string(body), checker.Contains, want2) assert.Contains(s.T(), string(body), want2)
reachedServers[s.whoami2IP]++ reachedServers[s.whoami2IP]++
continue continue
} }
if reachedServers[s.whoami2IP] > reachedServers[s.whoami4IP] { if reachedServers[s.whoami2IP] > reachedServers[s.whoami4IP] {
c.Assert(string(body), checker.Contains, want4) assert.Contains(s.T(), string(body), want4)
reachedServers[s.whoami4IP]++ reachedServers[s.whoami4IP]++
continue continue
} }
@ -356,48 +343,48 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
} }
} }
c.Assert(reachedServers[s.whoami2IP], checker.Equals, 2) assert.Equal(s.T(), 2, reachedServers[s.whoami2IP])
c.Assert(reachedServers[s.whoami4IP], checker.Equals, 2) assert.Equal(s.T(), 2, reachedServers[s.whoami4IP])
fooReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) fooReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
fooReq.Host = "foo.localhost" fooReq.Host = "foo.localhost"
// Verify load-balancing on foo still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc. // Verify load-balancing on foo still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc.
want := `IP: ` + s.whoami2IP want := `IP: ` + s.whoami2IP
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(fooReq) resp, err := client.Do(fooReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(body), checker.Contains, want) assert.Contains(s.T(), string(body), want)
} }
barReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) barReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
barReq.Host = "bar.localhost" barReq.Host = "bar.localhost"
// Verify load-balancing on bar still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc. // Verify load-balancing on bar still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc.
want = `IP: ` + s.whoami2IP want = `IP: ` + s.whoami2IP
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(barReq) resp, err := client.Do(barReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(body), checker.Contains, want) assert.Contains(s.T(), string(body), want)
} }
// Bring whoami2 and whoami4 down // Bring whoami2 and whoami4 down
whoamiHosts = []string{s.whoami2IP, s.whoami4IP} whoamiHosts = []string{s.whoami2IP, s.whoami4IP}
for _, whoami := range whoamiHosts { for _, whoami := range whoamiHosts {
statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusInternalServerErrorReq) _, err = client.Do(statusInternalServerErrorReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
try.Sleep(time.Second) try.Sleep(time.Second)
@ -405,25 +392,25 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
// Verify that everything is down, and that we get 503s everywhere. // Verify that everything is down, and that we get 503s everywhere.
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
resp, err := client.Do(rootReq) resp, err := client.Do(rootReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode)
resp, err = client.Do(fooReq) resp, err = client.Do(fooReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode)
resp, err = client.Do(barReq) resp, err = client.Do(barReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode)
} }
// Bring everything back up. // Bring everything back up.
whoamiHosts = []string{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP} whoamiHosts = []string{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP}
for _, whoami := range whoamiHosts { for _, whoami := range whoamiHosts {
statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("200")) statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("200"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusOKReq) _, err = client.Do(statusOKReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
try.Sleep(time.Second) try.Sleep(time.Second)
@ -432,10 +419,10 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
reachedServers = make(map[string]int) reachedServers = make(map[string]int)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(rootReq) resp, err := client.Do(rootReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if strings.Contains(string(body), `IP: `+s.whoami1IP) { if strings.Contains(string(body), `IP: `+s.whoami1IP) {
reachedServers[s.whoami1IP]++ reachedServers[s.whoami1IP]++
@ -458,19 +445,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
} }
} }
c.Assert(reachedServers[s.whoami1IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami1IP])
c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami2IP])
c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami3IP])
c.Assert(reachedServers[s.whoami4IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami4IP])
// Verify everything is up on foo router. // Verify everything is up on foo router.
reachedServers = make(map[string]int) reachedServers = make(map[string]int)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(fooReq) resp, err := client.Do(fooReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if strings.Contains(string(body), `IP: `+s.whoami1IP) { if strings.Contains(string(body), `IP: `+s.whoami1IP) {
reachedServers[s.whoami1IP]++ reachedServers[s.whoami1IP]++
@ -493,19 +480,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
} }
} }
c.Assert(reachedServers[s.whoami1IP], checker.Equals, 2) assert.Equal(s.T(), 2, reachedServers[s.whoami1IP])
c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami2IP])
c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami3IP])
c.Assert(reachedServers[s.whoami4IP], checker.Equals, 0) assert.Equal(s.T(), 0, reachedServers[s.whoami4IP])
// Verify everything is up on bar router. // Verify everything is up on bar router.
reachedServers = make(map[string]int) reachedServers = make(map[string]int)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
resp, err := client.Do(barReq) resp, err := client.Do(barReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if strings.Contains(string(body), `IP: `+s.whoami1IP) { if strings.Contains(string(body), `IP: `+s.whoami1IP) {
reachedServers[s.whoami1IP]++ reachedServers[s.whoami1IP]++
@ -528,102 +515,87 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) {
} }
} }
c.Assert(reachedServers[s.whoami1IP], checker.Equals, 2) assert.Equal(s.T(), 2, reachedServers[s.whoami1IP])
c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami2IP])
c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) assert.Equal(s.T(), 1, reachedServers[s.whoami3IP])
c.Assert(reachedServers[s.whoami4IP], checker.Equals, 0) assert.Equal(s.T(), 0, reachedServers[s.whoami4IP])
} }
func (s *HealthCheckSuite) TestPropagateNoHealthCheck(c *check.C) { func (s *HealthCheckSuite) TestPropagateNoHealthCheck() {
file := s.adaptFile(c, "fixtures/healthcheck/propagate_no_healthcheck.toml", struct { file := s.adaptFile("fixtures/healthcheck/propagate_no_healthcheck.toml", struct {
Server1 string Server1 string
}{s.whoami1IP}) }{s.whoami1IP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`noop.localhost`)"), try.BodyNotContains("Host(`root.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`noop.localhost`)"), try.BodyNotContains("Host(`root.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq.Host = "root.localhost" rootReq.Host = "root.localhost"
err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *HealthCheckSuite) TestPropagateReload(c *check.C) { func (s *HealthCheckSuite) TestPropagateReload() {
// Setup a WSP service without the healthcheck enabled (wsp-service1) // Setup a WSP service without the healthcheck enabled (wsp-service1)
withoutHealthCheck := s.adaptFile(c, "fixtures/healthcheck/reload_without_healthcheck.toml", struct { withoutHealthCheck := s.adaptFile("fixtures/healthcheck/reload_without_healthcheck.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{s.whoami1IP, s.whoami2IP}) }{s.whoami1IP, s.whoami2IP})
defer os.Remove(withoutHealthCheck) withHealthCheck := s.adaptFile("fixtures/healthcheck/reload_with_healthcheck.toml", struct {
withHealthCheck := s.adaptFile(c, "fixtures/healthcheck/reload_with_healthcheck.toml", struct {
Server1 string Server1 string
Server2 string Server2 string
}{s.whoami1IP, s.whoami2IP}) }{s.whoami1IP, s.whoami2IP})
defer os.Remove(withHealthCheck)
cmd, display := s.traefikCmd(withConfigFile(withoutHealthCheck)) s.traefikCmd(withConfigFile(withoutHealthCheck))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Allow one of the underlying services on it to fail all servers HC (whoami2) // Allow one of the underlying services on it to fail all servers HC (whoami2)
client := http.Client{ client := http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("500")) statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("500"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = client.Do(statusOKReq) _, err = client.Do(statusOKReq)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rootReq.Host = "root.localhost" rootReq.Host = "root.localhost"
// Check the failed service (whoami2) is getting requests, but answer 500 // Check the failed service (whoami2) is getting requests, but answer 500
err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusServiceUnavailable)) err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusServiceUnavailable))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Enable the healthcheck on the root WSP (wsp-service1) and let Traefik reload the config // Enable the healthcheck on the root WSP (wsp-service1) and let Traefik reload the config
fr1, err := os.OpenFile(withoutHealthCheck, os.O_APPEND|os.O_WRONLY, 0o644) fr1, err := os.OpenFile(withoutHealthCheck, os.O_APPEND|os.O_WRONLY, 0o644)
c.Assert(fr1, checker.NotNil) assert.NotNil(s.T(), fr1)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = fr1.Truncate(0) err = fr1.Truncate(0)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
fr2, err := os.ReadFile(withHealthCheck) fr2, err := os.ReadFile(withHealthCheck)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = fmt.Fprint(fr1, string(fr2)) _, err = fmt.Fprint(fr1, string(fr2))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = fr1.Close() err = fr1.Close()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
try.Sleep(1 * time.Second) // Waiting for the reflected change.
err = try.Request(rootReq, 5*time.Second, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
// Check the failed service (whoami2) is not getting requests // Check the failed service (whoami2) is not getting requests
wantIPs := []string{s.whoami1IP, s.whoami1IP, s.whoami1IP, s.whoami1IP} wantIPs := []string{s.whoami1IP, s.whoami1IP, s.whoami1IP, s.whoami1IP}
for _, ip := range wantIPs { for _, ip := range wantIPs {
want := "IP: " + ip err = try.Request(rootReq, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("IP: "+ip))
resp, err := client.Do(rootReq) require.NoError(s.T(), err)
c.Assert(err, checker.IsNil)
body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
c.Assert(string(body), checker.Contains, want)
} }
} }

View file

@ -2,27 +2,33 @@ package integration
import ( import (
"net/http" "net/http"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type HostResolverSuite struct{ BaseSuite } type HostResolverSuite struct{ BaseSuite }
func (s *HostResolverSuite) SetUpSuite(c *check.C) { func TestHostResolverSuite(t *testing.T) {
s.createComposeProject(c, "hostresolver") suite.Run(t, new(HostResolverSuite))
s.composeUp(c)
} }
func (s *HostResolverSuite) TestSimpleConfig(c *check.C) { func (s *HostResolverSuite) SetupSuite() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/simple_hostresolver.toml")) s.BaseSuite.SetupSuite()
defer display(c)
err := cmd.Start() s.createComposeProject("hostresolver")
c.Assert(err, checker.IsNil) s.composeUp()
defer s.killCmd(cmd) }
func (s *HostResolverSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *HostResolverSuite) TestSimpleConfig() {
s.traefikCmd(withConfigFile("fixtures/simple_hostresolver.toml"))
testCase := []struct { testCase := []struct {
desc string desc string
@ -43,10 +49,10 @@ func (s *HostResolverSuite) TestSimpleConfig(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = test.host req.Host = test.host
err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.status), try.HasBody()) err = try.Request(req, 5*time.Second, try.StatusCodeIs(test.status), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }

View file

@ -5,28 +5,27 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
checker "github.com/vdemeester/shakers"
) )
type HTTPSuite struct{ BaseSuite } type HTTPSuite struct{ BaseSuite }
func (s *HTTPSuite) TestSimpleConfiguration(c *check.C) { func TestHTTPSuite(t *testing.T) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/http/simple.toml")) suite.Run(t, new(HTTPSuite))
defer display(c) }
err := cmd.Start() func (s *HTTPSuite) TestSimpleConfiguration() {
c.Assert(err, checker.IsNil) s.traefikCmd(withConfigFile("fixtures/http/simple.toml"))
defer s.killCmd(cmd)
// Expect a 404 as we configured nothing. // Expect a 404 as we configured nothing.
err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Provide a configuration, fetched by Traefik provider. // Provide a configuration, fetched by Traefik provider.
configuration := &dynamic.Configuration{ configuration := &dynamic.Configuration{
@ -55,14 +54,14 @@ func (s *HTTPSuite) TestSimpleConfiguration(c *check.C) {
} }
configData, err := json.Marshal(configuration) configData, err := json.Marshal(configuration)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
server := startTestServerWithResponse(configData) server := startTestServerWithResponse(configData)
defer server.Close() defer server.Close()
// Expect configuration to be applied. // Expect configuration to be applied.
err = try.GetRequest("http://127.0.0.1:9090/api/rawdata", 3*time.Second, try.BodyContains("routerHTTP@http", "serviceHTTP@http", "http://bacon:80")) err = try.GetRequest("http://127.0.0.1:9090/api/rawdata", 3*time.Second, try.BodyContains("routerHTTP@http", "serviceHTTP@http", "http://bacon:80"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func startTestServerWithResponse(response []byte) (ts *httptest.Server) { func startTestServerWithResponse(response []byte) (ts *httptest.Server) {

File diff suppressed because it is too large Load diff

View file

@ -7,208 +7,327 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io"
"io/fs" "io/fs"
stdlog "log" stdlog "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"slices"
"strings" "strings"
"testing" "testing"
"text/template" "text/template"
"time" "time"
"github.com/compose-spec/compose-go/cli" "github.com/docker/docker/api/types/container"
"github.com/compose-spec/compose-go/types" "github.com/docker/docker/api/types/mount"
"github.com/docker/cli/cli/config/configfile" dockernetwork "github.com/docker/docker/api/types/network"
"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"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/go-check/check"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/sirupsen/logrus" "github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/logs" "github.com/stretchr/testify/suite"
checker "github.com/vdemeester/shakers" "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
"github.com/traefik/traefik/v3/integration/try"
"gopkg.in/yaml.v3"
) )
var ( var showLog = flag.Bool("tlog", false, "always show Traefik logs")
integration = flag.Bool("integration", false, "run integration tests")
showLog = flag.Bool("tlog", false, "always show Traefik logs")
)
func Test(t *testing.T) { type composeConfig struct {
if !*integration { Services map[string]composeService `yaml:"services"`
log.Info().Msg("Integration tests disabled.") }
return
}
log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}). type composeService struct {
With().Timestamp().Caller().Logger() Image string `yaml:"image"`
zerolog.SetGlobalLevel(zerolog.InfoLevel) Labels map[string]string `yaml:"labels"`
Hostname string `yaml:"hostname"`
Volumes []string `yaml:"volumes"`
CapAdd []string `yaml:"cap_add"`
Command []string `yaml:"command"`
Environment map[string]string `yaml:"environment"`
Privileged bool `yaml:"privileged"`
Deploy composeDeploy `yaml:"deploy"`
}
// Global logrus replacement type composeDeploy struct {
logrus.StandardLogger().Out = logs.NoLevel(log.Logger, zerolog.DebugLevel) Replicas int `yaml:"replicas"`
// configure default standard log.
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
stdlog.SetOutput(logs.NoLevel(log.Logger, zerolog.DebugLevel))
// 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
}
}
check.Suite(&AccessLogSuite{})
if !useVPN {
check.Suite(&AcmeSuite{})
}
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{})
if !useVPN {
check.Suite(&K8sSuite{})
}
check.Suite(&KeepAliveSuite{})
check.Suite(&LogRotationSuite{})
if !useVPN {
check.Suite(&ProxyProtocolSuite{})
}
check.Suite(&RateLimitSuite{})
check.Suite(&RedisSuite{})
check.Suite(&RestSuite{})
check.Suite(&RetrySuite{})
check.Suite(&SimpleSuite{})
check.Suite(&TCPSuite{})
check.Suite(&TimeoutSuite{})
check.Suite(&ThrottlingSuite{})
check.Suite(&TLSClientHeadersSuite{})
check.Suite(&TracingSuite{})
check.Suite(&UDPSuite{})
check.Suite(&WebsocketSuite{})
check.Suite(&ZookeeperSuite{})
check.TestingT(t)
} }
var traefikBinary = "../dist/traefik" var traefikBinary = "../dist/traefik"
type BaseSuite struct { type BaseSuite struct {
composeProject *types.Project suite.Suite
dockerComposeService composeapi.Service containers map[string]testcontainers.Container
dockerClient *client.Client network *testcontainers.DockerNetwork
hostIP string
} }
func (s *BaseSuite) TearDownSuite(c *check.C) { func (s *BaseSuite) waitForTraefik(containerName string) {
if s.composeProject != nil && s.dockerComposeService != nil { time.Sleep(1 * time.Second)
s.composeDown(c)
// Wait for Traefik to turn ready.
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
require.NoError(s.T(), err)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName))
require.NoError(s.T(), err)
}
func (s *BaseSuite) displayTraefikLogFile(path string) {
if s.T().Failed() {
if _, err := os.Stat(path); !os.IsNotExist(err) {
content, errRead := os.ReadFile(path)
// TODO TestName
// fmt.Printf("%s: Traefik logs: \n", c.TestName())
fmt.Print("Traefik logs: \n")
if errRead == nil {
fmt.Println(content)
} else {
fmt.Println(errRead)
} }
} else {
// fmt.Printf("%s: No Traefik logs.\n", c.TestName())
fmt.Print("No Traefik logs.\n")
}
errRemove := os.Remove(path)
if errRemove != nil {
fmt.Println(errRemove)
}
}
}
func (s *BaseSuite) SetupSuite() {
// configure default standard log.
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
// TODO
// stdlog.SetOutput(log.Logger)
ctx := context.Background()
// Create docker network
// docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24
var opts []network.NetworkCustomizer
opts = append(opts, network.WithDriver("bridge"))
opts = append(opts, network.WithIPAM(&dockernetwork.IPAM{
Driver: "default",
Config: []dockernetwork.IPAMConfig{
{
Subnet: "172.31.42.0/24",
},
},
}))
dockerNetwork, err := network.New(ctx, opts...)
require.NoError(s.T(), err)
s.network = dockerNetwork
s.hostIP = "172.31.42.1"
if isDockerDesktop(ctx, s.T()) {
s.hostIP = getDockerDesktopHostIP(ctx, s.T())
s.setupVPN("tailscale.secret")
}
}
func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string {
t.Helper()
req := testcontainers.ContainerRequest{
Image: "alpine",
HostConfigModifier: func(config *container.HostConfig) {
config.AutoRemove = true
},
Cmd: []string{"getent", "hosts", "host.docker.internal"},
}
con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
require.NoError(t, err)
closer, err := con.Logs(ctx)
require.NoError(t, err)
all, err := io.ReadAll(closer)
require.NoError(t, err)
ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
matches := ipRegex.FindAllString(string(all), -1)
require.Len(t, matches, 1)
return matches[0]
}
func isDockerDesktop(ctx context.Context, t *testing.T) bool {
t.Helper()
cli, err := testcontainers.NewDockerClientWithOpts(ctx)
if err != nil {
t.Fatalf("failed to create docker client: %s", err)
}
info, err := cli.Info(ctx)
if err != nil {
t.Fatalf("failed to get docker info: %s", err)
}
return info.OperatingSystem == "Docker Desktop"
}
func (s *BaseSuite) TearDownSuite() {
s.composeDown()
err := try.Do(5*time.Second, func() error {
if s.network != nil {
err := s.network.Remove(context.Background())
if err != nil {
return err
}
}
return nil
})
require.NoError(s.T(), err)
} }
// createComposeProject creates the docker compose project stored as a field in the BaseSuite. // 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. // This method should be called before starting and/or stopping compose services.
func (s *BaseSuite) createComposeProject(c *check.C, name string) { func (s *BaseSuite) createComposeProject(name string) {
projectName := fmt.Sprintf("traefik-integration-test-%s", name)
composeFile := fmt.Sprintf("resources/compose/%s.yml", name) composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
var err error file, err := os.ReadFile(composeFile)
s.dockerClient, err = client.NewClientWithOpts() require.NoError(s.T(), err)
c.Assert(err, checker.IsNil)
s.dockerComposeService = compose.NewComposeService(s.dockerClient, &configfile.ConfigFile{}) var composeConfigData composeConfig
ops, err := cli.NewProjectOptions([]string{composeFile}, cli.WithName(projectName)) err = yaml.Unmarshal(file, &composeConfigData)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.composeProject, err = cli.ProjectFromOptions(ops) if s.containers == nil {
c.Assert(err, checker.IsNil) s.containers = map[string]testcontainers.Container{}
}
ctx := context.Background()
for id, containerConfig := range composeConfigData.Services {
var mounts []mount.Mount
for _, volume := range containerConfig.Volumes {
split := strings.Split(volume, ":")
if len(split) != 2 {
continue
}
if strings.HasPrefix(split[0], "./") {
path, err := os.Getwd()
if err != nil {
log.Err(err).Msg("can't determine current directory")
continue
}
split[0] = strings.Replace(split[0], "./", path+"/", 1)
}
abs, err := filepath.Abs(split[0])
require.NoError(s.T(), err)
mounts = append(mounts, mount.Mount{Source: abs, Target: split[1], Type: mount.TypeBind})
}
if containerConfig.Deploy.Replicas > 0 {
for i := 0; i < containerConfig.Deploy.Replicas; i++ {
id = fmt.Sprintf("%s-%d", id, i+1)
con, err := s.createContainer(ctx, containerConfig, id, mounts)
require.NoError(s.T(), err)
s.containers[id] = con
}
continue
}
con, err := s.createContainer(ctx, containerConfig, id, mounts)
require.NoError(s.T(), err)
s.containers[id] = con
}
}
func (s *BaseSuite) createContainer(ctx context.Context, containerConfig composeService, id string, mounts []mount.Mount) (testcontainers.Container, error) {
req := testcontainers.ContainerRequest{
Image: containerConfig.Image,
Env: containerConfig.Environment,
Cmd: containerConfig.Command,
Labels: containerConfig.Labels,
Name: id,
Hostname: containerConfig.Hostname,
Privileged: containerConfig.Privileged,
Networks: []string{s.network.Name},
HostConfigModifier: func(config *container.HostConfig) {
if containerConfig.CapAdd != nil {
config.CapAdd = containerConfig.CapAdd
}
if !isDockerDesktop(ctx, s.T()) {
config.ExtraHosts = append(config.ExtraHosts, "host.docker.internal:"+s.hostIP)
}
config.Mounts = mounts
},
}
con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: false,
})
return con, err
} }
// composeUp starts the given services of the current docker compose project, if they are not already started. // 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). // Already running services are not affected (i.e. not stopped).
func (s *BaseSuite) composeUp(c *check.C, services ...string) { func (s *BaseSuite) composeUp(services ...string) {
c.Assert(s.composeProject, check.NotNil) for name, con := range s.containers {
c.Assert(s.dockerComposeService, check.NotNil) if len(services) == 0 || slices.Contains(services, name) {
err := con.Start(context.Background())
// We use Create and Restart instead of Up, because the only option that actually works to control which containers require.NoError(s.T(), err)
// 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)
}
// 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)
} }
// composeStop stops the given services of the current docker compose project and removes the corresponding containers. // 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) { func (s *BaseSuite) composeStop(services ...string) {
c.Assert(s.dockerComposeService, check.NotNil) for name, con := range s.containers {
c.Assert(s.composeProject, check.NotNil) if len(services) == 0 || slices.Contains(services, name) {
timeout := 10 * time.Second
err := s.dockerComposeService.Stop(context.Background(), s.composeProject, composeapi.StopOptions{Services: services}) err := con.Stop(context.Background(), &timeout)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
}
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. // composeDown stops all compose project services and removes the corresponding containers.
func (s *BaseSuite) composeDown(c *check.C) { func (s *BaseSuite) composeDown() {
c.Assert(s.dockerComposeService, check.NotNil) for _, c := range s.containers {
c.Assert(s.composeProject, check.NotNil) err := c.Terminate(context.Background())
require.NoError(s.T(), err)
err := s.dockerComposeService.Down(context.Background(), s.composeProject.Name, composeapi.DownOptions{}) }
c.Assert(err, checker.IsNil) s.containers = map[string]testcontainers.Container{}
} }
func (s *BaseSuite) cmdTraefik(args ...string) (*exec.Cmd, *bytes.Buffer) { func (s *BaseSuite) cmdTraefik(args ...string) (*exec.Cmd, *bytes.Buffer) {
cmd := exec.Command(traefikBinary, args...) cmd := exec.Command(traefikBinary, args...)
s.T().Cleanup(func() {
s.killCmd(cmd)
})
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &out cmd.Stderr = &out
err := cmd.Start()
require.NoError(s.T(), err)
return cmd, &out return cmd, &out
} }
func (s *BaseSuite) killCmd(cmd *exec.Cmd) { func (s *BaseSuite) killCmd(cmd *exec.Cmd) {
if cmd.Process == nil {
log.Error().Msg("No process to kill")
return
}
err := cmd.Process.Kill() err := cmd.Process.Kill()
if err != nil { if err != nil {
log.Error().Err(err).Msg("Kill") log.Error().Err(err).Msg("Kill")
@ -217,15 +336,18 @@ func (s *BaseSuite) killCmd(cmd *exec.Cmd) {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
func (s *BaseSuite) traefikCmd(args ...string) (*exec.Cmd, func(*check.C)) { func (s *BaseSuite) traefikCmd(args ...string) *exec.Cmd {
cmd, out := s.cmdTraefik(args...) cmd, out := s.cmdTraefik(args...)
return cmd, func(c *check.C) {
if c.Failed() || *showLog { s.T().Cleanup(func() {
if s.T().Failed() || *showLog {
s.displayLogK3S() s.displayLogK3S()
s.displayLogCompose(c) s.displayLogCompose()
s.displayTraefikLog(c, out) s.displayTraefikLog(out)
}
} }
})
return cmd
} }
func (s *BaseSuite) displayLogK3S() { func (s *BaseSuite) displayLogK3S() {
@ -242,30 +364,33 @@ func (s *BaseSuite) displayLogK3S() {
log.Print() log.Print()
} }
func (s *BaseSuite) displayLogCompose(c *check.C) { func (s *BaseSuite) displayLogCompose() {
if s.dockerComposeService == nil || s.composeProject == nil { for name, ctn := range s.containers {
log.Info().Str("testName", c.TestName()).Msg("No docker compose logs.") readCloser, err := ctn.Logs(context.Background())
return require.NoError(s.T(), err)
for {
b := make([]byte, 1024)
_, err := readCloser.Read(b)
if errors.Is(err, io.EOF) {
break
} }
require.NoError(s.T(), err)
log.Info().Str("testName", c.TestName()).Msg("docker compose logs") trimLogs := bytes.Trim(bytes.TrimSpace(b), string([]byte{0}))
if len(trimLogs) > 0 {
logConsumer := formatter.NewLogConsumer(context.Background(), logs.NoLevel(log.Logger, zerolog.InfoLevel), false, true) log.Info().Str("container", name).Msg(string(trimLogs))
}
err := s.dockerComposeService.Logs(context.Background(), s.composeProject.Name, logConsumer, composeapi.LogOptions{}) }
c.Assert(err, checker.IsNil) }
log.Print()
log.Print("################################")
log.Print()
} }
func (s *BaseSuite) displayTraefikLog(c *check.C, output *bytes.Buffer) { func (s *BaseSuite) displayTraefikLog(output *bytes.Buffer) {
if output == nil || output.Len() == 0 { if output == nil || output.Len() == 0 {
log.Info().Str("testName", c.TestName()).Msg("No Traefik logs.") log.Info().Msg("No Traefik logs.")
} else { } else {
log.Info().Str("testName", c.TestName()). for _, line := range strings.Split(output.String(), "\n") {
Str("logs", output.String()).Msg("Traefik logs") log.Info().Msg(line)
}
} }
} }
@ -279,67 +404,47 @@ func (s *BaseSuite) getDockerHost() string {
return dockerHost return dockerHost
} }
func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string { func (s *BaseSuite) adaptFile(path string, tempObjects interface{}) string {
// Load file // Load file
tmpl, err := template.ParseFiles(path) tmpl, err := template.ParseFiles(path)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
folder, prefix := filepath.Split(path) folder, prefix := filepath.Split(path)
tmpFile, err := os.CreateTemp(folder, strings.TrimSuffix(prefix, filepath.Ext(prefix))+"_*"+filepath.Ext(prefix)) tmpFile, err := os.CreateTemp(folder, strings.TrimSuffix(prefix, filepath.Ext(prefix))+"_*"+filepath.Ext(prefix))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer tmpFile.Close() defer tmpFile.Close()
model := structs.Map(tempObjects) model := structs.Map(tempObjects)
model["SelfFilename"] = tmpFile.Name() model["SelfFilename"] = tmpFile.Name()
err = tmpl.ExecuteTemplate(tmpFile, prefix, model) err = tmpl.ExecuteTemplate(tmpFile, prefix, model)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = tmpFile.Sync() err = tmpFile.Sync()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.T().Cleanup(func() {
os.Remove(tmpFile.Name())
})
return tmpFile.Name() return tmpFile.Name()
} }
func (s *BaseSuite) getComposeServiceIP(c *check.C, name string) string { func (s *BaseSuite) getComposeServiceIP(name string) string {
filter := filters.NewArgs( container, ok := s.containers[name]
filters.Arg("label", fmt.Sprintf("%s=%s", composeapi.ProjectLabel, s.composeProject.Name)), if !ok {
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 "" return ""
}
ip, err := container.ContainerIP(context.Background())
if err != nil {
return ""
}
return ip
} }
func withConfigFile(file string) string { func withConfigFile(file string) string {
return "--configFile=" + file return "--configFile=" + file
} }
// 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 // setupVPN starts Tailscale on the corresponding container, and makes it a subnet
// router, for all the other containers (whoamis, etc) subsequently started for the // router, for all the other containers (whoamis, etc) subsequently started for the
// integration tests. // integration tests.
@ -355,25 +460,35 @@ type tailscaleNotSuite struct{ BaseSuite }
// "172.0.0.0/8": ["your_tailscale_identity"], // "172.0.0.0/8": ["your_tailscale_identity"],
// }, // },
// }, // },
// func (s *BaseSuite) setupVPN(keyFile string) {
// 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 {
data, err := os.ReadFile(keyFile) data, err := os.ReadFile(keyFile)
if err != nil { if err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
log.Fatal().Err(err).Send() log.Error().Err(err).Send()
} }
return nil
return
} }
authKey := strings.TrimSpace(string(data)) authKey := strings.TrimSpace(string(data))
// TODO: copy and create versions that don't need a check.C? // // TODO: copy and create versions that don't need a check.C?
vpn := &tailscaleNotSuite{} s.createComposeProject("tailscale")
vpn.createComposeProject(c, "tailscale") s.composeUp()
vpn.composeUp(c)
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
// If we ever change the docker subnet in the Makefile, // If we ever change the docker subnet in the Makefile,
// we need to change this one below correspondingly. // we need to change this one below correspondingly.
vpn.composeExec(c, "tailscaled", "tailscale", "up", "--authkey="+authKey, "--advertise-routes=172.31.42.0/24") s.composeExec("tailscaled", "tailscale", "up", "--authkey="+authKey, "--advertise-routes=172.31.42.0/24")
return vpn }
// 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(service string, args ...string) string {
require.Contains(s.T(), s.containers, service)
_, reader, err := s.containers[service].Exec(context.Background(), args)
require.NoError(s.T(), err)
content, err := io.ReadAll(reader)
require.NoError(s.T(), err)
return string(content)
} }

View file

@ -7,18 +7,21 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
checker "github.com/vdemeester/shakers"
) )
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata") var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
@ -26,25 +29,39 @@ var updateExpected = flag.Bool("update_expected", false, "Update expected files
// K8sSuite tests suite. // K8sSuite tests suite.
type K8sSuite struct{ BaseSuite } type K8sSuite struct{ BaseSuite }
func (s *K8sSuite) SetUpSuite(c *check.C) { func TestK8sSuite(t *testing.T) {
s.createComposeProject(c, "k8s") suite.Run(t, new(K8sSuite))
s.composeUp(c) }
func (s *K8sSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("k8s")
s.composeUp()
abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml") abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Do(60*time.Second, func() error { err = try.Do(60*time.Second, func() error {
_, err := os.Stat(abs) _, err := os.Stat(abs)
return err return err
}) })
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
data, err := os.ReadFile(abs)
require.NoError(s.T(), err)
content := strings.ReplaceAll(string(data), "https://server:6443", fmt.Sprintf("https://%s", net.JoinHostPort(s.getComposeServiceIP("server"), "6443")))
err = os.WriteFile(abs, []byte(content), 0o644)
require.NoError(s.T(), err)
err = os.Setenv("KUBECONFIG", abs) err = os.Setenv("KUBECONFIG", abs)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *K8sSuite) TearDownSuite(c *check.C) { func (s *K8sSuite) TearDownSuite() {
s.composeDown(c) s.BaseSuite.TearDownSuite()
generatedFiles := []string{ generatedFiles := []string{
"./fixtures/k8s/config.skip/kubeconfig.yaml", "./fixtures/k8s/config.skip/kubeconfig.yaml",
@ -62,120 +79,84 @@ func (s *K8sSuite) TearDownSuite(c *check.C) {
} }
} }
func (s *K8sSuite) TestIngressConfiguration(c *check.C) { func (s *K8sSuite) TestIngressConfiguration() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_default.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_default.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-ingress.json", "8080")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingress.json", "8080")
} }
func (s *K8sSuite) TestIngressLabelSelector(c *check.C) { func (s *K8sSuite) TestIngressLabelSelector() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingress_label_selector.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_ingress_label_selector.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-ingress-label-selector.json", "8080")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingress-label-selector.json", "8080")
} }
func (s *K8sSuite) TestCRDConfiguration(c *check.C) { func (s *K8sSuite) TestCRDConfiguration() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_crd.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_crd.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-crd.json", "8000")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-crd.json", "8000")
} }
func (s *K8sSuite) TestCRDLabelSelector(c *check.C) { func (s *K8sSuite) TestCRDLabelSelector() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_crd_label_selector.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_crd_label_selector.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-crd-label-selector.json", "8000")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-crd-label-selector.json", "8000")
} }
func (s *K8sSuite) TestGatewayConfiguration(c *check.C) { func (s *K8sSuite) TestGatewayConfiguration() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_gateway.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_gateway.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-gateway.json", "8080")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-gateway.json", "8080")
} }
func (s *K8sSuite) TestIngressclass(c *check.C) { func (s *K8sSuite) TestIngressclass() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-ingressclass.json", "8080")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080")
} }
func (s *K8sSuite) TestDisableIngressclassLookup(c *check.C) { func (s *K8sSuite) TestDisableIngressclassLookup() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass_disabled.toml")) s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass_disabled.toml"))
defer display(c)
err := cmd.Start() s.testConfiguration("testdata/rawdata-ingressclass-disabled.json", "8080")
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
testConfiguration(c, "testdata/rawdata-ingressclass-disabled.json", "8080")
} }
func testConfiguration(c *check.C, path, apiPort string) { func (s *K8sSuite) testConfiguration(path, apiPort string) {
err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash(path) expectedJSON := filepath.FromSlash(path)
if *updateExpected { if *updateExpected {
fi, err := os.Create(expectedJSON) fi, err := os.Create(expectedJSON)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = fi.Close() err = fi.Close()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
var buf bytes.Buffer var buf bytes.Buffer
err = try.GetRequest("http://127.0.0.1:"+apiPort+"/api/rawdata", 1*time.Minute, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf)) err = try.GetRequest("http://127.0.0.1:"+apiPort+"/api/rawdata", 1*time.Minute, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf))
if !*updateExpected { if !*updateExpected {
if err != nil { require.NoError(s.T(), err)
c.Error(err)
}
return return
} }
if err != nil { if err != nil {
c.Logf("In file update mode, got expected error: %v", err) log.Info().Msgf("In file update mode, got expected error: %v", err)
} }
var rtRepr api.RunTimeRepresentation var rtRepr api.RunTimeRepresentation
err = json.Unmarshal(buf.Bytes(), &rtRepr) err = json.Unmarshal(buf.Bytes(), &rtRepr)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
newJSON, err := json.MarshalIndent(rtRepr, "", "\t") newJSON, err := json.MarshalIndent(rtRepr, "", "\t")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = os.WriteFile(expectedJSON, newJSON, 0o644) err = os.WriteFile(expectedJSON, newJSON, 0o644)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Errorf("We do not want a passing test in file update mode")
s.T().Fatal("We do not want a passing test in file update mode")
} }
func matchesConfig(wantConfig string, buf *bytes.Buffer) try.ResponseCondition { func matchesConfig(wantConfig string, buf *bytes.Buffer) try.ResponseCondition {

View file

@ -5,18 +5,23 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type KeepAliveSuite struct { type KeepAliveSuite struct {
BaseSuite BaseSuite
} }
func TestKeepAliveSuite(t *testing.T) {
suite.Run(t, new(KeepAliveSuite))
}
type KeepAliveConfig struct { type KeepAliveConfig struct {
KeepAliveServer string KeepAliveServer string
IdleConnTimeout string IdleConnTimeout string
@ -27,7 +32,7 @@ type connStateChangeEvent struct {
state http.ConnState state http.ConnState
} }
func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c *check.C) { func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime() {
idleTimeout := time.Duration(75) * time.Millisecond idleTimeout := time.Duration(75) * time.Millisecond
connStateChanges := make(chan connStateChangeEvent) connStateChanges := make(chan connStateChangeEvent)
@ -59,18 +64,18 @@ func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c *
case <-noMoreRequests: case <-noMoreRequests:
moreRequestsExpected = false moreRequestsExpected = false
case <-maxWaitTimeExceeded: case <-maxWaitTimeExceeded:
c.Logf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout) log.Info().Msgf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout)
c.Fail() s.T().Fail()
close(completed) close(completed)
return return
} }
} }
c.Check(connCount, checker.Equals, 1) require.Equal(s.T(), 1, connCount)
for _, idlePeriod := range idlePeriodLengthMap { for _, idlePeriod := range idlePeriodLengthMap {
// Our method of measuring the actual idle period is not precise, so allow some sub-ms deviation // Our method of measuring the actual idle period is not precise, so allow some sub-ms deviation
c.Check(math.Round(idlePeriod.Seconds()), checker.LessOrEqualThan, idleTimeout.Seconds()) require.LessOrEqual(s.T(), math.Round(idlePeriod.Seconds()), idleTimeout.Seconds())
} }
close(completed) close(completed)
@ -87,22 +92,16 @@ func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c *
defer server.Close() defer server.Close()
config := KeepAliveConfig{KeepAliveServer: server.URL, IdleConnTimeout: idleTimeout.String()} config := KeepAliveConfig{KeepAliveServer: server.URL, IdleConnTimeout: idleTimeout.String()}
file := s.adaptFile(c, "fixtures/timeout/keepalive.toml", config) file := s.adaptFile("fixtures/timeout/keepalive.toml", config)
defer os.Remove(file) s.traefikCmd(withConfigFile(file))
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Check(err, checker.IsNil)
defer s.killCmd(cmd)
// Wait for Traefik // 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`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Duration(1)*time.Second, try.StatusCodeIs(200), try.BodyContains("PathPrefix(`/keepalive`)"))
c.Check(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200)) err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200))
c.Check(err, checker.IsNil) require.NoError(s.T(), err)
close(noMoreRequests) close(noMoreRequests)
<-completed <-completed

View file

@ -9,12 +9,14 @@ import (
"os" "os"
"strings" "strings"
"syscall" "syscall"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated" const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated"
@ -22,13 +24,23 @@ const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated"
// Log rotation integration test suite. // Log rotation integration test suite.
type LogRotationSuite struct{ BaseSuite } type LogRotationSuite struct{ BaseSuite }
func (s *LogRotationSuite) SetUpSuite(c *check.C) { func TestLogRorationSuite(t *testing.T) {
s.createComposeProject(c, "access_log") suite.Run(t, new(LogRotationSuite))
s.composeUp(c)
} }
func (s *LogRotationSuite) TearDownSuite(c *check.C) { func (s *LogRotationSuite) SetupSuite() {
s.composeDown(c) s.BaseSuite.SetupSuite()
os.Remove(traefikTestAccessLogFile)
os.Remove(traefikTestLogFile)
os.Remove(traefikTestAccessLogFileRotated)
s.createComposeProject("access_log")
s.composeUp()
}
func (s *LogRotationSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
generatedFiles := []string{ generatedFiles := []string{
traefikTestLogFile, traefikTestLogFile,
@ -43,84 +55,80 @@ func (s *LogRotationSuite) TearDownSuite(c *check.C) {
} }
} }
func (s *LogRotationSuite) TestAccessLogRotation(c *check.C) { func (s *LogRotationSuite) TestAccessLogRotation() {
// Start Traefik // Start Traefik
cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml"))
defer display(c) defer s.displayTraefikLogFile(traefikTestLogFile)
defer displayTraefikLogFile(c, traefikTestLogFile)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Verify Traefik started ok // Verify Traefik started ok
verifyEmptyErrorLog(c, "traefik.log") s.verifyEmptyErrorLog("traefik.log")
waitForTraefik(c, "server1") s.waitForTraefik("server1")
// Make some requests // Make some requests
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req.Host = "frontend1.docker.local" req.Host = "frontend1.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Rename access log // Rename access log
err = os.Rename(traefikTestAccessLogFile, traefikTestAccessLogFileRotated) err = os.Rename(traefikTestAccessLogFile, traefikTestAccessLogFileRotated)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// in the midst of the requests, issue SIGUSR1 signal to server process // in the midst of the requests, issue SIGUSR1 signal to server process
err = cmd.Process.Signal(syscall.SIGUSR1) err = cmd.Process.Signal(syscall.SIGUSR1)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// continue issuing requests // continue issuing requests
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Verify access.log.rotated output as expected // Verify access.log.rotated output as expected
logAccessLogFile(c, traefikTestAccessLogFileRotated) s.logAccessLogFile(traefikTestAccessLogFileRotated)
lineCount := verifyLogLines(c, traefikTestAccessLogFileRotated, 0, true) lineCount := s.verifyLogLines(traefikTestAccessLogFileRotated, 0, true)
c.Assert(lineCount, checker.GreaterOrEqualThan, 1) assert.GreaterOrEqual(s.T(), lineCount, 1)
// make sure that the access log file is at least created before we do assertions on it // make sure that the access log file is at least created before we do assertions on it
err = try.Do(1*time.Second, func() error { err = try.Do(1*time.Second, func() error {
_, err := os.Stat(traefikTestAccessLogFile) _, err := os.Stat(traefikTestAccessLogFile)
return err return err
}) })
c.Assert(err, checker.IsNil, check.Commentf("access log file was not created in time")) assert.NoError(s.T(), err, "access log file was not created in time")
// Verify access.log output as expected // Verify access.log output as expected
logAccessLogFile(c, traefikTestAccessLogFile) s.logAccessLogFile(traefikTestAccessLogFile)
lineCount = verifyLogLines(c, traefikTestAccessLogFile, lineCount, true) lineCount = s.verifyLogLines(traefikTestAccessLogFile, lineCount, true)
c.Assert(lineCount, checker.Equals, 3) assert.Equal(s.T(), 3, lineCount)
verifyEmptyErrorLog(c, traefikTestLogFile) s.verifyEmptyErrorLog(traefikTestLogFile)
} }
func logAccessLogFile(c *check.C, fileName string) { func (s *LogRotationSuite) logAccessLogFile(fileName string) {
output, err := os.ReadFile(fileName) output, err := os.ReadFile(fileName)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Logf("Contents of file %s\n%s", fileName, string(output)) log.Info().Msgf("Contents of file %s\n%s", fileName, string(output))
} }
func verifyEmptyErrorLog(c *check.C, name string) { func (s *LogRotationSuite) verifyEmptyErrorLog(name string) {
err := try.Do(5*time.Second, func() error { err := try.Do(5*time.Second, func() error {
traefikLog, e2 := os.ReadFile(name) traefikLog, e2 := os.ReadFile(name)
if e2 != nil { if e2 != nil {
return e2 return e2
} }
c.Assert(string(traefikLog), checker.HasLen, 0) assert.Empty(s.T(), string(traefikLog))
return nil return nil
}) })
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func verifyLogLines(c *check.C, fileName string, countInit int, accessLog bool) int { func (s *LogRotationSuite) verifyLogLines(fileName string, countInit int, accessLog bool) int {
rotated, err := os.Open(fileName) rotated, err := os.Open(fileName)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
rotatedLog := bufio.NewScanner(rotated) rotatedLog := bufio.NewScanner(rotated)
count := countInit count := countInit
for rotatedLog.Scan() { for rotatedLog.Scan() {
@ -128,7 +136,7 @@ func verifyLogLines(c *check.C, fileName string, countInit int, accessLog bool)
if accessLog { if accessLog {
if len(line) > 0 { if len(line) > 0 {
if !strings.Contains(line, "/api/rawdata") { if !strings.Contains(line, "/api/rawdata") {
CheckAccessLogFormat(c, line, count) s.CheckAccessLogFormat(line, count)
count++ count++
} }
} }

View file

@ -1,103 +1,138 @@
package integration package integration
import ( import (
"net/http" "bufio"
"os" "net"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/pires/go-proxyproto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type ProxyProtocolSuite struct { type ProxyProtocolSuite struct {
BaseSuite BaseSuite
gatewayIP string
haproxyIP string
whoamiIP string whoamiIP string
} }
func (s *ProxyProtocolSuite) SetUpSuite(c *check.C) { func TestProxyProtocolSuite(t *testing.T) {
s.createComposeProject(c, "proxy-protocol") suite.Run(t, new(ProxyProtocolSuite))
s.composeUp(c)
s.gatewayIP = s.getContainerIP(c, "traefik")
s.haproxyIP = s.getComposeServiceIP(c, "haproxy")
s.whoamiIP = s.getComposeServiceIP(c, "whoami")
} }
func (s *ProxyProtocolSuite) TestProxyProtocolTrusted(c *check.C) { func (s *ProxyProtocolSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/proxy-protocol/with.toml", struct { s.BaseSuite.SetupSuite()
s.createComposeProject("proxy-protocol")
s.composeUp()
s.whoamiIP = s.getComposeServiceIP("whoami")
}
func (s *ProxyProtocolSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *ProxyProtocolSuite) TestProxyProtocolTrusted() {
file := s.adaptFile("fixtures/proxy-protocol/proxy-protocol.toml", struct {
HaproxyIP string HaproxyIP string
WhoamiIP string WhoamiIP string
}{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) }{WhoamiIP: s.whoamiIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+s.haproxyIP+"/whoami", 1*time.Second, err := try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second)
try.StatusCodeIs(http.StatusOK), require.NoError(s.T(), err)
try.BodyContains("X-Forwarded-For: "+s.gatewayIP))
c.Assert(err, checker.IsNil) content, err := proxyProtoRequest("127.0.0.1:8000", 1)
require.NoError(s.T(), err)
assert.Contains(s.T(), content, "X-Forwarded-For: 1.2.3.4")
content, err = proxyProtoRequest("127.0.0.1:8000", 2)
require.NoError(s.T(), err)
assert.Contains(s.T(), content, "X-Forwarded-For: 1.2.3.4")
} }
func (s *ProxyProtocolSuite) TestProxyProtocolV2Trusted(c *check.C) { func (s *ProxyProtocolSuite) TestProxyProtocolNotTrusted() {
file := s.adaptFile(c, "fixtures/proxy-protocol/with.toml", struct { file := s.adaptFile("fixtures/proxy-protocol/proxy-protocol.toml", struct {
HaproxyIP string HaproxyIP string
WhoamiIP string WhoamiIP string
}{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) }{WhoamiIP: s.whoamiIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://"+s.haproxyIP+":81/whoami", 1*time.Second, err := try.GetRequest("http://127.0.0.1:9000/whoami", 10*time.Second)
try.StatusCodeIs(http.StatusOK), require.NoError(s.T(), err)
try.BodyContains("X-Forwarded-For: "+s.gatewayIP))
c.Assert(err, checker.IsNil) content, err := proxyProtoRequest("127.0.0.1:9000", 1)
require.NoError(s.T(), err)
assert.Contains(s.T(), content, "X-Forwarded-For: 127.0.0.1")
content, err = proxyProtoRequest("127.0.0.1:9000", 2)
require.NoError(s.T(), err)
assert.Contains(s.T(), content, "X-Forwarded-For: 127.0.0.1")
} }
func (s *ProxyProtocolSuite) TestProxyProtocolNotTrusted(c *check.C) { func proxyProtoRequest(address string, version byte) (string, error) {
file := s.adaptFile(c, "fixtures/proxy-protocol/without.toml", struct { // Open a TCP connection to the server
HaproxyIP string conn, err := net.Dial("tcp", address)
WhoamiIP string if err != nil {
}{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) return "", err
defer os.Remove(file) }
defer conn.Close()
cmd, display := s.traefikCmd(withConfigFile(file)) // Create a Proxy Protocol header with v1
defer display(c) proxyHeader := &proxyproto.Header{
err := cmd.Start() Version: version,
c.Assert(err, checker.IsNil) Command: proxyproto.PROXY,
defer s.killCmd(cmd) TransportProtocol: proxyproto.TCPv4,
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 8000,
},
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("1.2.3.4"),
Port: 62541,
},
}
err = try.GetRequest("http://"+s.haproxyIP+"/whoami", 1*time.Second, // After the connection was created write the proxy headers first
try.StatusCodeIs(http.StatusOK), _, err = proxyHeader.WriteTo(conn)
try.BodyContains("X-Forwarded-For: "+s.haproxyIP)) if err != nil {
c.Assert(err, checker.IsNil) return "", err
} }
func (s *ProxyProtocolSuite) TestProxyProtocolV2NotTrusted(c *check.C) { // Create an HTTP request
file := s.adaptFile(c, "fixtures/proxy-protocol/without.toml", struct { request := "GET /whoami HTTP/1.1\r\n" +
HaproxyIP string "Host: 127.0.0.1\r\n" +
WhoamiIP string "Connection: close\r\n" +
}{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) "\r\n"
defer os.Remove(file)
// Write the HTTP request to the TCP connection
cmd, display := s.traefikCmd(withConfigFile(file)) writer := bufio.NewWriter(conn)
defer display(c) _, err = writer.WriteString(request)
err := cmd.Start() if err != nil {
c.Assert(err, checker.IsNil) return "", err
defer s.killCmd(cmd) }
err = try.GetRequest("http://"+s.haproxyIP+":81/whoami", 1*time.Second, // Flush the buffer to ensure the request is sent
try.StatusCodeIs(http.StatusOK), err = writer.Flush()
try.BodyContains("X-Forwarded-For: "+s.haproxyIP)) if err != nil {
c.Assert(err, checker.IsNil) return "", err
}
// Read the response from the server
var content string
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
content += scanner.Text() + "\n"
}
if scanner.Err() != nil {
return "", err
}
return content, nil
} }

View file

@ -2,12 +2,12 @@ package integration
import ( import (
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type RateLimitSuite struct { type RateLimitSuite struct {
@ -15,33 +15,38 @@ type RateLimitSuite struct {
ServerIP string ServerIP string
} }
func (s *RateLimitSuite) SetUpSuite(c *check.C) { func TestRateLimitSuite(t *testing.T) {
s.createComposeProject(c, "ratelimit") suite.Run(t, new(RateLimitSuite))
s.composeUp(c)
s.ServerIP = s.getComposeServiceIP(c, "whoami1")
} }
func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) { func (s *RateLimitSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/ratelimit/simple.toml", struct { s.BaseSuite.SetupSuite()
s.createComposeProject("ratelimit")
s.composeUp()
s.ServerIP = s.getComposeServiceIP("whoami1")
}
func (s *RateLimitSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *RateLimitSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/ratelimit/simple.toml", struct {
Server1 string Server1 string
}{s.ServerIP}) }{s.ServerIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
start := time.Now() start := time.Now()
count := 0 count := 0
for { for {
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
count++ count++
if count > 100 { if count > 100 {
break break
@ -50,6 +55,6 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
stop := time.Now() stop := time.Now()
elapsed := stop.Sub(start) elapsed := stop.Sub(start)
if elapsed < time.Second*99/100 { if elapsed < time.Second*99/100 {
c.Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed) s.T().Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed)
} }
} }

View file

@ -0,0 +1,201 @@
package integration
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"text/template"
"time"
"github.com/fatih/structs"
"github.com/kvtools/redis"
"github.com/kvtools/valkeyrie"
"github.com/kvtools/valkeyrie/store"
"github.com/pmezard/go-difflib/difflib"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api"
)
// Redis test suites.
type RedisSentinelSuite struct {
BaseSuite
kvClient store.Store
redisEndpoints []string
}
func TestRedisSentinelSuite(t *testing.T) {
suite.Run(t, new(RedisSentinelSuite))
}
func (s *RedisSentinelSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.setupSentinelConfiguration([]string{"26379", "26379", "26379"})
s.createComposeProject("redis_sentinel")
s.composeUp()
s.redisEndpoints = []string{
net.JoinHostPort(s.getComposeServiceIP("sentinel1"), "26379"),
net.JoinHostPort(s.getComposeServiceIP("sentinel2"), "26379"),
net.JoinHostPort(s.getComposeServiceIP("sentinel3"), "26379"),
}
kv, err := valkeyrie.NewStore(
context.Background(),
redis.StoreName,
s.redisEndpoints,
&redis.Config{
Sentinel: &redis.Sentinel{
MasterName: "mymaster",
},
},
)
require.NoError(s.T(), err, "Cannot create store redis")
s.kvClient = kv
// wait for redis
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
require.NoError(s.T(), err)
}
func (s *RedisSentinelSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
for _, filename := range []string{"sentinel1.conf", "sentinel2.conf", "sentinel3.conf"} {
_ = os.Remove(filepath.Join(".", "resources", "compose", "config", filename))
}
}
func (s *RedisSentinelSuite) setupSentinelConfiguration(ports []string) {
for i, port := range ports {
templateValue := struct{ SentinelPort string }{SentinelPort: port}
// Load file
templateFile := "resources/compose/config/sentinel_template.conf"
tmpl, err := template.ParseFiles(templateFile)
require.NoError(s.T(), err)
folder, prefix := filepath.Split(templateFile)
fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1)
tmpFile, err := os.Create(fileName)
require.NoError(s.T(), err)
defer tmpFile.Close()
model := structs.Map(templateValue)
model["SelfFilename"] = tmpFile.Name()
err = tmpl.ExecuteTemplate(tmpFile, prefix, model)
require.NoError(s.T(), err)
err = tmpFile.Sync()
require.NoError(s.T(), err)
}
}
func (s *RedisSentinelSuite) TestSentinelConfiguration() {
file := s.adaptFile("fixtures/redis/sentinel.toml", struct{ RedisAddress string }{
RedisAddress: strings.Join(s.redisEndpoints, `","`),
})
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "true",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "true",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
}
for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
require.NoError(s.T(), err)
}
s.traefikCmd(withConfigFile(file))
// wait for traefik
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`),
)
require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
require.NoError(s.T(), err)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
require.NoError(s.T(), err)
got, err := json.MarshalIndent(obtained, "", " ")
require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666)
require.NoError(s.T(), err)
}
expected, err := os.ReadFile(expectedJSON)
require.NoError(s.T(), err)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
require.NoError(s.T(), err)
log.Info().Msg(text)
}
}

View file

@ -4,26 +4,22 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io/fs"
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template" "testing"
"time" "time"
"github.com/fatih/structs"
"github.com/go-check/check"
"github.com/kvtools/redis" "github.com/kvtools/redis"
"github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie"
"github.com/kvtools/valkeyrie/store" "github.com/kvtools/valkeyrie/store"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
checker "github.com/vdemeester/shakers"
) )
// Redis test suites. // Redis test suites.
@ -33,23 +29,18 @@ type RedisSuite struct {
redisEndpoints []string redisEndpoints []string
} }
func (s *RedisSuite) TearDownSuite(c *check.C) { func TestRedisSuite(t *testing.T) {
s.composeDown(c) suite.Run(t, new(RedisSuite))
for _, filename := range []string{"sentinel1.conf", "sentinel2.conf", "sentinel3.conf"} {
err := os.Remove(filepath.Join(".", "resources", "compose", "config", filename))
if err != nil && !errors.Is(err, fs.ErrNotExist) {
c.Fatal("unable to clean configuration file for sentinel: ", err)
}
}
} }
func (s *RedisSuite) setupStore(c *check.C) { func (s *RedisSuite) SetupSuite() {
s.createComposeProject(c, "redis") s.BaseSuite.SetupSuite()
s.composeUp(c)
s.createComposeProject("redis")
s.composeUp()
s.redisEndpoints = []string{} s.redisEndpoints = []string{}
s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379")) s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP("redis"), "6379"))
kv, err := valkeyrie.NewStore( kv, err := valkeyrie.NewStore(
context.Background(), context.Background(),
@ -57,23 +48,23 @@ func (s *RedisSuite) setupStore(c *check.C) {
s.redisEndpoints, s.redisEndpoints,
&redis.Config{}, &redis.Config{},
) )
if err != nil { require.NoError(s.T(), err, "Cannot create store redis")
c.Fatal("Cannot create store redis: ", err)
}
s.kvClient = kv s.kvClient = kv
// wait for redis // wait for redis
err = try.Do(60*time.Second, try.KVExists(kv, "test")) err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *RedisSuite) TestSimpleConfiguration(c *check.C) { func (s *RedisSuite) TearDownSuite() {
s.setupStore(c) s.BaseSuite.TearDownSuite()
}
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{ func (s *RedisSuite) TestSimpleConfiguration() {
file := s.adaptFile("fixtures/redis/simple.toml", struct{ RedisAddress string }{
RedisAddress: strings.Join(s.redisEndpoints, ","), RedisAddress: strings.Join(s.redisEndpoints, ","),
}) })
defer os.Remove(file)
data := map[string]string{ data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web", "traefik/http/routers/Router0/entryPoints/0": "web",
@ -122,39 +113,35 @@ func (s *RedisSuite) TestSimpleConfiguration(c *check.C) {
for k, v := range data { for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil) err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`), try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var obtained api.RunTimeRepresentation var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained) err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
got, err := json.MarshalIndent(obtained, "", " ") got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json") expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
if *updateExpected { if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666) err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
expected, err := os.ReadFile(expectedJSON) expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if !bytes.Equal(expected, got) { if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{ diff := difflib.UnifiedDiff{
@ -166,170 +153,6 @@ func (s *RedisSuite) TestSimpleConfiguration(c *check.C) {
} }
text, err := difflib.GetUnifiedDiffString(diff) text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err, text)
c.Error(text)
}
}
func (s *RedisSuite) setupSentinelStore(c *check.C) {
s.setupSentinelConfiguration(c, []string{"26379", "36379", "46379"})
s.createComposeProject(c, "redis_sentinel")
s.composeUp(c)
s.redisEndpoints = []string{
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel1"), "26379"),
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel2"), "36379"),
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel3"), "46379"),
}
kv, err := valkeyrie.NewStore(
context.Background(),
redis.StoreName,
s.redisEndpoints,
&redis.Config{
Sentinel: &redis.Sentinel{
MasterName: "mymaster",
},
},
)
if err != nil {
c.Fatal("Cannot create store redis sentinel")
}
s.kvClient = kv
// wait for redis
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *RedisSuite) setupSentinelConfiguration(c *check.C, ports []string) {
for i, port := range ports {
templateValue := struct{ SentinelPort string }{SentinelPort: port}
// Load file
templateFile := "resources/compose/config/sentinel_template.conf"
tmpl, err := template.ParseFiles(templateFile)
c.Assert(err, checker.IsNil)
folder, prefix := filepath.Split(templateFile)
fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1)
tmpFile, err := os.Create(fileName)
c.Assert(err, checker.IsNil)
defer tmpFile.Close()
model := structs.Map(templateValue)
model["SelfFilename"] = tmpFile.Name()
err = tmpl.ExecuteTemplate(tmpFile, prefix, model)
c.Assert(err, checker.IsNil)
err = tmpFile.Sync()
c.Assert(err, checker.IsNil)
}
}
func (s *RedisSuite) TestSentinelConfiguration(c *check.C) {
s.setupSentinelStore(c)
file := s.adaptFile(c, "fixtures/redis/sentinel.toml", struct{ RedisAddress string }{
RedisAddress: strings.Join(s.redisEndpoints, `","`),
})
defer os.Remove(file)
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "true",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "true",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
}
for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`),
)
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil)
}
expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
} }
} }

View file

@ -40,7 +40,7 @@ services:
traefik.http.routers.rt-authFrontend.entryPoints: httpFrontendAuth traefik.http.routers.rt-authFrontend.entryPoints: httpFrontendAuth
traefik.http.routers.rt-authFrontend.rule: Host(`frontend.auth.docker.local`) traefik.http.routers.rt-authFrontend.rule: Host(`frontend.auth.docker.local`)
traefik.http.routers.rt-authFrontend.middlewares: basicauth traefik.http.routers.rt-authFrontend.middlewares: basicauth
traefik.http.middlewares.basicauth.basicauth.users: test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/ traefik.http.middlewares.basicauth.basicauth.users: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
traefik.http.services.service3.loadbalancer.server.port: 80 traefik.http.services.service3.loadbalancer.server.port: 80
digestAuthMiddleware: digestAuthMiddleware:
@ -94,8 +94,3 @@ services:
traefik.http.routers.rt-preflightCORS.middlewares: preflightCORS traefik.http.routers.rt-preflightCORS.middlewares: preflightCORS
traefik.http.middlewares.preflightCORS.headers.accessControlAllowMethods: OPTIONS, GET traefik.http.middlewares.preflightCORS.headers.accessControlAllowMethods: OPTIONS, GET
traefik.http.services.preflightCORS.loadbalancer.server.port: 80 traefik.http.services.preflightCORS.loadbalancer.server.port: 80
networks:
default:
name: traefik-test-network
external: true

View file

@ -34,8 +34,3 @@ services:
traefik.http.routers.rt4.middlewares: wl4 traefik.http.routers.rt4.middlewares: wl4
traefik.http.middlewares.wl4.ipallowlist.sourceRange: 8.8.8.8 traefik.http.middlewares.wl4.ipallowlist.sourceRange: 8.8.8.8
traefik.http.middlewares.wl4.ipallowlist.ipStrategy.excludedIPs: 10.0.0.1,10.0.0.2 traefik.http.middlewares.wl4.ipallowlist.ipStrategy.excludedIPs: 10.0.0.1,10.0.0.2
networks:
default:
name: traefik-test-network
external: true

View file

@ -11,8 +11,3 @@ services:
image: traefik/whoami image: traefik/whoami
labels: labels:
traefik.enable: false traefik.enable: false
networks:
default:
name: traefik-test-network
external: true

View file

@ -4,8 +4,3 @@ services:
image: consul:1.6 image: consul:1.6
whoami: whoami:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,11 +2,24 @@ version: "3.8"
services: services:
consul: consul:
image: consul:1.6.2 image: consul:1.6.2
command: agent -server -bootstrap -ui -client 0.0.0.0 -hcl 'connect { enabled = true }' command:
- agent
- -server
- -bootstrap
- -ui
- -client
- 0.0.0.0
- -hcl
- 'connect { enabled = true }'
consul-agent: consul-agent:
image: consul:1.6.2 image: consul:1.6.2
command: agent -retry-join consul -client 0.0.0.0 command:
- agent
- -retry-join
- consul
- -client
- 0.0.0.0
whoami1: whoami1:
image: traefik/whoami image: traefik/whoami
@ -30,8 +43,3 @@ services:
PORT: 443 PORT: 443
BIND: 0.0.0.0 BIND: 0.0.0.0
CONSUL_HTTP_ADDR: http://consul:8500 CONSUL_HTTP_ADDR: http://consul:8500
networks:
default:
name: traefik-test-network
external: true

View file

@ -36,8 +36,3 @@ services:
labels: labels:
traefik.http.Routers.Super.Rule: Host(`my.super.host`) traefik.http.Routers.Super.Rule: Host(`my.super.host`)
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375 traefik.http.Services.powpow.LoadBalancer.server.Port: 2375
networks:
default:
name: traefik-test-network
external: true

View file

@ -1,12 +1,7 @@
version: "3.8" version: "3.8"
services: services:
nginx1: nginx1:
image: nginx:1.13.8-alpine image: nginx:1.25.3-alpine3.18
nginx2: nginx2:
image: nginx:1.13.8-alpine image: nginx:1.25.3-alpine3.18
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,9 +2,9 @@ version: "3.8"
services: services:
etcd: etcd:
image: quay.io/coreos/etcd:v3.3.18 image: quay.io/coreos/etcd:v3.3.18
command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2380 command:
- etcd
networks: - --listen-client-urls
default: - http://0.0.0.0:2379
name: traefik-test-network - --advertise-client-urls
external: true - http://0.0.0.0:2380

View file

@ -14,8 +14,3 @@ services:
whoami5: whoami5:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -11,8 +11,3 @@ services:
whoami4: whoami4:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -6,8 +6,3 @@ services:
traefik.enable: true traefik.enable: true
traefik.http.services.service1.loadbalancer.server.port: 80 traefik.http.services.service1.loadbalancer.server.port: 80
traefik.http.routers.router1.rule: Host(`github.com`) traefik.http.routers.router1.rule: Host(`github.com`)
networks:
default:
name: traefik-test-network
external: true

View file

@ -1,10 +1,24 @@
version: "3.8" version: "3.8"
services: services:
server: server:
image: rancher/k3s:v1.23.17-k3s1 image: rancher/k3s:v1.20.15-k3s1
command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log --bind-address=server --tls-san=server privileged: true
command:
- server
- --disable-agent
- --disable=coredns
- --disable=servicelb
- --disable=traefik
- --disable=local-storage
- --disable=metrics-server
- --log=/output/k3s.log
- --bind-address=server
- --tls-san=server
- --tls-san=172.31.42.3
- --tls-san=172.31.42.4
environment: environment:
K3S_CLUSTER_SECRET: somethingtotallyrandom K3S_CLUSTER_SECRET: somethingtotallyrandom
K3S_TOKEN: somethingtotallyrandom
K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml
K3S_KUBECONFIG_MODE: 666 K3S_KUBECONFIG_MODE: 666
volumes: volumes:
@ -12,13 +26,9 @@ services:
- ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests - ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests
node: node:
image: rancher/k3s:v1.23.17-k3s1 image: rancher/k3s:v1.20.15-k3s1
privileged: true privileged: true
environment: environment:
K3S_TOKEN: somethingtotallyrandom
K3S_URL: https://server:6443 K3S_URL: https://server:6443
K3S_CLUSTER_SECRET: somethingtotallyrandom K3S_CLUSTER_SECRET: somethingtotallyrandom
networks:
default:
name: traefik-test-network
external: true

View file

@ -3,12 +3,9 @@ services:
whoami1: whoami1:
image: traefik/whoami image: traefik/whoami
labels: labels:
traefik.http.Routers.RouterMini.Rule: PathPrefix(`/whoami`) traefik.http.routers.router-mini.Rule: PathPrefix(`/whoami`)
traefik.http.routers.router-mini.service: service-mini
traefik.http.services.service-mini.loadbalancer.server.port: 80
traefik.enable: true traefik.enable: true
deploy: deploy:
replicas: 2 replicas: 2
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,14 +2,12 @@ version: "3.8"
services: services:
pebble: pebble:
image: letsencrypt/pebble:v2.3.1 image: letsencrypt/pebble:v2.3.1
command: pebble --dnsserver traefik:5053 command:
- pebble
- --dnsserver
- host.docker.internal:5053
environment: environment:
# https://github.com/letsencrypt/pebble#testing-at-full-speed # https://github.com/letsencrypt/pebble#testing-at-full-speed
PEBBLE_VA_NOSLEEP: 1 PEBBLE_VA_NOSLEEP: 1
# https://github.com/letsencrypt/pebble#invalid-anti-replay-nonce-errors # https://github.com/letsencrypt/pebble#invalid-anti-replay-nonce-errors
PEBBLE_WFE_NONCEREJECT: 0 PEBBLE_WFE_NONCEREJECT: 0
networks:
default:
name: traefik-test-network
external: true

View file

@ -1,14 +1,4 @@
version: "3.8" version: "3.8"
services: services:
haproxy:
image: haproxy:2.2
volumes:
- ./resources/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
whoami: whoami:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
whoami1: whoami1:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
redis: redis:
image: redis:5.0 image: redis:5.0
networks:
default:
name: traefik-test-network
external: true

View file

@ -3,59 +3,51 @@ services:
master: master:
image: redis image: redis
container_name: redis-master container_name: redis-master
command: redis-server --port 6380 command:
ports: - redis-server
- 6380:6380 - --port
healthcheck: - 6380
test: redis-cli -p 6380 ping
node1: node1:
image: redis image: redis
container_name: redis-node-1 container_name: redis-node-1
ports: command:
- 6381:6381 - redis-server
command: redis-server --port 6381 --slaveof redis-master 6380 - --port
healthcheck: - 6381
test: redis-cli -p 6381 ping - --slaveof
- redis-master
- 6380
node2: node2:
image: redis image: redis
container_name: redis-node-2 container_name: redis-node-2
ports: command:
- 6382:6382 - redis-server
command: redis-server --port 6382 --slaveof redis-master 6380 - --port
healthcheck: - 6382
test: redis-cli -p 6382 ping - --slaveof
- redis-master
- 6380
sentinel1: sentinel1:
image: redis image: redis
container_name: redis-sentinel-1 container_name: redis-sentinel-1
ports: command:
- 26379:26379 - redis-sentinel
command: redis-sentinel /usr/local/etc/redis/conf/sentinel1.conf - /usr/local/etc/redis/conf/sentinel1.conf
healthcheck:
test: redis-cli -p 26379 ping
volumes: volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf - ./resources/compose/config:/usr/local/etc/redis/conf
sentinel2: sentinel2:
image: redis image: redis
container_name: redis-sentinel-2 container_name: redis-sentinel-2
ports: command:
- 36379:26379 - redis-sentinel
command: redis-sentinel /usr/local/etc/redis/conf/sentinel2.conf - /usr/local/etc/redis/conf/sentinel2.conf
healthcheck:
test: redis-cli -p 36379 ping
volumes: volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf - ./resources/compose/config:/usr/local/etc/redis/conf
sentinel3: sentinel3:
image: redis image: redis
container_name: redis-sentinel-3 container_name: redis-sentinel-3
ports: command:
- 46379:26379 - redis-sentinel
command: redis-sentinel /usr/local/etc/redis/conf/sentinel3.conf - /usr/local/etc/redis/conf/sentinel3.conf
healthcheck:
test: redis-cli -p 46379 ping
volumes: volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf - ./resources/compose/config:/usr/local/etc/redis/conf
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
whoami: whoami:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
whoami1: whoami1:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
whoami: whoami:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -5,8 +5,3 @@ services:
whoami2: whoami2:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -9,9 +9,5 @@ services:
cap_add: # Required for tailscale to work cap_add: # Required for tailscale to work
- net_admin - net_admin
- sys_module - sys_module
command: tailscaled command:
- tailscaled
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,38 +2,58 @@ version: "3.8"
services: services:
whoami-a: whoami-a:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-a -certFile /certs/whoami-a.crt -keyFile /certs/whoami-a.key command:
- -name
- whoami-a
- -certFile
- /certs/whoami-a.crt
- -keyFile
- /certs/whoami-a.key
volumes: volumes:
- ./fixtures/tcp:/certs - ./fixtures/tcp:/certs
whoami-b: whoami-b:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-b -certFile /certs/whoami-b.crt -keyFile /certs/whoami-b.key command:
- -name
- whoami-b
- -certFile
- /certs/whoami-b.crt
- -keyFile
- /certs/whoami-b.key
volumes: volumes:
- ./fixtures/tcp:/certs - ./fixtures/tcp:/certs
whoami-ab: whoami-ab:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-ab -certFile /certs/whoami-b.crt -keyFile /certs/whoami-b.key command:
- -name
- whoami-ab
- -certFile
- /certs/whoami-b.crt
- -keyFile
- /certs/whoami-b.key
volumes: volumes:
- ./fixtures/tcp:/certs - ./fixtures/tcp:/certs
whoami-no-cert: whoami-no-cert:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-no-cert command:
- -name
- whoami-no-cert
whoami-no-tls: whoami-no-tls:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-no-tls command:
- -name
- whoami-no-tls
whoami: whoami:
image: traefik/whoami image: traefik/whoami
whoami-banner: whoami-banner:
image: traefik/whoamitcp image: traefik/whoamitcp
command: -name whoami-banner --banner command:
- -name
networks: - whoami-banner
default: - --banner
name: traefik-test-network
external: true

View file

@ -5,8 +5,3 @@ services:
environment: environment:
PROTO: http PROTO: http
PORT: 9000 PORT: 9000
networks:
default:
name: traefik-test-network
external: true

View file

@ -7,8 +7,3 @@ services:
traefik.http.routers.route1.middlewares: passtls traefik.http.routers.route1.middlewares: passtls
traefik.http.routers.route1.tls: true traefik.http.routers.route1.tls: true
traefik.http.middlewares.passtls.passtlsclientcert.pem: true traefik.http.middlewares.passtls.passtlsclientcert.pem: true
networks:
default:
name: traefik-test-network
external: true

View file

@ -12,8 +12,3 @@ services:
- ./fixtures/tracing/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml - ./fixtures/tracing/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
whoami: whoami:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,20 +2,21 @@ version: "3.8"
services: services:
whoami-a: whoami-a:
image: traefik/whoamiudp:latest image: traefik/whoamiudp:latest
command: -name whoami-a command:
- -name
- whoami-a
whoami-b: whoami-b:
image: traefik/whoamiudp:latest image: traefik/whoamiudp:latest
command: -name whoami-b command:
- -name
- whoami-b
whoami-c: whoami-c:
image: traefik/whoamiudp:latest image: traefik/whoamiudp:latest
command: -name whoami-c command:
- -name
- whoami-c
whoami-d: whoami-d:
image: traefik/whoami image: traefik/whoami
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,8 +2,3 @@ version: "3.8"
services: services:
zookeeper: zookeeper:
image: zookeeper:3.5 image: zookeeper:3.5
networks:
default:
name: traefik-test-network
external: true

View file

@ -1,30 +0,0 @@
global
maxconn 4096
defaults
log global
mode http
retries 3
option redispatch
maxconn 2000
timeout connect 5000
timeout client 50000
timeout server 50000
frontend TestServerTest
bind 0.0.0.0:80
mode tcp
default_backend TestServerNodes
frontend TestServerTestV2
bind 0.0.0.0:81
mode tcp
default_backend TestServerNodesV2
backend TestServerNodes
mode tcp
server TestServer01 traefik:8000 send-proxy
backend TestServerNodesV2
mode tcp
server TestServer01 traefik:8000 send-proxy-v2

View file

@ -5,14 +5,15 @@ import (
"encoding/json" "encoding/json"
"net" "net"
"net/http" "net/http"
"os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
checker "github.com/vdemeester/shakers"
) )
type RestSuite struct { type RestSuite struct {
@ -20,28 +21,33 @@ type RestSuite struct {
whoamiAddr string whoamiAddr string
} }
func (s *RestSuite) SetUpSuite(c *check.C) { func TestRestSuite(t *testing.T) {
s.createComposeProject(c, "rest") suite.Run(t, new(RestSuite))
s.composeUp(c)
s.whoamiAddr = net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80")
} }
func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) { func (s *RestSuite) SetupSuite() {
cmd, display := s.traefikCmd(withConfigFile("fixtures/rest/simple.toml")) s.BaseSuite.SetupSuite()
defer display(c) s.createComposeProject("rest")
err := cmd.Start() s.composeUp()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd) s.whoamiAddr = net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80")
}
func (s *RestSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *RestSuite) TestSimpleConfigurationInsecure() {
s.traefikCmd(withConfigFile("fixtures/rest/simple.toml"))
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Expected a 404 as we did not configure anything. // Expected a 404 as we did not configure anything.
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
testCase := []struct { testCase := []struct {
desc string desc string
@ -105,47 +111,41 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
data, err := json.Marshal(test.config) data, err := json.Marshal(test.config)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data)) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, response.StatusCode)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 3*time.Second, try.BodyContains(test.ruleMatch)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 3*time.Second, try.BodyContains(test.ruleMatch))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }
func (s *RestSuite) TestSimpleConfiguration(c *check.C) { func (s *RestSuite) TestSimpleConfiguration() {
file := s.adaptFile(c, "fixtures/rest/simple_secure.toml", struct{}{}) file := s.adaptFile("fixtures/rest/simple_secure.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// Expected a 404 as we did not configure anything. // Expected a 404 as we did not configure anything.
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2000*time.Millisecond, try.BodyContains("PathPrefix(`/secure`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2000*time.Millisecond, try.BodyContains("PathPrefix(`/secure`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", strings.NewReader("{}")) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", strings.NewReader("{}"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusNotFound) assert.Equal(s.T(), http.StatusNotFound, response.StatusCode)
testCase := []struct { testCase := []struct {
desc string desc string
@ -209,19 +209,19 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) {
for _, test := range testCase { for _, test := range testCase {
data, err := json.Marshal(test.config) data, err := json.Marshal(test.config)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/secure/api/providers/rest", bytes.NewReader(data)) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/secure/api/providers/rest", bytes.NewReader(data))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, response.StatusCode)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains(test.ruleMatch)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains(test.ruleMatch))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
} }

View file

@ -3,13 +3,14 @@ package integration
import ( import (
"io" "io"
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type RetrySuite struct { type RetrySuite struct {
@ -17,96 +18,86 @@ type RetrySuite struct {
whoamiIP string whoamiIP string
} }
func (s *RetrySuite) SetUpSuite(c *check.C) { func TestRetrySuite(t *testing.T) {
s.createComposeProject(c, "retry") suite.Run(t, new(RetrySuite))
s.composeUp(c)
s.whoamiIP = s.getComposeServiceIP(c, "whoami")
} }
func (s *RetrySuite) TestRetry(c *check.C) { func (s *RetrySuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) s.BaseSuite.SetupSuite()
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.createComposeProject("retry")
defer display(c) s.composeUp()
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) s.whoamiIP = s.getComposeServiceIP("whoami")
c.Assert(err, checker.IsNil) }
func (s *RetrySuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *RetrySuite) TestRetry() {
file := s.adaptFile("fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
require.NoError(s.T(), err)
response, err := http.Get("http://127.0.0.1:8000/") response, err := http.Get("http://127.0.0.1:8000/")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// The test only verifies that the retry middleware makes sure that the working service is eventually reached. // The test only verifies that the retry middleware makes sure that the working service is eventually reached.
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, response.StatusCode)
} }
func (s *RetrySuite) TestRetryBackoff(c *check.C) { func (s *RetrySuite) TestRetryBackoff() {
file := s.adaptFile(c, "fixtures/retry/backoff.toml", struct{ WhoamiIP string }{s.whoamiIP}) file := s.adaptFile("fixtures/retry/backoff.toml", struct{ WhoamiIP string }{s.whoamiIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.Get("http://127.0.0.1:8000/") response, err := http.Get("http://127.0.0.1:8000/")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// The test only verifies that the retry middleware allows finally to reach the working service. // The test only verifies that the retry middleware allows finally to reach the working service.
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) assert.Equal(s.T(), http.StatusOK, response.StatusCode)
} }
func (s *RetrySuite) TestRetryWebsocket(c *check.C) { func (s *RetrySuite) TestRetryWebsocket() {
file := s.adaptFile(c, "fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) file := s.adaptFile("fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// The test only verifies that the retry middleware makes sure that the working service is eventually reached. // The test only verifies that the retry middleware makes sure that the working service is eventually reached.
_, response, err := websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil) _, response, err := websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusSwitchingProtocols) assert.Equal(s.T(), http.StatusSwitchingProtocols, response.StatusCode)
// The test verifies a second time that the working service is eventually reached. // The test verifies a second time that the working service is eventually reached.
_, response, err = websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil) _, response, err = websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusSwitchingProtocols) assert.Equal(s.T(), http.StatusSwitchingProtocols, response.StatusCode)
} }
func (s *RetrySuite) TestRetryWithStripPrefix(c *check.C) { func (s *RetrySuite) TestRetryWithStripPrefix() {
file := s.adaptFile(c, "fixtures/retry/strip_prefix.toml", struct{ WhoamiIP string }{s.whoamiIP}) file := s.adaptFile("fixtures/retry/strip_prefix.toml", struct{ WhoamiIP string }{s.whoamiIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
response, err := http.Get("http://127.0.0.1:8000/test") response, err := http.Get("http://127.0.0.1:8000/test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(body), checker.Contains, "GET / HTTP/1.1") assert.Contains(s.T(), string(body), "GET / HTTP/1.1")
c.Assert(string(body), checker.Contains, "X-Forwarded-Prefix: /test") assert.Contains(s.T(), string(body), "X-Forwarded-Prefix: /test")
} }

File diff suppressed because it is too large Load diff

View file

@ -5,63 +5,69 @@ import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type TCPSuite struct{ BaseSuite } type TCPSuite struct{ BaseSuite }
func (s *TCPSuite) SetUpSuite(c *check.C) { func TestTCPSuite(t *testing.T) {
s.createComposeProject(c, "tcp") suite.Run(t, new(TCPSuite))
s.composeUp(c)
} }
func (s *TCPSuite) TestMixed(c *check.C) { func (s *TCPSuite) SetupSuite() {
file := s.adaptFile(c, "fixtures/tcp/mixed.toml", struct { s.BaseSuite.SetupSuite()
s.createComposeProject("tcp")
s.composeUp()
}
func (s *TCPSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *TCPSuite) TestMixed() {
file := s.adaptFile("fixtures/tcp/mixed.toml", struct {
Whoami string Whoami string
WhoamiA string WhoamiA string
WhoamiB string WhoamiB string
WhoamiNoCert string WhoamiNoCert string
}{ }{
Whoami: "http://" + s.getComposeServiceIP(c, "whoami") + ":80", Whoami: "http://" + s.getComposeServiceIP("whoami") + ":80",
WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080",
WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080",
WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)"))
c.Assert(err, checker.IsNil)
// Traefik passes through, termination handled by whoami-a // Traefik passes through, termination handled by whoami-a
out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-a") assert.Contains(s.T(), out, "whoami-a")
// Traefik passes through, termination handled by whoami-b // Traefik passes through, termination handled by whoami-b
out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-b") assert.Contains(s.T(), out, "whoami-b")
// Termination handled by traefik // Termination handled by traefik
out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true) out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-cert") assert.Contains(s.T(), out, "whoami-no-cert")
tr1 := &http.Transport{ tr1 := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
@ -69,174 +75,143 @@ func (s *TCPSuite) TestMixed(c *check.C) {
}, },
} }
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/whoami/", nil) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/whoami/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusOK)) err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/not-found/", nil) req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/not-found/", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusNotFound)) err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8093/test", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8093/test", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8093/not-found", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8093/not-found", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *TCPSuite) TestTLSOptions(c *check.C) { func (s *TCPSuite) TestTLSOptions() {
file := s.adaptFile(c, "fixtures/tcp/multi-tls-options.toml", struct { file := s.adaptFile("fixtures/tcp/multi-tls-options.toml", struct {
WhoamiNoCert string WhoamiNoCert string
}{ }{
WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-c.test`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-c.test`)"))
c.Assert(err, checker.IsNil)
// Check that we can use a client tls version <= 1.2 with hostSNI 'whoami-c.test' // Check that we can use a client tls version <= 1.2 with hostSNI 'whoami-c.test'
out, err := guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-c.test", true, tls.VersionTLS12) out, err := guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-c.test", true, tls.VersionTLS12)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-cert") assert.Contains(s.T(), out, "whoami-no-cert")
// Check that we can use a client tls version <= 1.3 with hostSNI 'whoami-d.test' // Check that we can use a client tls version <= 1.3 with hostSNI 'whoami-d.test'
out, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS13) out, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS13)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-cert") assert.Contains(s.T(), out, "whoami-no-cert")
// Check that we cannot use a client tls version <= 1.2 with hostSNI 'whoami-d.test' // Check that we cannot use a client tls version <= 1.2 with hostSNI 'whoami-d.test'
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12) _, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
c.Assert(err, checker.NotNil) assert.ErrorContains(s.T(), err, "protocol version not supported")
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
// Check that we can't reach a route with an invalid mTLS configuration. // Check that we can't reach a route with an invalid mTLS configuration.
conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{ conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
ServerName: "whoami-i.test", ServerName: "whoami-i.test",
InsecureSkipVerify: true, InsecureSkipVerify: true,
}) })
c.Assert(conn, checker.IsNil) assert.Nil(s.T(), conn)
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
} }
func (s *TCPSuite) TestNonTLSFallback(c *check.C) { func (s *TCPSuite) TestNonTLSFallback() {
file := s.adaptFile(c, "fixtures/tcp/non-tls-fallback.toml", struct { file := s.adaptFile("fixtures/tcp/non-tls-fallback.toml", struct {
WhoamiA string WhoamiA string
WhoamiB string WhoamiB string
WhoamiNoCert string WhoamiNoCert string
WhoamiNoTLS string WhoamiNoTLS string
}{ }{
WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080",
WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080",
WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080",
WhoamiNoTLS: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", WhoamiNoTLS: s.getComposeServiceIP("whoami-no-tls") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil)
// Traefik passes through, termination handled by whoami-a // Traefik passes through, termination handled by whoami-a
out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-a") assert.Contains(s.T(), out, "whoami-a")
// Traefik passes through, termination handled by whoami-b // Traefik passes through, termination handled by whoami-b
out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-b") assert.Contains(s.T(), out, "whoami-b")
// Termination handled by traefik // Termination handled by traefik
out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true) out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-cert") assert.Contains(s.T(), out, "whoami-no-cert")
out, err = guessWho("127.0.0.1:8093", "", false) out, err = guessWho("127.0.0.1:8093", "", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-tls") assert.Contains(s.T(), out, "whoami-no-tls")
} }
func (s *TCPSuite) TestNonTlsTcp(c *check.C) { func (s *TCPSuite) TestNonTlsTcp() {
file := s.adaptFile(c, "fixtures/tcp/non-tls.toml", struct { file := s.adaptFile("fixtures/tcp/non-tls.toml", struct {
WhoamiNoTLS string WhoamiNoTLS string
}{ }{
WhoamiNoTLS: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", WhoamiNoTLS: s.getComposeServiceIP("whoami-no-tls") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil)
// Traefik will forward every requests on the given port to whoami-no-tls // Traefik will forward every requests on the given port to whoami-no-tls
out, err := guessWho("127.0.0.1:8093", "", false) out, err := guessWho("127.0.0.1:8093", "", false)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-no-tls") assert.Contains(s.T(), out, "whoami-no-tls")
} }
func (s *TCPSuite) TestCatchAllNoTLS(c *check.C) { func (s *TCPSuite) TestCatchAllNoTLS() {
file := s.adaptFile(c, "fixtures/tcp/catch-all-no-tls.toml", struct { file := s.adaptFile("fixtures/tcp/catch-all-no-tls.toml", struct {
WhoamiBannerAddress string WhoamiBannerAddress string
}{ }{
WhoamiBannerAddress: s.getComposeServiceIP(c, "whoami-banner") + ":8080", WhoamiBannerAddress: s.getComposeServiceIP("whoami-banner") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil)
// Traefik will forward every requests on the given port to whoami-no-tls // Traefik will forward every requests on the given port to whoami-no-tls
out, err := welcome("127.0.0.1:8093") out, err := welcome("127.0.0.1:8093")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "Welcome") assert.Contains(s.T(), out, "Welcome")
} }
func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS(c *check.C) { func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS() {
file := s.adaptFile(c, "fixtures/tcp/catch-all-no-tls-with-https.toml", struct { file := s.adaptFile("fixtures/tcp/catch-all-no-tls-with-https.toml", struct {
WhoamiNoTLSAddress string WhoamiNoTLSAddress string
WhoamiURL string WhoamiURL string
}{ }{
WhoamiNoTLSAddress: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", WhoamiNoTLSAddress: s.getComposeServiceIP("whoami-no-tls") + ":8080",
WhoamiURL: "http://" + s.getComposeServiceIP(c, "whoami") + ":80", WhoamiURL: "http://" + s.getComposeServiceIP("whoami") + ":80",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)"))
c.Assert(err, checker.IsNil)
req := httptest.NewRequest(http.MethodGet, "https://127.0.0.1:8093/test", nil) req := httptest.NewRequest(http.MethodGet, "https://127.0.0.1:8093/test", nil)
req.RequestURI = "" req.RequestURI = ""
@ -246,64 +221,52 @@ func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS(c *check.C) {
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
}, try.StatusCodeIs(http.StatusOK)) }, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *TCPSuite) TestMiddlewareAllowList(c *check.C) { func (s *TCPSuite) TestMiddlewareAllowList() {
file := s.adaptFile(c, "fixtures/tcp/ipallowlist.toml", struct { file := s.adaptFile("fixtures/tcp/ip-allowlist.toml", struct {
WhoamiA string WhoamiA string
WhoamiB string WhoamiB string
}{ }{
WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080",
WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)"))
c.Assert(err, checker.IsNil)
// Traefik not passes through, ipAllowList closes connection // Traefik not passes through, ipAllowList closes connection
_, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") _, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test")
c.Assert(err, checker.ErrorMatches, "EOF") assert.ErrorIs(s.T(), err, io.EOF)
// Traefik passes through, termination handled by whoami-b // Traefik passes through, termination handled by whoami-b
out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(out, checker.Contains, "whoami-b") assert.Contains(s.T(), out, "whoami-b")
} }
func (s *TCPSuite) TestWRR(c *check.C) { func (s *TCPSuite) TestWRR() {
file := s.adaptFile(c, "fixtures/tcp/wrr.toml", struct { file := s.adaptFile("fixtures/tcp/wrr.toml", struct {
WhoamiB string WhoamiB string
WhoamiAB string WhoamiAB string
}{ }{
WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080",
WhoamiAB: s.getComposeServiceIP(c, "whoami-ab") + ":8080", WhoamiAB: s.getComposeServiceIP("whoami-ab") + ":8080",
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-b.test`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-b.test`)"))
c.Assert(err, checker.IsNil)
call := map[string]int{} call := map[string]int{}
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
// Traefik passes through, termination handled by whoami-b or whoami-bb // Traefik passes through, termination handled by whoami-b or whoami-bb
out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
switch { switch {
case strings.Contains(out, "whoami-b"): case strings.Contains(out, "whoami-b"):
call["whoami-b"]++ call["whoami-b"]++
@ -315,7 +278,7 @@ func (s *TCPSuite) TestWRR(c *check.C) {
time.Sleep(time.Second) time.Sleep(time.Second)
} }
c.Assert(call, checker.DeepEquals, map[string]int{"whoami-b": 3, "whoami-ab": 1}) assert.EqualValues(s.T(), call, map[string]int{"whoami-b": 3, "whoami-ab": 1})
} }
func welcome(addr string) (string, error) { func welcome(addr string) (string, error) {

View file

@ -4,47 +4,53 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"os" "testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type TimeoutSuite struct{ BaseSuite } type TimeoutSuite struct{ BaseSuite }
func (s *TimeoutSuite) SetUpSuite(c *check.C) { func TestTimeoutSuite(t *testing.T) {
s.createComposeProject(c, "timeout") suite.Run(t, new(TimeoutSuite))
s.composeUp(c)
} }
func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) { func (s *TimeoutSuite) SetupSuite() {
timeoutEndpointIP := s.getComposeServiceIP(c, "timeoutEndpoint") s.BaseSuite.SetupSuite()
file := s.adaptFile(c, "fixtures/timeout/forwarding_timeouts.toml", struct{ TimeoutEndpoint string }{timeoutEndpointIP})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.createComposeProject("timeout")
defer display(c) s.composeUp()
err := cmd.Start() }
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)")) func (s *TimeoutSuite) TearDownSuite() {
c.Assert(err, checker.IsNil) s.BaseSuite.TearDownSuite()
}
func (s *TimeoutSuite) TestForwardingTimeouts() {
timeoutEndpointIP := s.getComposeServiceIP("timeoutEndpoint")
file := s.adaptFile("fixtures/timeout/forwarding_timeouts.toml", struct{ TimeoutEndpoint string }{timeoutEndpointIP})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)"))
require.NoError(s.T(), err)
// This simulates a DialTimeout when connecting to the backend server. // This simulates a DialTimeout when connecting to the backend server.
response, err := http.Get("http://127.0.0.1:8000/dialTimeout") response, err := http.Get("http://127.0.0.1:8000/dialTimeout")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout) assert.Equal(s.T(), http.StatusGatewayTimeout, response.StatusCode)
// Check that timeout service is available // Check that timeout service is available
statusURL := fmt.Sprintf("http://%s/statusTest?status=200", statusURL := fmt.Sprintf("http://%s/statusTest?status=200",
net.JoinHostPort(timeoutEndpointIP, "9000")) net.JoinHostPort(timeoutEndpointIP, "9000"))
c.Assert(try.GetRequest(statusURL, 60*time.Second, try.StatusCodeIs(http.StatusOK)), checker.IsNil) assert.NoError(s.T(), try.GetRequest(statusURL, 60*time.Second, try.StatusCodeIs(http.StatusOK)))
// This simulates a ResponseHeaderTimeout. // This simulates a ResponseHeaderTimeout.
response, err = http.Get("http://127.0.0.1:8000/responseHeaderTimeout?sleep=1000") response, err = http.Get("http://127.0.0.1:8000/responseHeaderTimeout?sleep=1000")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout) assert.Equal(s.T(), http.StatusGatewayTimeout, response.StatusCode)
} }

View file

@ -4,11 +4,13 @@ import (
"crypto/tls" "crypto/tls"
"net/http" "net/http"
"os" "os"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
const ( const (
@ -19,20 +21,30 @@ const (
type TLSClientHeadersSuite struct{ BaseSuite } type TLSClientHeadersSuite struct{ BaseSuite }
func (s *TLSClientHeadersSuite) SetUpSuite(c *check.C) { func TestTLSClientHeadersSuite(t *testing.T) {
s.createComposeProject(c, "tlsclientheaders") suite.Run(t, new(TLSClientHeadersSuite))
s.composeUp(c)
} }
func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { func (s *TLSClientHeadersSuite) SetupSuite() {
rootCertContent, err := os.ReadFile(rootCertPath) s.BaseSuite.SetupSuite()
c.Assert(err, check.IsNil)
serverCertContent, err := os.ReadFile(certPemPath)
c.Assert(err, check.IsNil)
ServerKeyContent, err := os.ReadFile(certKeyPath)
c.Assert(err, check.IsNil)
file := s.adaptFile(c, "fixtures/tlsclientheaders/simple.toml", struct { s.createComposeProject("tlsclientheaders")
s.composeUp()
}
func (s *TLSClientHeadersSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
}
func (s *TLSClientHeadersSuite) TestTLSClientHeaders() {
rootCertContent, err := os.ReadFile(rootCertPath)
assert.NoError(s.T(), err)
serverCertContent, err := os.ReadFile(certPemPath)
assert.NoError(s.T(), err)
ServerKeyContent, err := os.ReadFile(certKeyPath)
assert.NoError(s.T(), err)
file := s.adaptFile("fixtures/tlsclientheaders/simple.toml", struct {
RootCertContent string RootCertContent string
ServerCertContent string ServerCertContent string
ServerKeyContent string ServerKeyContent string
@ -41,22 +53,17 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) {
ServerCertContent: string(serverCertContent), ServerCertContent: string(serverCertContent),
ServerKeyContent: string(ServerKeyContent), ServerKeyContent: string(ServerKeyContent),
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err = cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("PathPrefix(`/foo`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("PathPrefix(`/foo`)"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443/foo", nil) request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443/foo", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
certificate, err := tls.LoadX509KeyPair(certPemPath, certKeyPath) certificate, err := tls.LoadX509KeyPair(certPemPath, certKeyPath)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
@ -66,5 +73,5 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) {
} }
err = try.RequestWithTransport(request, 2*time.Second, tr, try.BodyContains("Forwarded-Tls-Client-Cert: MIIDNTCCAh0CFD0QQcHXUJuKwMBYDA+bBExVSP26MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazENMAsGA1UEAwwEcm9vdDAeFw0yMTAxMDgxNzQ0MjRaFw0zMTAxMDYxNzQ0MjRaMFgxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazEPMA0GA1UEAwwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYK2z8gLPOfFLgXNWP2460aeJ9vrH47x/lhKLlv4amSDHDx8Cmz/6blOUM8XOfMRW1xx++AgChWN9dx/kf7G2xlA5grZxRvUQ6xj7AvFG9TQUA3muNh2hvm9c3IjaZBNKH27bRKuDIBvZBvXdX4NL/aaFy7w7v7IKxk8j4WkfB23sgyH43g4b7NqKHJugZiedFu5GALmtLbShVOFbjWcre7Wvatdw8dIBmiFJqZQT3UjIuGAgqczIShtLxo4V+XyVkIPmzfPrRV+4zoMFIFOIaj3syyxb4krPBtxhe7nz2cWvvq0wePB2y4YbAAoVY8NYpd5JsMFwZtG6Uk59ygv4QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDaPg69wNeFNFisfBJTrscqVCTW+B80gMhpLdxXD+KO0/Wgc5xpB/wLSirNtRQyxAa3+EEcIwJv/wdh8EyjlDLSpFm/8ghntrKhkOfIOPDFE41M5HNfx/Fuh5btKEenOL/XdapqtNUt2ZE4RrsfbL79sPYepa9kDUVi2mCbeH5ollZ0MDU68HpB2YwHbCEuQNk5W3pjYK2NaDkVnxTkfEDM1k+3QydO1lqB5JJmcrs59BEveTqaJ3eeh/0I4OOab6OkTTZ0JNjJp1573oxO+fce/bfGud8xHY5gSN9huU7U6RsgvO7Dhmal/sDNl8XC8oU90hVDVXZdA7ewh4jjaoIv")) err = try.RequestWithTransport(request, 2*time.Second, tr, try.BodyContains("Forwarded-Tls-Client-Cert: MIIDNTCCAh0CFD0QQcHXUJuKwMBYDA+bBExVSP26MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazENMAsGA1UEAwwEcm9vdDAeFw0yMTAxMDgxNzQ0MjRaFw0zMTAxMDYxNzQ0MjRaMFgxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazEPMA0GA1UEAwwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYK2z8gLPOfFLgXNWP2460aeJ9vrH47x/lhKLlv4amSDHDx8Cmz/6blOUM8XOfMRW1xx++AgChWN9dx/kf7G2xlA5grZxRvUQ6xj7AvFG9TQUA3muNh2hvm9c3IjaZBNKH27bRKuDIBvZBvXdX4NL/aaFy7w7v7IKxk8j4WkfB23sgyH43g4b7NqKHJugZiedFu5GALmtLbShVOFbjWcre7Wvatdw8dIBmiFJqZQT3UjIuGAgqczIShtLxo4V+XyVkIPmzfPrRV+4zoMFIFOIaj3syyxb4krPBtxhe7nz2cWvvq0wePB2y4YbAAoVY8NYpd5JsMFwZtG6Uk59ygv4QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDaPg69wNeFNFisfBJTrscqVCTW+B80gMhpLdxXD+KO0/Wgc5xpB/wLSirNtRQyxAa3+EEcIwJv/wdh8EyjlDLSpFm/8ghntrKhkOfIOPDFE41M5HNfx/Fuh5btKEenOL/XdapqtNUt2ZE4RrsfbL79sPYepa9kDUVi2mCbeH5ollZ0MDU68HpB2YwHbCEuQNk5W3pjYK2NaDkVnxTkfEDM1k+3QydO1lqB5JJmcrs59BEveTqaJ3eeh/0I4OOab6OkTTZ0JNjJp1573oxO+fce/bfGud8xHY5gSN9huU7U6RsgvO7Dhmal/sDNl8XC8oU90hVDVXZdA7ewh4jjaoIv"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }

View file

@ -7,12 +7,14 @@ import (
"net/url" "net/url"
"os" "os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type TracingSuite struct { type TracingSuite struct {
@ -23,6 +25,10 @@ type TracingSuite struct {
otelCollectorIP string otelCollectorIP string
} }
func TestTracingSuite(t *testing.T) {
suite.Run(t, new(TracingSuite))
}
type TracingTemplate struct { type TracingTemplate struct {
WhoamiIP string WhoamiIP string
WhoamiPort int WhoamiPort int
@ -31,59 +37,60 @@ type TracingTemplate struct {
IsHTTP bool IsHTTP bool
} }
func (s *TracingSuite) SetUpSuite(c *check.C) { func (s *TracingSuite) SetupSuite() {
s.createComposeProject(c, "tracing") s.BaseSuite.SetupSuite()
s.composeUp(c)
}
func (s *TracingSuite) SetUpTest(c *check.C) { s.createComposeProject("tracing")
s.composeUp(c, "tempo", "otel-collector", "whoami") s.composeUp()
s.whoamiIP = s.getComposeServiceIP(c, "whoami") s.whoamiIP = s.getComposeServiceIP("whoami")
s.whoamiPort = 80 s.whoamiPort = 80
// Wait for whoami to turn ready. // Wait for whoami to turn ready.
err := try.GetRequest("http://"+s.whoamiIP+":80", 30*time.Second, try.StatusCodeIs(http.StatusOK)) err := try.GetRequest("http://"+s.whoamiIP+":80", 30*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
s.tempoIP = s.getComposeServiceIP(c, "tempo") s.otelCollectorIP = s.getComposeServiceIP("otel-collector")
// Wait for tempo to turn ready.
err = try.GetRequest("http://"+s.tempoIP+":3200/ready", 30*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
s.otelCollectorIP = s.getComposeServiceIP(c, "otel-collector")
// Wait for otel collector to turn ready. // Wait for otel collector to turn ready.
err = try.GetRequest("http://"+s.otelCollectorIP+":13133/", 30*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://"+s.otelCollectorIP+":13133/", 30*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *TracingSuite) TearDownTest(c *check.C) { func (s *TracingSuite) TearDownSuite() {
s.composeStop(c, "tempo") s.BaseSuite.TearDownSuite()
} }
func (s *TracingSuite) TestOpentelemetryBasic_HTTP(c *check.C) { func (s *TracingSuite) SetupTest() {
file := s.adaptFile(c, "fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ s.composeUp("tempo")
s.tempoIP = s.getComposeServiceIP("tempo")
// Wait for tempo to turn ready.
err := try.GetRequest("http://"+s.tempoIP+":3200/ready", 30*time.Second, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
}
func (s *TracingSuite) TearDownTest() {
s.composeStop("tempo")
}
func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort, WhoamiPort: s.whoamiPort,
IP: s.otelCollectorIP, IP: s.otelCollectorIP,
IsHTTP: true, IsHTTP: true,
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contains := []map[string]string{ contains := []map[string]string{
{ {
@ -113,11 +120,11 @@ func (s *TracingSuite) TestOpentelemetryBasic_HTTP(c *check.C) {
}, },
} }
checkTraceContent(c, s.tempoIP, contains) s.checkTraceContent(contains)
} }
func (s *TracingSuite) TestOpentelemetryBasic_gRPC(c *check.C) { func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
file := s.adaptFile(c, "fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort, WhoamiPort: s.whoamiPort,
IP: s.otelCollectorIP, IP: s.otelCollectorIP,
@ -125,18 +132,14 @@ func (s *TracingSuite) TestOpentelemetryBasic_gRPC(c *check.C) {
}) })
defer os.Remove(file) defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contains := []map[string]string{ contains := []map[string]string{
{ {
@ -166,51 +169,47 @@ func (s *TracingSuite) TestOpentelemetryBasic_gRPC(c *check.C) {
}, },
} }
checkTraceContent(c, s.tempoIP, contains) s.checkTraceContent(contains)
} }
func (s *TracingSuite) TestOpentelemetryRateLimit(c *check.C) { func (s *TracingSuite) TestOpentelemetryRateLimit() {
file := s.adaptFile(c, "fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort, WhoamiPort: s.whoamiPort,
IP: s.otelCollectorIP, IP: s.otelCollectorIP,
}) })
defer os.Remove(file) defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// sleep for 4 seconds to be certain the configured time period has elapsed // sleep for 4 seconds to be certain the configured time period has elapsed
// then test another request and verify a 200 status code // then test another request and verify a 200 status code
time.Sleep(4 * time.Second) time.Sleep(4 * time.Second)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// continue requests at 3 second intervals to test the other rate limit time period // continue requests at 3 second intervals to test the other rate limit time period
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contains := []map[string]string{ contains := []map[string]string{
{ {
@ -289,29 +288,25 @@ func (s *TracingSuite) TestOpentelemetryRateLimit(c *check.C) {
}, },
} }
checkTraceContent(c, s.tempoIP, contains) s.checkTraceContent(contains)
} }
func (s *TracingSuite) TestOpentelemetryRetry(c *check.C) { func (s *TracingSuite) TestOpentelemetryRetry() {
file := s.adaptFile(c, "fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
WhoamiPort: 81, WhoamiPort: 81,
IP: s.otelCollectorIP, IP: s.otelCollectorIP,
}) })
defer os.Remove(file) defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contains := []map[string]string{ contains := []map[string]string{
{ {
@ -374,29 +369,25 @@ func (s *TracingSuite) TestOpentelemetryRetry(c *check.C) {
}, },
} }
checkTraceContent(c, s.tempoIP, contains) s.checkTraceContent(contains)
} }
func (s *TracingSuite) TestOpentelemetryAuth(c *check.C) { func (s *TracingSuite) TestOpentelemetryAuth() {
file := s.adaptFile(c, "fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
WhoamiPort: s.whoamiPort, WhoamiPort: s.whoamiPort,
IP: s.otelCollectorIP, IP: s.otelCollectorIP,
}) })
defer os.Remove(file) defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contains := []map[string]string{ contains := []map[string]string{
{ {
@ -420,12 +411,14 @@ func (s *TracingSuite) TestOpentelemetryAuth(c *check.C) {
}, },
} }
checkTraceContent(c, s.tempoIP, contains) s.checkTraceContent(contains)
} }
func checkTraceContent(c *check.C, tempoIP string, expectedJSON []map[string]string) { func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) {
baseURL, err := url.Parse("http://" + tempoIP + ":3200/api/search") s.T().Helper()
c.Assert(err, checker.IsNil)
baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search")
require.NoError(s.T(), err)
req := &http.Request{ req := &http.Request{
Method: http.MethodGet, Method: http.MethodGet,
@ -434,22 +427,20 @@ func checkTraceContent(c *check.C, tempoIP string, expectedJSON []map[string]str
// Wait for traces to be available. // Wait for traces to be available.
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
resp, err := try.Response(req, 5*time.Second) resp, err := try.Response(req, 5*time.Second)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
out := &TraceResponse{} out := &TraceResponse{}
content, err := io.ReadAll(resp.Body) content, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = json.Unmarshal(content, &out) err = json.Unmarshal(content, &out)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if len(out.Traces) == 0 { s.NotEmptyf(len(out.Traces), "expected at least one trace")
c.Fatalf("expected at least one trace, got %d (%s)", len(out.Traces), string(content))
}
var contents []string var contents []string
for _, t := range out.Traces { for _, t := range out.Traces {
baseURL, err := url.Parse("http://" + tempoIP + ":3200/api/traces/" + t.TraceID) baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
req := &http.Request{ req := &http.Request{
Method: http.MethodGet, Method: http.MethodGet,
@ -457,20 +448,20 @@ func checkTraceContent(c *check.C, tempoIP string, expectedJSON []map[string]str
} }
resp, err := try.Response(req, 5*time.Second) resp, err := try.Response(req, 5*time.Second)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
content, err := io.ReadAll(resp.Body) content, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
contents = append(contents, string(content)) contents = append(contents, string(content))
} }
for _, expected := range expectedJSON { for _, expected := range expectedJSON {
containsAll(c, expected, contents) containsAll(expected, contents)
} }
} }
func containsAll(c *check.C, expectedJSON map[string]string, contents []string) { func containsAll(expectedJSON map[string]string, contents []string) {
for k, v := range expectedJSON { for k, v := range expectedJSON {
found := false found := false
for _, content := range contents { for _, content := range contents {
@ -481,8 +472,8 @@ func containsAll(c *check.C, expectedJSON map[string]string, contents []string)
} }
if !found { if !found {
c.Log("[" + strings.Join(contents, ",") + "]") log.Info().Msgf("[" + strings.Join(contents, ",") + "]")
c.Errorf("missing element: \nKey: %q\nValue: %q ", k, v) log.Error().Msgf("missing element: \nKey: %q\nValue: %q ", k, v)
} }
} }
} }

View file

@ -3,20 +3,32 @@ package integration
import ( import (
"net" "net"
"net/http" "net/http"
"os"
"strings" "strings"
"testing"
"time" "time"
"github.com/go-check/check" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
) )
type UDPSuite struct{ BaseSuite } type UDPSuite struct{ BaseSuite }
func (s *UDPSuite) SetUpSuite(c *check.C) { func TestUDPSuite(t *testing.T) {
s.createComposeProject(c, "udp") suite.Run(t, new(UDPSuite))
s.composeUp(c) }
func (s *UDPSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("udp")
s.composeUp()
}
func (s *UDPSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
} }
func guessWhoUDP(addr string) (string, error) { func guessWhoUDP(addr string) (string, error) {
@ -46,39 +58,33 @@ func guessWhoUDP(addr string) (string, error) {
return string(out[:n]), nil return string(out[:n]), nil
} }
func (s *UDPSuite) TestWRR(c *check.C) { func (s *UDPSuite) TestWRR() {
file := s.adaptFile(c, "fixtures/udp/wrr.toml", struct { file := s.adaptFile("fixtures/udp/wrr.toml", struct {
WhoamiAIP string WhoamiAIP string
WhoamiBIP string WhoamiBIP string
WhoamiCIP string WhoamiCIP string
WhoamiDIP string WhoamiDIP string
}{ }{
WhoamiAIP: s.getComposeServiceIP(c, "whoami-a"), WhoamiAIP: s.getComposeServiceIP("whoami-a"),
WhoamiBIP: s.getComposeServiceIP(c, "whoami-b"), WhoamiBIP: s.getComposeServiceIP("whoami-b"),
WhoamiCIP: s.getComposeServiceIP(c, "whoami-c"), WhoamiCIP: s.getComposeServiceIP("whoami-c"),
WhoamiDIP: s.getComposeServiceIP(c, "whoami-d"), WhoamiDIP: s.getComposeServiceIP("whoami-d"),
}) })
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start() err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8093/who", 5*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8093/who", 5*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
stop := make(chan struct{}) stop := make(chan struct{})
go func() { go func() {
call := map[string]int{} call := map[string]int{}
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
out, err := guessWhoUDP("127.0.0.1:8093") out, err := guessWhoUDP("127.0.0.1:8093")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
switch { switch {
case strings.Contains(out, "whoami-a"): case strings.Contains(out, "whoami-a"):
call["whoami-a"]++ call["whoami-a"]++
@ -90,13 +96,13 @@ func (s *UDPSuite) TestWRR(c *check.C) {
call["unknown"]++ call["unknown"]++
} }
} }
c.Assert(call, checker.DeepEquals, map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}) assert.EqualValues(s.T(), call, map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3})
close(stop) close(stop)
}() }()
select { select {
case <-stop: case <-stop:
case <-time.Tick(5 * time.Second): case <-time.Tick(5 * time.Second):
c.Error("Timeout") log.Info().Msg("Timeout")
} }
} }

View file

@ -8,19 +8,25 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"testing"
"time" "time"
"github.com/go-check/check"
gorillawebsocket "github.com/gorilla/websocket" gorillawebsocket "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
checker "github.com/vdemeester/shakers"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
// WebsocketSuite tests suite. // WebsocketSuite tests suite.
type WebsocketSuite struct{ BaseSuite } type WebsocketSuite struct{ BaseSuite }
func (s *WebsocketSuite) TestBase(c *check.C) { func TestWebsocketSuite(t *testing.T) {
suite.Run(t, new(WebsocketSuite))
}
func (s *WebsocketSuite) TestBase() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -41,36 +47,30 @@ func (s *WebsocketSuite) TestBase(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestWrongOrigin(c *check.C) { func (s *WebsocketSuite) TestWrongOrigin() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -91,35 +91,28 @@ func (s *WebsocketSuite) TestWrongOrigin(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:800") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:800")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, err = websocket.NewClient(config, conn) _, err = websocket.NewClient(config, conn)
c.Assert(err, checker.NotNil) assert.ErrorContains(s.T(), err, "bad status")
c.Assert(err, checker.ErrorMatches, "bad status")
} }
func (s *WebsocketSuite) TestOrigin(c *check.C) { func (s *WebsocketSuite) TestOrigin() {
// use default options // use default options
upgrader := gorillawebsocket.Upgrader{} upgrader := gorillawebsocket.Upgrader{}
@ -141,44 +134,38 @@ func (s *WebsocketSuite) TestOrigin(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second)
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
client, err := websocket.NewClient(config, conn) client, err := websocket.NewClient(config, conn)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
n, err := client.Write([]byte("OK")) n, err := client.Write([]byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
msg := make([]byte, 2) msg := make([]byte, 2)
n, err = client.Read(msg) n, err = client.Read(msg)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { func (s *WebsocketSuite) TestWrongOriginIgnoredByServer() {
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool { upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
return true return true
}} }}
@ -201,44 +188,38 @@ func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:80") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:80")
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
client, err := websocket.NewClient(config, conn) client, err := websocket.NewClient(config, conn)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
n, err := client.Write([]byte("OK")) n, err := client.Write([]byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
msg := make([]byte, 2) msg := make([]byte, 2)
n, err = client.Read(msg) n, err = client.Read(msg)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestSSLTermination(c *check.C) { func (s *WebsocketSuite) TestSSLTermination() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -258,44 +239,38 @@ func (s *WebsocketSuite) TestSSLTermination(c *check.C) {
} }
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config_https.toml", struct { file := s.adaptFile("fixtures/websocket/config_https.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Add client self-signed cert // Add client self-signed cert
roots := x509.NewCertPool() roots := x509.NewCertPool()
certContent, err := os.ReadFile("./resources/tls/local.cert") certContent, err := os.ReadFile("./resources/tls/local.cert")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
roots.AppendCertsFromPEM(certContent) roots.AppendCertsFromPEM(certContent)
gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{ gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{
RootCAs: roots, RootCAs: roots,
} }
conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/ws", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/ws", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestBasicAuth(c *check.C) { func (s *WebsocketSuite) TestBasicAuth() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -306,8 +281,8 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) {
defer conn.Close() defer conn.Close()
user, password, _ := r.BasicAuth() user, password, _ := r.BasicAuth()
c.Assert(user, check.Equals, "traefiker") assert.Equal(s.T(), "traefiker", user)
c.Assert(password, check.Equals, "secret") assert.Equal(s.T(), "secret", password)
for { for {
mt, message, err := conn.ReadMessage() mt, message, err := conn.ReadMessage()
@ -320,78 +295,66 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) {
} }
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000")
auth := "traefiker:secret" auth := "traefiker:secret"
config.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) config.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
c.Assert(err, check.IsNil) assert.NoError(s.T(), err)
conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
client, err := websocket.NewClient(config, conn) client, err := websocket.NewClient(config, conn)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
n, err := client.Write([]byte("OK")) n, err := client.Write([]byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
msg := make([]byte, 2) msg := make([]byte, 2)
n, err = client.Read(msg) n, err = client.Read(msg)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(n, checker.Equals, 2) assert.Equal(s.T(), 2, n)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestSpecificResponseFromBackend(c *check.C) { func (s *WebsocketSuite) TestSpecificResponseFromBackend() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, resp, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) _, resp, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil)
c.Assert(err, checker.NotNil) assert.Error(s.T(), err)
c.Assert(resp.StatusCode, check.Equals, http.StatusUnauthorized) assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode)
} }
func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) { func (s *WebsocketSuite) TestURLWithURLEncodedChar() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.URL.EscapedPath(), check.Equals, "/ws/http%3A%2F%2Ftest") assert.Equal(s.T(), "/ws/http%3A%2F%2Ftest", r.URL.EscapedPath())
conn, err := upgrader.Upgrade(w, r, nil) conn, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
return return
@ -409,36 +372,30 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws/http%3A%2F%2Ftest", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws/http%3A%2F%2Ftest", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestSSLhttp2(c *check.C) { func (s *WebsocketSuite) TestSSLhttp2() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -463,48 +420,42 @@ func (s *WebsocketSuite) TestSSLhttp2(c *check.C) {
ts.TLS.NextProtos = append(ts.TLS.NextProtos, `h2`, `http/1.1`) ts.TLS.NextProtos = append(ts.TLS.NextProtos, `h2`, `http/1.1`)
ts.StartTLS() ts.StartTLS()
file := s.adaptFile(c, "fixtures/websocket/config_https.toml", struct { file := s.adaptFile("fixtures/websocket/config_https.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: ts.URL, WebsocketServer: ts.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
// Add client self-signed cert // Add client self-signed cert
roots := x509.NewCertPool() roots := x509.NewCertPool()
certContent, err := os.ReadFile("./resources/tls/local.cert") certContent, err := os.ReadFile("./resources/tls/local.cert")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
roots.AppendCertsFromPEM(certContent) roots.AppendCertsFromPEM(certContent)
gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{ gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{
RootCAs: roots, RootCAs: roots,
} }
conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/echo", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/echo", nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }
func (s *WebsocketSuite) TestHeaderAreForwarded(c *check.C) { func (s *WebsocketSuite) TestHeaderAreForwarded() {
upgrader := gorillawebsocket.Upgrader{} // use default options upgrader := gorillawebsocket.Upgrader{} // use default options
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Header.Get("X-Token"), check.Equals, "my-token") assert.Equal(s.T(), "my-token", r.Header.Get("X-Token"))
c, err := upgrader.Upgrade(w, r, nil) c, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
return return
@ -522,33 +473,27 @@ func (s *WebsocketSuite) TestHeaderAreForwarded(c *check.C) {
} }
})) }))
file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { file := s.adaptFile("fixtures/websocket/config.toml", struct {
WebsocketServer string WebsocketServer string
}{ }{
WebsocketServer: srv.URL, WebsocketServer: srv.URL,
}) })
defer os.Remove(file) s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG")
defer display(c)
err := cmd.Start()
c.Assert(err, check.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
headers := http.Header{} headers := http.Header{}
headers.Add("X-Token", "my-token") headers.Add("X-Token", "my-token")
conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", headers) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", headers)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Assert(string(msg), checker.Equals, "OK") assert.Equal(s.T(), "OK", string(msg))
} }

View file

@ -8,16 +8,18 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"testing"
"time" "time"
"github.com/go-check/check"
"github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie"
"github.com/kvtools/valkeyrie/store" "github.com/kvtools/valkeyrie/store"
"github.com/kvtools/zookeeper" "github.com/kvtools/zookeeper"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/api"
checker "github.com/vdemeester/shakers"
) )
// Zk test suites. // Zk test suites.
@ -27,11 +29,17 @@ type ZookeeperSuite struct {
zookeeperAddr string zookeeperAddr string
} }
func (s *ZookeeperSuite) setupStore(c *check.C) { func TestZookeeperSuite(t *testing.T) {
s.createComposeProject(c, "zookeeper") suite.Run(t, new(ZookeeperSuite))
s.composeUp(c) }
s.zookeeperAddr = net.JoinHostPort(s.getComposeServiceIP(c, "zookeeper"), "2181") func (s *ZookeeperSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("zookeeper")
s.composeUp()
s.zookeeperAddr = net.JoinHostPort(s.getComposeServiceIP("zookeeper"), "2181")
var err error var err error
s.kvClient, err = valkeyrie.NewStore( s.kvClient, err = valkeyrie.NewStore(
@ -42,20 +50,19 @@ func (s *ZookeeperSuite) setupStore(c *check.C) {
ConnectionTimeout: 10 * time.Second, ConnectionTimeout: 10 * time.Second,
}, },
) )
if err != nil { require.NoError(s.T(), err, "Cannot create store zookeeper")
c.Fatal("Cannot create store zookeeper")
}
// wait for zk // wait for zk
err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test")) err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test"))
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) { func (s *ZookeeperSuite) TearDownSuite() {
s.setupStore(c) s.BaseSuite.TearDownSuite()
}
file := s.adaptFile(c, "fixtures/zookeeper/simple.toml", struct{ ZkAddress string }{s.zookeeperAddr}) func (s *ZookeeperSuite) TestSimpleConfiguration() {
defer os.Remove(file) file := s.adaptFile("fixtures/zookeeper/simple.toml", struct{ ZkAddress string }{s.zookeeperAddr})
data := map[string]string{ data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web", "traefik/http/routers/Router0/entryPoints/0": "web",
@ -104,39 +111,35 @@ func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) {
for k, v := range data { for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil) err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
cmd, display := s.traefikCmd(withConfigFile(file)) s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second,
try.BodyContains(`"striper@zookeeper":`, `"compressor@zookeeper":`, `"srvcA@zookeeper":`, `"srvcB@zookeeper":`), try.BodyContains(`"striper@zookeeper":`, `"compressor@zookeeper":`, `"srvcA@zookeeper":`, `"srvcB@zookeeper":`),
) )
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
var obtained api.RunTimeRepresentation var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained) err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
got, err := json.MarshalIndent(obtained, "", " ") got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
expectedJSON := filepath.FromSlash("testdata/rawdata-zk.json") expectedJSON := filepath.FromSlash("testdata/rawdata-zk.json")
if *updateExpected { if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666) err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
} }
expected, err := os.ReadFile(expectedJSON) expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
if !bytes.Equal(expected, got) { if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{ diff := difflib.UnifiedDiff{
@ -148,7 +151,7 @@ func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) {
} }
text, err := difflib.GetUnifiedDiffString(diff) text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil) require.NoError(s.T(), err)
c.Error(text) log.Info().Msg(text)
} }
} }

View file

@ -5,7 +5,7 @@ set -e -o pipefail
source /go/src/k8s.io/code-generator/kube_codegen.sh source /go/src/k8s.io/code-generator/kube_codegen.sh
git config --global --add safe.directory /go/src/${PROJECT_MODULE} git config --global --add safe.directory "/go/src/${PROJECT_MODULE}"
rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}" rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}"
mkdir -p "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/" mkdir -p "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/"

View file

@ -3,18 +3,9 @@ set -e
export DEST=. export DEST=.
TESTFLAGS+=("-test.timeout=20m" -check.v)
if [ -n "${VERBOSE}" ]; then
TESTFLAGS+=(-v)
elif [ -n "${VERBOSE_INTEGRATION}" ]; then
TESTFLAGS+=(-v)
fi
cd integration
echo "Testing against..." echo "Testing against..."
docker version docker version
# shellcheck disable=SC2086 # shellcheck disable=SC2086
# shellcheck disable=SC2048 # shellcheck disable=SC2048
CGO_ENABLED=0 go test -integration ${TESTFLAGS[*]} go test ./integration -test.timeout=20m -failfast -v ${TESTFLAGS[*]}