Skip to content
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

wip: nettest: add framework for nesting network disruptions in protocols #350

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkg/agent/protocol/nettest/containers/cutter/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM debian:bookworm

RUN apt update && apt install -y curl zstd

# Upstream for cutter website is unavailable at the time of writing, the arch linux package is the most stable
# source I was able to find.
RUN curl -vL https://archive.archlinux.org/packages/c/cutter/cutter-1.04-3-x86_64.pkg.tar.zst \
| tar --zstd -xvC /

4 changes: 4 additions & 0 deletions pkg/agent/protocol/nettest/containers/iptables/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM alpine:3.18

RUN apk update && apk add iptables

9 changes: 9 additions & 0 deletions pkg/agent/protocol/nettest/containers/redis-go/Dockerfile
Original file line number Diff line number Diff line change
@@ -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" ]
13 changes: 13 additions & 0 deletions pkg/agent/protocol/nettest/containers/redis-go/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
17 changes: 17 additions & 0 deletions pkg/agent/protocol/nettest/containers/redis-go/go.sum
Original file line number Diff line number Diff line change
@@ -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=
41 changes: 41 additions & 0 deletions pkg/agent/protocol/nettest/containers/redis-go/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 5 additions & 0 deletions pkg/agent/protocol/nettest/containers/tcpdump/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM debian:bookworm

RUN apt update && apt install -y tcpdump

ENTRYPOINT ["/bin/tcpdump"]
3 changes: 3 additions & 0 deletions pkg/agent/protocol/nettest/containers/tcpkill/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM debian:bookworm

RUN apt update && apt install -y dsniff
126 changes: 126 additions & 0 deletions pkg/agent/protocol/nettest/redis/cutter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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"

"github.com/grafana/xk6-disruptor/pkg/agent/protocol/nettest/util"
)

func Test_Redis_Cutter(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{
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)
})

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)
}

// TODO: Calling terminate with a log attached makes the test hang.
// See: https://github.com/testcontainers/testcontainers-go/issues/1669
// t.Cleanup(func() {
// _ = redisGo.Terminate(ctx)
// })

redisGo.FollowOutput(util.Mirror{T: t, Name: "redis-go"})
err = redisGo.StartLogProducer(ctx)
if err != nil {
t.Fatal(err)
}
// TODO: See above.
// t.Cleanup(func() {
// redisGo.StopLogProducer()
// })

redisGoStatus, err := redisGo.State(ctx)
if err != nil {
t.Fatal(err)
}
if !redisGoStatus.Running {
t.Fatalf("Redis client container failed")
}

time.Sleep(3 * time.Second)

cutter, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ProviderType: testcontainers.ProviderDocker,
ContainerRequest: testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Dockerfile: "Dockerfile",
Context: filepath.Join("..", "containers", "cutter"),
},
NetworkMode: container.NetworkMode("container:" + redis.GetContainerID()),
Cmd: []string{"/bin/sh", "-c", "tcp-cutter 127.0.0.1 6379"},
Privileged: true,
},
Started: true,
})
if err != nil {
t.Fatalf("failed to create cutter container %v", err)
}

// t.Cleanup(func() {
// _ = cutter.Terminate(ctx)
// })

cutter.FollowOutput(util.Mirror{T: t, Name: "cutter"})
err = cutter.StartLogProducer(ctx)
if err != nil {
t.Fatal(err)
}

// t.Cleanup(func() {
// cutter.StopLogProducer()
// })

time.Sleep(2 * time.Second)

redisGoStatus, err = redisGo.State(ctx)
if err != nil {
t.Fatal(err)
}

if !redisGoStatus.Running {
t.Fatalf("Redis client container failed")
}
}
123 changes: 123 additions & 0 deletions pkg/agent/protocol/nettest/redis/iptables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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"

"github.com/grafana/xk6-disruptor/pkg/agent/protocol/nettest/util"
)

const iptablesRule = "INPUT -p tcp --dport 6379 -j REJECT --reject-with tcp-reset"

func Test_Redis_Iptables(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{
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)
}

// TODO: Calling terminate with a log attached makes the test hang.
// See: https://github.com/testcontainers/testcontainers-go/issues/1669
// t.Cleanup(func() {
// _ = redisGo.Terminate(ctx)
// })

redisGo.FollowOutput(util.Mirror{T: t, Name: "redis-go"})
err = redisGo.StartLogProducer(ctx)
if err != nil {
t.Fatal(err)
}
// TODO: See above.
// t.Cleanup(func() {
// redisGo.StopLogProducer()
// })

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")
}
}
Loading
Loading