Skip to content

Commit

Permalink
Merge pull request #169 from grafana/add-transparent-proxy-option
Browse files Browse the repository at this point in the history
Add transparent proxy option
  • Loading branch information
pablochacin authored May 30, 2023
2 parents a25b781 + b4f081f commit 84a5039
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 17 deletions.
17 changes: 14 additions & 3 deletions cmd/agent/commands/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ func BuildGrpcCmd() *cobra.Command {
var port uint
var target uint
var iface string
upstreamHost := "localhost"
transparent := true

cmd := &cobra.Command{
Use: "grpc",
Short: "grpc disruptor",
Long: "Disrupts grpc request by introducing delays and errors." +
" Requires NET_ADMIM capabilities for setting iptable rules.",
Long: "Disrupts http request by introducing delays and errors." +
" When running as a transparent proxy requires NET_ADMIM capabilities for setting" +
" iptable rules.",
RunE: func(cmd *cobra.Command, args []string) error {
listenAddress := fmt.Sprintf(":%d", port)
upstreamAddress := fmt.Sprintf(":%d", target)
upstreamAddress := fmt.Sprintf("%s:%d", upstreamHost, target)
proxy, err := grpc.NewProxy(
grpc.ProxyConfig{
ListenAddress: listenAddress,
Expand All @@ -33,6 +37,12 @@ func BuildGrpcCmd() *cobra.Command {
return err
}

// run as a regular proxy
if !transparent {
// TODO: pass a context with a timeout using the duration argument
return proxy.Start()
}

disruptor, err := protocol.NewDisruptor(
protocol.DisruptorConfig{
TargetPort: target,
Expand All @@ -59,6 +69,7 @@ func BuildGrpcCmd() *cobra.Command {
cmd.Flags().UintVarP(&target, "target", "t", 80, "port the proxy will redirect request to")
cmd.Flags().StringSliceVarP(&disruption.Excluded, "exclude", "x", []string{}, "comma-separated list of grpc services"+
" to be excluded from disruption")
cmd.Flags().BoolVar(&transparent, "transparent", true, "run as transparent proxy")

return cmd
}
15 changes: 13 additions & 2 deletions cmd/agent/commands/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ func BuildHTTPCmd() *cobra.Command {
var port uint
var target uint
var iface string
upstreamHost := "localhost"
transparent := true

cmd := &cobra.Command{
Use: "http",
Short: "http disruptor",
Long: "Disrupts http request by introducing delays and errors." +
" Requires NET_ADMIM capabilities for setting iptable rules.",
" When running as a transparent proxy requires NET_ADMIM capabilities for setting" +
" iptable rules.",
RunE: func(cmd *cobra.Command, args []string) error {
listenAddress := fmt.Sprintf(":%d", port)
upstreamAddress := fmt.Sprintf("http://127.0.0.1:%d", target)
upstreamAddress := fmt.Sprintf("http://%s:%d", upstreamHost, target)
proxy, err := http.NewProxy(
http.ProxyConfig{
ListenAddress: listenAddress,
Expand All @@ -33,6 +37,12 @@ func BuildHTTPCmd() *cobra.Command {
return err
}

// run as a regular proxy
if !transparent {
// TODO: pass a context with a timeout using the duration argument
return proxy.Start()
}

disruptor, err := protocol.NewDisruptor(
protocol.DisruptorConfig{
TargetPort: target,
Expand All @@ -56,6 +66,7 @@ func BuildHTTPCmd() *cobra.Command {
cmd.Flags().StringVarP(&disruption.ErrorBody, "body", "b", "", "body for injected faults")
cmd.Flags().StringSliceVarP(&disruption.Excluded, "exclude", "x", []string{}, "comma-separated list of path(s)"+
" to be excluded from disruption")
cmd.Flags().BoolVar(&transparent, "transparent", true, "run as transparent proxy")
cmd.Flags().StringVarP(&iface, "interface", "i", "eth0", "interface to disrupt")
cmd.Flags().UintVarP(&port, "port", "p", 8080, "port the proxy will listen to")
cmd.Flags().UintVarP(&target, "target", "t", 80, "port the proxy will redirect request to")
Expand Down
36 changes: 29 additions & 7 deletions cmd/agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main
import (
"fmt"
"os"
"path"
"runtime"
"runtime/pprof"
runtimetrace "runtime/trace"
Expand All @@ -13,7 +14,31 @@ import (
"github.com/spf13/cobra"
)

const lockFile = "/var/run/xk6-disruptor"
const lockFile = "xk6-disruptor"

// returns the path to the lock file
func getLockPath() string {
// get runtime directory for user
lockDir := os.Getenv("XDG_RUNTIME_DIR")
if lockDir == "" {
lockDir = os.TempDir()
}

return path.Join(lockDir, lockFile)
}

// ensure only one instance of the agent runs
func lockExecution() error {
acquired, err := process.Lock(getLockPath())
if err != nil {
return fmt.Errorf("failed to create lock file %q: %w", getLockPath(), err)
}
if !acquired {
return fmt.Errorf("another disruptor command is already in execution")
}

return nil
}

//nolint:funlen,gocognit
func main() {
Expand All @@ -33,12 +58,9 @@ func main() {
Long: "A command for injecting disruptions in a target system.\n" +
"It can run as stand-alone process or in a container",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
acquired, err := process.Lock(lockFile)
err := lockExecution()
if err != nil {
return fmt.Errorf("error creating lock file: %w", err)
}
if !acquired {
return fmt.Errorf("another disruptor command is already in execution")
return err
}

// cpu profiling
Expand Down Expand Up @@ -80,7 +102,7 @@ func main() {
return nil
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
_ = process.Unlock(lockFile)
_ = process.Unlock(getLockPath())
if cpuProfile {
pprof.StopCPUProfile()
}
Expand Down
35 changes: 30 additions & 5 deletions docs/01-development/01-contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ If using `minikube` the following command loads the image into the cluster:
minikube image load ghcr.io/grafana/xk6-disruptor-agent:latest
```

### Debugging the disruptor agent
## Debugging the disruptor agent

The disruptor agent is the responsible for injecting faults in the targets (e.g. pods). The agent is injected into the targets by the xk6-disruptor extension as an ephemeral container with the name `xk6-agent`.

You can debug the agent by running it manually at a target.
### Running manually

First enter the agent's container using an interactive console:
Once the agent is injected in a target (using the [xk6-disruptor API](https://k6.io/docs/javascript-api/xk6-disruptor/api/) in a k6 test), you can debug the agent by running it manually.

First enter the agent's container at the target pod using an interactive console:

```bash
kubectl exec -it <target pod> -c xk6-agent -- sh
Expand All @@ -90,17 +92,40 @@ Once you get the prompt, you can inject faults by running the `xk6-disruptor-age
xk6-disruptor-agent [arguments for fault injection]
```

### Running locally

It is possible to run the agent locally in your machine using the following command:

```bash
xk6-disruptor-agent [arguments for fault injection]
```

This is useful for debugging and also to facilitate [CPU and memory profiling](#tracing-and-profiling)

### Running as a proxy

When debugging issues with protocols, you can run the agent in your local machine as a proxy that redirects the traffic to an upstream destination.

For running the protocol fault injection as a (non-transparent) proxy use the `--transparent=false` option:

```bash
xk6-disruptor-agent <protocol> --transparent=false [arguments for fault injection]
```

### Tracing and profiling

In order to facilitate debugging `xk6-disruptor-agent` offers options for generating execution traces:
* `--trace`: generate traces. The `--trace-file` option allows specifying the output file for traces (default `trace.out`)
* `--cpu-profile`: generate CPU profiling information. The `--cpu-profile-file` option allows specifying the output file for profile information (default `cpu.pprof`)
* `--mem-profile`: generate memory profiling information. By default, it sets the [memory profile rate](https://pkg.go.dev/runtime#pkg-variables) to `1`, which will profile every allocation. This rate can be controlled using the `--mem-profile-rate` option. The `--mem-profile-file` option allows specifying the output file for profile information (default `mem.pprof`)

In order to analyze those files you have to copy them from the target pod to your local machine. For example, for copying the `trace.out` file:
If you run the [disrupor manually](#running-manually) in a pod you have to copy them from the target pod to your local machine. For example, for copying the `trace.out` file:

```bash
kubectl cp <target pod>:trace.out -c xk6-agent trace.out
```

### e2e tests
## e2e tests

End to end tests are meant to test the components of the project in a test environment without mocks.
These tests are slow and resource consuming. To prevent them to be executed as part of the `test` target
Expand Down

0 comments on commit 84a5039

Please sign in to comment.