From 9c42d30e6bac6c3d4629d337a09008a38a91f5d4 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 --- go.mod | 4 +- .../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 | 106 ++++++++++++++++++ pkg/agent/protocol/nettest/tlog/tlog.go | 20 ++++ 8 files changed, 212 insertions(+), 2 deletions(-) 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/go.mod b/go.mod index a8cc4d49..c1fc4880 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/docker/docker v24.0.5+incompatible + github.com/docker/go-connections v0.4.0 github.com/dop251/goja v0.0.0-20230621100801-7749907a8a20 github.com/google/go-cmp v0.5.9 github.com/sirupsen/logrus v1.9.3 @@ -24,7 +25,6 @@ require ( github.com/containerd/containerd v1.7.3 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect @@ -61,7 +61,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jhump/protoreflect v1.15.1 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..14fbaba5 --- /dev/null +++ b/pkg/agent/protocol/nettest/redis/iptables_test.go @@ -0,0 +1,106 @@ +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) { + 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") + } + + iptables.Exec(context.TODO(), []string{"/bin/sh", "-c", "iptables -I " + iptablesRule}) + time.Sleep(2 * time.Second) + 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..966153fc --- /dev/null +++ b/pkg/agent/protocol/nettest/tlog/tlog.go @@ -0,0 +1,20 @@ +// Package tlog implements a testcontainers log handler that mirrors logs to a test logger. +package tlog + +import ( + "testing" + + "github.com/testcontainers/testcontainers-go" +) + +type Mirror struct { + T *testing.T +} + +func (m Mirror) Accept(log testcontainers.Log) { + if log.Content == nil { + return + } + + m.T.Logf("%s: %s", log.LogType, log.Content) +}