-
-
Notifications
You must be signed in to change notification settings - Fork 512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug]: Restarted container cannot fetch its port #2580
Comments
Hey @artuross each time the container is stopped a different random, free port is given for each exposed port, so you must refresh the connection string variable for it. As you are "caching" the lifecycle methods of the container (start, stop, terminate...), and an initial DSN string, could it be that the initial DSN is still in use? I used your example and this test case works for me: func TestRestart(t *testing.T) {
ctx := context.Background()
dbName := "repository-test"
dbUser := "postgres"
dbPassword := "postgres"
container, err := postgres.RunContainer(
ctx,
testcontainers.WithImage("docker.io/postgres:16.1-alpine3.19"),
postgres.WithPassword(dbPassword),
postgres.WithUsername(dbUser),
postgres.WithDatabase(dbName),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(60*time.Second),
),
)
require.NoError(t, err, "failed to start container")
err = container.Snapshot(ctx)
require.NoError(t, err, "failed to snapshot container")
dsn, err := container.ConnectionString(ctx)
require.NoError(t, err, "failed to get connection string")
connectFn := func(dsn string) {
conn, err := pgx.Connect(context.Background(), dsn)
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
require.NoError(t, err, "failed to create new pgx conn")
}
connectFn(dsn)
stopContainer := func(ctx context.Context) {
duration := 30 * time.Second
err := container.Stop(ctx, &duration)
require.NoError(t, err, "failed to stop container")
}
startContainer := func(ctx context.Context) {
err := container.Start(ctx)
require.NoError(t, err, "failed to start container")
var dsn string
for i := 0; i < 30; i++ {
dsn, err = container.ConnectionString(ctx)
if err == nil {
break
}
t.Logf("failed to get connection string: %v\n", err)
time.Sleep(time.Second)
}
connectFn(dsn)
}
terminateContainer := func(ctx context.Context) {
err := container.Terminate(ctx)
require.NoError(t, err, "failed to terminate container")
}
resetState := func(ctx context.Context) {
err := container.Restore(ctx)
require.NoError(t, err, "failed to restore container to initial state")
}
resetState(ctx)
stopContainer(ctx)
startContainer(ctx)
terminateContainer(ctx)
}
As each container receives its own DNS, each DB object receives its own DSN/configuration. |
Hey, thanks for checking. I don't think that's the case, because I actually try to create new connection from new DSN in That said, I will try your code and report back if that also has the same issue (maybe I made a small, but significant mistake in some other place). |
Yes, that's my first thought. Please let me know if it's not, so we can debug it more in depth, thanks! |
This is the code that I've used (repo here). It's almost identical to yours except I start and stop the container a few times. package githubtest
import (
"context"
"testing"
"time"
pgx "github.com/jackc/pgx/v5"
"github.com/stretchr/testify/require"
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestRestart(t *testing.T) {
ctx := context.Background()
dbName := "repository-test"
dbUser := "postgres"
dbPassword := "postgres"
container, err := postgres.RunContainer(
ctx,
testcontainers.WithImage("docker.io/postgres:16.1-alpine3.19"),
postgres.WithPassword(dbPassword),
postgres.WithUsername(dbUser),
postgres.WithDatabase(dbName),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(60*time.Second),
),
)
require.NoError(t, err, "failed to start container")
err = container.Snapshot(ctx)
require.NoError(t, err, "failed to snapshot container")
dsn, err := container.ConnectionString(ctx)
require.NoError(t, err, "failed to get connection string")
connectFn := func(dsn string) {
conn, err := pgx.Connect(context.Background(), dsn)
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background())
require.NoError(t, err, "failed to create new pgx conn")
}
connectFn(dsn)
stopContainer := func(ctx context.Context) {
duration := 30 * time.Second
err := container.Stop(ctx, &duration)
require.NoError(t, err, "failed to stop container")
}
startContainer := func(ctx context.Context) {
err := container.Start(ctx)
require.NoError(t, err, "failed to start container")
var dsn string
for i := 0; i < 30; i++ {
dsn, err = container.ConnectionString(ctx)
if err == nil {
break
}
t.Logf("failed to get connection string: %v\n", err)
time.Sleep(time.Second)
}
connectFn(dsn)
}
terminateContainer := func(ctx context.Context) {
err := container.Terminate(ctx)
require.NoError(t, err, "failed to terminate container")
}
resetState := func(ctx context.Context) {
err := container.Restore(ctx)
require.NoError(t, err, "failed to restore container to initial state")
}
resetState(ctx)
stopContainer(ctx)
startContainer(ctx)
resetState(ctx)
stopContainer(ctx)
startContainer(ctx)
resetState(ctx)
stopContainer(ctx)
startContainer(ctx)
resetState(ctx)
stopContainer(ctx)
startContainer(ctx)
terminateContainer(ctx)
} This is the log: Running tool: /Users/artur/.asdf/shims/go test -timeout 30s -run ^TestRestart$ githubtest
=== RUN TestRestart
2024/06/15 13:04:12 github.com/testcontainers/testcontainers-go - Connected to docker:
Server Version: 26.1.4
API Version: 1.44
Operating System: Docker Desktop
Total Memory: 2917 MB
Resolved Docker Host: unix:///var/run/docker.sock
Resolved Docker Socket Path: /var/run/docker.sock
Test SessionID: c848e213e2617e36909f2f3cac17f686012bfcf0f24d4a0ecc319038ffd3c33d
Test ProcessID: d758ad5a-616f-464f-95f0-5f8dbfd9bba3
2024/06/15 13:04:12 🐳 Creating container for image testcontainers/ryuk:0.7.0
2024/06/15 13:04:12 ✅ Container created: 7501f347b030
2024/06/15 13:04:12 🐳 Starting container: 7501f347b030
2024/06/15 13:04:12 ✅ Container started: 7501f347b030
2024/06/15 13:04:12 🚧 Waiting for container id 7501f347b030 image: testcontainers/ryuk:0.7.0. Waiting for: &{Port:8080/tcp timeout:<nil> PollInterval:100ms}
2024/06/15 13:04:13 🔔 Container is ready: 7501f347b030
2024/06/15 13:04:13 🐳 Creating container for image docker.io/postgres:16.1-alpine3.19
2024/06/15 13:04:13 ✅ Container created: 9495e1319a52
2024/06/15 13:04:13 🐳 Starting container: 9495e1319a52
2024/06/15 13:04:13 ✅ Container started: 9495e1319a52
2024/06/15 13:04:13 🚧 Waiting for container id 9495e1319a52 image: docker.io/postgres:16.1-alpine3.19. Waiting for: &{timeout:<nil> deadline:0x140003eb7b0 Strategies:[0x14000432510]}
2024/06/15 13:04:14 🔔 Container is ready: 9495e1319a52
2024/06/15 13:04:14 🐳 Stopping container: 9495e1319a52
2024/06/15 13:04:14 ✅ Container stopped: 9495e1319a52
2024/06/15 13:04:14 🐳 Starting container: 9495e1319a52
2024/06/15 13:04:15 ✅ Container started: 9495e1319a52
2024/06/15 13:04:15 🚧 Waiting for container id 9495e1319a52 image: docker.io/postgres:16.1-alpine3.19. Waiting for: &{timeout:<nil> deadline:0x140003eb7b0 Strategies:[0x14000432510]}
2024/06/15 13:04:15 🔔 Container is ready: 9495e1319a52
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
/Users/artur/code/github/restart_test.go:70: failed to get connection string: port not found
panic: test timed out after 30s
running tests:
TestRestart (30s)
goroutine 133 [running]:
testing.(*M).startAlarm.func1()
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:2366 +0x30c
created by time.goFunc
/Users/artur/.asdf/installs/golang/1.22.4/go/src/time/sleep.go:177 +0x38
goroutine 1 [chan receive]:
testing.(*T).Run(0x140004521a0, {0x1045cc7ce?, 0x1400025fb38?}, 0x104814000)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:1750 +0x32c
testing.runTests.func1(0x140004521a0)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:2161 +0x40
testing.tRunner(0x140004521a0, 0x1400025fc58)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:1689 +0xec
testing.runTests(0x1400000e030, {0x104b3ea90, 0x1, 0x1}, {0x1400025fd18?, 0x104225520?, 0x104bb2160?})
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:2159 +0x3b0
testing.(*M).Run(0x14000444140)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:2027 +0x5a4
main.main()
_testmain.go:47 +0x16c
goroutine 2 [sleep]:
time.Sleep(0x3b9aca00)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/runtime/time.go:195 +0xfc
githubtest.TestRestart.func3({0x10481ea30, 0x104c19a00})
/Users/artur/code/github/restart_test.go:71 +0xe0
githubtest.TestRestart(0x14000452340)
/Users/artur/code/github/restart_test.go:89 +0x4cc
testing.tRunner(0x14000452340, 0x104814000)
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:1689 +0xec
created by testing.(*T).Run in goroutine 1
/Users/artur/.asdf/installs/golang/1.22.4/go/src/testing/testing.go:1742 +0x318
goroutine 82 [chan receive]:
github.com/testcontainers/testcontainers-go.(*Reaper).Connect.func1({0x104821df8, 0x14000078830})
/Users/artur/.asdf/installs/golang/1.22.4/packages/pkg/mod/github.com/testcontainers/[email protected]/reaper.go:358 +0x5ec
created by github.com/testcontainers/testcontainers-go.(*Reaper).Connect in goroutine 2
/Users/artur/.asdf/installs/golang/1.22.4/packages/pkg/mod/github.com/testcontainers/[email protected]/reaper.go:323 +0x174
FAIL githubtest 30.240s This is relatively consistent - I don't think I've seen it complete without any error, however I would say the behavior is quite flaky. It's not the only, but most recurring error that I experience. I've also updated Go and Docker to newest versions - still the same issue. |
Testcontainers version
0.31.0
Using the latest Testcontainers version?
Yes
Host OS
MacOS
Host arch
Apple Silicon
Go version
1.22.1
Docker version
Client: Cloud integration: v1.0.35+desktop.5 Version: 24.0.6 API version: 1.43 Go version: go1.20.7 Git commit: ed223bc Built: Mon Sep 4 12:28:49 2023 OS/Arch: darwin/arm64 Context: desktop-linux Server: Docker Desktop 4.25.2 (129061) Engine: Version: 24.0.6 API version: 1.43 (minimum version 1.12) Go version: go1.20.7 Git commit: 1a79695 Built: Mon Sep 4 12:31:36 2023 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.6.22 GitCommit: 8165feabfdfe38c65b599c4993d227328c231fca runc: Version: 1.1.8 GitCommit: v1.1.8-0-g82f18fe docker-init: Version: 0.19.0 GitCommit: de40ad0
Docker info
What happened?
I have a following code to create container and setup database for me
In one test case, I stop the container and start it on cleanup (done with
startContainer
function). This behaviour is inconsistent, but about 50% executions fail on the call tocontainer.ConnectionString(ctx)
with errorport not found
.Initially, I thought that the container was simply not up yet, but as you can see, I added a for loop to try few times. Additionally, I can see a port mapped when looking at container via Docker Desktop (also can check logs and can confirm that the container is ready).
Interestingly, I've never had this happen when I wasn't restarting container (creating new container on each test case).
Relevant log output
Additional information
No response
The text was updated successfully, but these errors were encountered: