From 56c650fccfa0295323dfe68aa9baedfbf80f84f2 Mon Sep 17 00:00:00 2001 From: Roberto Santalla Date: Wed, 20 Sep 2023 21:00:26 +0200 Subject: [PATCH] wip: nettest: add framework for nesting network disruptions in protocols --- .../nettest/containers/iptables/Dockerfile | 4 + .../nettest/containers/redis-go/Dockerfile | 9 ++ .../nettest/containers/redis-go/go.mod | 13 ++ .../nettest/containers/redis-go/go.sum | 17 +++ .../nettest/containers/redis-go/main.go | 41 +++++++ .../protocol/nettest/redis/iptables_test.go | 112 ++++++++++++++++++ pkg/agent/protocol/nettest/tlog/tlog.go | 18 +++ 7 files changed, 214 insertions(+) create mode 100644 pkg/agent/protocol/nettest/containers/iptables/Dockerfile create mode 100644 pkg/agent/protocol/nettest/containers/redis-go/Dockerfile create mode 100644 pkg/agent/protocol/nettest/containers/redis-go/go.mod create mode 100644 pkg/agent/protocol/nettest/containers/redis-go/go.sum create mode 100644 pkg/agent/protocol/nettest/containers/redis-go/main.go create mode 100644 pkg/agent/protocol/nettest/redis/iptables_test.go create mode 100644 pkg/agent/protocol/nettest/tlog/tlog.go diff --git a/pkg/agent/protocol/nettest/containers/iptables/Dockerfile b/pkg/agent/protocol/nettest/containers/iptables/Dockerfile new file mode 100644 index 00000000..d4dace98 --- /dev/null +++ b/pkg/agent/protocol/nettest/containers/iptables/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.18 + +RUN apk update && apk add iptables + diff --git a/pkg/agent/protocol/nettest/containers/redis-go/Dockerfile b/pkg/agent/protocol/nettest/containers/redis-go/Dockerfile new file mode 100644 index 00000000..b0ffe7a4 --- /dev/null +++ b/pkg/agent/protocol/nettest/containers/redis-go/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.21-bookworm as build + +WORKDIR /app +COPY . . +RUN go build -o redis-go + +FROM debian:bookworm +COPY --from=build /app/redis-go /bin +ENTRYPOINT [ "/bin/redis-go" ] diff --git a/pkg/agent/protocol/nettest/containers/redis-go/go.mod b/pkg/agent/protocol/nettest/containers/redis-go/go.mod new file mode 100644 index 00000000..961db484 --- /dev/null +++ b/pkg/agent/protocol/nettest/containers/redis-go/go.mod @@ -0,0 +1,13 @@ +module redisgo + +go 1.19 + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) + +require ( + github.com/go-redis/redis/v8 v8.11.5 + github.com/onsi/gomega v1.27.10 // indirect +) diff --git a/pkg/agent/protocol/nettest/containers/redis-go/go.sum b/pkg/agent/protocol/nettest/containers/redis-go/go.sum new file mode 100644 index 00000000..da4a8515 --- /dev/null +++ b/pkg/agent/protocol/nettest/containers/redis-go/go.sum @@ -0,0 +1,17 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/agent/protocol/nettest/containers/redis-go/main.go b/pkg/agent/protocol/nettest/containers/redis-go/main.go new file mode 100644 index 00000000..db9834b8 --- /dev/null +++ b/pkg/agent/protocol/nettest/containers/redis-go/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "log" + "os" + "time" + + "github.com/go-redis/redis/v8" +) + +func main() { + rdb := redis.NewClient(&redis.Options{ + Addr: os.Args[1], + Password: "", // no password set + DB: 0, // use default DB + }) + + ctx := context.Background() + + err := rdb.Set(ctx, "counter", 0.0, 0).Err() + if err != nil { + log.Fatalf("creating redis key: %v", err) + } + + for { + err = rdb.Incr(ctx, "counter").Err() + if err != nil { + log.Fatalf("incrementing counter: %v", err) + } + + cmd := rdb.Get(ctx, "counter") + if err := cmd.Err(); err != nil { + log.Fatalf("getting current value: %v", err) + } + + current, _ := cmd.Float64() + log.Printf("Current value: %f", current) + time.Sleep(time.Second) + } +} diff --git a/pkg/agent/protocol/nettest/redis/iptables_test.go b/pkg/agent/protocol/nettest/redis/iptables_test.go new file mode 100644 index 00000000..aac29cf3 --- /dev/null +++ b/pkg/agent/protocol/nettest/redis/iptables_test.go @@ -0,0 +1,112 @@ +package redis_test + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const iptablesRule = "INPUT -p tcp --dport 6379 -j REJECT --reject-with tcp-reset" + +func Test_Redis(t *testing.T) { + t.Parallel() + + if os.Getenv("NETTEST") == "" { + t.Skip("Skipping network protocol test as NETTEST is not set") + } + + ctx := context.TODO() + + redis, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ProviderType: testcontainers.ProviderDocker, + ContainerRequest: testcontainers.ContainerRequest{ + Networks: []string{}, + Image: "redis", + ExposedPorts: []string{"6379/tcp"}, + WaitingFor: wait.ForExposedPort(), + }, + Started: true, + }) + if err != nil { + t.Fatalf("failed to create redis container %v", err) + } + + t.Cleanup(func() { + _ = redis.Terminate(ctx) + }) + + iptables, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ProviderType: testcontainers.ProviderDocker, + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Dockerfile: "Dockerfile", + Context: filepath.Join("..", "containers", "iptables"), + }, + NetworkMode: container.NetworkMode("container:" + redis.GetContainerID()), + Cmd: []string{"/bin/sh", "-c", "echo ready && sleep infinity"}, + Privileged: true, + WaitingFor: wait.ForLog("ready"), + }, + Started: true, + }) + if err != nil { + t.Fatalf("failed to create agent container %v", err) + } + + t.Cleanup(func() { + _ = iptables.Terminate(ctx) + }) + + redisGo, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ProviderType: testcontainers.ProviderDocker, + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Dockerfile: "Dockerfile", + Context: filepath.Join("..", "containers", "redis-go"), + }, + Cmd: []string{"localhost:6379"}, + NetworkMode: container.NetworkMode("container:" + redis.GetContainerID()), + }, + Started: true, + }) + if err != nil { + t.Fatalf("failed to create agent container %v", err) + } + + t.Cleanup(func() { + _ = redisGo.Terminate(ctx) + }) + + // TODO:Follow container under test logs. Currently doing so hangs out the test forever. + + redisGoStatus, err := redisGo.State(ctx) + if err != nil { + t.Fatal(err) + } + if !redisGoStatus.Running { + t.Fatalf("Redis client container failed") + } + + //nolint:errcheck,gosec // Error checking elided for brevity. TODO: Wrap this in a helper function. + iptables.Exec(context.TODO(), []string{"/bin/sh", "-c", "iptables -I " + iptablesRule}) + + time.Sleep(2 * time.Second) + + //nolint:errcheck,gosec // Error checking elided for brevity. TODO: Wrap this in a helper function. + iptables.Exec(context.TODO(), []string{"/bin/sh", "-c", "iptables -D " + iptablesRule}) + + redisGoStatus, err = redisGo.State(ctx) + if err != nil { + t.Fatal(err) + } + + if !redisGoStatus.Running { + t.Fatalf("Redis client container failed") + } +} diff --git a/pkg/agent/protocol/nettest/tlog/tlog.go b/pkg/agent/protocol/nettest/tlog/tlog.go new file mode 100644 index 00000000..ff9d9aae --- /dev/null +++ b/pkg/agent/protocol/nettest/tlog/tlog.go @@ -0,0 +1,18 @@ +// Package tlog implements a testcontainers log handler that mirrors logs to a test logger. +package tlog + +import ( + "testing" + + "github.com/testcontainers/testcontainers-go" +) + +// Mirror is a testcontainers log adapter that mirrors container output to testing.T.Log. +type Mirror struct { + T *testing.T +} + +// Accept implements the testcontainers adapter interface by writing received output to the test logger. +func (m Mirror) Accept(log testcontainers.Log) { + m.T.Logf("%s: %s", log.LogType, log.Content) +}