Skip to content

Commit 753446b

Browse files
committed
feat: add an option to pass a config file
1 parent c66b273 commit 753446b

File tree

4 files changed

+153
-7
lines changed

4 files changed

+153
-7
lines changed

docs/modules/toxiproxy.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ you to create a unique proxy for each container. The default port range is `31`.
6565
func WithPortRange(portRange int) Option
6666
```
6767

68+
#### WithConfigFile
69+
70+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
71+
72+
The `WithConfigFile` option allows you to specify a config file for the Toxiproxy container, in the form of an `io.Reader` representing
73+
the JSON file with the Toxiproxy configuration.
74+
75+
!!! warning
76+
The config file is not validated by the Toxiproxy container.
77+
78+
```golang
79+
func WithConfigFile(r io.Reader) testcontainers.CustomizeRequestOption
80+
```
81+
6882
### Container Methods
6983

7084
The Toxiproxy container exposes the following methods:

modules/toxiproxy/options.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package toxiproxy
22

33
import (
44
"errors"
5+
"io"
56

67
"github.com/testcontainers/testcontainers-go"
78
)
@@ -40,3 +41,21 @@ func WithPortRange(portRange int) Option {
4041
return nil
4142
}
4243
}
44+
45+
// WithConfigFile sets the config file for the Toxiproxy container, copying
46+
// the file to the "/tmp/tc-toxiproxy.json" path. It also appends the "-host=0.0.0.0"
47+
// and "-config=/tmp/tc-toxiproxy.json" flags to the command line.
48+
// The config file is a JSON file that contains the configuration for the Toxiproxy container,
49+
// and it is not validated by the Toxiproxy container.
50+
func WithConfigFile(r io.Reader) testcontainers.CustomizeRequestOption {
51+
return func(req *testcontainers.GenericContainerRequest) error {
52+
req.Files = append(req.Files, testcontainers.ContainerFile{
53+
Reader: r,
54+
ContainerFilePath: "/tmp/tc-toxiproxy.json",
55+
FileMode: 0o644,
56+
})
57+
58+
req.Cmd = append(req.Cmd, "-host=0.0.0.0", "-config=/tmp/tc-toxiproxy.json")
59+
return nil
60+
}
61+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"name": "redis",
4+
"listen": "0.0.0.0:8666",
5+
"upstream": "redis:6379",
6+
"enabled": true
7+
}
8+
]

modules/toxiproxy/toxiproxy_test.go

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
package toxiproxy_test
22

33
import (
4+
"bytes"
45
"context"
6+
_ "embed"
7+
"fmt"
58
"testing"
9+
"time"
610

11+
toxiproxy "github.com/Shopify/toxiproxy/v2/client"
12+
"github.com/go-redis/redis/v8"
13+
"github.com/google/uuid"
714
"github.com/stretchr/testify/require"
815

916
"github.com/testcontainers/testcontainers-go"
10-
"github.com/testcontainers/testcontainers-go/modules/toxiproxy"
17+
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
18+
tctoxiproxy "github.com/testcontainers/testcontainers-go/modules/toxiproxy"
19+
"github.com/testcontainers/testcontainers-go/network"
1120
)
1221

1322
func TestRun(t *testing.T) {
1423
ctx := context.Background()
1524

16-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0")
25+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0")
1726
testcontainers.CleanupContainer(t, ctr)
1827
require.NoError(t, err)
1928

@@ -26,7 +35,7 @@ func TestRun_withPortRange(t *testing.T) {
2635
t.Run("no-port-range", func(t *testing.T) {
2736
portsCount := 31 // default port range is 31 (not exposed)
2837

29-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0")
38+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0")
3039
testcontainers.CleanupContainer(t, ctr)
3140
require.NoError(t, err)
3241
jsonInspect, err := ctr.Inspect(ctx)
@@ -37,7 +46,7 @@ func TestRun_withPortRange(t *testing.T) {
3746
t.Run("negative-port", func(t *testing.T) {
3847
portsCount := -1
3948

40-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", toxiproxy.WithPortRange(portsCount))
49+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", tctoxiproxy.WithPortRange(portsCount))
4150
testcontainers.CleanupContainer(t, ctr)
4251
require.Error(t, err)
4352
require.Nil(t, ctr)
@@ -46,7 +55,7 @@ func TestRun_withPortRange(t *testing.T) {
4655
t.Run("zero-port", func(t *testing.T) {
4756
portsCount := 0
4857

49-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", toxiproxy.WithPortRange(portsCount))
58+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", tctoxiproxy.WithPortRange(portsCount))
5059
testcontainers.CleanupContainer(t, ctr)
5160
require.Error(t, err)
5261
require.Nil(t, ctr)
@@ -55,7 +64,7 @@ func TestRun_withPortRange(t *testing.T) {
5564
t.Run("one-port", func(t *testing.T) {
5665
portsCount := 1
5766

58-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", toxiproxy.WithPortRange(portsCount))
67+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", tctoxiproxy.WithPortRange(portsCount))
5968
testcontainers.CleanupContainer(t, ctr)
6069
require.NoError(t, err)
6170

@@ -67,7 +76,7 @@ func TestRun_withPortRange(t *testing.T) {
6776
t.Run("more-than-default-port", func(t *testing.T) {
6877
portsCount := 75
6978

70-
ctr, err := toxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", toxiproxy.WithPortRange(portsCount))
79+
ctr, err := tctoxiproxy.Run(ctx, "ghcr.io/shopify/toxiproxy:2.12.0", tctoxiproxy.WithPortRange(portsCount))
7180
testcontainers.CleanupContainer(t, ctr)
7281
require.NoError(t, err)
7382

@@ -76,3 +85,99 @@ func TestRun_withPortRange(t *testing.T) {
7685
require.Len(t, jsonInspect.HostConfig.PortBindings, portsCount+1)
7786
})
7887
}
88+
89+
//go:embed testdata/toxiproxy.json
90+
var configFile []byte
91+
92+
func TestRun_withConfigFile(t *testing.T) {
93+
ctx := context.Background()
94+
95+
nw, err := network.New(ctx)
96+
require.NoError(t, err)
97+
t.Cleanup(func() {
98+
require.NoError(t, nw.Remove(ctx))
99+
})
100+
101+
redisContainer, err := tcredis.Run(
102+
ctx,
103+
"redis:6-alpine",
104+
network.WithNetwork([]string{"redis"}, nw),
105+
)
106+
testcontainers.CleanupContainer(t, redisContainer)
107+
require.NoError(t, err)
108+
109+
toxiproxyContainer, err := tctoxiproxy.Run(
110+
ctx,
111+
"ghcr.io/shopify/toxiproxy:2.12.0",
112+
tctoxiproxy.WithPortRange(31),
113+
tctoxiproxy.WithConfigFile(bytes.NewReader(configFile)),
114+
network.WithNetwork([]string{"toxiproxy"}, nw),
115+
)
116+
testcontainers.CleanupContainer(t, toxiproxyContainer)
117+
require.NoError(t, err)
118+
119+
toxiURI, err := toxiproxyContainer.URI(ctx)
120+
require.NoError(t, err)
121+
122+
toxiproxyClient := toxiproxy.NewClient(toxiURI)
123+
124+
toxiproxyProxyPort, err := toxiproxyContainer.MappedPort(ctx, "8666/tcp")
125+
require.NoError(t, err)
126+
127+
toxiproxyProxyHostIP, err := toxiproxyContainer.Host(ctx)
128+
require.NoError(t, err)
129+
130+
// Create a redis client that connects to the toxiproxy container.
131+
// We are defining a read timeout of 2 seconds, because we are adding
132+
// a latency toxic of 1 second to the request, +/- 100ms jitter.
133+
redisURI := fmt.Sprintf("redis://%s:%s?read_timeout=2s", toxiproxyProxyHostIP, toxiproxyProxyPort.Port())
134+
135+
options, err := redis.ParseURL(redisURI)
136+
require.NoError(t, err)
137+
138+
redisCli := redis.NewClient(options)
139+
t.Cleanup(func() {
140+
require.NoError(t, redisCli.FlushAll(ctx).Err())
141+
})
142+
143+
key := fmt.Sprintf("{user.%s}.favoritefood", uuid.NewString())
144+
value := "Cabbage Biscuits"
145+
ttl, err := time.ParseDuration("2h")
146+
require.NoError(t, err)
147+
148+
err = redisCli.Set(ctx, key, value, ttl).Err()
149+
require.NoError(t, err)
150+
151+
// Add a latency toxic to the proxy
152+
toxicOptions := &toxiproxy.ToxicOptions{
153+
ProxyName: "redis", // name of the proxy in the config file
154+
ToxicName: "latency_down",
155+
ToxicType: "latency",
156+
Toxicity: 1.0,
157+
Stream: "downstream",
158+
Attributes: map[string]interface{}{
159+
"latency": 1_000,
160+
"jitter": 100,
161+
},
162+
}
163+
_, err = toxiproxyClient.AddToxic(toxicOptions)
164+
require.NoError(t, err)
165+
166+
start := time.Now()
167+
// Get data
168+
savedValue, err := redisCli.Get(ctx, key).Result()
169+
require.NoError(t, err)
170+
171+
duration := time.Since(start)
172+
173+
t.Logf("Duration: %s\n", duration)
174+
175+
// The value is retrieved successfully
176+
require.Equal(t, value, savedValue)
177+
178+
// Check that latency is within expected range (900ms-1100ms)
179+
// The latency toxic adds 1000ms (1000ms +/- 100ms jitter)
180+
minDuration := 900 * time.Millisecond
181+
maxDuration := 1100 * time.Millisecond
182+
require.True(t, duration >= minDuration && duration <= maxDuration)
183+
}

0 commit comments

Comments
 (0)