From 533329b716d70794708b4a96d6a3eee79c88732a Mon Sep 17 00:00:00 2001 From: Igor Kolomiyets Date: Sun, 5 Apr 2020 19:07:07 +0100 Subject: [PATCH 1/2] #173: Reusing existing reaper for subsequent container/network create calls. This ensures that any networks, created as part of the test case will be removed. --- network_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- reaper.go | 15 ++++++--- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/network_test.go b/network_test.go index 02e55f9b3a..41a5b33f9a 100644 --- a/network_test.go +++ b/network_test.go @@ -1,6 +1,12 @@ package testcontainers -import "context" +import ( + "context" + "fmt" + "github.com/testcontainers/testcontainers-go/wait" + "testing" + "time" +) // Create a network using a provider. By default it is Docker. func ExampleNetworkProvider_CreateNetwork() { @@ -28,3 +34,77 @@ func ExampleNetworkProvider_CreateNetwork() { defer nginxC.Terminate(ctx) nginxC.GetContainerID() } + +func Test_MultipleContainersInTheNewNetwork(t *testing.T) { + ctx := context.Background() + + networkName := "test-network" + + networkRequest := NetworkRequest{ + Driver: "bridge", + Name: networkName, + Attachable: true, + } + + env := make(map[string]string) + env["POSTGRES_PASSWORD"] = "Password1" + dbContainerRequest := ContainerRequest{ + Image: "postgres:12.2", + ExposedPorts: []string{"5432/tcp"}, + AutoRemove: true, + Env: env, + WaitingFor: wait.ForListeningPort("5432/tcp"), + Networks: []string{networkName}, + } + + gcr := GenericContainerRequest{ + ContainerRequest: dbContainerRequest, + Started: true, + } + + provider, err := gcr.ProviderType.GetProvider() + if err != nil { + t.Fatal("cannot get provider") + } + + net, err := provider.CreateNetwork(ctx, networkRequest) + if err != nil { + t.Fatal("cannot create network") + } + + defer net.Remove(ctx) + + postgres, err := GenericContainer(ctx, gcr) + if err != nil { + t.Fatal(err) + } + + defer postgres.Terminate(ctx) + + env = make(map[string]string) + env["RABBITMQ_ERLANG_COOKIE"] = "f2a2d3d27c75" + env["RABBITMQ_DEFAULT_USER"] = "admin" + env["RABBITMQ_DEFAULT_PASS"] = "Password1" + hp := wait.ForListeningPort("5672/tcp") + hp.WithStartupTimeout(3 * time.Minute) + amqpRequest := ContainerRequest{ + Image: "rabbitmq:management-alpine", + ExposedPorts: []string{"15672/tcp", "5672/tcp"}, + Env: env, + AutoRemove: true, + WaitingFor: hp, + Networks: []string{networkName}, + } + rabbitmq, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: amqpRequest, + Started: true, + }) + if err != nil { + t.Fatal(err) + return + } + + defer rabbitmq.Terminate(ctx) + fmt.Println(postgres.GetContainerID()) + fmt.Println(rabbitmq.GetContainerID()) +} diff --git a/reaper.go b/reaper.go index bee6b25395..ff56be9d09 100644 --- a/reaper.go +++ b/reaper.go @@ -21,6 +21,8 @@ const ( ReaperDefaultImage = "quay.io/testcontainers/ryuk:0.2.3" ) +var reaper *Reaper // We would like to create reaper only once + // ReaperProvider represents a provider for the reaper to run itself with // The ContainerProvider interface should usually satisfy this as well, so it is pluggable type ReaperProvider interface { @@ -36,14 +38,19 @@ type Reaper struct { // NewReaper creates a Reaper with a sessionID to identify containers and a provider to use func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error) { - r := &Reaper{ + // If reaper already exists re-use it + if reaper != nil { + return reaper, nil + } + + // Otherwise create a new one + reaper = &Reaper{ Provider: provider, SessionID: sessionID, } listeningPort := nat.Port("8080/tcp") - // TODO: reuse reaper if there already is one req := ContainerRequest{ Image: reaperImage(reaperImageName), ExposedPorts: []string{string(listeningPort)}, @@ -68,9 +75,9 @@ func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, r if err != nil { return nil, err } - r.Endpoint = endpoint + reaper.Endpoint = endpoint - return r, nil + return reaper, nil } func reaperImage(reaperImageName string) string { From 8d295a68a0ac7b861483ff7024e37b84faba6add Mon Sep 17 00:00:00 2001 From: Igor Kolomiyets Date: Tue, 7 Apr 2020 00:33:18 +0100 Subject: [PATCH 2/2] The underlying issue is apparently more complex. I think, that partially it also was that unlike container, network was not removed explicitly. I've rectified this in the latest commit by modifying the way network is created and removed. --- docker.go | 9 ++++----- docker_test.go | 26 ++++++++++++++++++-------- generic.go | 20 ++++++++++++++++++++ network_test.go | 36 ++++++++++++++++-------------------- 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/docker.go b/docker.go index 38256366d0..8bb03ce439 100644 --- a/docker.go +++ b/docker.go @@ -383,11 +383,9 @@ type DockerNetwork struct { } // Remove is used to remove the network. It is usually triggered by as defer function. -func (n *DockerNetwork) Remove(_ context.Context) error { - if n.terminationSignal != nil { - n.terminationSignal <- true - } - return nil +func (n *DockerNetwork) Remove(ctx context.Context) error { + + return n.provider.client.NetworkRemove(ctx, n.ID) } // DockerProvider implements the ContainerProvider interface @@ -704,6 +702,7 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) Driver: req.Driver, Name: req.Name, terminationSignal: termSignal, + provider: p, } return n, nil diff --git a/docker_test.go b/docker_test.go index afdde1eeb5..d0b71cb5d1 100644 --- a/docker_test.go +++ b/docker_test.go @@ -46,12 +46,13 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { }, } - provider, err := gcr.ProviderType.GetProvider() - - newNetwork, err := provider.CreateNetwork(ctx, NetworkRequest{ - Name: networkName, - CheckDuplicate: true, + newNetwork, err := GenericNetwork(ctx, GenericNetworkRequest{ + NetworkRequest: NetworkRequest{ + Name: networkName, + CheckDuplicate: true, + }, }) + if err != nil { t.Fatal(err) } @@ -98,8 +99,12 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { gcr := GenericContainerRequest{ ContainerRequest: ContainerRequest{ Image: "nginx", + Privileged: true, SkipReaper: true, NetworkMode: "host", + ExposedPorts: []string{ + "80/tcp", + }, }, Started: true, } @@ -111,12 +116,17 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { defer nginxC.Terminate(ctx) - host, err := nginxC.Host(ctx) + //host, err := nginxC.Host(ctx) + //if err != nil { + // t.Errorf("Expected host %s. Got '%d'.", host, err) + //} + // + endpoint, err := nginxC.Endpoint(ctx, "http") if err != nil { - t.Errorf("Expected host %s. Got '%d'.", host, err) + t.Errorf("Expected server endpoint. Got '%v'.", err) } - _, err = http.Get("http://" + host + ":80") + _, err = http.Get(endpoint) if err != nil { t.Errorf("Expected OK response. Got '%d'.", err) } diff --git a/generic.go b/generic.go index cf39b8c1a0..5c611e0905 100644 --- a/generic.go +++ b/generic.go @@ -13,6 +13,26 @@ type GenericContainerRequest struct { ProviderType ProviderType // which provider to use, Docker if empty } +// GenericNetworkRequest represents parameters to a generic network +type GenericNetworkRequest struct { + NetworkRequest // embedded request for provider + ProviderType ProviderType // which provider to use, Docker if empty +} + +// GenericNetwork creates a generic network with parameters +func GenericNetwork(ctx context.Context, req GenericNetworkRequest) (Network, error) { + provider, err := req.ProviderType.GetProvider() + if err != nil { + return nil, err + } + network, err := provider.CreateNetwork(ctx, req.NetworkRequest) + if err != nil { + return nil, errors.Wrap(err, "failed to create network") + } + + return network, nil +} + // GenericContainer creates a generic container with parameters func GenericContainer(ctx context.Context, req GenericContainerRequest) (Container, error) { provider, err := req.ProviderType.GetProvider() diff --git a/network_test.go b/network_test.go index 41a5b33f9a..43791de3f9 100644 --- a/network_test.go +++ b/network_test.go @@ -12,7 +12,15 @@ import ( func ExampleNetworkProvider_CreateNetwork() { ctx := context.Background() networkName := "new-network" - gcr := GenericContainerRequest{ + net, _ := GenericNetwork(ctx, GenericNetworkRequest{ + NetworkRequest: NetworkRequest{ + Name: networkName, + CheckDuplicate: true, + }, + }) + defer net.Remove(ctx) + + nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ Image: "nginx", ExposedPorts: []string{ @@ -22,15 +30,7 @@ func ExampleNetworkProvider_CreateNetwork() { networkName, }, }, - } - provider, _ := gcr.ProviderType.GetProvider() - net, _ := provider.CreateNetwork(ctx, NetworkRequest{ - Name: networkName, - CheckDuplicate: true, }) - defer net.Remove(ctx) - - nginxC, _ := GenericContainer(ctx, gcr) defer nginxC.Terminate(ctx) nginxC.GetContainerID() } @@ -57,24 +57,20 @@ func Test_MultipleContainersInTheNewNetwork(t *testing.T) { Networks: []string{networkName}, } - gcr := GenericContainerRequest{ - ContainerRequest: dbContainerRequest, - Started: true, - } - - provider, err := gcr.ProviderType.GetProvider() - if err != nil { - t.Fatal("cannot get provider") - } + net, err := GenericNetwork(ctx, GenericNetworkRequest{ + NetworkRequest: networkRequest, + }) - net, err := provider.CreateNetwork(ctx, networkRequest) if err != nil { t.Fatal("cannot create network") } defer net.Remove(ctx) - postgres, err := GenericContainer(ctx, gcr) + postgres, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: dbContainerRequest, + Started: true, + }) if err != nil { t.Fatal(err) }