refactoring - e2e network (#1401)

* change to testcontainers-go/network
This commit is contained in:
Kwitsch 2024-03-18 13:02:03 +01:00 committed by GitHub
parent 7eef4bf6e2
commit c3a319f199
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 128 additions and 174 deletions

View File

@ -14,18 +14,24 @@ import (
)
var _ = Describe("Basic functional tests", func() {
var blocky testcontainers.Container
var err error
var (
e2eNet *testcontainers.DockerNetwork
blocky testcontainers.Container
err error
)
BeforeEach(func(ctx context.Context) {
e2eNet = getRandomNetwork(ctx)
})
Describe("Container start", func() {
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
})
When("wrong port configuration is provided", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -50,7 +56,7 @@ var _ = Describe("Basic functional tests", func() {
})
When("Minimal configuration is provided", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -81,7 +87,7 @@ var _ = Describe("Basic functional tests", func() {
Context("http port configuration", func() {
When("'httpPort' is not defined", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -101,7 +107,7 @@ var _ = Describe("Basic functional tests", func() {
})
When("'httpPort' is defined", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -137,12 +143,12 @@ var _ = Describe("Basic functional tests", func() {
Describe("Logging", func() {
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
})
When("log privacy is enabled", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",

View File

@ -11,17 +11,23 @@ import (
)
var _ = Describe("External lists and query blocking", func() {
var blocky testcontainers.Container
var err error
var (
e2eNet *testcontainers.DockerNetwork
blocky testcontainers.Container
err error
)
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka", `A google/NOERROR("A 1.2.3.4 123")`)
e2eNet = getRandomNetwork(ctx)
_, err = createDNSMokkaContainer(ctx, "moka", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
})
Describe("List download on startup", func() {
When("external blacklist ist not available", func() {
Context("loading.strategy = blocking", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -56,7 +62,7 @@ var _ = Describe("External lists and query blocking", func() {
})
Context("loading.strategy = failOnError", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -93,10 +99,10 @@ var _ = Describe("External lists and query blocking", func() {
Describe("Query blocking against external blacklists", func() {
When("external blacklists are defined and available", func() {
BeforeEach(func(ctx context.Context) {
_, err = createHTTPServerContainer(ctx, "httpserver", "list.txt", "blockeddomain.com")
_, err = createHTTPServerContainer(ctx, "httpserver", e2eNet, "list.txt", "blockeddomain.com")
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",

View File

@ -46,7 +46,9 @@ const (
// createDNSMokkaContainer creates a DNS mokka container with the given rules attached to the test network
// under the given alias.
// It is automatically terminated when the test is finished.
func createDNSMokkaContainer(ctx context.Context, alias string, rules ...string) (testcontainers.Container, error) {
func createDNSMokkaContainer(ctx context.Context, alias string, e2eNet *testcontainers.DockerNetwork,
rules ...string,
) (testcontainers.Container, error) {
mokaRules := make(map[string]string)
for i, rule := range rules {
@ -60,13 +62,14 @@ func createDNSMokkaContainer(ctx context.Context, alias string, rules ...string)
Env: mokaRules,
}
return startContainerWithNetwork(ctx, req, alias)
return startContainerWithNetwork(ctx, req, alias, e2eNet)
}
// createHTTPServerContainer creates a static HTTP server container that serves one file with the given lines
// and is attached to the test network under the given alias.
// It is automatically terminated when the test is finished.
func createHTTPServerContainer(ctx context.Context, alias, filename string, lines ...string,
func createHTTPServerContainer(ctx context.Context, alias string, e2eNet *testcontainers.DockerNetwork,
filename string, lines ...string,
) (testcontainers.Container, error) {
file := createTempFile(lines...)
@ -84,23 +87,25 @@ func createHTTPServerContainer(ctx context.Context, alias, filename string, line
},
}
return startContainerWithNetwork(ctx, req, alias)
return startContainerWithNetwork(ctx, req, alias, e2eNet)
}
// createRedisContainer creates a redis container attached to the test network under the alias 'redis'.
// It is automatically terminated when the test is finished.
func createRedisContainer(ctx context.Context) (*redis.RedisContainer, error) {
func createRedisContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
) (*redis.RedisContainer, error) {
return deferTerminate(redis.RunContainer(ctx,
testcontainers.WithImage(redisImage),
redis.WithLogLevel(redis.LogLevelVerbose),
WithNetwork(ctx, "redis"),
withNetwork("redis", e2eNet),
))
}
// createPostgresContainer creates a postgres container attached to the test network under the alias 'postgres'.
// It creates a database 'user' with user 'user' and password 'user'.
// It is automatically terminated when the test is finished.
func createPostgresContainer(ctx context.Context) (*postgres.PostgresContainer, error) {
func createPostgresContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
) (*postgres.PostgresContainer, error) {
const waitLogOccurrence = 2
return deferTerminate(postgres.RunContainer(ctx,
@ -113,27 +118,28 @@ func createPostgresContainer(ctx context.Context) (*postgres.PostgresContainer,
wait.ForLog("database system is ready to accept connections").
WithOccurrence(waitLogOccurrence).
WithStartupTimeout(startupTimeout)),
WithNetwork(ctx, "postgres"),
withNetwork("postgres", e2eNet),
))
}
// createMariaDBContainer creates a mariadb container attached to the test network under the alias 'mariaDB'.
// It creates a database 'user' with user 'user' and password 'user'.
// It is automatically terminated when the test is finished.
func createMariaDBContainer(ctx context.Context) (*mariadb.MariaDBContainer, error) {
func createMariaDBContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
) (*mariadb.MariaDBContainer, error) {
return deferTerminate(mariadb.RunContainer(ctx,
testcontainers.WithImage(mariaDBImage),
mariadb.WithDatabase("user"),
mariadb.WithUsername("user"),
mariadb.WithPassword("user"),
WithNetwork(ctx, "mariaDB"),
withNetwork("mariaDB", e2eNet),
))
}
// createBlockyContainer creates a blocky container with a config provided by the given lines.
// It is attached to the test network under the alias 'blocky'.
// It is automatically terminated when the test is finished.
func createBlockyContainer(ctx context.Context,
func createBlockyContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
lines ...string,
) (testcontainers.Container, error) {
confFile := createTempFile(lines...)
@ -163,7 +169,7 @@ func createBlockyContainer(ctx context.Context,
WaitingFor: wait.ForHealthCheck().WithStartupTimeout(startupTimeout),
}
container, err := startContainerWithNetwork(ctx, req, "blocky")
container, err := startContainerWithNetwork(ctx, req, "blocky", e2eNet)
if err != nil {
// attach container log if error occurs
if r, err := container.Logs(ctx); err == nil {
@ -202,7 +208,6 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
retry.Attempts(retryAttempts),
retry.DelayType(retry.BackOffDelay),
retry.Delay(time.Second))
if err != nil {
return fmt.Errorf("can't perform the DNS healthcheck request: %w", err)
}
@ -210,6 +215,7 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
for _, httpPort := range cfg.Ports.HTTP {
parts := strings.Split(httpPort, ":")
port := parts[len(parts)-1]
err = retry.Do(
func() error {
return doHTTPRequest(ctx, container, port)
@ -220,7 +226,6 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
retry.Attempts(retryAttempts),
retry.DelayType(retry.BackOffDelay),
retry.Delay(time.Second))
if err != nil {
return fmt.Errorf("can't perform the HTTP request: %w", err)
}

View File

@ -3,74 +3,32 @@ package e2e
import (
"bufio"
"context"
"fmt"
"net"
"strings"
"time"
"github.com/docker/go-connections/nat"
"github.com/miekg/dns"
"github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/testcontainers/testcontainers-go"
testNet "github.com/testcontainers/testcontainers-go/network"
)
//nolint:gochecknoglobals
var (
// currentNetwork is the global test network instance.
currentNetwork = testNetwork{}
)
// getRandomNetwork returns a new test network which is used for the tests and removed afterwards.
func getRandomNetwork(ctx context.Context) *testcontainers.DockerNetwork {
e2eNet, err := testNet.New(ctx)
Expect(err).Should(Succeed())
DeferCleanup(func(ctx context.Context) {
Expect(e2eNet.Remove(ctx)).Should(Succeed())
})
// WithNetwork attaches the container with the given alias to the test network
//
//nolint:staticcheck
func WithNetwork(ctx context.Context, alias string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) {
networkName := currentNetwork.Name()
network, err := testcontainers.GenericNetwork(ctx, testcontainers.GenericNetworkRequest{
NetworkRequest: testcontainers.NetworkRequest{
Name: networkName,
CheckDuplicate: true, // force the Docker provider to reuse an existing network
Attachable: true,
},
})
if err != nil && !strings.Contains(err.Error(), "already exists") {
ginkgo.Fail(fmt.Sprintf("Failed to create network '%s'. Container won't be attached to this network: %v",
networkName, err))
return
}
// decrement the network counter when the test is finished and remove the network if it is not used anymore.
ginkgo.DeferCleanup(func(ctx context.Context) error {
if currentNetwork.Detach() {
if err := network.Remove(ctx); err != nil &&
!strings.Contains(err.Error(), "removing") &&
!strings.Contains(err.Error(), "not found") {
return err
}
}
return nil
})
// increment the network counter when the container is created.
currentNetwork.Attach()
// attaching to the network because it was created with success or it already existed.
req.Networks = append(req.Networks, networkName)
if req.NetworkAliases == nil {
req.NetworkAliases = make(map[string][]string)
}
req.NetworkAliases[networkName] = []string{alias}
}
return e2eNet
}
// deferTerminate is a helper function to terminate the container when the test is finished.
func deferTerminate[T testcontainers.Container](container T, err error) (T, error) {
ginkgo.DeferCleanup(func(ctx context.Context) error {
DeferCleanup(func(ctx context.Context) error {
if container.IsRunning() {
return container.Terminate(ctx)
}
@ -84,12 +42,13 @@ func deferTerminate[T testcontainers.Container](container T, err error) (T, erro
// startContainerWithNetwork starts the container with the given alias and attaches it to the test network.
// The container is wrapped with deferTerminate to terminate the container when the test is finished.
func startContainerWithNetwork(ctx context.Context, req testcontainers.ContainerRequest, alias string,
e2eNet *testcontainers.DockerNetwork,
) (testcontainers.Container, error) {
greq := testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
}
WithNetwork(ctx, alias).Customize(&greq)
withNetwork(alias, e2eNet).Customize(&greq)
return deferTerminate(testcontainers.GenericContainer(ctx, greq))
}
@ -121,7 +80,6 @@ func getContainerHostPort(ctx context.Context, c testcontainers.Container, p nat
}
host, err = c.Host(ctx)
if err != nil {
return "", "", err
}
@ -149,3 +107,8 @@ func getContainerLogs(ctx context.Context, c testcontainers.Container) (lines []
return nil, err
}
// withNetwork returns a CustomizeRequestOption which attaches the container to the given network with the given alias.
func withNetwork(alias string, e2eNet *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption {
return testNet.WithNetwork([]string{alias}, e2eNet)
}

View File

@ -16,26 +16,34 @@ import (
)
var _ = Describe("Metrics functional tests", func() {
var blocky testcontainers.Container
var err error
var metricsURL string
var (
e2eNet *testcontainers.DockerNetwork
blocky testcontainers.Container
err error
metricsURL string
)
BeforeEach(func(ctx context.Context) {
e2eNet = getRandomNetwork(ctx)
})
Describe("Metrics", func() {
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
_, err = createHTTPServerContainer(ctx, "httpserver1", "list1.txt", "domain1.com")
_, err = createHTTPServerContainer(ctx, "httpserver1", e2eNet, "list1.txt", "domain1.com")
Expect(err).Should(Succeed())
_, err = createHTTPServerContainer(ctx, "httpserver2", "list2.txt",
_, err = createHTTPServerContainer(ctx, "httpserver2", e2eNet, "list2.txt",
"domain1.com", "domain2", "domain3")
Expect(err).Should(Succeed())
_, err = createHTTPServerContainer(ctx, "httpserver2", "list2.txt", "domain1.com", "domain2", "domain3")
_, err = createHTTPServerContainer(ctx, "httpserver2", e2eNet, "list2.txt",
"domain1.com", "domain2", "domain3")
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",

View File

@ -16,23 +16,29 @@ import (
)
var _ = Describe("Query logs functional tests", func() {
var blocky testcontainers.Container
var postgresDB *postgres.PostgresContainer
var mariaDB *mariadb.MariaDBContainer
var db *gorm.DB
var err error
var (
e2eNet *testcontainers.DockerNetwork
blocky testcontainers.Container
postgresDB *postgres.PostgresContainer
mariaDB *mariadb.MariaDBContainer
db *gorm.DB
err error
)
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`, `A unknown/NXDOMAIN()`)
e2eNet = getRandomNetwork(ctx)
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`,
`A unknown/NXDOMAIN()`)
Expect(err).Should(Succeed())
})
Describe("Query logging into the mariaDB database", func() {
BeforeEach(func(ctx context.Context) {
mariaDB, err = createMariaDBContainer(ctx)
mariaDB, err = createMariaDBContainer(ctx, e2eNet)
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -104,10 +110,10 @@ var _ = Describe("Query logs functional tests", func() {
Describe("Query logging into the postgres database", func() {
BeforeEach(func(ctx context.Context) {
postgresDB, err = createPostgresContainer(ctx)
postgresDB, err = createPostgresContainer(ctx, e2eNet)
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",

View File

@ -13,12 +13,17 @@ import (
)
var _ = Describe("Redis configuration tests", func() {
var blocky1, blocky2, mokka testcontainers.Container
var redisClient *redis.Client
var err error
var (
e2eNet *testcontainers.DockerNetwork
blocky1, blocky2, mokka testcontainers.Container
redisClient *redis.Client
err error
)
BeforeEach(func(ctx context.Context) {
redisDB, err := createRedisContainer(ctx)
e2eNet = getRandomNetwork(ctx)
redisDB, err := createRedisContainer(ctx, e2eNet)
Expect(err).Should(Succeed())
redisConnectionString, err := redisDB.ConnectionString(ctx)
@ -32,14 +37,14 @@ var _ = Describe("Redis configuration tests", func() {
Expect(dbSize(ctx, redisClient)).Should(BeNumerically("==", 0))
mokka, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
mokka, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
})
Describe("Cache sharing between blocky instances", func() {
When("Redis and 2 blocky instances are configured", func() {
BeforeEach(func(ctx context.Context) {
blocky1, err = createBlockyContainer(ctx,
blocky1, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -51,7 +56,7 @@ var _ = Describe("Redis configuration tests", func() {
)
Expect(err).Should(Succeed())
blocky2, err = createBlockyContainer(ctx,
blocky2, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -102,7 +107,7 @@ var _ = Describe("Redis configuration tests", func() {
Describe("Cache loading on startup", func() {
When("Redis and 1 blocky instance are configured", func() {
BeforeEach(func(ctx context.Context) {
blocky1, err = createBlockyContainer(ctx,
blocky1, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -130,7 +135,7 @@ var _ = Describe("Redis configuration tests", func() {
})
By("start other instance of blocky now -> it should load the cache from redis", func() {
blocky2, err = createBlockyContainer(ctx,
blocky2, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",

View File

@ -1,52 +0,0 @@
package e2e
import (
"fmt"
"sync/atomic"
"time"
)
// testNetwork is a helper struct to create a unique network name and count the number of attached containers.
type testNetwork struct {
name atomic.Value
counter atomic.Int32
}
// Name returns the name of the test network.
func (n *testNetwork) Name() string {
if v := n.name.Load(); v != nil {
return v.(string)
}
n.Reset()
return n.Name()
}
// Reset generates a new network name.
func (n *testNetwork) Reset() {
n.name.Store(fmt.Sprintf("blocky-e2e-network_%d", time.Now().Unix()))
}
// Attach increments the network counter.
func (n *testNetwork) Attach() {
n.counter.Add(1)
}
// Detach decrements the network counter and returns true if the counter hits zero which indicates that the
// network can be removed.
func (n *testNetwork) Detach() bool {
if n.counter.Load() <= 0 {
return false
}
n.counter.Add(-1)
if n.counter.Load() == 0 {
n.Reset()
return true
}
return false
}

View File

@ -12,13 +12,20 @@ import (
)
var _ = Describe("Upstream resolver configuration tests", func() {
var blocky testcontainers.Container
var err error
var (
e2eNet *testcontainers.DockerNetwork
blocky testcontainers.Container
err error
)
BeforeEach(func(ctx context.Context) {
e2eNet = getRandomNetwork(ctx)
})
Describe("'upstreams.init.strategy' parameter handling", func() {
When("'upstreams.init.strategy' is fast and upstream server as IP is not reachable", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -39,7 +46,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
})
When("'upstreams.init.strategy' is fast and upstream server as host name is not reachable", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
@ -58,7 +65,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
})
When("'upstreams.init.strategy' is failOnError and upstream as IP address server is not reachable", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -76,7 +83,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
})
When("'upstreams.init.strategy' is failOnError and upstream server as host name is not reachable", func() {
BeforeEach(func(ctx context.Context) {
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",
@ -95,12 +102,12 @@ var _ = Describe("Upstream resolver configuration tests", func() {
})
Describe("'upstreams.timeout' parameter handling", func() {
BeforeEach(func(ctx context.Context) {
_, err = createDNSMokkaContainer(ctx, "moka1",
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet,
`A example.com/NOERROR("A 1.2.3.4 123")`,
`A delay.com/delay(NOERROR("A 1.1.1.1 100"), "300ms")`)
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx,
blocky, err = createBlockyContainer(ctx, e2eNet,
"upstreams:",
" groups:",
" default:",