@@ -3,17 +3,137 @@ package chaos
33import (
44 "context"
55 "fmt"
6+ "slices"
67 "strings"
78 "time"
89
910 "github.com/docker/docker/api/types/container"
1011 "github.com/docker/docker/api/types/mount"
1112 "github.com/google/uuid"
1213 "github.com/testcontainers/testcontainers-go"
14+ "github.com/testcontainers/testcontainers-go/wait"
1315
1416 "github.com/smartcontractkit/chainlink-testing-framework/framework"
1517)
1618
19+ /*
20+ * A simple wrapper for "docker-tc" chaos actions.
21+ * One small caveat is that in order for chaos to work your containers should be on a network so
22+ * interfaces like 'vetha57f116' are created inside them.
23+ * Works for any network, by default we are testing containers on "ctf" network.
24+ */
25+
26+ const (
27+ // Docker and docker-tc commands
28+ CmdPause = "pause"
29+ CmdDelay = "delay"
30+ CmdLoss = "loss"
31+ CmdDuplicate = "duplicate"
32+ CmdCorrupt = "corrupt"
33+ )
34+
35+ const (
36+ // dockerTCContainerName default "docker-tc" container name
37+ dockerTCContainerName = "dtc"
38+ // dockerTCInternalSvc docker-tc internal service name
39+ dockerTCInternalSvc = "localhost:4080"
40+ )
41+
42+ var (
43+ defaultCURLCMD = fmt .Sprintf ("docker exec %s curl" , dockerTCContainerName )
44+ tcCommands = []string {CmdDelay , CmdLoss , CmdCorrupt , CmdDuplicate }
45+ )
46+
47+ // DockerChaos is a chaos generator for Docker
48+ type DockerChaos struct {
49+ Experiments map [string ]string
50+ }
51+
52+ // NewDockerChaos creates a new "docker-tc" instance or reuses existing one
53+ func NewDockerChaos (ctx context.Context ) (* DockerChaos , error ) {
54+ framework .L .Info ().
55+ Str ("Container" , dockerTCContainerName ).
56+ Msg ("Starting new docker-tc container" )
57+ req := testcontainers.ContainerRequest {
58+ Image : "lukaszlach/docker-tc" ,
59+ Name : dockerTCContainerName ,
60+ CapAdd : []string {"NET_ADMIN" },
61+ WaitingFor : wait .ForLog ("Starting Docker Traffic Control" ),
62+ HostConfigModifier : func (h * container.HostConfig ) {
63+ h .Privileged = true
64+ h .NetworkMode = "host"
65+ h .Mounts = []mount.Mount {
66+ {
67+ Type : "bind" ,
68+ Source : "/var/run/docker.sock" ,
69+ Target : "/var/run/docker.sock" ,
70+ ReadOnly : true ,
71+ },
72+ }
73+ },
74+ }
75+ _ , err := testcontainers .GenericContainer (ctx , testcontainers.GenericContainerRequest {
76+ ContainerRequest : req ,
77+ Started : true ,
78+ Reuse : true ,
79+ })
80+ if err != nil {
81+ return nil , fmt .Errorf ("failed to start docker-tc container: %w" , err )
82+ }
83+ return & DockerChaos {
84+ Experiments : make (map [string ]string , 0 ),
85+ }, nil
86+ }
87+
88+ // RemoveAll removes all the experiments
89+ func (m * DockerChaos ) RemoveAll () error {
90+ for containerName , experimentCmd := range m .Experiments {
91+ framework .L .Info ().
92+ Str ("Container" , containerName ).
93+ Str ("Cmd" , experimentCmd ).
94+ Msg ("Removing chaos for container" )
95+ if _ , err := framework .ExecCmd (experimentCmd ); err != nil {
96+ return fmt .Errorf ("failed to remove chaos experiment: name: %s, command:%s, err: %w" , containerName , experimentCmd , err )
97+ }
98+ }
99+ m .Experiments = make (map [string ]string )
100+ return nil
101+ }
102+
103+ // Chaos executes either Docker or "docker-tc" commands
104+ func (m * DockerChaos ) Chaos (containerName string , cmd , val string ) error {
105+ if _ , ok := m .Experiments [containerName ]; ok {
106+ return fmt .Errorf ("chaos is already applied, only a single chaos can be applied to a container, call RemoveAll first" )
107+ }
108+ // tc commands
109+ if slices .Contains (tcCommands , cmd ) {
110+ m .Experiments [containerName ] = fmt .Sprintf ("%s -X DELETE %s/%s" , defaultCURLCMD , dockerTCInternalSvc , containerName )
111+ out , err := framework .ExecCmd (fmt .Sprintf ("%s -d %s=%s %s/%s" , defaultCURLCMD , cmd , val , dockerTCInternalSvc , containerName ))
112+ if err != nil {
113+ return err
114+ }
115+ return verifyTCOutput (string (out ))
116+ }
117+ // docker commands
118+ m .Experiments [containerName ] = fmt .Sprintf ("docker unpause %s" , containerName )
119+ _ , err := framework .ExecCmd (fmt .Sprintf ("docker pause %s" , containerName ))
120+ if err != nil {
121+ return err
122+ }
123+ return nil
124+ }
125+
126+ func verifyTCOutput (out string ) error {
127+ if ! strings .Contains (out , "Controlling traffic" ) {
128+ return fmt .Errorf ("experiment failed to apply, set debug logs, export CTF_LOG_LEVEL=debug. Your container also must be on a network, 'ctf' or any other, won't work with default 'bridge'" )
129+ }
130+ return nil
131+ }
132+
133+ // DEPRECATED: Since Pumba has outdated Docker dependencies it may not work without additional
134+ // setting to allow using Docker client which is out of client<>server compatibility range.
135+ // Use NewDockerChaos for pause and network experiments!
136+ //
17137// ExecPumba executes Pumba (https://github.com/alexei-led/pumba) command
18138// since handling various docker race conditions is hard and there is no easy API for that
19139// for now you can provide time to wait until chaos is applied
0 commit comments