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

POC: agent integration tests using test containers #322

Merged
merged 3 commits into from
Aug 29, 2023
Merged
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
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ agent_image ?= ghcr.io/grafana/xk6-disruptor-agent:latest

all: build

agent-image: build-agent test
agent-image: build-agent
docker build --build-arg TARGETARCH=${arch} -t $(agent_image) images/agent

disruptor-image:
Expand All @@ -21,6 +21,7 @@ build-e2e:
go build -tags e2e -o build/e2e-cluster ./cmd/e2e-cluster/main.go

build-agent:
go test ./pkg/agent/...
GOOS=linux CGO_ENABLED=0 go build -o images/agent/build/xk6-disruptor-agent-linux-${arch} ./cmd/agent

clean:
Expand All @@ -44,6 +45,11 @@ e2e-setup: build-e2e
format:
go fmt ./...

integration-agent: agent-image
go test -tags integration ./pkg/agent/...

integration: integration-agent

# Running with -buildvcs=false works around the issue of `go list all` failing when git, which runs as root inside
# the container, refuses to operate on the disruptor source tree as it is not owned by the same user (root).
lint:
Expand Down
19 changes: 10 additions & 9 deletions docs/01-development/01-contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ $ git clone https://github.com/grafana/xk6-disruptor.git
$ cd xk6-disruptor
```

### Makefile

Most of the development tasks can be executed using `make` targets:
* `agent-image`: builds the `xk6-disruptor-agent` image locally
* `build`: builds k6 with the `xk6-disruptor` extension
* `clean`: removes local build and other work directories
* `e2e`: executes the end-to-end tests. These tests can take several minutes.
* `test`: executes unit tests
* `lint`: runs the linter
## Integration tests

Integration tests are implemented in the same packages than the component they test. Execution is conditioned using the `integration` build tag.

In order to run integration tests (if any) for a package, use the following command:

```sh
go test -tags integration ./path/to/package/...
```


### Extension/agent image versions dependencies

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ module github.com/grafana/xk6-disruptor
go 1.19

require (
github.com/docker/docker v24.0.5+incompatible
github.com/dop251/goja v0.0.0-20230621100801-7749907a8a20
github.com/google/go-cmp v0.5.9
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.5.0
github.com/testcontainers/testcontainers-go v0.23.0
go.k6.io/k6 v0.46.0
k8s.io/api v0.27.4
k8s.io/apimachinery v0.27.4
Expand All @@ -23,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/docker v24.0.5+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
Expand All @@ -38,7 +39,6 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/testcontainers/testcontainers-go v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
Expand Down
152 changes: 152 additions & 0 deletions pkg/agent/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//go:build integration
// +build integration

package agent

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/docker/docker/api/types/container"
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

func injectHTTPError(rate float64, code int, upstream string) []string {
return []string{
"xk6-disruptor-agent",
"http",
"--duration",
"300s",
"--rate",
fmt.Sprintf("%.2f", rate),
"--error",
fmt.Sprintf("%d", code),
"--port",
"8080",
"--target",
"80",
"--upstream-host",
upstream,
}
}

func Test_HTTPFaultInjection(t *testing.T) {
t.Parallel()

testCases := []struct {
test string
rate float64
code int
request int
expect int
}{
{
test: "inject 418 error",
rate: 1.0,
code: 418,
request: 200,
expect: 418,
},
{
test: "inject no error",
rate: 0.0,
code: 0,
request: 200,
expect: 200,
},
{
test: "handle upstream error",
rate: 0.0,
code: 0,
request: 500,
expect: 500,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.test, func(t *testing.T) {
t.Parallel()

ctx := context.Background()

gcr := testcontainers.GenericContainerRequest{
ProviderType: testcontainers.ProviderDocker,
ContainerRequest: testcontainers.ContainerRequest{
Image: "kennethreitz/httpbin",
ExposedPorts: []string{
"80",
},
WaitingFor: wait.ForExposedPort(),
},
Started: true,
}
httpbin, err := testcontainers.GenericContainer(ctx, gcr)
if err != nil {
t.Fatalf("failed to create httpbin container %v", err)
}

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

httpbinIP, err := httpbin.ContainerIP(ctx)
if err != nil {
t.Fatalf("failed to get httpbin IP:\n%v", err)
}

// make the agent run using the same stack than the httpbin container
httpbinNetwork := container.NetworkMode("container:" + httpbin.GetContainerID())
gcr = testcontainers.GenericContainerRequest{
ProviderType: testcontainers.ProviderDocker,
ContainerRequest: testcontainers.ContainerRequest{
Image: "ghcr.io/grafana/xk6-disruptor-agent",
NetworkMode: httpbinNetwork,
Cmd: injectHTTPError(tc.rate, tc.code, httpbinIP),
Privileged: true,
// TODO: find a better way for checking the agent is ready
WaitingFor: wait.ForExec([]string{"pgrep", "xk6-disruptor-agent"}),
},
Started: true,
}

agent, err := testcontainers.GenericContainer(ctx, gcr)
if err != nil {
t.Fatalf("failed to create agent container %v", err)
}

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

httpPort, err := httpbin.MappedPort(ctx, "80")
if err != nil {
t.Fatalf("failed to get httpbin port:\n%v", err)
}

// access httpbin
url := fmt.Sprintf("http://localhost:%s/status/%d", httpPort.Port(), tc.request)

request, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Fatalf("failed to create request %v", err)
}

resp, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatalf("failed request to %q: %v", url, err)
}

defer func() {
_ = resp.Body.Close()
}()

if resp.StatusCode != tc.expect {
t.Fatalf("expected status code %d but %d received", tc.expect, resp.StatusCode)
}
})
}
}