From 320e54df7e2b9813b3182a3bb95d93d69ec8d745 Mon Sep 17 00:00:00 2001 From: Da boi Date: Fri, 19 Apr 2024 14:39:43 +0300 Subject: [PATCH 01/49] fixed kafka internal docker connection --- modules/kafka/kafka.go | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 399f17fb70..2e8447478d 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -62,7 +62,19 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize Entrypoint: []string{"sh"}, // this CMD will wait for the starter script to be copied into the container and then execute it Cmd: []string{"-c", "while [ ! -f " + starterScript + " ]; do sleep 0.1; done; bash " + starterScript}, - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + opt.Customize(&genericContainerReq) + } + + genericContainerReq.ContainerRequest.LifecycleHooks = + []testcontainers.ContainerLifecycleHooks{ { PostStarts: []testcontainers.ContainerHook{ // 1. copy the starter script into the container @@ -77,7 +89,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize return err } - scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), host) + // fix for internal docker connection + alias := host + if len(genericContainerReq.ContainerRequest.Networks) > 0 { + nw := genericContainerReq.ContainerRequest.Networks[0] + if len(genericContainerReq.ContainerRequest.NetworkAliases[nw]) > 0 { + alias = genericContainerReq.ContainerRequest.NetworkAliases[nw][0] + } + } + + scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), alias) return c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755) }, @@ -87,17 +108,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize }, }, }, - }, - } - - genericContainerReq := testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - } - - for _, opt := range opts { - opt.Customize(&genericContainerReq) - } + } err := validateKRaftVersion(genericContainerReq.Image) if err != nil { From 9b6537da47d6d61d400d7c6155954d2d36db2ab4 Mon Sep 17 00:00:00 2001 From: Da boi Date: Fri, 10 May 2024 20:56:48 +0300 Subject: [PATCH 02/49] added new option for kafka listeners --- modules/kafka/kafka.go | 133 ++++++++++++++++++++++++++++++++------- modules/kafka/options.go | 77 +++++++++++++++++++++++ 2 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 modules/kafka/options.go diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 2e8447478d..dba02eae8a 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -20,7 +20,7 @@ const ( // starterScript { starterScriptContent = `#!/bin/bash source /etc/confluent/docker/bash-config -export KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://%s:%d,BROKER://%s:9092 +export KAFKA_ADVERTISED_LISTENERS=%s echo Starting Kafka KRaft mode sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure echo 'kafka-storage format --ignore-formatted -t "$(kafka-storage random-uuid)" -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure @@ -34,6 +34,13 @@ echo '' > /etc/confluent/docker/ensure type KafkaContainer struct { testcontainers.Container ClusterID string + Listeners KafkaListener +} + +type KafkaListener struct { + Name string + Ip string + Port string } // RunContainer creates an instance of the Kafka container type @@ -43,10 +50,10 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize ExposedPorts: []string{string(publicPort)}, Env: map[string]string{ // envVars { - "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", - "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT", - "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER", + "KAFKA_LISTENERS": "EXTERNAL://0.0.0.0:9093,INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", + "KAFKA_REST_BOOTSTRAP_SERVERS": "EXTERNAL://0.0.0.0:9093,INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT", + "KAFKA_INTER_BROKER_LISTENER_NAME": "INTERNAL", "KAFKA_BROKER_ID": "1", "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1", "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1", @@ -69,36 +76,52 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize Started: true, } + settings := defaultOptions() for _, opt := range opts { + if apply, ok := opt.(Option); ok { + apply(&settings) + } opt.Customize(&genericContainerReq) } + trimListeners(settings.Listeners) + if err := validateListeners(settings.Listeners); err != nil { + return nil, fmt.Errorf("listeners validation: %w", err) + } + + // apply envs for listeners + envChange := editEnvsForListeners(settings.Listeners) + for key, item := range envChange { + genericContainerReq.Env[key] = item + } + genericContainerReq.ContainerRequest.LifecycleHooks = []testcontainers.ContainerLifecycleHooks{ { PostStarts: []testcontainers.ContainerHook{ // 1. copy the starter script into the container func(ctx context.Context, c testcontainers.Container) error { - host, err := c.Host(ctx) - if err != nil { - return err + if len(settings.Listeners) == 0 { + defaultInternal, err := internalListener(ctx, c) + if err != nil { + return fmt.Errorf("can't create default internal listener: %w", err) + } + settings.Listeners = append(settings.Listeners, defaultInternal) } - port, err := c.MappedPort(ctx, publicPort) + defaultExternal, err := externalListener(ctx, c) if err != nil { - return err + return fmt.Errorf("can't create default external listener: %w", err) } - // fix for internal docker connection - alias := host - if len(genericContainerReq.ContainerRequest.Networks) > 0 { - nw := genericContainerReq.ContainerRequest.Networks[0] - if len(genericContainerReq.ContainerRequest.NetworkAliases[nw]) > 0 { - alias = genericContainerReq.ContainerRequest.NetworkAliases[nw][0] - } + settings.Listeners = append(settings.Listeners, defaultExternal) + + var advertised []string + for _, item := range settings.Listeners { + advertised = append(advertised, fmt.Sprintf("%s://%s:%s", item.Name, item.Ip, item.Port)) } - scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), alias) + scriptContent := fmt.Sprintf(starterScriptContent, strings.Join(advertised, ",")) return c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755) }, @@ -127,12 +150,80 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize return &KafkaContainer{Container: container, ClusterID: clusterID}, nil } -func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - req.Env["CLUSTER_ID"] = clusterID +func trimListeners(listeners []KafkaListener) { + for i := 0; i < len(listeners); i++ { + listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) + listeners[i].Ip = strings.Trim(listeners[i].Ip, " ") + listeners[i].Port = strings.Trim(listeners[i].Port, " ") } } +func validateListeners(listeners []KafkaListener) error { + var ports map[string]bool = make(map[string]bool, len(listeners)+2) + var names map[string]bool = make(map[string]bool, len(listeners)+2) + + // check for default listeners + ports["9094"] = true + ports["9093"] = true + + // check for default listeners + names["CONTROLLER"] = true + names["EXTERNAL"] = true + + for _, item := range listeners { + if names[item.Name] { + return fmt.Errorf("duplicate of listener name: %s", item.Name) + } + names[item.Name] = true + + if ports[item.Port] { + return fmt.Errorf("duplicate of listener port: %s", item.Port) + } + ports[item.Port] = true + } + + return nil +} + +func editEnvsForListeners(listeners []KafkaListener) map[string]string { + if len(listeners) == 0 { + // no change + return map[string]string{} + } + + envs := map[string]string{ + "KAFKA_LISTENERS": "CONTROLLER://0.0.0.0:9094, EXTERNAL://0.0.0.0:9093", + "KAFKA_REST_BOOTSTRAP_SERVERS": "CONTROLLER://0.0.0.0:9094, EXTERNAL://0.0.0.0:9093", + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "CONTROLLER:PLAINTEXT, EXTERNAL:PLAINTEXT", + } + + // expect first listener has common network between kafka instances + envs["KAFKA_INTER_BROKER_LISTENER_NAME"] = listeners[0].Name + + // expect small number of listeners, so joins is okay + for _, item := range listeners { + envs["KAFKA_LISTENERS"] = strings.Join( + []string{ + envs["KAFKA_LISTENERS"], + fmt.Sprintf("%s://0.0.0.0:%s", item.Name, item.Port), + }, + ",", + ) + + envs["KAFKA_REST_BOOTSTRAP_SERVERS"] = envs["KAFKA_LISTENERS"] + + envs["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"] = strings.Join( + []string{ + envs["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"], + item.Name + ":" + "PLAINTEXT", + }, + ",", + ) + } + + return envs +} + // Brokers retrieves the broker connection strings from Kafka with only one entry, // defined by the exposed public port. func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) { diff --git a/modules/kafka/options.go b/modules/kafka/options.go new file mode 100644 index 0000000000..ca1c529d3a --- /dev/null +++ b/modules/kafka/options.go @@ -0,0 +1,77 @@ +package kafka + +import ( + "context" + + "github.com/testcontainers/testcontainers-go" +) + +type options struct { + // Listeners is a list of custom listeners that can be provided to access the + // containers form within docker networks + Listeners []KafkaListener +} + +func defaultOptions() options { + return options{ + Listeners: make([]KafkaListener, 0), + } +} + +// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (*Option)(nil) + +// Option is an option for the Redpanda container. +type Option func(*options) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o Option) Customize(*testcontainers.GenericContainerRequest) { + // NOOP to satisfy interface. +} + +func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) { + req.Env["CLUSTER_ID"] = clusterID + } +} + +// WithListener adds a custom listener to the Redpanda containers. Listener +// will be aliases to all networks, so they can be accessed from within docker +// networks. At leas one network must be attached to the container, if not an +// error will be thrown when starting the container. +func WithListener(listeners []KafkaListener) Option { + return func(o *options) { + o.Listeners = append(o.Listeners, listeners...) + } +} + +func externalListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { + host, err := c.Host(ctx) + if err != nil { + return KafkaListener{}, err + } + + port, err := c.MappedPort(ctx, publicPort) + if err != nil { + return KafkaListener{}, err + } + + return KafkaListener{ + Name: "EXTERNAL", + Ip: host, + Port: port.Port(), + }, nil +} + +func internalListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { + host, err := c.Host(ctx) + if err != nil { + return KafkaListener{}, err + } + + return KafkaListener{ + Name: "INTERNAL", + Ip: host, + Port: "9092", + }, nil +} From c7552f43d6bd8a1f32069aacd35ec10841fe39df Mon Sep 17 00:00:00 2001 From: Da boi Date: Mon, 13 May 2024 10:13:15 +0300 Subject: [PATCH 03/49] fixed kafka docs with new options --- docs/modules/kafka.md | 50 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 86bf846de9..7b8e65ab34 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -63,6 +63,54 @@ The environment variables that are already set by default are: {% include "../features/common_functional_options.md" %} + +#### ClusterId + +You can set up cluster id by using `WithClusterID` option. + +``` +KafkaContainer, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1")) +``` + +#### Listeners + +If you need to connect new listeners, you can use `WithListener(listeners []KafkaListener)`. +This option controls next env parameters: +- `KAFKA_LISTENERS` +- `KAFKA_REST_BOOTSTRAP_SERVERS` +- `KAFKA_LISTENER_SECURITY_PROTOCOL_MAP` +- `KAFKA_INTER_BROKER_LISTENER_NAME` +- `KAFKA_ADVERTISED_LISTENERS` + +Example: +``` +KafkaContainer, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + network.WithNetwork([]string{"kafka"}, Network), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9092", + }, + }), + ) +``` + +Here we created network for our container and added kafka to it, so they can communicate. Then we marked port 9092 for our internal usage. + +First listener in slice will be written in `KAFKA_INTER_BROKER_LISTENER_NAME` + +Every listener's name will be converted in upper case. Every name and port should be unique and will be checked in validation step. + +If you are not using this option or list is empty, there will be 2 default listeners with next addresses + +External - Host():MappedPort() +Internal - Host():9092 + ### Container Methods The Kafka container exposes the following methods: @@ -73,4 +121,4 @@ The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containin [Get Kafka brokers](../../modules/kafka/kafka_test.go) inside_block:getBrokers - + \ No newline at end of file From 8904938a4ad0566a21ff66ae1f3907168da96a4e Mon Sep 17 00:00:00 2001 From: DaBoi Date: Tue, 14 May 2024 10:00:32 +0300 Subject: [PATCH 04/49] Update docs/modules/kafka.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel de la Peña --- docs/modules/kafka.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 7b8e65ab34..5ea445f97c 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -77,7 +77,7 @@ KafkaContainer, err = kafka.RunContainer(ctx, #### Listeners If you need to connect new listeners, you can use `WithListener(listeners []KafkaListener)`. -This option controls next env parameters: +This option controls the following environment variables for the Kafka container: - `KAFKA_LISTENERS` - `KAFKA_REST_BOOTSTRAP_SERVERS` - `KAFKA_LISTENER_SECURITY_PROTOCOL_MAP` From 7b38c28c074458742d310d737b638ebd0603f6ba Mon Sep 17 00:00:00 2001 From: DaBoi Date: Tue, 14 May 2024 10:03:32 +0300 Subject: [PATCH 05/49] Apply suggestions from code review for docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel de la Peña --- docs/modules/kafka.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 5ea445f97c..fbf2c2d2eb 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -100,13 +100,13 @@ KafkaContainer, err = kafka.RunContainer(ctx, ) ``` -Here we created network for our container and added kafka to it, so they can communicate. Then we marked port 9092 for our internal usage. +In the above code, we created a network for our container and attached kafka to it, so they can communicate. Then we marked port 9092 for our internal usage. First listener in slice will be written in `KAFKA_INTER_BROKER_LISTENER_NAME` -Every listener's name will be converted in upper case. Every name and port should be unique and will be checked in validation step. +Every listener's name will be converted in upper case. Every name and port should be unique and will be checked in a validation step. -If you are not using this option or list is empty, there will be 2 default listeners with next addresses +If you are not using this option or the listeners list is empty, there will be 2 default listeners with the following addresses and ports: External - Host():MappedPort() Internal - Host():9092 From 98793d760e241aa5840caee1dea51f1d7815d9c5 Mon Sep 17 00:00:00 2001 From: Da boi Date: Thu, 16 May 2024 16:45:30 +0300 Subject: [PATCH 06/49] added new test, fixed docs from code review --- docs/modules/kafka.md | 20 +--- modules/kafka/docker/Dockerfile | 14 +++ modules/kafka/docker/go.mod | 14 +++ modules/kafka/docker/go.sum | 72 ++++++++++++++ modules/kafka/docker/main.go | 165 ++++++++++++++++++++++++++++++++ modules/kafka/kafka_test.go | 137 ++++++++++++++++++++++++++ 6 files changed, 407 insertions(+), 15 deletions(-) create mode 100644 modules/kafka/docker/Dockerfile create mode 100644 modules/kafka/docker/go.mod create mode 100644 modules/kafka/docker/go.sum create mode 100644 modules/kafka/docker/main.go diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 7b8e65ab34..40c63c3b42 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -85,24 +85,14 @@ This option controls next env parameters: - `KAFKA_ADVERTISED_LISTENERS` Example: -``` -KafkaContainer, err = kafka.RunContainer(ctx, - kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), - network.WithNetwork([]string{"kafka"}, Network), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "INTERNAL", - Ip: "kafka", - Port: "9092", - }, - }), - ) -``` + + +[Get Kafka brokers](../../modules/kafka/kafka_test.go) inside_block:kafkaWithListener + Here we created network for our container and added kafka to it, so they can communicate. Then we marked port 9092 for our internal usage. -First listener in slice will be written in `KAFKA_INTER_BROKER_LISTENER_NAME` +The first listener in the slice will be written in the env parameter `KAFKA_INTER_BROKER_LISTENER_NAME` Every listener's name will be converted in upper case. Every name and port should be unique and will be checked in validation step. diff --git a/modules/kafka/docker/Dockerfile b/modules/kafka/docker/Dockerfile new file mode 100644 index 0000000000..a5afc156d7 --- /dev/null +++ b/modules/kafka/docker/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:bullseye + +WORKDIR /app + +COPY go.mod . +COPY go.sum . + +RUN go mod tidy + +COPY . . + +RUN go build -o ./pg_test + +CMD /app/pg_test \ No newline at end of file diff --git a/modules/kafka/docker/go.mod b/modules/kafka/docker/go.mod new file mode 100644 index 0000000000..15256e1e22 --- /dev/null +++ b/modules/kafka/docker/go.mod @@ -0,0 +1,14 @@ +module amogus + +go 1.21.6 + +require ( + github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 + github.com/google/uuid v1.6.0 + github.com/pkg/errors v0.9.1 +) + +require ( + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/modules/kafka/docker/go.sum b/modules/kafka/docker/go.sum new file mode 100644 index 0000000000..38e7e8f3f8 --- /dev/null +++ b/modules/kafka/docker/go.sum @@ -0,0 +1,72 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= +github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= +github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= +github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= +github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= +github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8= +github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 h1:0BOZf6qNozI3pkN3fJLwNubheHJYHhMh91GRFOWWK08= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/kafka/docker/main.go b/modules/kafka/docker/main.go new file mode 100644 index 0000000000..96ea131822 --- /dev/null +++ b/modules/kafka/docker/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "log/slog" + "os" + "time" + + "github.com/confluentinc/confluent-kafka-go/v2/kafka" + "github.com/google/uuid" + "github.com/pkg/errors" +) + +const ( + client = "kafka_rw_test" + group = "test_group" +) + +func main() { + brokers, _ := os.LookupEnv("KAFKA_BROKERS") + input_topic, _ := os.LookupEnv("KAFKA_TOPIC_IN") + output_topic, _ := os.LookupEnv("KAFKA_TOPIC_OUT") + + log.Printf("Got brokers: %v\n", brokers) + log.Printf("Got input topic: %v\n", input_topic) + log.Printf("Got output topic: %v\n", output_topic) + + consumer, err := InitNativeKafkaConsumer(client, brokers, group) + if err != nil { + log.Fatal(fmt.Errorf("failed to start kafka consumer: %w", err)) + } + + defer consumer.Close() + + meta, err := consumer.GetMetadata(nil, true, 1000) + log.Printf("Metadata: %#+v, %v", meta, err) + + producer, err := InitNativeKafkaProducer(client, brokers, "1", 30000) + if err != nil { + log.Fatal(fmt.Errorf("failed to start kafka producer: %w", err)) + } + + defer producer.Close() + + err = consumer.SubscribeTopics([]string{input_topic}, nil) + if err != nil { + log.Fatal(fmt.Errorf("failed to subscribe to kafka topic: %w", err)) + } + + StartConsuming(context.TODO(), consumer, producer, output_topic) + + fmt.Print("Finished\n") +} + +func StartConsuming(ctx context.Context, consumer *kafka.Consumer, producer *kafka.Producer, outTopic string) { + log.Println("start consuming events") + + run := true + + for run { + msg, err := consumer.ReadMessage(time.Second * 1) + if err != nil { + kErr, ok := err.(kafka.Error) + if ok && kErr.IsTimeout() { + log.Println(fmt.Errorf("read timeout: %w", kErr)) + continue + } + + log.Println(fmt.Errorf("failed to read message: %w", err)) + continue + } + + log.Printf("got message: %s\n", string(msg.Value)) + + outputText := string(msg.Value) + "-from-internal" + output := MakeMsg(outTopic, string(msg.Key), outputText) + + err = producer.Produce(&output, nil) + + if err != nil { + log.Println(fmt.Errorf("failed to write message: %w", err)) + continue + } + + log.Printf("written: %s\n", outputText) + } +} + +func MakeMsg(topic, key string, message interface{}) kafka.Message { + headers := []kafka.Header{ + { + Key: "DateAdd", Value: []byte(time.Now().Format(time.RFC3339Nano)), + }, + { + Key: "MessageId", Value: []byte(uuid.NewString()), + }, + } + + messageJson, _ := json.Marshal(message) + + keyJson, _ := json.Marshal(key) + + msg := kafka.Message{ + TopicPartition: kafka.TopicPartition{ + Topic: &topic, + Partition: kafka.PartitionAny, + }, + Value: messageJson, + Key: keyJson, + Headers: headers, + } + + return msg +} + +func InitNativeKafkaProducer( + clientID string, + brokers string, + acks string, + bufMaxMsg int, +) (*kafka.Producer, error) { + cfg := kafka.ConfigMap{ + "bootstrap.servers": brokers, + "client.id": clientID, + "acks": acks, + "queue.buffering.max.messages": bufMaxMsg, + "go.delivery.reports": false, + } + + p, err := kafka.NewProducer(&cfg) + if err != nil { + slog.Error("new producer", err) + return nil, err + } + + slog.Info(fmt.Sprintf("kafka producer %s created", clientID)) + + return p, nil +} + +func InitNativeKafkaConsumer( + clientID string, + brokers string, + group string, +) (*kafka.Consumer, error) { + config := kafka.ConfigMap{ + "bootstrap.servers": brokers, + "group.id": group, + "client.id": clientID, + "auto.offset.reset": "earliest", + "auto.commit.interval.ms": 3000, + } + + c, err := kafka.NewConsumer(&config) + if err != nil { + return nil, errors.Wrapf(err, "create kafka consumer") + } + + slog.Info(fmt.Sprintf("kafka consumer %s created", clientID)) + + return c, nil +} diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 8b3af3b11a..640479dd36 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -2,6 +2,7 @@ package kafka_test import ( "context" + "fmt" "io" "strings" "testing" @@ -10,6 +11,8 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" ) func TestKafka(t *testing.T) { @@ -97,6 +100,140 @@ func TestKafka_invalidVersion(t *testing.T) { } } +func TestKafka_network(t *testing.T) { + ctx := context.Background() + var err error + + const ( + topic_in = "topic_in" + topic_out = "topic_out" + ) + + Network, err := network.New(ctx, network.WithCheckDuplicate()) + if err != nil { + t.Fatal(err) + } + + // kafkaWithListener { + KafkaContainer, err := kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + network.WithNetwork([]string{"kafka"}, Network), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9092", + }, + }), + ) + // } + + if err != nil { + t.Fatalf("failed to start container: %s", err) + } + + brokers, err := KafkaContainer.Brokers(context.TODO()) + if err != nil { + t.Fatal("failed to get brokers", err) + } + + createTopics(brokers, []string{topic_in, topic_out}) + + initKafkaTest(ctx, Network.Name, "kafka:9092", topic_in, topic_out) + + // perform assertions + + // set config to true because successfully delivered messages will be returned on the Successes channel + config := sarama.NewConfig() + config.Producer.Return.Successes = true + + producer, err := sarama.NewSyncProducer(brokers, config) + if err != nil { + t.Fatal(err) + } + + // Act + key := "wow" + text_msg := "test-input-external" + + if _, _, err := producer.SendMessage(&sarama.ProducerMessage{ + Topic: topic_in, + Key: sarama.StringEncoder(key), + Value: sarama.StringEncoder(text_msg), + }); err != nil { + t.Fatal(err) + } + + client, err := sarama.NewConsumerGroup(brokers, "groupName", config) + if err != nil { + t.Fatal(err) + } + + consumer, _, done, cancel := NewTestKafkaConsumer(t) + go func() { + if err := client.Consume(context.Background(), []string{topic_out}, consumer); err != nil { + cancel() + } + }() + + // wait for the consumer to be ready + <-done + + if consumer.message == nil { + t.Fatal("Empty message") + } + + // Assert + if !strings.Contains(string(consumer.message.Value), text_msg) { + t.Error("got wrong string") + } +} + +func initKafkaTest(ctx context.Context, network string, brokers string, input string, output string) (testcontainers.Container, error) { + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: "./docker", + Dockerfile: "Dockerfile", + PrintBuildLog: true, + KeepImage: true, + }, + WaitingFor: wait.ForLog("start consuming events"), + Env: map[string]string{ + "KAFKA_BROKERS": brokers, + "KAFKA_TOPIC_IN": input, + "KAFKA_TOPIC_OUT": output, + }, + Networks: []string{network}, + } + + return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) +} + +func createTopics(brokers []string, topics []string) error { + t := &sarama.CreateTopicsRequest{} + t.TopicDetails = make(map[string]*sarama.TopicDetail, len(topics)) + for _, elem := range topics { + t.TopicDetails[elem] = &sarama.TopicDetail{NumPartitions: 1} + } + + var err error + + c, err := sarama.NewClient(brokers, sarama.NewConfig()) + + _, err = c.Brokers()[0].CreateTopics(t) + if err != nil { + return fmt.Errorf("failed to create topics: %w", err) + } + + fmt.Println("succesfully created topics") + + return nil +} + // assertAdvertisedListeners checks that the advertised listeners are set correctly: // - The INTERNAL:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { From bfa9191c0986f019a4911a254f1fbd26c6215f68 Mon Sep 17 00:00:00 2001 From: Da boi Date: Thu, 16 May 2024 17:23:20 +0300 Subject: [PATCH 07/49] renamed docker folder to testdata and fixed test name --- modules/kafka/kafka_test.go | 4 ++-- modules/kafka/{docker => testdata}/Dockerfile | 0 modules/kafka/{docker => testdata}/go.mod | 0 modules/kafka/{docker => testdata}/go.sum | 0 modules/kafka/{docker => testdata}/main.go | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename modules/kafka/{docker => testdata}/Dockerfile (100%) rename modules/kafka/{docker => testdata}/go.mod (100%) rename modules/kafka/{docker => testdata}/go.sum (100%) rename modules/kafka/{docker => testdata}/main.go (100%) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 640479dd36..3372b958ae 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -100,7 +100,7 @@ func TestKafka_invalidVersion(t *testing.T) { } } -func TestKafka_network(t *testing.T) { +func TestKafka_networkConnectivity(t *testing.T) { ctx := context.Background() var err error @@ -193,7 +193,7 @@ func TestKafka_network(t *testing.T) { func initKafkaTest(ctx context.Context, network string, brokers string, input string, output string) (testcontainers.Container, error) { req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: "./docker", + Context: "./testdata", Dockerfile: "Dockerfile", PrintBuildLog: true, KeepImage: true, diff --git a/modules/kafka/docker/Dockerfile b/modules/kafka/testdata/Dockerfile similarity index 100% rename from modules/kafka/docker/Dockerfile rename to modules/kafka/testdata/Dockerfile diff --git a/modules/kafka/docker/go.mod b/modules/kafka/testdata/go.mod similarity index 100% rename from modules/kafka/docker/go.mod rename to modules/kafka/testdata/go.mod diff --git a/modules/kafka/docker/go.sum b/modules/kafka/testdata/go.sum similarity index 100% rename from modules/kafka/docker/go.sum rename to modules/kafka/testdata/go.sum diff --git a/modules/kafka/docker/main.go b/modules/kafka/testdata/main.go similarity index 100% rename from modules/kafka/docker/main.go rename to modules/kafka/testdata/main.go From 128d31a14aa5c6bda50460fc048ce70c554ed50a Mon Sep 17 00:00:00 2001 From: Da boi Date: Thu, 23 May 2024 20:41:32 +0300 Subject: [PATCH 08/49] added test for input listeners validation and refactor from code review --- modules/kafka/kafka.go | 9 ++- modules/kafka/kafka_test.go | 111 ++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 580515606f..3e5ff3d134 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -86,8 +86,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } } - trimListeners(settings.Listeners) - if err := validateListeners(settings.Listeners); err != nil { + if err := trimValidateListeners(settings.Listeners); err != nil { return nil, fmt.Errorf("listeners validation: %w", err) } @@ -152,15 +151,15 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize return &KafkaContainer{Container: container, ClusterID: clusterID}, nil } -func trimListeners(listeners []KafkaListener) { +func trimValidateListeners(listeners []KafkaListener) error { + // Trim for i := 0; i < len(listeners); i++ { listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) listeners[i].Ip = strings.Trim(listeners[i].Ip, " ") listeners[i].Port = strings.Trim(listeners[i].Port, " ") } -} -func validateListeners(listeners []KafkaListener) error { + // Validate var ports map[string]bool = make(map[string]bool, len(listeners)+2) var names map[string]bool = make(map[string]bool, len(listeners)+2) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 3372b958ae..a4a818d4d6 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -190,6 +190,117 @@ func TestKafka_networkConnectivity(t *testing.T) { } } +func TestKafka_listenersValidation(t *testing.T) { + ctx := context.Background() + var err error + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9093", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to reserved listener port duplication") + } + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9094", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to reserved listener port duplication") + } + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: " cOnTrOller ", + Ip: "kafka", + Port: "9092", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to reserved listener name duplication") + } + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "external", + Ip: "kafka", + Port: "9092", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to reserved listener name duplication") + } + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "test", + Ip: "kafka", + Port: "9092", + }, + { + Name: "test2", + Ip: "kafka", + Port: "9092", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to port duplication") + } + + _, err = kafka.RunContainer(ctx, + kafka.WithClusterID("test-cluster"), + testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "test", + Ip: "kafka", + Port: "9092", + }, + { + Name: "test", + Ip: "kafka", + Port: "9095", + }, + }), + ) + + if err == nil { + t.Fatalf("expected to fail due to name duplication") + } +} + func initKafkaTest(ctx context.Context, network string, brokers string, input string, output string) (testcontainers.Container, error) { req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ From ead068a76fb573b6a5bdfc96e4ecca9074f96622 Mon Sep 17 00:00:00 2001 From: Da boi Date: Tue, 28 May 2024 20:17:40 +0300 Subject: [PATCH 09/49] added unit test for trimValidateListeners --- modules/kafka/kafka_helpers_test.go | 108 ++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 4a49a00f50..20e2b91ca6 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -109,3 +109,111 @@ func TestValidateKRaftVersion(t *testing.T) { }) } } + +func TestTrimValidateListeners(t *testing.T) { + + tests := []struct { + name string + listeners []KafkaListener + wantErr bool + description string + }{ + { + listeners: []KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9093", + }, + }, + wantErr: true, + description: "expected to fail due to reserved listener port duplication", + }, + { + listeners: []KafkaListener{ + { + Name: "INTERNAL", + Ip: "kafka", + Port: "9094", + }, + }, + wantErr: true, + description: "expected to fail due to reserved listener port duplication", + }, + { + listeners: []KafkaListener{ + { + Name: " cOnTrOller ", + Ip: "kafka", + Port: "9092", + }, + }, + wantErr: true, + description: "expected to fail due to reserved listener name CONTROLLER duplication", + }, + { + listeners: []KafkaListener{ + { + Name: "external", + Ip: "kafka", + Port: "9092", + }, + }, + wantErr: true, + description: "expected to fail due to reserved listener name EXTERNAL duplication", + }, + { + listeners: []KafkaListener{ + { + Name: "test", + Ip: "kafka", + Port: "9092", + }, + { + Name: "test2", + Ip: "kafka", + Port: "9092", + }, + }, + wantErr: true, + description: "expected to fail due to port duplication", + }, + { + listeners: []KafkaListener{ + { + Name: "test", + Ip: "kafka", + Port: "9092", + }, + { + Name: "test", + Ip: "kafka", + Port: "9095", + }, + }, + wantErr: true, + description: "expected to fail due to name duplication", + }, + { + listeners: []KafkaListener{ + { + Name: "test", + Ip: "kafka", + Port: "9095", + }, + }, + wantErr: false, + description: "expected no errors", + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + err := trimValidateListeners(test.listeners) + + if test.wantErr != (err != nil) { + t.Fatalf(test.description) + } + }) + } +} From 5c147d44fd3895ff9326c2fbefdeedb7c3bf2714 Mon Sep 17 00:00:00 2001 From: Da boi Date: Sun, 25 Aug 2024 12:31:42 +0300 Subject: [PATCH 10/49] go mod update --- modules/kafka/go.mod | 12 +++++------- modules/kafka/go.sum | 20 ++++---------------- modules/kafka/kafka.go | 20 ++++++-------------- modules/kafka/kafka_helpers_test.go | 18 +++++++++--------- modules/kafka/kafka_test.go | 18 +++++++++--------- modules/kafka/options.go | 6 +++--- 6 files changed, 36 insertions(+), 58 deletions(-) diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index 775cff27b7..f3b1594d23 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -65,13 +65,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index dce7fe559c..989179ecf8 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -4,14 +4,10 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= -github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= github.com/IBM/sarama v1.43.2 h1:HABeEqRUh32z8yzY2hGB/j8mHSzC/HA9zlEjqFNCzSw= github.com/IBM/sarama v1.43.2/go.mod h1:Kyo4WkF24Z+1nz7xeVUFWIuKVV8RS3wM8mkvPKMdXFQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= @@ -35,8 +31,6 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0= -github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= @@ -90,8 +84,6 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -114,8 +106,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -191,10 +181,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index c32c950520..b93859b480 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -39,7 +39,7 @@ type KafkaContainer struct { type KafkaListener struct { Name string - Ip string + Host string Port string } @@ -111,7 +111,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom // if the starter script fails to copy. func(ctx context.Context, c testcontainers.Container) error { // 1. copy the starter script into the container - if err := copyStarterScript(ctx, c); err != nil { + if err := copyStarterScript(ctx, c, &settings); err != nil { return fmt.Errorf("copy starter script: %w", err) } @@ -143,7 +143,7 @@ func trimValidateListeners(listeners []KafkaListener) error { // Trim for i := 0; i < len(listeners); i++ { listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) - listeners[i].Ip = strings.Trim(listeners[i].Ip, " ") + listeners[i].Host = strings.Trim(listeners[i].Host, " ") listeners[i].Port = strings.Trim(listeners[i].Port, " ") } @@ -173,8 +173,9 @@ func trimValidateListeners(listeners []KafkaListener) error { return nil } + // copyStarterScript copies the starter script into the container. -func copyStarterScript(ctx context.Context, c testcontainers.Container) error { +func copyStarterScript(ctx context.Context, c testcontainers.Container, settings *options) error { if err := wait.ForListeningPort(publicPort). SkipInternalCheck(). WaitUntilReady(ctx, c); err != nil { @@ -215,7 +216,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container) error { var advertised []string for _, item := range settings.Listeners { - advertised = append(advertised, fmt.Sprintf("%s://%s:%s", item.Name, item.Ip, item.Port)) + advertised = append(advertised, fmt.Sprintf("%s://%s:%s", item.Name, item.Host, item.Port)) } scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), hostname, strings.Join(advertised, ",")) @@ -227,15 +228,6 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container) error { return nil } -func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) error { - req.Env["CLUSTER_ID"] = clusterID - - return nil - } -} - - func editEnvsForListeners(listeners []KafkaListener) map[string]string { if len(listeners) == 0 { // no change diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 20e2b91ca6..6d26e1029f 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -122,7 +122,7 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "INTERNAL", - Ip: "kafka", + Host: "kafka", Port: "9093", }, }, @@ -133,7 +133,7 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "INTERNAL", - Ip: "kafka", + Host: "kafka", Port: "9094", }, }, @@ -144,7 +144,7 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: " cOnTrOller ", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }, @@ -155,7 +155,7 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "external", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }, @@ -166,12 +166,12 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9092", }, { Name: "test2", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }, @@ -182,12 +182,12 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9092", }, { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9095", }, }, @@ -198,7 +198,7 @@ func TestTrimValidateListeners(t *testing.T) { listeners: []KafkaListener{ { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9095", }, }, diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 7161b05331..92f4f2b995 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -122,7 +122,7 @@ func TestKafka_networkConnectivity(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "INTERNAL", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }), @@ -200,7 +200,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "INTERNAL", - Ip: "kafka", + Host: "kafka", Port: "9093", }, }), @@ -216,7 +216,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "INTERNAL", - Ip: "kafka", + Host: "kafka", Port: "9094", }, }), @@ -232,7 +232,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: " cOnTrOller ", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }), @@ -248,7 +248,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "external", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }), @@ -264,12 +264,12 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9092", }, { Name: "test2", - Ip: "kafka", + Host: "kafka", Port: "9092", }, }), @@ -285,12 +285,12 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithListener([]kafka.KafkaListener{ { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9092", }, { Name: "test", - Ip: "kafka", + Host: "kafka", Port: "9095", }, }), diff --git a/modules/kafka/options.go b/modules/kafka/options.go index bbdad44517..a3fc1d0108 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -21,7 +21,7 @@ func defaultOptions() options { // Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. var _ testcontainers.ContainerCustomizer = (*Option)(nil) -// Option is an option for the Redpanda container. +// Option is an option for the Kafka container. type Option func(*options) // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. @@ -60,7 +60,7 @@ func externalListener(ctx context.Context, c testcontainers.Container) (KafkaLis return KafkaListener{ Name: "EXTERNAL", - Ip: host, + Host: host, Port: port.Port(), }, nil } @@ -73,7 +73,7 @@ func internalListener(ctx context.Context, c testcontainers.Container) (KafkaLis return KafkaListener{ Name: "INTERNAL", - Ip: host, + Host: host, Port: "9092", }, nil } From b31e1af44dc2d77e5e62409510837b2df801cff5 Mon Sep 17 00:00:00 2001 From: Da boi Date: Sun, 25 Aug 2024 12:46:36 +0300 Subject: [PATCH 11/49] todo for PR --- modules/kafka/kafka_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 92f4f2b995..7075479d81 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -190,6 +190,10 @@ func TestKafka_networkConnectivity(t *testing.T) { } } +func TestKafka_restProxyService(t *testing.T) { + // TODO: test kafka rest proxy service +} + func TestKafka_listenersValidation(t *testing.T) { ctx := context.Background() var err error @@ -322,6 +326,37 @@ func initKafkaTest(ctx context.Context, network string, brokers string, input st ContainerRequest: req, Started: true, }) + + // TODO: use kcat + /* + try ( + Network network = Network.newNetwork(); + // registerListener { + KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE) + .withListener(() -> "kafka:19092") + .withNetwork(network); + // } + // createKCatContainer { + GenericContainer kcat = new GenericContainer<>("confluentinc/cp-kcat:7.4.1") + .withCreateContainerCmdModifier(cmd -> { + cmd.withEntrypoint("sh"); + }) + .withCopyToContainer(Transferable.of("Message produced by kcat"), "/data/msgs.txt") + .withNetwork(network) + .withCommand("-c", "tail -f /dev/null") + // } + ) { + kafka.start(); + kcat.start(); + // produceConsumeMessage { + kcat.execInContainer("kcat", "-b", "kafka:19092", "-t", "msgs", "-P", "-l", "/data/msgs.txt"); + String stdout = kcat + .execInContainer("kcat", "-b", "kafka:19092", "-C", "-t", "msgs", "-c", "1") + .getStdout(); + // } + assertThat(stdout).contains("Message produced by kcat"); + } + */ } func createTopics(brokers []string, topics []string) error { From 9f23a4da438bef91bd83a16ae5f9a53132eef435 Mon Sep 17 00:00:00 2001 From: Da boi Date: Sun, 25 Aug 2024 17:20:57 +0300 Subject: [PATCH 12/49] updated copyStarterScript for upstream --- modules/kafka/kafka.go | 19 +------------------ modules/kafka/options.go | 7 ++++--- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index b93859b480..a3920b08c5 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -182,23 +182,6 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings return fmt.Errorf("wait for exposed port: %w", err) } - host, err := c.Host(ctx) - if err != nil { - return fmt.Errorf("host: %w", err) - } - - inspect, err := c.Inspect(ctx) - if err != nil { - return fmt.Errorf("inspect: %w", err) - } - - hostname := inspect.Config.Hostname - - port, err := c.MappedPort(ctx, publicPort) - if err != nil { - return fmt.Errorf("mapped port: %w", err) - } - if len(settings.Listeners) == 0 { defaultInternal, err := internalListener(ctx, c) if err != nil { @@ -219,7 +202,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings advertised = append(advertised, fmt.Sprintf("%s://%s:%s", item.Name, item.Host, item.Port)) } - scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), hostname, strings.Join(advertised, ",")) + scriptContent := fmt.Sprintf(starterScriptContent, strings.Join(advertised, ",")) if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil { return fmt.Errorf("copy to container: %w", err) diff --git a/modules/kafka/options.go b/modules/kafka/options.go index a3fc1d0108..430c6ab47f 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -2,6 +2,7 @@ package kafka import ( "context" + "fmt" "github.com/testcontainers/testcontainers-go" ) @@ -66,14 +67,14 @@ func externalListener(ctx context.Context, c testcontainers.Container) (KafkaLis } func internalListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { - host, err := c.Host(ctx) + inspect, err := c.Inspect(ctx) if err != nil { - return KafkaListener{}, err + return KafkaListener{}, fmt.Errorf("inspect: %w", err) } return KafkaListener{ Name: "INTERNAL", - Host: host, + Host: inspect.Config.Hostname, Port: "9092", }, nil } From e63a604978eca987430881e759d2bf8d0b1dfb7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 16:56:40 +0200 Subject: [PATCH 13/49] fix: typo --- modules/kafka/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/options.go b/modules/kafka/options.go index 430c6ab47f..1de85f1e6e 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -38,7 +38,7 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { } } -// WithListener adds a custom listener to the Redpanda containers. Listener +// WithListener adds a custom listener to the Kafka containers. Listener // will be aliases to all networks, so they can be accessed from within docker // networks. At leas one network must be attached to the container, if not an // error will be thrown when starting the container. From f9cb1551fb9a3b2c28f2dcfed2a2fbb55630412b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:28:17 +0200 Subject: [PATCH 14/49] fix: use Run method --- docs/modules/kafka.md | 6 +++--- modules/kafka/kafka_test.go | 28 ++++++++++++++-------------- modules/redpanda/redpanda_test.go | 3 ++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index c359aeeae8..7fc6d0b51a 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -77,9 +77,9 @@ The environment variables that are already set by default are: You can set up cluster id by using `WithClusterID` option. ``` -KafkaContainer, err = kafka.RunContainer(ctx, - kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1")) +KafkaContainer, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", + kafka.WithClusterID("test-cluster")) ``` #### Listeners diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 7075479d81..15f9cdab3e 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -115,9 +115,9 @@ func TestKafka_networkConnectivity(t *testing.T) { } // kafkaWithListener { - KafkaContainer, err := kafka.RunContainer(ctx, + KafkaContainer, err := kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), network.WithNetwork([]string{"kafka"}, Network), kafka.WithListener([]kafka.KafkaListener{ { @@ -198,9 +198,9 @@ func TestKafka_listenersValidation(t *testing.T) { ctx := context.Background() var err error - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: "INTERNAL", @@ -214,9 +214,9 @@ func TestKafka_listenersValidation(t *testing.T) { t.Fatalf("expected to fail due to reserved listener port duplication") } - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: "INTERNAL", @@ -230,9 +230,9 @@ func TestKafka_listenersValidation(t *testing.T) { t.Fatalf("expected to fail due to reserved listener port duplication") } - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: " cOnTrOller ", @@ -246,9 +246,9 @@ func TestKafka_listenersValidation(t *testing.T) { t.Fatalf("expected to fail due to reserved listener name duplication") } - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: "external", @@ -262,9 +262,9 @@ func TestKafka_listenersValidation(t *testing.T) { t.Fatalf("expected to fail due to reserved listener name duplication") } - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: "test", @@ -283,9 +283,9 @@ func TestKafka_listenersValidation(t *testing.T) { t.Fatalf("expected to fail due to port duplication") } - _, err = kafka.RunContainer(ctx, + _, err = kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.6.1"), kafka.WithListener([]kafka.KafkaListener{ { Name: "test", diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index 09bad2c0d0..80668964f6 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -578,7 +578,8 @@ func TestRedpandaListener_NoNetwork(t *testing.T) { func TestRedpandaBootstrapConfig(t *testing.T) { ctx := context.Background() - container, err := redpanda.RunContainer(ctx, + container, err := redpanda.Run(ctx, + "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithEnableWasmTransform(), // These configs would require a restart if applied to a live Redpanda instance redpanda.WithBootstrapConfig("data_transforms_per_core_memory_reservation", 33554432), From 83945290ff73f2da64bc1b28fedaaf7559b1c77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:31:33 +0200 Subject: [PATCH 15/49] chore: use PLAINTEXT and BROKER --- modules/kafka/kafka.go | 20 ++++++++++---------- modules/kafka/kafka_helpers_test.go | 6 +++--- modules/kafka/kafka_test.go | 12 ++++++------ modules/kafka/options.go | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index a3920b08c5..a3beef1ef4 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -56,10 +56,10 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom ExposedPorts: []string{string(publicPort)}, Env: map[string]string{ // envVars { - "KAFKA_LISTENERS": "EXTERNAL://0.0.0.0:9093,INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", - "KAFKA_REST_BOOTSTRAP_SERVERS": "EXTERNAL://0.0.0.0:9093,INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT", - "KAFKA_INTER_BROKER_LISTENER_NAME": "INTERNAL", + "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", + "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094", + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT", + "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER", "KAFKA_BROKER_ID": "1", "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1", "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1", @@ -157,7 +157,7 @@ func trimValidateListeners(listeners []KafkaListener) error { // check for default listeners names["CONTROLLER"] = true - names["EXTERNAL"] = true + names["PLAINTEXT"] = true for _, item := range listeners { if names[item.Name] { @@ -183,14 +183,14 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings } if len(settings.Listeners) == 0 { - defaultInternal, err := internalListener(ctx, c) + defaultInternal, err := brokerListener(ctx, c) if err != nil { return fmt.Errorf("can't create default internal listener: %w", err) } settings.Listeners = append(settings.Listeners, defaultInternal) } - defaultExternal, err := externalListener(ctx, c) + defaultExternal, err := plainTextListener(ctx, c) if err != nil { return fmt.Errorf("can't create default external listener: %w", err) } @@ -218,9 +218,9 @@ func editEnvsForListeners(listeners []KafkaListener) map[string]string { } envs := map[string]string{ - "KAFKA_LISTENERS": "CONTROLLER://0.0.0.0:9094, EXTERNAL://0.0.0.0:9093", - "KAFKA_REST_BOOTSTRAP_SERVERS": "CONTROLLER://0.0.0.0:9094, EXTERNAL://0.0.0.0:9093", - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "CONTROLLER:PLAINTEXT, EXTERNAL:PLAINTEXT", + "KAFKA_LISTENERS": "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093", + "KAFKA_REST_BOOTSTRAP_SERVERS": "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093", + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "CONTROLLER:PLAINTEXT, PLAINTEXT:PLAINTEXT", } // expect first listener has common network between kafka instances diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 6d26e1029f..4a047c899d 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -121,7 +121,7 @@ func TestTrimValidateListeners(t *testing.T) { { listeners: []KafkaListener{ { - Name: "INTERNAL", + Name: "PLAINTEXT", Host: "kafka", Port: "9093", }, @@ -132,7 +132,7 @@ func TestTrimValidateListeners(t *testing.T) { { listeners: []KafkaListener{ { - Name: "INTERNAL", + Name: "PLAINTEXT", Host: "kafka", Port: "9094", }, @@ -160,7 +160,7 @@ func TestTrimValidateListeners(t *testing.T) { }, }, wantErr: true, - description: "expected to fail due to reserved listener name EXTERNAL duplication", + description: "expected to fail due to reserved listener name PLAINTEXT duplication", }, { listeners: []KafkaListener{ diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 15f9cdab3e..16f8c182d7 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -121,7 +121,7 @@ func TestKafka_networkConnectivity(t *testing.T) { network.WithNetwork([]string{"kafka"}, Network), kafka.WithListener([]kafka.KafkaListener{ { - Name: "INTERNAL", + Name: "BROKER", Host: "kafka", Port: "9092", }, @@ -203,7 +203,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithClusterID("test-cluster"), kafka.WithListener([]kafka.KafkaListener{ { - Name: "INTERNAL", + Name: "BROKER", Host: "kafka", Port: "9093", }, @@ -219,7 +219,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithClusterID("test-cluster"), kafka.WithListener([]kafka.KafkaListener{ { - Name: "INTERNAL", + Name: "BROKER", Host: "kafka", Port: "9094", }, @@ -381,7 +381,7 @@ func createTopics(brokers []string, topics []string) error { } // assertAdvertisedListeners checks that the advertised listeners are set correctly: -// - The INTERNAL:// protocol is using the hostname of the Kafka container +// - The BROKER:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { hostname, err := container.Host(context.Background()) if err != nil { @@ -401,7 +401,7 @@ func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) t.Fatal(err) } - if !strings.Contains(string(bs), "INTERNAL://"+hostname+":9092") { - t.Fatalf("expected advertised listeners to contain %s, got %s", "INTERNAL://"+hostname+":9092", string(bs)) + if !strings.Contains(string(bs), "BROKER://"+hostname+":9092") { + t.Fatalf("expected advertised listeners to contain %s, got %s", "BROKER://"+hostname+":9092", string(bs)) } } diff --git a/modules/kafka/options.go b/modules/kafka/options.go index 1de85f1e6e..4e44f4f4fd 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -48,7 +48,7 @@ func WithListener(listeners []KafkaListener) Option { } } -func externalListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { +func plainTextListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { host, err := c.Host(ctx) if err != nil { return KafkaListener{}, err @@ -60,20 +60,20 @@ func externalListener(ctx context.Context, c testcontainers.Container) (KafkaLis } return KafkaListener{ - Name: "EXTERNAL", + Name: "PLAINTEXT", Host: host, Port: port.Port(), }, nil } -func internalListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { +func brokerListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { inspect, err := c.Inspect(ctx) if err != nil { return KafkaListener{}, fmt.Errorf("inspect: %w", err) } return KafkaListener{ - Name: "INTERNAL", + Name: "BROKER", Host: inspect.Config.Hostname, Port: "9092", }, nil From 31950f4151ae2e5e1edd2ec626c05c4193fa112c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:32:19 +0200 Subject: [PATCH 16/49] fix: handle error in tests --- modules/kafka/kafka_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 16f8c182d7..0d60fa2bd2 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -369,6 +369,9 @@ func createTopics(brokers []string, topics []string) error { var err error c, err := sarama.NewClient(brokers, sarama.NewConfig()) + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } _, err = c.Brokers()[0].CreateTopics(t) if err != nil { From 3aa2cbfe82f3047087ba24438346488d0708a378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:34:15 +0200 Subject: [PATCH 17/49] chore: make lint --- modules/kafka/kafka.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index a3beef1ef4..431333894a 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -102,25 +102,24 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom genericContainerReq.Env[key] = item } - genericContainerReq.ContainerRequest.LifecycleHooks = - []testcontainers.ContainerLifecycleHooks{ - { - PostStarts: []testcontainers.ContainerHook{ - // Use a single hook to copy the starter script and wait for - // the Kafka server to be ready. This prevents the wait running - // if the starter script fails to copy. - func(ctx context.Context, c testcontainers.Container) error { - // 1. copy the starter script into the container - if err := copyStarterScript(ctx, c, &settings); err != nil { - return fmt.Errorf("copy starter script: %w", err) - } - - // 2. wait for the Kafka server to be ready - return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c) - }, + genericContainerReq.ContainerRequest.LifecycleHooks = []testcontainers.ContainerLifecycleHooks{ + { + PostStarts: []testcontainers.ContainerHook{ + // Use a single hook to copy the starter script and wait for + // the Kafka server to be ready. This prevents the wait running + // if the starter script fails to copy. + func(ctx context.Context, c testcontainers.Container) error { + // 1. copy the starter script into the container + if err := copyStarterScript(ctx, c, &settings); err != nil { + return fmt.Errorf("copy starter script: %w", err) + } + + // 2. wait for the Kafka server to be ready + return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c) }, }, - } + }, + } err := validateKRaftVersion(genericContainerReq.Image) if err != nil { From d61004fc1543b47e19bf27b2c0072dc262077bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:35:14 +0200 Subject: [PATCH 18/49] chore: use non deprecated APIs --- modules/kafka/kafka_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 0d60fa2bd2..f11c95ef0c 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -109,7 +109,7 @@ func TestKafka_networkConnectivity(t *testing.T) { topic_out = "topic_out" ) - Network, err := network.New(ctx, network.WithCheckDuplicate()) + Network, err := network.New(ctx) if err != nil { t.Fatal(err) } From df53491d1337447cac2c037ca29242a72db82035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 17:37:25 +0200 Subject: [PATCH 19/49] chore: handle errors in testss --- modules/kafka/go.mod | 4 ++++ modules/kafka/go.sum | 9 +++++++++ modules/kafka/kafka_helpers_test.go | 1 - modules/kafka/kafka_test.go | 10 ++++++---- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index f3b1594d23..646f47edd2 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/IBM/sarama v1.43.2 github.com/docker/go-connections v0.5.0 + github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 golang.org/x/mod v0.16.0 ) @@ -41,6 +42,7 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/klauspost/compress v1.17.8 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -53,6 +55,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -70,6 +73,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 989179ecf8..d4c3419647 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -18,6 +18,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -86,6 +87,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -116,6 +121,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -237,6 +244,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 4a047c899d..cb9b2b5ac1 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -111,7 +111,6 @@ func TestValidateKRaftVersion(t *testing.T) { } func TestTrimValidateListeners(t *testing.T) { - tests := []struct { name string listeners []KafkaListener diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index f11c95ef0c..ec628b795f 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/IBM/sarama" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" @@ -128,7 +129,6 @@ func TestKafka_networkConnectivity(t *testing.T) { }), ) // } - if err != nil { t.Fatalf("failed to start container: %s", err) } @@ -138,9 +138,11 @@ func TestKafka_networkConnectivity(t *testing.T) { t.Fatal("failed to get brokers", err) } - createTopics(brokers, []string{topic_in, topic_out}) + err = createTopics(brokers, []string{topic_in, topic_out}) + require.NoError(t, err) - initKafkaTest(ctx, Network.Name, "kafka:9092", topic_in, topic_out) + _, err = initKafkaTest(ctx, Network.Name, "kafka:9092", topic_in, topic_out) + require.NoError(t, err) // perform assertions @@ -378,7 +380,7 @@ func createTopics(brokers []string, topics []string) error { return fmt.Errorf("failed to create topics: %w", err) } - fmt.Println("succesfully created topics") + fmt.Println("successfully created topics") return nil } From bf02852f6ac72b0c177e6bd52611b5fa4ea698fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 18:07:36 +0200 Subject: [PATCH 20/49] fix: close sarama client --- modules/kafka/kafka_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index ec628b795f..657d0829bf 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -374,6 +374,7 @@ func createTopics(brokers []string, topics []string) error { if err != nil { return fmt.Errorf("failed to create client: %w", err) } + defer c.Close() _, err = c.Brokers()[0].CreateTopics(t) if err != nil { From 152ef0cbffc2b1efbe48a02bbd24af5fc09ce704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 18:07:47 +0200 Subject: [PATCH 21/49] fix: validation in test --- modules/kafka/kafka_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 657d0829bf..7b627a440a 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -253,7 +253,7 @@ func TestKafka_listenersValidation(t *testing.T) { kafka.WithClusterID("test-cluster"), kafka.WithListener([]kafka.KafkaListener{ { - Name: "external", + Name: "plaintext", Host: "kafka", Port: "9092", }, From efa0f7d84095926d30b5c64687f2fb233c4ec8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 18:14:49 +0200 Subject: [PATCH 22/49] chore: refactor test --- modules/kafka/kafka_test.go | 175 ++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 97 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 7b627a440a..082846911b 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -198,112 +198,93 @@ func TestKafka_restProxyService(t *testing.T) { func TestKafka_listenersValidation(t *testing.T) { ctx := context.Background() - var err error - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9093", + testCases := []struct { + name string + listeners []kafka.KafkaListener + }{ + { + name: "reserved listener port duplication 1", + listeners: []kafka.KafkaListener{ + { + Name: "BROKER", + Host: "kafka", + Port: "9093", + }, }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to reserved listener port duplication") - } - - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9094", + }, + { + name: "reserved listener port duplication 2", + listeners: []kafka.KafkaListener{ + { + Name: "BROKER", + Host: "kafka", + Port: "9094", + }, }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to reserved listener port duplication") - } - - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: " cOnTrOller ", - Host: "kafka", - Port: "9092", + }, + { + name: "reserved listener name duplication (controller)", + listeners: []kafka.KafkaListener{ + { + Name: " cOnTrOller ", + Host: "kafka", + Port: "9092", + }, }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to reserved listener name duplication") - } - - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "plaintext", - Host: "kafka", - Port: "9092", + }, + { + name: "reserved listener name duplication (plaintext)", + listeners: []kafka.KafkaListener{ + { + Name: "plaintext", + Host: "kafka", + Port: "9092", + }, }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to reserved listener name duplication") - } - - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", + }, + { + name: "duplicated ports not allowed", + listeners: []kafka.KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", + }, + { + Name: "test2", + Host: "kafka", + Port: "9092", + }, }, - { - Name: "test2", - Host: "kafka", - Port: "9092", + }, + { + name: "duplicated names not allowed", + listeners: []kafka.KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", + }, + { + Name: "test", + Host: "kafka", + Port: "9095", + }, }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to port duplication") + }, } - _, err = kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener([]kafka.KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test", - Host: "kafka", - Port: "9095", - }, - }), - ) - - if err == nil { - t.Fatalf("expected to fail due to name duplication") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c, err := kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", + kafka.WithClusterID("test-cluster"), + kafka.WithListener(tc.listeners), + ) + require.Error(t, err) + require.Nil(t, c, "expected container to be nil") + }) } } From d669bfb9f1268e800af4264fad82269d8eb3b541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 2 Sep 2024 18:15:05 +0200 Subject: [PATCH 23/49] chore: add test using kcat --- modules/kafka/kafka_test.go | 111 ++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 082846911b..3f633b17d0 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -192,6 +192,86 @@ func TestKafka_networkConnectivity(t *testing.T) { } } +func TestKafka_withListener(t *testing.T) { + ctx := context.Background() + + // 1. Create network + rpNetwork, err := network.New(ctx) + require.NoError(t, err) + + // 2. Start Kafka ctr + // withListenerRP { + ctr, err := kafka.Run(ctx, + "confluentinc/confluent-local:7.6.1", + network.WithNetwork([]string{"kafka"}, rpNetwork), + kafka.WithListener([]kafka.KafkaListener{ + { + Name: "BROKER", + Host: "kafka", + Port: "9092", + }, + }), + ) + // } + require.NoError(t, err) + + // 3. Start KCat container + // withListenerKcat { + kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "confluentinc/cp-kcat:7.4.1", + Networks: []string{ + rpNetwork.Name, + }, + Entrypoint: []string{ + "sh", + }, + Cmd: []string{ + "-c", + "tail -f /dev/null", + }, + }, + Started: true, + }) + // } + + require.NoError(t, err) + + // 4. Copy message to kcat + err = kcat.CopyToContainer(ctx, []byte("Message produced by kcat"), "/tmp/msgs.txt", 700) + require.NoError(t, err) + + // 5. Produce message to Kafka + // withListenerExec { + _, _, err = kcat.Exec(ctx, []string{"kcat", "-b", "kafka:9092", "-t", "msgs", "-P", "-l", "/tmp/msgs.txt"}) + // } + + require.NoError(t, err) + + // 6. Consume message from Kafka + _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", "kafka:9092", "-C", "-t", "msgs", "-c", "1"}) + require.NoError(t, err) + + // 7. Read Message from stdout + out, err := io.ReadAll(stdout) + require.NoError(t, err) + + require.Contains(t, string(out), "Message produced by kcat") + + t.Cleanup(func() { + if err := kcat.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate kcat container: %s", err) + } + if err := ctr.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate Kafka container: %s", err) + } + + if err := rpNetwork.Remove(ctx); err != nil { + t.Fatalf("failed to remove network: %s", err) + } + }) +} + func TestKafka_restProxyService(t *testing.T) { // TODO: test kafka rest proxy service } @@ -309,37 +389,6 @@ func initKafkaTest(ctx context.Context, network string, brokers string, input st ContainerRequest: req, Started: true, }) - - // TODO: use kcat - /* - try ( - Network network = Network.newNetwork(); - // registerListener { - KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE) - .withListener(() -> "kafka:19092") - .withNetwork(network); - // } - // createKCatContainer { - GenericContainer kcat = new GenericContainer<>("confluentinc/cp-kcat:7.4.1") - .withCreateContainerCmdModifier(cmd -> { - cmd.withEntrypoint("sh"); - }) - .withCopyToContainer(Transferable.of("Message produced by kcat"), "/data/msgs.txt") - .withNetwork(network) - .withCommand("-c", "tail -f /dev/null") - // } - ) { - kafka.start(); - kcat.start(); - // produceConsumeMessage { - kcat.execInContainer("kcat", "-b", "kafka:19092", "-t", "msgs", "-P", "-l", "/data/msgs.txt"); - String stdout = kcat - .execInContainer("kcat", "-b", "kafka:19092", "-C", "-t", "msgs", "-c", "1") - .getStdout(); - // } - assertThat(stdout).contains("Message produced by kcat"); - } - */ } func createTopics(brokers []string, topics []string) error { From b51be12e7f3307f5595c41c73651d80af053d9c6 Mon Sep 17 00:00:00 2001 From: Da boi Date: Tue, 17 Sep 2024 20:17:05 +0300 Subject: [PATCH 24/49] fixed basic kafka test but network one is broken --- modules/kafka/go.mod | 12 +-- modules/kafka/go.sum | 46 +++++---- modules/kafka/kafka_test.go | 113 +++++++++++--------- modules/kafka/kcat_test.go | 63 ++++++++++++ modules/kafka/testdata/Dockerfile | 14 --- modules/kafka/testdata/go.mod | 14 --- modules/kafka/testdata/go.sum | 72 ------------- modules/kafka/testdata/main.go | 165 ------------------------------ 8 files changed, 161 insertions(+), 338 deletions(-) create mode 100644 modules/kafka/kcat_test.go delete mode 100644 modules/kafka/testdata/Dockerfile delete mode 100644 modules/kafka/testdata/go.mod delete mode 100644 modules/kafka/testdata/go.sum delete mode 100644 modules/kafka/testdata/main.go diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index 646f47edd2..effe129a00 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -3,7 +3,7 @@ module github.com/testcontainers/testcontainers-go/modules/kafka go 1.22 require ( - github.com/IBM/sarama v1.43.2 + github.com/IBM/sarama v1.43.3 github.com/docker/go-connections v0.5.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 @@ -23,7 +23,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/eapache/go-resiliency v1.6.0 // indirect + github.com/eapache/go-resiliency v1.7.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -41,7 +41,7 @@ require ( github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -68,9 +68,9 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index d4c3419647..9dbc9510c7 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -4,8 +4,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/IBM/sarama v1.43.2 h1:HABeEqRUh32z8yzY2hGB/j8mHSzC/HA9zlEjqFNCzSw= -github.com/IBM/sarama v1.43.2/go.mod h1:Kyo4WkF24Z+1nz7xeVUFWIuKVV8RS3wM8mkvPKMdXFQ= +github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= +github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -32,8 +32,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= -github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= +github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= @@ -85,8 +85,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -134,6 +134,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -172,8 +174,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -188,14 +192,16 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -211,19 +217,23 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 3f633b17d0..b3a3af60f0 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -13,10 +13,9 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" "github.com/testcontainers/testcontainers-go/network" - "github.com/testcontainers/testcontainers-go/wait" ) -func TestKafka(t *testing.T) { +func TestKafka_Basic(t *testing.T) { topic := "some-topic" ctx := context.Background() @@ -106,8 +105,15 @@ func TestKafka_networkConnectivity(t *testing.T) { var err error const ( + // config topic_in = "topic_in" topic_out = "topic_out" + + address = "kafka:9092" + + // test data + key = "wow" + text_msg = "test-input-external" ) Network, err := network.New(ctx) @@ -129,21 +135,38 @@ func TestKafka_networkConnectivity(t *testing.T) { }), ) // } - if err != nil { - t.Fatalf("failed to start container: %s", err) - } + require.NoError(t, err, "failed to start kafka container") - brokers, err := KafkaContainer.Brokers(context.TODO()) - if err != nil { - t.Fatal("failed to get brokers", err) - } + kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "confluentinc/cp-kcat:7.4.1", + Networks: []string{ + Network.Name, + }, + Entrypoint: []string{ + "sh", + }, + Cmd: []string{ + "-c", + "tail -f /dev/null", + }, + }, + Started: true, + }) + // } - err = createTopics(brokers, []string{topic_in, topic_out}) - require.NoError(t, err) + require.NoError(t, err, "failed to start kcat") - _, err = initKafkaTest(ctx, Network.Name, "kafka:9092", topic_in, topic_out) + // 4. Copy message to kcat + err = kcat.CopyToContainer(ctx, []byte("Message produced by kcat"), "/tmp/msgs.txt", 700) require.NoError(t, err) + brokers, err := KafkaContainer.Brokers(context.TODO()) + require.NoError(t, err, "failed to get brokers") + + err = createTopics(brokers, []string{topic_in, topic_out}) + require.NoError(t, err, "create topics") + // perform assertions // set config to true because successfully delivered messages will be returned on the Successes channel @@ -151,26 +174,37 @@ func TestKafka_networkConnectivity(t *testing.T) { config.Producer.Return.Successes = true producer, err := sarama.NewSyncProducer(brokers, config) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "create kafka producer") // Act - key := "wow" - text_msg := "test-input-external" - if _, _, err := producer.SendMessage(&sarama.ProducerMessage{ + // External write + _, _, err = producer.SendMessage(&sarama.ProducerMessage{ Topic: topic_in, Key: sarama.StringEncoder(key), Value: sarama.StringEncoder(text_msg), - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err, "send message") + + // Internal read + _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) + require.NoError(t, err) + out, err := io.ReadAll(stdout) + require.NoError(t, err, "read message in kcat") + + // Internal write + tempfile := "/tmp/msgs.txt" + + err = kcat.CopyToContainer(ctx, []byte(out), tempfile, 700) + require.NoError(t, err) + + _, _, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-t", topic_out, "-P", "-l", tempfile}) + require.NoError(t, err, "send message with kcat") + + // External read client, err := sarama.NewConsumerGroup(brokers, "groupName", config) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "create consumer group") consumer, _, done, cancel := NewTestKafkaConsumer(t) go func() { @@ -368,29 +402,6 @@ func TestKafka_listenersValidation(t *testing.T) { } } -func initKafkaTest(ctx context.Context, network string, brokers string, input string, output string) (testcontainers.Container, error) { - req := testcontainers.ContainerRequest{ - FromDockerfile: testcontainers.FromDockerfile{ - Context: "./testdata", - Dockerfile: "Dockerfile", - PrintBuildLog: true, - KeepImage: true, - }, - WaitingFor: wait.ForLog("start consuming events"), - Env: map[string]string{ - "KAFKA_BROKERS": brokers, - "KAFKA_TOPIC_IN": input, - "KAFKA_TOPIC_OUT": output, - }, - Networks: []string{network}, - } - - return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - }) -} - func createTopics(brokers []string, topics []string) error { t := &sarama.CreateTopicsRequest{} t.TopicDetails = make(map[string]*sarama.TopicDetail, len(topics)) @@ -406,7 +417,9 @@ func createTopics(brokers []string, topics []string) error { } defer c.Close() - _, err = c.Brokers()[0].CreateTopics(t) + bs := c.Brokers() + + _, err = bs[0].CreateTopics(t) if err != nil { return fmt.Errorf("failed to create topics: %w", err) } @@ -419,10 +432,12 @@ func createTopics(brokers []string, topics []string) error { // assertAdvertisedListeners checks that the advertised listeners are set correctly: // - The BROKER:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { - hostname, err := container.Host(context.Background()) + inspect, err := container.Inspect(context.Background()) if err != nil { t.Fatal(err) } + hostname := inspect.Config.Hostname + code, r, err := container.Exec(context.Background(), []string{"cat", "/usr/sbin/testcontainers_start.sh"}) if err != nil { t.Fatal(err) diff --git a/modules/kafka/kcat_test.go b/modules/kafka/kcat_test.go new file mode 100644 index 0000000000..5c1d56e83e --- /dev/null +++ b/modules/kafka/kcat_test.go @@ -0,0 +1,63 @@ +package kafka_test + +import ( + "context" + "fmt" + "io" + + "github.com/testcontainers/testcontainers-go" +) + +type KcatContainer struct { + Container testcontainers.Container + FilePath string +} + +func createKCat(ctx context.Context, network, filepath string) (KcatContainer, error) { + kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "confluentinc/cp-kcat:7.4.1", + Networks: []string{ + network, + }, + Entrypoint: []string{ + "sh", + }, + Cmd: []string{ + "-c", + "tail -f /dev/null", + }, + }, + Started: true, + }) + + if err != nil { + return KcatContainer{}, fmt.Errorf("create generic container: %w", err) + } + + return KcatContainer{Container: kcat, FilePath: filepath}, nil +} + +func (kcat *KcatContainer) SaveFile(ctx context.Context, data string) error { + return kcat.Container.CopyToContainer(ctx, []byte(data), kcat.FilePath, 700) +} + +func (kcat *KcatContainer) ProduceMessageFromFile(ctx context.Context, broker, topic string) error { + cmd := []string{"kcat", "-b", broker, "-t", topic, "-P", "-l", kcat.FilePath} + _, _, err := kcat.Container.Exec(ctx, cmd) + + return err +} + +func (kcat *KcatContainer) ConsumeMessage(ctx context.Context, broker, topic string) (string, error) { + cmd := []string{"kcat", "-b", broker, "-C", "-t", topic, "-c1"} + _, stdout, err := kcat.Container.Exec(ctx, cmd) + + if err != nil { + return "", err + } + + out, err := io.ReadAll(stdout) + + return string(out), err +} diff --git a/modules/kafka/testdata/Dockerfile b/modules/kafka/testdata/Dockerfile deleted file mode 100644 index a5afc156d7..0000000000 --- a/modules/kafka/testdata/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM golang:bullseye - -WORKDIR /app - -COPY go.mod . -COPY go.sum . - -RUN go mod tidy - -COPY . . - -RUN go build -o ./pg_test - -CMD /app/pg_test \ No newline at end of file diff --git a/modules/kafka/testdata/go.mod b/modules/kafka/testdata/go.mod deleted file mode 100644 index 15256e1e22..0000000000 --- a/modules/kafka/testdata/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module amogus - -go 1.21.6 - -require ( - github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 - github.com/google/uuid v1.6.0 - github.com/pkg/errors v0.9.1 -) - -require ( - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.15.0 // indirect -) diff --git a/modules/kafka/testdata/go.sum b/modules/kafka/testdata/go.sum deleted file mode 100644 index 38e7e8f3f8..0000000000 --- a/modules/kafka/testdata/go.sum +++ /dev/null @@ -1,72 +0,0 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= -github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= -github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= -github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= -github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= -github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= -github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= -github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8= -github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 h1:0BOZf6qNozI3pkN3fJLwNubheHJYHhMh91GRFOWWK08= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/kafka/testdata/main.go b/modules/kafka/testdata/main.go deleted file mode 100644 index 96ea131822..0000000000 --- a/modules/kafka/testdata/main.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "log" - "log/slog" - "os" - "time" - - "github.com/confluentinc/confluent-kafka-go/v2/kafka" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -const ( - client = "kafka_rw_test" - group = "test_group" -) - -func main() { - brokers, _ := os.LookupEnv("KAFKA_BROKERS") - input_topic, _ := os.LookupEnv("KAFKA_TOPIC_IN") - output_topic, _ := os.LookupEnv("KAFKA_TOPIC_OUT") - - log.Printf("Got brokers: %v\n", brokers) - log.Printf("Got input topic: %v\n", input_topic) - log.Printf("Got output topic: %v\n", output_topic) - - consumer, err := InitNativeKafkaConsumer(client, brokers, group) - if err != nil { - log.Fatal(fmt.Errorf("failed to start kafka consumer: %w", err)) - } - - defer consumer.Close() - - meta, err := consumer.GetMetadata(nil, true, 1000) - log.Printf("Metadata: %#+v, %v", meta, err) - - producer, err := InitNativeKafkaProducer(client, brokers, "1", 30000) - if err != nil { - log.Fatal(fmt.Errorf("failed to start kafka producer: %w", err)) - } - - defer producer.Close() - - err = consumer.SubscribeTopics([]string{input_topic}, nil) - if err != nil { - log.Fatal(fmt.Errorf("failed to subscribe to kafka topic: %w", err)) - } - - StartConsuming(context.TODO(), consumer, producer, output_topic) - - fmt.Print("Finished\n") -} - -func StartConsuming(ctx context.Context, consumer *kafka.Consumer, producer *kafka.Producer, outTopic string) { - log.Println("start consuming events") - - run := true - - for run { - msg, err := consumer.ReadMessage(time.Second * 1) - if err != nil { - kErr, ok := err.(kafka.Error) - if ok && kErr.IsTimeout() { - log.Println(fmt.Errorf("read timeout: %w", kErr)) - continue - } - - log.Println(fmt.Errorf("failed to read message: %w", err)) - continue - } - - log.Printf("got message: %s\n", string(msg.Value)) - - outputText := string(msg.Value) + "-from-internal" - output := MakeMsg(outTopic, string(msg.Key), outputText) - - err = producer.Produce(&output, nil) - - if err != nil { - log.Println(fmt.Errorf("failed to write message: %w", err)) - continue - } - - log.Printf("written: %s\n", outputText) - } -} - -func MakeMsg(topic, key string, message interface{}) kafka.Message { - headers := []kafka.Header{ - { - Key: "DateAdd", Value: []byte(time.Now().Format(time.RFC3339Nano)), - }, - { - Key: "MessageId", Value: []byte(uuid.NewString()), - }, - } - - messageJson, _ := json.Marshal(message) - - keyJson, _ := json.Marshal(key) - - msg := kafka.Message{ - TopicPartition: kafka.TopicPartition{ - Topic: &topic, - Partition: kafka.PartitionAny, - }, - Value: messageJson, - Key: keyJson, - Headers: headers, - } - - return msg -} - -func InitNativeKafkaProducer( - clientID string, - brokers string, - acks string, - bufMaxMsg int, -) (*kafka.Producer, error) { - cfg := kafka.ConfigMap{ - "bootstrap.servers": brokers, - "client.id": clientID, - "acks": acks, - "queue.buffering.max.messages": bufMaxMsg, - "go.delivery.reports": false, - } - - p, err := kafka.NewProducer(&cfg) - if err != nil { - slog.Error("new producer", err) - return nil, err - } - - slog.Info(fmt.Sprintf("kafka producer %s created", clientID)) - - return p, nil -} - -func InitNativeKafkaConsumer( - clientID string, - brokers string, - group string, -) (*kafka.Consumer, error) { - config := kafka.ConfigMap{ - "bootstrap.servers": brokers, - "group.id": group, - "client.id": clientID, - "auto.offset.reset": "earliest", - "auto.commit.interval.ms": 3000, - } - - c, err := kafka.NewConsumer(&config) - if err != nil { - return nil, errors.Wrapf(err, "create kafka consumer") - } - - slog.Info(fmt.Sprintf("kafka consumer %s created", clientID)) - - return c, nil -} From c8c47f45c7618f172146971ee2c19b1bdd5f38f3 Mon Sep 17 00:00:00 2001 From: Da boi Date: Mon, 23 Sep 2024 20:59:00 +0300 Subject: [PATCH 25/49] not working test with kcat --- modules/kafka/go.sum | 12 ++---------- modules/kafka/kafka_test.go | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 9dbc9510c7..3a543e3791 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -174,8 +174,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -192,8 +190,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -217,23 +213,19 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index b3a3af60f0..58892d42c1 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -6,6 +6,7 @@ import ( "io" "strings" "testing" + "time" "github.com/IBM/sarama" "github.com/stretchr/testify/require" @@ -164,14 +165,19 @@ func TestKafka_networkConnectivity(t *testing.T) { brokers, err := KafkaContainer.Brokers(context.TODO()) require.NoError(t, err, "failed to get brokers") - err = createTopics(brokers, []string{topic_in, topic_out}) - require.NoError(t, err, "create topics") + // err = createTopics(brokers, []string{topic_in, topic_out}) + _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in}) + require.NoError(t, err, "create topic topic_in") + + _, stdout, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_out}) + require.NoError(t, err, "create topic topic_out") // perform assertions // set config to true because successfully delivered messages will be returned on the Successes channel config := sarama.NewConfig() config.Producer.Return.Successes = true + config.Consumer.MaxWaitTime = 2 * time.Second producer, err := sarama.NewSyncProducer(brokers, config) require.NoError(t, err, "create kafka producer") @@ -187,11 +193,12 @@ func TestKafka_networkConnectivity(t *testing.T) { require.NoError(t, err, "send message") // Internal read - _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) + _, stdout, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) require.NoError(t, err) out, err := io.ReadAll(stdout) require.NoError(t, err, "read message in kcat") + require.Contains(t, string(out), text_msg) // Internal write tempfile := "/tmp/msgs.txt" @@ -207,14 +214,20 @@ func TestKafka_networkConnectivity(t *testing.T) { require.NoError(t, err, "create consumer group") consumer, _, done, cancel := NewTestKafkaConsumer(t) + + sCtx, _ := context.WithTimeout(context.Background(), time.Second) go func() { - if err := client.Consume(context.Background(), []string{topic_out}, consumer); err != nil { + if err := client.Consume(sCtx, []string{topic_out}, consumer); err != nil { cancel() } }() - // wait for the consumer to be ready - <-done + // wait for the consumer to receive message + select { + case <-sCtx.Done(): + t.Log("exit by timeout") + case <-done: + } if consumer.message == nil { t.Fatal("Empty message") From 84cd9190891e9be6b07e544260bdabd5025ec8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 13:49:00 +0100 Subject: [PATCH 26/49] fix: use PLAINTEXT in test --- modules/kafka/kafka_helpers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index b43cc90c68..95a47c8212 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -151,7 +151,7 @@ func TestTrimValidateListeners(t *testing.T) { { listeners: []KafkaListener{ { - Name: "external", + Name: "plaintext", Host: "kafka", Port: "9092", }, From 8a65a7b6ad201df3b0506913776fb42dfb799623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 14:07:21 +0100 Subject: [PATCH 27/49] chor: rename container variable --- modules/kafka/kafka_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 2e6c60eddd..e6312dbb5e 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -100,7 +100,7 @@ func TestKafka_networkConnectivity(t *testing.T) { } // kafkaWithListener { - KafkaContainer, err := kafka.Run(ctx, + kafkaContainer, err := kafka.Run(ctx, "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), network.WithNetwork([]string{"kafka"}, Network), @@ -139,7 +139,7 @@ func TestKafka_networkConnectivity(t *testing.T) { err = kcat.CopyToContainer(ctx, []byte("Message produced by kcat"), "/tmp/msgs.txt", 700) require.NoError(t, err) - brokers, err := KafkaContainer.Brokers(context.TODO()) + brokers, err := kafkaContainer.Brokers(context.TODO()) require.NoError(t, err, "failed to get brokers") // err = createTopics(brokers, []string{topic_in, topic_out}) From fdbabd21fcdefddfb2b7e4e2ddd529e041b41131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 14:48:10 +0100 Subject: [PATCH 28/49] chore: refactor unit tests for the new test function pattern --- modules/kafka/kafka_helpers_test.go | 327 ++++++++++++---------------- 1 file changed, 145 insertions(+), 182 deletions(-) diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 95a47c8212..c392bb737e 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -9,208 +9,171 @@ import ( ) func TestConfigureQuorumVoters(t *testing.T) { - tests := []struct { - name string - req *testcontainers.GenericContainerRequest - expectedVoters string - }{ - { - name: "voters on localhost", - req: &testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Env: map[string]string{}, - }, + testConfigureControllerQuorumVotersFn := func(t *testing.T, req *testcontainers.GenericContainerRequest, expectedVoters string) { + t.Helper() + + configureControllerQuorumVoters(req) + require.Equalf(t, expectedVoters, req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", expectedVoters, req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"]) + } + + t.Run("voters/localhost", func(t *testing.T) { + testConfigureControllerQuorumVotersFn(t, &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Env: map[string]string{}, }, - expectedVoters: "1@localhost:9094", - }, - { - name: "voters on first network alias of the first network", - req: &testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Env: map[string]string{}, - Networks: []string{"foo", "bar", "baaz"}, - NetworkAliases: map[string][]string{ - "foo": {"foo0", "foo1", "foo2", "foo3"}, - "bar": {"bar0", "bar1", "bar2", "bar3"}, - "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"}, - }, + }, "1@localhost:9094") + }) + + t.Run("voters/first-network-alias/first-network", func(t *testing.T) { + testConfigureControllerQuorumVotersFn(t, &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Env: map[string]string{}, + Networks: []string{"foo", "bar", "baaz"}, + NetworkAliases: map[string][]string{ + "foo": {"foo0", "foo1", "foo2", "foo3"}, + "bar": {"bar0", "bar1", "bar2", "bar3"}, + "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"}, }, }, - expectedVoters: "1@foo0:9094", - }, - { - name: "voters on localhost if alias but no networks", - req: &testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - NetworkAliases: map[string][]string{ - "foo": {"foo0", "foo1", "foo2", "foo3"}, - "bar": {"bar0", "bar1", "bar2", "bar3"}, - "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"}, - }, + }, "1@foo0:9094") + }) + + t.Run("voters/localhost/alias-no-networks", func(t *testing.T) { + testConfigureControllerQuorumVotersFn(t, &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + NetworkAliases: map[string][]string{ + "foo": {"foo0", "foo1", "foo2", "foo3"}, + "bar": {"bar0", "bar1", "bar2", "bar3"}, + "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"}, }, }, - expectedVoters: "1@localhost:9094", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - configureControllerQuorumVoters(test.req) - - require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"]) - }) - } + }, "1@localhost:9094") + }) } func TestValidateKRaftVersion(t *testing.T) { - tests := []struct { - name string - image string - wantErr bool - }{ - { - name: "Official: valid version", - image: "confluentinc/confluent-local:7.5.0", - wantErr: false, - }, - { - name: "Official: valid, limit version", - image: "confluentinc/confluent-local:7.4.0", - wantErr: false, - }, - { - name: "Official: invalid, low version", - image: "confluentinc/confluent-local:7.3.99", - wantErr: true, - }, - { - name: "Official: invalid, too low version", - image: "confluentinc/confluent-local:5.0.0", - wantErr: true, - }, - { - name: "Unofficial does not validate KRaft version", - image: "my-kafka:1.0.0", - wantErr: false, - }, - } + validateKRaftVersionFn := func(t *testing.T, image string, wantErr bool) { + t.Helper() - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := validateKRaftVersion(test.image) + err := validateKRaftVersion(image) - if test.wantErr { - require.Errorf(t, err, "expected error, got nil") - } else { - require.NoErrorf(t, err, "expected no error, got %s", err) - } - }) + if wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } } + + t.Run("official/valid-version", func(t *testing.T) { + validateKRaftVersionFn(t, "confluentinc/confluent-local:7.5.0", false) + }) + + t.Run("official/valid-limit-version", func(t *testing.T) { + validateKRaftVersionFn(t, "confluentinc/confluent-local:7.4.0", false) + }) + + t.Run("official/invalid-low-version", func(t *testing.T) { + validateKRaftVersionFn(t, "confluentinc/confluent-local:7.3.99", true) + }) + + t.Run("official/invalid-too-low-version", func(t *testing.T) { + validateKRaftVersionFn(t, "confluentinc/confluent-local:5.0.0", true) + }) + + t.Run("unofficial/does-not-validate-KRaft-version", func(t *testing.T) { + validateKRaftVersionFn(t, "my-kafka:1.0.0", false) + }) } func TestTrimValidateListeners(t *testing.T) { - tests := []struct { - name string - listeners []KafkaListener - wantErr bool - description string - }{ - { - listeners: []KafkaListener{ - { - Name: "PLAINTEXT", - Host: "kafka", - Port: "9093", - }, - }, - wantErr: true, - description: "expected to fail due to reserved listener port duplication", - }, - { - listeners: []KafkaListener{ - { - Name: "PLAINTEXT", - Host: "kafka", - Port: "9094", - }, + validateFn := func(t *testing.T, listeners []KafkaListener, wantErr bool) { + t.Helper() + + err := trimValidateListeners(listeners) + if wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } + + t.Run("fail/reserved-listener/port-9093", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "PLAINTEXT", + Host: "kafka", + Port: "9093", }, - wantErr: true, - description: "expected to fail due to reserved listener port duplication", - }, - { - listeners: []KafkaListener{ - { - Name: " cOnTrOller ", - Host: "kafka", - Port: "9092", - }, + }, true) + }) + + t.Run("fail/reserved-listener/port-9094", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "PLAINTEXT", + Host: "kafka", + Port: "9094", }, - wantErr: true, - description: "expected to fail due to reserved listener name CONTROLLER duplication", - }, - { - listeners: []KafkaListener{ - { - Name: "plaintext", - Host: "kafka", - Port: "9092", - }, + }, true) + }) + + t.Run("fail/reserved-listener/name-controller", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: " cOnTrOller ", + Host: "kafka", + Port: "9092", }, - wantErr: true, - description: "expected to fail due to reserved listener name PLAINTEXT duplication", - }, - { - listeners: []KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test2", - Host: "kafka", - Port: "9092", - }, + }, true) + }) + + t.Run("fail/reserved-listener/name-plaintext", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "plaintext", + Host: "kafka", + Port: "9092", }, - wantErr: true, - description: "expected to fail due to port duplication", - }, - { - listeners: []KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test", - Host: "kafka", - Port: "9095", - }, + }, true) + }) + + t.Run("fail/port-duplication", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", }, - wantErr: true, - description: "expected to fail due to name duplication", - }, - { - listeners: []KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9095", - }, + { + Name: "test2", + Host: "kafka", + Port: "9092", }, - wantErr: false, - description: "expected no errors", - }, - } + }, true) + }) - for _, test := range tests { - t.Run("", func(t *testing.T) { - err := trimValidateListeners(test.listeners) + t.Run("fail/name-duplication", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", + }, + { + Name: "test", + Host: "kafka", + Port: "9095", + }, + }, true) + }) - if test.wantErr != (err != nil) { - t.Fatalf(test.description) - } - }) - } + t.Run("success", func(t *testing.T) { + validateFn(t, []KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9095", + }, + }, false) + }) } From 3429d0b99b5e773c63d9059be20bec85c0087b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 15:07:32 +0100 Subject: [PATCH 29/49] chore: use kcat container function --- modules/kafka/kafka_test.go | 54 +++++++------------------------------ modules/kafka/kcat_test.go | 24 ++++++++++++----- 2 files changed, 26 insertions(+), 52 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index e6312dbb5e..f58a58ba5d 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -115,38 +115,21 @@ func TestKafka_networkConnectivity(t *testing.T) { // } require.NoError(t, err, "failed to start kafka container") - kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Image: "confluentinc/cp-kcat:7.4.1", - Networks: []string{ - Network.Name, - }, - Entrypoint: []string{ - "sh", - }, - Cmd: []string{ - "-c", - "tail -f /dev/null", - }, - }, - Started: true, - }) - // } - + kcat, err := runKcatContainer(ctx, Network.Name, "/tmp/msgs.txt") require.NoError(t, err, "failed to start kcat") // 4. Copy message to kcat - err = kcat.CopyToContainer(ctx, []byte("Message produced by kcat"), "/tmp/msgs.txt", 700) + err = kcat.SaveFile(ctx, "Message produced by kcat") require.NoError(t, err) brokers, err := kafkaContainer.Brokers(context.TODO()) require.NoError(t, err, "failed to get brokers") // err = createTopics(brokers, []string{topic_in, topic_out}) - _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in}) + err = kcat.CreateTopic(ctx, address, topic_in) require.NoError(t, err, "create topic topic_in") - _, stdout, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_out}) + err = kcat.CreateTopic(ctx, address, topic_out) require.NoError(t, err, "create topic topic_out") // perform assertions @@ -170,7 +153,7 @@ func TestKafka_networkConnectivity(t *testing.T) { require.NoError(t, err, "send message") // Internal read - _, stdout, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) + _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) require.NoError(t, err) out, err := io.ReadAll(stdout) @@ -241,45 +224,26 @@ func TestKafka_withListener(t *testing.T) { // 3. Start KCat container // withListenerKcat { - kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Image: "confluentinc/cp-kcat:7.4.1", - Networks: []string{ - rpNetwork.Name, - }, - Entrypoint: []string{ - "sh", - }, - Cmd: []string{ - "-c", - "tail -f /dev/null", - }, - }, - Started: true, - }) + kcat, err := runKcatContainer(ctx, rpNetwork.Name, "/tmp/msgs.txt") // } require.NoError(t, err) // 4. Copy message to kcat - err = kcat.CopyToContainer(ctx, []byte("Message produced by kcat"), "/tmp/msgs.txt", 700) + err = kcat.SaveFile(ctx, "Message produced by kcat") require.NoError(t, err) // 5. Produce message to Kafka // withListenerExec { - _, _, err = kcat.Exec(ctx, []string{"kcat", "-b", "kafka:9092", "-t", "msgs", "-P", "-l", "/tmp/msgs.txt"}) + err = kcat.ProduceMessageFromFile(ctx, "kafka:9092", "msgs") // } require.NoError(t, err) // 6. Consume message from Kafka - _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", "kafka:9092", "-C", "-t", "msgs", "-c", "1"}) - require.NoError(t, err) - // 7. Read Message from stdout - out, err := io.ReadAll(stdout) + out, err := kcat.ConsumeMessage(ctx, "kafka:9092", "msgs") require.NoError(t, err) - require.Contains(t, string(out), "Message produced by kcat") t.Cleanup(func() { diff --git a/modules/kafka/kcat_test.go b/modules/kafka/kcat_test.go index 5c1d56e83e..08f3afd3b5 100644 --- a/modules/kafka/kcat_test.go +++ b/modules/kafka/kcat_test.go @@ -9,12 +9,12 @@ import ( ) type KcatContainer struct { - Container testcontainers.Container - FilePath string + testcontainers.Container + FilePath string } -func createKCat(ctx context.Context, network, filepath string) (KcatContainer, error) { - kcat, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ +func runKcatContainer(ctx context.Context, network, filepath string) (*KcatContainer, error) { + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "confluentinc/cp-kcat:7.4.1", Networks: []string{ @@ -31,11 +31,15 @@ func createKCat(ctx context.Context, network, filepath string) (KcatContainer, e Started: true, }) + var c *KcatContainer + if ctr != nil { + c = &KcatContainer{Container: ctr, FilePath: filepath} + } if err != nil { - return KcatContainer{}, fmt.Errorf("create generic container: %w", err) + return c, fmt.Errorf("generic container: %w", err) } - return KcatContainer{Container: kcat, FilePath: filepath}, nil + return c, nil } func (kcat *KcatContainer) SaveFile(ctx context.Context, data string) error { @@ -49,10 +53,16 @@ func (kcat *KcatContainer) ProduceMessageFromFile(ctx context.Context, broker, t return err } +func (kcat *KcatContainer) CreateTopic(ctx context.Context, broker, topic string) error { + cmd := []string{"kcat", "-b", broker, "-C", "-t", topic} + _, _, err := kcat.Container.Exec(ctx, cmd) + + return err +} + func (kcat *KcatContainer) ConsumeMessage(ctx context.Context, broker, topic string) (string, error) { cmd := []string{"kcat", "-b", broker, "-C", "-t", topic, "-c1"} _, stdout, err := kcat.Container.Exec(ctx, cmd) - if err != nil { return "", err } From 01ba4e92e9ae9a76da82994ff638c7539afce8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 16:23:59 +0100 Subject: [PATCH 30/49] chore: use require and new CleanupContainer functions --- modules/kafka/kafka_test.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index f58a58ba5d..b1102799cf 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -113,9 +113,11 @@ func TestKafka_networkConnectivity(t *testing.T) { }), ) // } + testcontainers.CleanupContainer(t, kafkaContainer) require.NoError(t, err, "failed to start kafka container") kcat, err := runKcatContainer(ctx, Network.Name, "/tmp/msgs.txt") + testcontainers.CleanupContainer(t, kcat) require.NoError(t, err, "failed to start kcat") // 4. Copy message to kcat @@ -193,10 +195,8 @@ func TestKafka_networkConnectivity(t *testing.T) { t.Fatal("Empty message") } - // Assert - if !strings.Contains(string(consumer.message.Value), text_msg) { - t.Error("got wrong string") - } + require.Contains(t, string(consumer.message.Value), text_msg) + require.Contains(t, string(consumer.message.Key), key) } func TestKafka_withListener(t *testing.T) { @@ -206,6 +206,12 @@ func TestKafka_withListener(t *testing.T) { rpNetwork, err := network.New(ctx) require.NoError(t, err) + t.Cleanup(func() { + if err := rpNetwork.Remove(ctx); err != nil { + t.Fatalf("failed to remove network: %s", err) + } + }) + // 2. Start Kafka ctr // withListenerRP { ctr, err := kafka.Run(ctx, @@ -220,13 +226,14 @@ func TestKafka_withListener(t *testing.T) { }), ) // } + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // 3. Start KCat container // withListenerKcat { kcat, err := runKcatContainer(ctx, rpNetwork.Name, "/tmp/msgs.txt") // } - + testcontainers.CleanupContainer(t, kcat) require.NoError(t, err) // 4. Copy message to kcat @@ -245,19 +252,6 @@ func TestKafka_withListener(t *testing.T) { out, err := kcat.ConsumeMessage(ctx, "kafka:9092", "msgs") require.NoError(t, err) require.Contains(t, string(out), "Message produced by kcat") - - t.Cleanup(func() { - if err := kcat.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate kcat container: %s", err) - } - if err := ctr.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate Kafka container: %s", err) - } - - if err := rpNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } - }) } func TestKafka_restProxyService(t *testing.T) { From 63ee6cf41b3a301989d42da7c30befc46b6a2d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 16:29:59 +0100 Subject: [PATCH 31/49] chore: use test function pattern even more --- modules/kafka/kafka_test.go | 152 +++++++++++++++++------------------- 1 file changed, 72 insertions(+), 80 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index b1102799cf..01fce2160b 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -259,95 +259,87 @@ func TestKafka_restProxyService(t *testing.T) { } func TestKafka_listenersValidation(t *testing.T) { - ctx := context.Background() + runWithErrorFn := func(t *testing.T, listeners []kafka.KafkaListener) { + t.Helper() + + c, err := kafka.Run(context.Background(), + "confluentinc/confluent-local:7.6.1", + kafka.WithClusterID("test-cluster"), + kafka.WithListener(listeners), + ) + require.Error(t, err) + require.Nil(t, c, "expected container to be nil") + } - testCases := []struct { - name string - listeners []kafka.KafkaListener - }{ - { - name: "reserved listener port duplication 1", - listeners: []kafka.KafkaListener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9093", - }, + t.Run("reserved-listener/port-9093", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: "BROKER", + Host: "kafka", + Port: "9093", }, - }, - { - name: "reserved listener port duplication 2", - listeners: []kafka.KafkaListener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9094", - }, + }) + }) + + t.Run("reserved-listener/port-9094", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: "BROKER", + Host: "kafka", + Port: "9094", }, - }, - { - name: "reserved listener name duplication (controller)", - listeners: []kafka.KafkaListener{ - { - Name: " cOnTrOller ", - Host: "kafka", - Port: "9092", - }, + }) + }) + + t.Run("reserved-listener/controller-duplicated", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: " cOnTrOller ", + Host: "kafka", + Port: "9092", }, - }, - { - name: "reserved listener name duplication (plaintext)", - listeners: []kafka.KafkaListener{ - { - Name: "plaintext", - Host: "kafka", - Port: "9092", - }, + }) + }) + + t.Run("reserved-listener/plaintext-duplicated", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: "plaintext", + Host: "kafka", + Port: "9092", }, - }, - { - name: "duplicated ports not allowed", - listeners: []kafka.KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test2", - Host: "kafka", - Port: "9092", - }, + }) + }) + + t.Run("duplicated-ports", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", }, - }, - { - name: "duplicated names not allowed", - listeners: []kafka.KafkaListener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test", - Host: "kafka", - Port: "9095", - }, + { + Name: "test2", + Host: "kafka", + Port: "9092", }, - }, - } + }) + }) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - c, err := kafka.Run(ctx, - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener(tc.listeners), - ) - require.Error(t, err) - require.Nil(t, c, "expected container to be nil") + t.Run("duplicated-names", func(t *testing.T) { + runWithErrorFn(t, []kafka.KafkaListener{ + { + Name: "test", + Host: "kafka", + Port: "9092", + }, + { + Name: "test", + Host: "kafka", + Port: "9095", + }, }) - } + }) } func createTopics(brokers []string, topics []string) error { From 86601572d19fcf44d20ed25dd3bca76e57029c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 16:49:17 +0100 Subject: [PATCH 32/49] chore: exclude lint error --- modules/kafka/kafka_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 01fce2160b..a7e942f355 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -177,7 +177,7 @@ func TestKafka_networkConnectivity(t *testing.T) { consumer, _, done, cancel := NewTestKafkaConsumer(t) - sCtx, _ := context.WithTimeout(context.Background(), time.Second) + sCtx, _ := context.WithTimeout(context.Background(), time.Second) //nolint: govet go func() { if err := client.Consume(sCtx, []string{topic_out}, consumer); err != nil { cancel() From 3c1e739202b9626b59e888c4a2a1a62e582c3e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 21 Nov 2024 16:49:25 +0100 Subject: [PATCH 33/49] chore: remove unused code --- modules/kafka/kafka_test.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index a7e942f355..e1e720a30d 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -2,7 +2,6 @@ package kafka_test import ( "context" - "fmt" "io" "strings" "testing" @@ -342,33 +341,6 @@ func TestKafka_listenersValidation(t *testing.T) { }) } -func createTopics(brokers []string, topics []string) error { - t := &sarama.CreateTopicsRequest{} - t.TopicDetails = make(map[string]*sarama.TopicDetail, len(topics)) - for _, elem := range topics { - t.TopicDetails[elem] = &sarama.TopicDetail{NumPartitions: 1} - } - - var err error - - c, err := sarama.NewClient(brokers, sarama.NewConfig()) - if err != nil { - return fmt.Errorf("failed to create client: %w", err) - } - defer c.Close() - - bs := c.Brokers() - - _, err = bs[0].CreateTopics(t) - if err != nil { - return fmt.Errorf("failed to create topics: %w", err) - } - - fmt.Println("successfully created topics") - - return nil -} - // assertAdvertisedListeners checks that the advertised listeners are set correctly: // - The BROKER:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { From 4f152f2f5d112eab089475b7d9e844a58ca2eeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 10:54:05 +0100 Subject: [PATCH 34/49] chore: rename to Listener --- modules/kafka/kafka.go | 8 ++++---- modules/kafka/kafka_helpers_test.go | 16 ++++++++-------- modules/kafka/kafka_test.go | 18 +++++++++--------- modules/kafka/options.go | 20 ++++++++++---------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 69f47908d7..fa9cd9959e 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -36,10 +36,10 @@ echo '' > /etc/confluent/docker/ensure type KafkaContainer struct { testcontainers.Container ClusterID string - Listeners KafkaListener + listeners Listener } -type KafkaListener struct { +type Listener struct { Name string Host string Port string @@ -143,7 +143,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom return c, nil } -func trimValidateListeners(listeners []KafkaListener) error { +func trimValidateListeners(listeners []Listener) error { // Trim for i := 0; i < len(listeners); i++ { listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) @@ -215,7 +215,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings return nil } -func editEnvsForListeners(listeners []KafkaListener) map[string]string { +func editEnvsForListeners(listeners []Listener) map[string]string { if len(listeners) == 0 { // no change return map[string]string{} diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index c392bb737e..651ec2c1ac 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -86,7 +86,7 @@ func TestValidateKRaftVersion(t *testing.T) { } func TestTrimValidateListeners(t *testing.T) { - validateFn := func(t *testing.T, listeners []KafkaListener, wantErr bool) { + validateFn := func(t *testing.T, listeners []Listener, wantErr bool) { t.Helper() err := trimValidateListeners(listeners) @@ -98,7 +98,7 @@ func TestTrimValidateListeners(t *testing.T) { } t.Run("fail/reserved-listener/port-9093", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "PLAINTEXT", Host: "kafka", @@ -108,7 +108,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("fail/reserved-listener/port-9094", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "PLAINTEXT", Host: "kafka", @@ -118,7 +118,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("fail/reserved-listener/name-controller", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: " cOnTrOller ", Host: "kafka", @@ -128,7 +128,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("fail/reserved-listener/name-plaintext", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "plaintext", Host: "kafka", @@ -138,7 +138,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("fail/port-duplication", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "test", Host: "kafka", @@ -153,7 +153,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("fail/name-duplication", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "test", Host: "kafka", @@ -168,7 +168,7 @@ func TestTrimValidateListeners(t *testing.T) { }) t.Run("success", func(t *testing.T) { - validateFn(t, []KafkaListener{ + validateFn(t, []Listener{ { Name: "test", Host: "kafka", diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index e1e720a30d..6e1c53be62 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -103,7 +103,7 @@ func TestKafka_networkConnectivity(t *testing.T) { "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), network.WithNetwork([]string{"kafka"}, Network), - kafka.WithListener([]kafka.KafkaListener{ + kafka.WithListener([]kafka.Listener{ { Name: "BROKER", Host: "kafka", @@ -216,7 +216,7 @@ func TestKafka_withListener(t *testing.T) { ctr, err := kafka.Run(ctx, "confluentinc/confluent-local:7.6.1", network.WithNetwork([]string{"kafka"}, rpNetwork), - kafka.WithListener([]kafka.KafkaListener{ + kafka.WithListener([]kafka.Listener{ { Name: "BROKER", Host: "kafka", @@ -258,7 +258,7 @@ func TestKafka_restProxyService(t *testing.T) { } func TestKafka_listenersValidation(t *testing.T) { - runWithErrorFn := func(t *testing.T, listeners []kafka.KafkaListener) { + runWithErrorFn := func(t *testing.T, listeners []kafka.Listener) { t.Helper() c, err := kafka.Run(context.Background(), @@ -271,7 +271,7 @@ func TestKafka_listenersValidation(t *testing.T) { } t.Run("reserved-listener/port-9093", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: "BROKER", Host: "kafka", @@ -281,7 +281,7 @@ func TestKafka_listenersValidation(t *testing.T) { }) t.Run("reserved-listener/port-9094", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: "BROKER", Host: "kafka", @@ -291,7 +291,7 @@ func TestKafka_listenersValidation(t *testing.T) { }) t.Run("reserved-listener/controller-duplicated", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: " cOnTrOller ", Host: "kafka", @@ -301,7 +301,7 @@ func TestKafka_listenersValidation(t *testing.T) { }) t.Run("reserved-listener/plaintext-duplicated", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: "plaintext", Host: "kafka", @@ -311,7 +311,7 @@ func TestKafka_listenersValidation(t *testing.T) { }) t.Run("duplicated-ports", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: "test", Host: "kafka", @@ -326,7 +326,7 @@ func TestKafka_listenersValidation(t *testing.T) { }) t.Run("duplicated-names", func(t *testing.T) { - runWithErrorFn(t, []kafka.KafkaListener{ + runWithErrorFn(t, []kafka.Listener{ { Name: "test", Host: "kafka", diff --git a/modules/kafka/options.go b/modules/kafka/options.go index 4e44f4f4fd..da4b5ccdb0 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -10,12 +10,12 @@ import ( type options struct { // Listeners is a list of custom listeners that can be provided to access the // containers form within docker networks - Listeners []KafkaListener + Listeners []Listener } func defaultOptions() options { return options{ - Listeners: make([]KafkaListener, 0), + Listeners: make([]Listener, 0), } } @@ -42,37 +42,37 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { // will be aliases to all networks, so they can be accessed from within docker // networks. At leas one network must be attached to the container, if not an // error will be thrown when starting the container. -func WithListener(listeners []KafkaListener) Option { +func WithListener(listeners []Listener) Option { return func(o *options) { o.Listeners = append(o.Listeners, listeners...) } } -func plainTextListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { +func plainTextListener(ctx context.Context, c testcontainers.Container) (Listener, error) { host, err := c.Host(ctx) if err != nil { - return KafkaListener{}, err + return Listener{}, err } port, err := c.MappedPort(ctx, publicPort) if err != nil { - return KafkaListener{}, err + return Listener{}, err } - return KafkaListener{ + return Listener{ Name: "PLAINTEXT", Host: host, Port: port.Port(), }, nil } -func brokerListener(ctx context.Context, c testcontainers.Container) (KafkaListener, error) { +func brokerListener(ctx context.Context, c testcontainers.Container) (Listener, error) { inspect, err := c.Inspect(ctx) if err != nil { - return KafkaListener{}, fmt.Errorf("inspect: %w", err) + return Listener{}, fmt.Errorf("inspect: %w", err) } - return KafkaListener{ + return Listener{ Name: "BROKER", Host: inspect.Config.Hostname, Port: "9092", From 76cf56d43f3ba0ee37300e828cee48502efaab55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 11:19:05 +0100 Subject: [PATCH 35/49] chore: trim listeners in the option --- modules/kafka/kafka.go | 11 ++--------- modules/kafka/kafka_helpers_test.go | 8 ++++---- modules/kafka/options.go | 12 +++++++++++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index fa9cd9959e..7d53ec5694 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -94,7 +94,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } } - if err := trimValidateListeners(settings.Listeners); err != nil { + if err := validateListeners(settings.Listeners); err != nil { return nil, fmt.Errorf("listeners validation: %w", err) } @@ -143,14 +143,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom return c, nil } -func trimValidateListeners(listeners []Listener) error { - // Trim - for i := 0; i < len(listeners); i++ { - listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) - listeners[i].Host = strings.Trim(listeners[i].Host, " ") - listeners[i].Port = strings.Trim(listeners[i].Port, " ") - } - +func validateListeners(listeners []Listener) error { // Validate var ports map[string]bool = make(map[string]bool, len(listeners)+2) var names map[string]bool = make(map[string]bool, len(listeners)+2) diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 651ec2c1ac..17a8ab5d11 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -85,11 +85,11 @@ func TestValidateKRaftVersion(t *testing.T) { }) } -func TestTrimValidateListeners(t *testing.T) { +func TestValidateListeners(t *testing.T) { validateFn := func(t *testing.T, listeners []Listener, wantErr bool) { t.Helper() - err := trimValidateListeners(listeners) + err := validateListeners(listeners) if wantErr { require.Error(t, err) } else { @@ -120,7 +120,7 @@ func TestTrimValidateListeners(t *testing.T) { t.Run("fail/reserved-listener/name-controller", func(t *testing.T) { validateFn(t, []Listener{ { - Name: " cOnTrOller ", + Name: "CONTROLLER", Host: "kafka", Port: "9092", }, @@ -130,7 +130,7 @@ func TestTrimValidateListeners(t *testing.T) { t.Run("fail/reserved-listener/name-plaintext", func(t *testing.T) { validateFn(t, []Listener{ { - Name: "plaintext", + Name: "PLAINTEXT", Host: "kafka", Port: "9092", }, diff --git a/modules/kafka/options.go b/modules/kafka/options.go index da4b5ccdb0..b19f8b3cb8 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -3,6 +3,7 @@ package kafka import ( "context" "fmt" + "strings" "github.com/testcontainers/testcontainers-go" ) @@ -40,9 +41,18 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { // WithListener adds a custom listener to the Kafka containers. Listener // will be aliases to all networks, so they can be accessed from within docker -// networks. At leas one network must be attached to the container, if not an +// networks. At least one network must be attached to the container, if not an // error will be thrown when starting the container. +// This options sanitizes the listener names and ports, so they are in the +// correct format: name is uppercase and trimmed, and port is trimmed. func WithListener(listeners []Listener) Option { + // Trim + for i := 0; i < len(listeners); i++ { + listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) + listeners[i].Host = strings.Trim(listeners[i].Host, " ") + listeners[i].Port = strings.Trim(listeners[i].Port, " ") + } + return func(o *options) { o.Listeners = append(o.Listeners, listeners...) } From ec67726e35e0d6967d7cf45d78c7cfffcf6dcd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 11:22:36 +0100 Subject: [PATCH 36/49] chore: use empty struct to not consume memory --- modules/kafka/kafka.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 7d53ec5694..c4175c3f97 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -145,27 +145,27 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom func validateListeners(listeners []Listener) error { // Validate - var ports map[string]bool = make(map[string]bool, len(listeners)+2) - var names map[string]bool = make(map[string]bool, len(listeners)+2) + ports := make(map[string]struct{}, len(listeners)+2) + names := make(map[string]struct{}, len(listeners)+2) // check for default listeners - ports["9094"] = true - ports["9093"] = true + ports["9094"] = struct{}{} + ports["9093"] = struct{}{} // check for default listeners - names["CONTROLLER"] = true - names["PLAINTEXT"] = true + names["CONTROLLER"] = struct{}{} + names["PLAINTEXT"] = struct{}{} for _, item := range listeners { - if names[item.Name] { + if _, exists := names[item.Name]; exists { return fmt.Errorf("duplicate of listener name: %s", item.Name) } - names[item.Name] = true + names[item.Name] = struct{}{} - if ports[item.Port] { + if _, exists := ports[item.Port]; exists { return fmt.Errorf("duplicate of listener port: %s", item.Port) } - ports[item.Port] = true + ports[item.Port] = struct{}{} } return nil From da0deb807eb3a158c5ebcb90ab7580fb49913942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 11:31:28 +0100 Subject: [PATCH 37/49] chore: format errors --- modules/kafka/kafka.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index c4175c3f97..1421fea361 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -182,14 +182,14 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings if len(settings.Listeners) == 0 { defaultInternal, err := brokerListener(ctx, c) if err != nil { - return fmt.Errorf("can't create default internal listener: %w", err) + return fmt.Errorf("default internal listener: %w", err) } settings.Listeners = append(settings.Listeners, defaultInternal) } defaultExternal, err := plainTextListener(ctx, c) if err != nil { - return fmt.Errorf("can't create default external listener: %w", err) + return fmt.Errorf("default external listener: %w", err) } settings.Listeners = append(settings.Listeners, defaultExternal) From f2aaa60fb3072e6e7fc3061de8acbb6854566148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 11:34:06 +0100 Subject: [PATCH 38/49] chore: simplify advertised slice --- modules/kafka/kafka.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 1421fea361..7caa7f6063 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -194,9 +194,9 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings settings.Listeners = append(settings.Listeners, defaultExternal) - var advertised []string - for _, item := range settings.Listeners { - advertised = append(advertised, fmt.Sprintf("%s://%s:%s", item.Name, item.Host, item.Port)) + advertised := make([]string, 0, len(settings.Listeners)) + for i, item := range settings.Listeners { + advertised[i] = item.Name + "://" + item.Host + ":" + item.Port } scriptContent := fmt.Sprintf(starterScriptContent, strings.Join(advertised, ",")) From f82cfc9ee2507fb63a50bd56e9a37ea41ff12f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 11:38:58 +0100 Subject: [PATCH 39/49] chore: simplify envs for listeners --- modules/kafka/kafka.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 7caa7f6063..48feb92ef3 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -210,8 +210,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings func editEnvsForListeners(listeners []Listener) map[string]string { if len(listeners) == 0 { - // no change - return map[string]string{} + return nil } envs := map[string]string{ @@ -228,7 +227,7 @@ func editEnvsForListeners(listeners []Listener) map[string]string { envs["KAFKA_LISTENERS"] = strings.Join( []string{ envs["KAFKA_LISTENERS"], - fmt.Sprintf("%s://0.0.0.0:%s", item.Name, item.Port), + item.Name + "://0.0.0.0:" + item.Port, }, ",", ) From 2b0077401e3679e1c305297da3e0fbd946a083cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 13:33:55 +0100 Subject: [PATCH 40/49] chore: make WithListeners self-contained --- modules/kafka/kafka.go | 76 ----------------------------- modules/kafka/kafka_helpers_test.go | 6 +-- modules/kafka/kafka_test.go | 22 ++++----- modules/kafka/options.go | 74 ++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 95 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 48feb92ef3..88539a845c 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -94,16 +94,6 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } } - if err := validateListeners(settings.Listeners); err != nil { - return nil, fmt.Errorf("listeners validation: %w", err) - } - - // apply envs for listeners - envChange := editEnvsForListeners(settings.Listeners) - for key, item := range envChange { - genericContainerReq.Env[key] = item - } - genericContainerReq.ContainerRequest.LifecycleHooks = []testcontainers.ContainerLifecycleHooks{ { PostStarts: []testcontainers.ContainerHook{ @@ -143,34 +133,6 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom return c, nil } -func validateListeners(listeners []Listener) error { - // Validate - ports := make(map[string]struct{}, len(listeners)+2) - names := make(map[string]struct{}, len(listeners)+2) - - // check for default listeners - ports["9094"] = struct{}{} - ports["9093"] = struct{}{} - - // check for default listeners - names["CONTROLLER"] = struct{}{} - names["PLAINTEXT"] = struct{}{} - - for _, item := range listeners { - if _, exists := names[item.Name]; exists { - return fmt.Errorf("duplicate of listener name: %s", item.Name) - } - names[item.Name] = struct{}{} - - if _, exists := ports[item.Port]; exists { - return fmt.Errorf("duplicate of listener port: %s", item.Port) - } - ports[item.Port] = struct{}{} - } - - return nil -} - // copyStarterScript copies the starter script into the container. func copyStarterScript(ctx context.Context, c testcontainers.Container, settings *options) error { if err := wait.ForListeningPort(publicPort). @@ -208,44 +170,6 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings return nil } -func editEnvsForListeners(listeners []Listener) map[string]string { - if len(listeners) == 0 { - return nil - } - - envs := map[string]string{ - "KAFKA_LISTENERS": "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093", - "KAFKA_REST_BOOTSTRAP_SERVERS": "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093", - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "CONTROLLER:PLAINTEXT, PLAINTEXT:PLAINTEXT", - } - - // expect first listener has common network between kafka instances - envs["KAFKA_INTER_BROKER_LISTENER_NAME"] = listeners[0].Name - - // expect small number of listeners, so joins is okay - for _, item := range listeners { - envs["KAFKA_LISTENERS"] = strings.Join( - []string{ - envs["KAFKA_LISTENERS"], - item.Name + "://0.0.0.0:" + item.Port, - }, - ",", - ) - - envs["KAFKA_REST_BOOTSTRAP_SERVERS"] = envs["KAFKA_LISTENERS"] - - envs["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"] = strings.Join( - []string{ - envs["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"], - item.Name + ":" + "PLAINTEXT", - }, - ",", - ) - } - - return envs -} - // Brokers retrieves the broker connection strings from Kafka with only one entry, // defined by the exposed public port. func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) { diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 17a8ab5d11..9b704db511 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -89,7 +89,7 @@ func TestValidateListeners(t *testing.T) { validateFn := func(t *testing.T, listeners []Listener, wantErr bool) { t.Helper() - err := validateListeners(listeners) + err := validateListeners(listeners...) if wantErr { require.Error(t, err) } else { @@ -120,7 +120,7 @@ func TestValidateListeners(t *testing.T) { t.Run("fail/reserved-listener/name-controller", func(t *testing.T) { validateFn(t, []Listener{ { - Name: "CONTROLLER", + Name: " cOnTrOller ", Host: "kafka", Port: "9092", }, @@ -130,7 +130,7 @@ func TestValidateListeners(t *testing.T) { t.Run("fail/reserved-listener/name-plaintext", func(t *testing.T) { validateFn(t, []Listener{ { - Name: "PLAINTEXT", + Name: "plaintext", Host: "kafka", Port: "9092", }, diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 6e1c53be62..261a011325 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -103,12 +103,10 @@ func TestKafka_networkConnectivity(t *testing.T) { "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), network.WithNetwork([]string{"kafka"}, Network), - kafka.WithListener([]kafka.Listener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9092", - }, + kafka.WithListener(kafka.Listener{ + Name: "BROKER", + Host: "kafka", + Port: "9092", }), ) // } @@ -216,12 +214,10 @@ func TestKafka_withListener(t *testing.T) { ctr, err := kafka.Run(ctx, "confluentinc/confluent-local:7.6.1", network.WithNetwork([]string{"kafka"}, rpNetwork), - kafka.WithListener([]kafka.Listener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9092", - }, + kafka.WithListener(kafka.Listener{ + Name: "BROKER", + Host: "kafka", + Port: "9092", }), ) // } @@ -264,7 +260,7 @@ func TestKafka_listenersValidation(t *testing.T) { c, err := kafka.Run(context.Background(), "confluentinc/confluent-local:7.6.1", kafka.WithClusterID("test-cluster"), - kafka.WithListener(listeners), + kafka.WithListener(listeners...), ) require.Error(t, err) require.Nil(t, c, "expected container to be nil") diff --git a/modules/kafka/options.go b/modules/kafka/options.go index b19f8b3cb8..fdbabb9b2d 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -45,7 +45,53 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { // error will be thrown when starting the container. // This options sanitizes the listener names and ports, so they are in the // correct format: name is uppercase and trimmed, and port is trimmed. -func WithListener(listeners []Listener) Option { +func WithListener(listeners ...Listener) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + if err := validateListeners(listeners...); err != nil { + return fmt.Errorf("validate listeners: %w", err) + } + + applyListenersToEnv(req, listeners...) + + return nil + } +} + +func applyListenersToEnv(req *testcontainers.GenericContainerRequest, listeners ...Listener) { + if len(listeners) == 0 { + return + } + + req.Env["KAFKA_LISTENERS"] = "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093" + req.Env["KAFKA_REST_BOOTSTRAP_SERVERS"] = "CONTROLLER://0.0.0.0:9094, PLAINTEXT://0.0.0.0:9093" + req.Env["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"] = "CONTROLLER:PLAINTEXT, PLAINTEXT:PLAINTEXT" + + // expect first listener has common network between kafka instances + req.Env["KAFKA_INTER_BROKER_LISTENER_NAME"] = listeners[0].Name + + // expect small number of listeners, so joins is okay + for _, item := range listeners { + req.Env["KAFKA_LISTENERS"] = strings.Join( + []string{ + req.Env["KAFKA_LISTENERS"], + item.Name + "://0.0.0.0:" + item.Port, + }, + ",", + ) + + req.Env["KAFKA_REST_BOOTSTRAP_SERVERS"] = req.Env["KAFKA_LISTENERS"] + + req.Env["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"] = strings.Join( + []string{ + req.Env["KAFKA_LISTENER_SECURITY_PROTOCOL_MAP"], + item.Name + ":" + "PLAINTEXT", + }, + ",", + ) + } +} + +func validateListeners(listeners ...Listener) error { // Trim for i := 0; i < len(listeners); i++ { listeners[i].Name = strings.ToUpper(strings.Trim(listeners[i].Name, " ")) @@ -53,9 +99,31 @@ func WithListener(listeners []Listener) Option { listeners[i].Port = strings.Trim(listeners[i].Port, " ") } - return func(o *options) { - o.Listeners = append(o.Listeners, listeners...) + // Validate + ports := make(map[string]struct{}, len(listeners)+2) + names := make(map[string]struct{}, len(listeners)+2) + + // check for default listeners + ports["9094"] = struct{}{} + ports["9093"] = struct{}{} + + // check for default listeners + names["CONTROLLER"] = struct{}{} + names["PLAINTEXT"] = struct{}{} + + for _, item := range listeners { + if _, exists := names[item.Name]; exists { + return fmt.Errorf("duplicate of listener name: %s", item.Name) + } + names[item.Name] = struct{}{} + + if _, exists := ports[item.Port]; exists { + return fmt.Errorf("duplicate of listener port: %s", item.Port) + } + ports[item.Port] = struct{}{} } + + return nil } func plainTextListener(ctx context.Context, c testcontainers.Container) (Listener, error) { From 50f9bcc0502d45994108754f0a7d1abb7fe51c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 13:34:08 +0100 Subject: [PATCH 41/49] fix: proper advertised slice --- modules/kafka/kafka.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 88539a845c..23cad03846 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -156,7 +156,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container, settings settings.Listeners = append(settings.Listeners, defaultExternal) - advertised := make([]string, 0, len(settings.Listeners)) + advertised := make([]string, len(settings.Listeners)) for i, item := range settings.Listeners { advertised[i] = item.Name + "://" + item.Host + ":" + item.Port } From 7772f4f0f4055f434d6a4e3ad06fddd8a2c77f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 13:41:23 +0100 Subject: [PATCH 42/49] chore: simplify test helpers --- modules/kafka/kafka_helpers_test.go | 147 ++++++++++++---------------- 1 file changed, 60 insertions(+), 87 deletions(-) diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 9b704db511..897968f0bf 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -52,128 +52,101 @@ func TestConfigureQuorumVoters(t *testing.T) { } func TestValidateKRaftVersion(t *testing.T) { - validateKRaftVersionFn := func(t *testing.T, image string, wantErr bool) { - t.Helper() - - err := validateKRaftVersion(image) - - if wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - } - t.Run("official/valid-version", func(t *testing.T) { - validateKRaftVersionFn(t, "confluentinc/confluent-local:7.5.0", false) + err := validateKRaftVersion("confluentinc/confluent-local:7.5.0") + require.NoError(t, err) }) t.Run("official/valid-limit-version", func(t *testing.T) { - validateKRaftVersionFn(t, "confluentinc/confluent-local:7.4.0", false) + err := validateKRaftVersion("confluentinc/confluent-local:7.4.0") + require.NoError(t, err) }) t.Run("official/invalid-low-version", func(t *testing.T) { - validateKRaftVersionFn(t, "confluentinc/confluent-local:7.3.99", true) + err := validateKRaftVersion("confluentinc/confluent-local:7.3.99") + require.Error(t, err) }) t.Run("official/invalid-too-low-version", func(t *testing.T) { - validateKRaftVersionFn(t, "confluentinc/confluent-local:5.0.0", true) + err := validateKRaftVersion("confluentinc/confluent-local:5.0.0") + require.Error(t, err) }) t.Run("unofficial/does-not-validate-KRaft-version", func(t *testing.T) { - validateKRaftVersionFn(t, "my-kafka:1.0.0", false) + err := validateKRaftVersion("my-kafka:1.0.0") + require.NoError(t, err) }) } func TestValidateListeners(t *testing.T) { - validateFn := func(t *testing.T, listeners []Listener, wantErr bool) { - t.Helper() - - err := validateListeners(listeners...) - if wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - } - t.Run("fail/reserved-listener/port-9093", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "PLAINTEXT", - Host: "kafka", - Port: "9093", - }, - }, true) + err := validateListeners(Listener{ + Name: "PLAINTEXT", + Host: "kafka", + Port: "9093", + }) + require.Error(t, err) }) t.Run("fail/reserved-listener/port-9094", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "PLAINTEXT", - Host: "kafka", - Port: "9094", - }, - }, true) + err := validateListeners(Listener{ + Name: "PLAINTEXT", + Host: "kafka", + Port: "9094", + }) + require.Error(t, err) }) t.Run("fail/reserved-listener/name-controller", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: " cOnTrOller ", - Host: "kafka", - Port: "9092", - }, - }, true) + err := validateListeners(Listener{ + Name: " cOnTrOller ", + Host: "kafka", + Port: "9092", + }) + require.Error(t, err) }) t.Run("fail/reserved-listener/name-plaintext", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "plaintext", - Host: "kafka", - Port: "9092", - }, - }, true) + err := validateListeners(Listener{ + Name: "plaintext", + Host: "kafka", + Port: "9092", + }) + require.Error(t, err) }) t.Run("fail/port-duplication", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test2", - Host: "kafka", - Port: "9092", - }, - }, true) + err := validateListeners(Listener{ + Name: "test", + Host: "kafka", + Port: "9092", + }, Listener{ + Name: "test2", + Host: "kafka", + Port: "9092", + }) + require.Error(t, err) }) t.Run("fail/name-duplication", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { - Name: "test", - Host: "kafka", - Port: "9095", - }, - }, true) + err := validateListeners(Listener{ + Name: "test", + Host: "kafka", + Port: "9092", + }, Listener{ + Name: "test", + Host: "kafka", + Port: "9095", + }) + require.Error(t, err) }) t.Run("success", func(t *testing.T) { - validateFn(t, []Listener{ - { - Name: "test", - Host: "kafka", - Port: "9095", - }, - }, false) + err := validateListeners(Listener{ + Name: "test", + Host: "kafka", + Port: "9092", + }) + require.NoError(t, err) }) } From 45f66609fe0f8e7a21e39f04d7c9e2b4f645ef94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 13:49:01 +0100 Subject: [PATCH 43/49] chore: use require --- modules/kafka/kafka_test.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 261a011325..d863391983 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -94,9 +94,7 @@ func TestKafka_networkConnectivity(t *testing.T) { ) Network, err := network.New(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // kafkaWithListener { kafkaContainer, err := kafka.Run(ctx, @@ -111,25 +109,25 @@ func TestKafka_networkConnectivity(t *testing.T) { ) // } testcontainers.CleanupContainer(t, kafkaContainer) - require.NoError(t, err, "failed to start kafka container") + require.NoError(t, err) kcat, err := runKcatContainer(ctx, Network.Name, "/tmp/msgs.txt") testcontainers.CleanupContainer(t, kcat) - require.NoError(t, err, "failed to start kcat") + require.NoError(t, err) // 4. Copy message to kcat err = kcat.SaveFile(ctx, "Message produced by kcat") require.NoError(t, err) brokers, err := kafkaContainer.Brokers(context.TODO()) - require.NoError(t, err, "failed to get brokers") + require.NoError(t, err) // err = createTopics(brokers, []string{topic_in, topic_out}) err = kcat.CreateTopic(ctx, address, topic_in) - require.NoError(t, err, "create topic topic_in") + require.NoError(t, err) err = kcat.CreateTopic(ctx, address, topic_out) - require.NoError(t, err, "create topic topic_out") + require.NoError(t, err) // perform assertions @@ -139,7 +137,7 @@ func TestKafka_networkConnectivity(t *testing.T) { config.Consumer.MaxWaitTime = 2 * time.Second producer, err := sarama.NewSyncProducer(brokers, config) - require.NoError(t, err, "create kafka producer") + require.NoError(t, err) // Act @@ -149,14 +147,14 @@ func TestKafka_networkConnectivity(t *testing.T) { Key: sarama.StringEncoder(key), Value: sarama.StringEncoder(text_msg), }) - require.NoError(t, err, "send message") + require.NoError(t, err) // Internal read _, stdout, err := kcat.Exec(ctx, []string{"kcat", "-b", address, "-C", "-t", topic_in, "-c", "1"}) require.NoError(t, err) out, err := io.ReadAll(stdout) - require.NoError(t, err, "read message in kcat") + require.NoError(t, err) require.Contains(t, string(out), text_msg) // Internal write @@ -166,11 +164,11 @@ func TestKafka_networkConnectivity(t *testing.T) { require.NoError(t, err) _, _, err = kcat.Exec(ctx, []string{"kcat", "-b", address, "-t", topic_out, "-P", "-l", tempfile}) - require.NoError(t, err, "send message with kcat") + require.NoError(t, err) // External read client, err := sarama.NewConsumerGroup(brokers, "groupName", config) - require.NoError(t, err, "create consumer group") + require.NoError(t, err) consumer, _, done, cancel := NewTestKafkaConsumer(t) @@ -204,9 +202,8 @@ func TestKafka_withListener(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { - if err := rpNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } + err := rpNetwork.Remove(ctx) + require.NoError(t, err) }) // 2. Start Kafka ctr From aa341f83d0d5ee690fe1720515b45549e3060402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 13:54:41 +0100 Subject: [PATCH 44/49] chore: extract inline helper to a function --- modules/kafka/kafka_test.go | 95 +++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index d863391983..f4c1a65ed9 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -251,89 +251,80 @@ func TestKafka_restProxyService(t *testing.T) { } func TestKafka_listenersValidation(t *testing.T) { - runWithErrorFn := func(t *testing.T, listeners []kafka.Listener) { - t.Helper() - - c, err := kafka.Run(context.Background(), - "confluentinc/confluent-local:7.6.1", - kafka.WithClusterID("test-cluster"), - kafka.WithListener(listeners...), - ) - require.Error(t, err) - require.Nil(t, c, "expected container to be nil") - } - t.Run("reserved-listener/port-9093", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9093", - }, + runWithError(t, kafka.Listener{ + Name: "BROKER", + Host: "kafka", + Port: "9093", }) }) t.Run("reserved-listener/port-9094", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: "BROKER", - Host: "kafka", - Port: "9094", - }, + runWithError(t, kafka.Listener{ + Name: "BROKER", + Host: "kafka", + Port: "9094", }) }) t.Run("reserved-listener/controller-duplicated", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: " cOnTrOller ", - Host: "kafka", - Port: "9092", - }, + runWithError(t, kafka.Listener{ + Name: " cOnTrOller ", + Host: "kafka", + Port: "9092", }) }) t.Run("reserved-listener/plaintext-duplicated", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: "plaintext", - Host: "kafka", - Port: "9092", - }, + runWithError(t, kafka.Listener{ + Name: "plaintext", + Host: "kafka", + Port: "9092", }) }) t.Run("duplicated-ports", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { + runWithError(t, kafka.Listener{ + Name: "test", + Host: "kafka", + Port: "9092", + }, + kafka.Listener{ Name: "test2", Host: "kafka", Port: "9092", }, - }) + ) }) t.Run("duplicated-names", func(t *testing.T) { - runWithErrorFn(t, []kafka.Listener{ - { - Name: "test", - Host: "kafka", - Port: "9092", - }, - { + runWithError(t, kafka.Listener{ + Name: "test", + Host: "kafka", + Port: "9092", + }, + kafka.Listener{ Name: "test", Host: "kafka", Port: "9095", }, - }) + ) }) } +// runWithError runs the Kafka container with the provided listeners and expects an error +func runWithError(t *testing.T, listeners ...kafka.Listener) { + t.Helper() + + c, err := kafka.Run(context.Background(), + "confluentinc/confluent-local:7.6.1", + kafka.WithClusterID("test-cluster"), + kafka.WithListener(listeners...), + ) + require.Error(t, err) + require.Nil(t, c) +} + // assertAdvertisedListeners checks that the advertised listeners are set correctly: // - The BROKER:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { From 278ec359c1a0473686893cd38da227cb639c6e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 14:28:03 +0100 Subject: [PATCH 45/49] chore: refactor options to support writing to the container request --- modules/kafka/kafka.go | 6 ++++-- modules/kafka/options.go | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index 23cad03846..b56b3292ec 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -84,10 +84,12 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom Started: true, } - settings := defaultOptions() + settings := defaultOptions(&genericContainerReq) for _, opt := range opts { if apply, ok := opt.(Option); ok { - apply(&settings) + if err := apply(&settings); err != nil { + return nil, err + } } if err := opt.Customize(&genericContainerReq); err != nil { return nil, err diff --git a/modules/kafka/options.go b/modules/kafka/options.go index fdbabb9b2d..9d01e7e164 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -12,11 +12,15 @@ type options struct { // Listeners is a list of custom listeners that can be provided to access the // containers form within docker networks Listeners []Listener + // req is the container request that will be used to create the container. + // It's needed to apply the listeners to the container. + req *testcontainers.GenericContainerRequest } -func defaultOptions() options { +func defaultOptions(req *testcontainers.GenericContainerRequest) options { return options{ Listeners: make([]Listener, 0), + req: req, } } @@ -24,7 +28,7 @@ func defaultOptions() options { var _ testcontainers.ContainerCustomizer = (*Option)(nil) // Option is an option for the Kafka container. -type Option func(*options) +type Option func(*options) error // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. func (o Option) Customize(*testcontainers.GenericContainerRequest) error { @@ -39,19 +43,21 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { } } -// WithListener adds a custom listener to the Kafka containers. Listener +// WithListener adds custom listeners to the Kafka containers. Each listener // will be aliases to all networks, so they can be accessed from within docker // networks. At least one network must be attached to the container, if not an // error will be thrown when starting the container. // This options sanitizes the listener names and ports, so they are in the // correct format: name is uppercase and trimmed, and port is trimmed. -func WithListener(listeners ...Listener) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) error { +func WithListener(listeners ...Listener) Option { + return func(o *options) error { if err := validateListeners(listeners...); err != nil { return fmt.Errorf("validate listeners: %w", err) } - applyListenersToEnv(req, listeners...) + applyListenersToEnv(o.req, listeners...) + + o.Listeners = append(o.Listeners, listeners...) return nil } From fc81847c848af8387e86ab0d6da9e55ae061ba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 14:30:36 +0100 Subject: [PATCH 46/49] docs: refinement --- docs/modules/kafka.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 7fc6d0b51a..5c1b1a3f40 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -84,7 +84,7 @@ KafkaContainer, err = kafka.Run(ctx, #### Listeners -If you need to connect new listeners, you can use `WithListener(listeners []KafkaListener)`. +If you need to connect new listeners, you can use `WithListener(listeners ...Listener)`. This option controls the following environment variables for the Kafka container: - `KAFKA_LISTENERS` - `KAFKA_REST_BOOTSTRAP_SERVERS` @@ -119,4 +119,4 @@ The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containin [Get Kafka brokers](../../modules/kafka/kafka_test.go) inside_block:getBrokers - \ No newline at end of file + From 4ab2546d50068531f68fb7f697997eb5f6b1b6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 14:34:07 +0100 Subject: [PATCH 47/49] docs: document withClusterID function --- modules/kafka/options.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/kafka/options.go b/modules/kafka/options.go index 9d01e7e164..558bbe3cc9 100644 --- a/modules/kafka/options.go +++ b/modules/kafka/options.go @@ -36,6 +36,7 @@ func (o Option) Customize(*testcontainers.GenericContainerRequest) error { return nil } +// WithClusterID sets the cluster ID for the Kafka container. func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { req.Env["CLUSTER_ID"] = clusterID From 9adbd978c60227c9ba3b5ed0e3fdebed92e3965a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 14:37:42 +0100 Subject: [PATCH 48/49] chore: remove unused --- modules/kafka/kafka.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index b56b3292ec..1308598097 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -36,7 +36,6 @@ echo '' > /etc/confluent/docker/ensure type KafkaContainer struct { testcontainers.Container ClusterID string - listeners Listener } type Listener struct { From 67c3480961ffdb61c1c72510deb79e686e9a4ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 25 Nov 2024 14:38:28 +0100 Subject: [PATCH 49/49] WIP --- modules/kafka/consumer_test.go | 58 ++++++++++++++++++++++++++++++++++ modules/kafka/kafka_test.go | 51 +++++++++++++++++++++--------- 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/modules/kafka/consumer_test.go b/modules/kafka/consumer_test.go index 9df926e72d..4d95ba9ee5 100644 --- a/modules/kafka/consumer_test.go +++ b/modules/kafka/consumer_test.go @@ -54,3 +54,61 @@ func (k *TestKafkaConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, cl } } } + +// Consumer represents a Sarama consumer group consumer +type TestConsumer struct { + t *testing.T + ready chan bool + messages []*sarama.ConsumerMessage +} + +func NewTestConsumer(t *testing.T) TestConsumer { + t.Helper() + + return TestConsumer{ + t: t, + ready: make(chan bool), + } +} + +// Setup is run at the beginning of a new session, before ConsumeClaim +func (c *TestConsumer) Setup(sarama.ConsumerGroupSession) error { + // Mark the consumer as ready + close(c.ready) + return nil +} + +// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited +func (consumer *TestConsumer) Cleanup(sarama.ConsumerGroupSession) error { + return nil +} + +// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages(). +// Once the Messages() channel is closed, the Handler must finish its processing +// loop and exit. +func (c *TestConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { + // NOTE: + // Do not move the code below to a goroutine. + // The `ConsumeClaim` itself is called within a goroutine, see: + // https://github.com/IBM/sarama/blob/main/consumer_group.go#L27-L29 + for { + select { + case message, ok := <-claim.Messages(): + if !ok { + c.t.Log("message channel was closed") + return nil + } + c.t.Logf("Message claimed: value = %s, timestamp = %v, topic = %s", string(message.Value), message.Timestamp, message.Topic) + session.MarkMessage(message, "") + + // Store the message to be consumed later + c.messages = append(c.messages, message) + + // Should return when `session.Context()` is done. + // If not, will raise `ErrRebalanceInProgress` or `read tcp :: i/o timeout` when kafka rebalance. see: + // https://github.com/IBM/sarama/issues/1192 + case <-session.Context().Done(): + return nil + } + } +} diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index f4c1a65ed9..a3e4b054ff 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -2,8 +2,10 @@ package kafka_test import ( "context" + "errors" "io" "strings" + "sync" "testing" "time" @@ -167,31 +169,50 @@ func TestKafka_networkConnectivity(t *testing.T) { require.NoError(t, err) // External read + consumer := NewTestConsumer(t) + client, err := sarama.NewConsumerGroup(brokers, "groupName", config) require.NoError(t, err) - consumer, _, done, cancel := NewTestKafkaConsumer(t) + wg := &sync.WaitGroup{} + wg.Add(1) + + sCtx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - sCtx, _ := context.WithTimeout(context.Background(), time.Second) //nolint: govet go func() { - if err := client.Consume(sCtx, []string{topic_out}, consumer); err != nil { - cancel() + defer wg.Done() + for { + // `Consume` should be called inside an infinite loop, when a + // server-side rebalance happens, the consumer session will need to be + // recreated to get the new claims + if err := client.Consume(sCtx, []string{topic_out}, &consumer); err != nil { + if errors.Is(err, sarama.ErrClosedConsumerGroup) { + return + } + require.NoError(t, err) + } + // check if context was cancelled, signaling that the consumer should stop + if ctx.Err() != nil { + return + } + consumer.ready = make(chan bool) } }() - // wait for the consumer to receive message - select { - case <-sCtx.Done(): - t.Log("exit by timeout") - case <-done: - } + <-consumer.ready // Await till the consumer has been set up + t.Log("Sarama consumer up and running!...") + + cancel() + wg.Wait() + err = client.Close() + require.NoError(t, err) - if consumer.message == nil { - t.Fatal("Empty message") - } + msgs := consumer.messages + require.Len(t, msgs, 1) - require.Contains(t, string(consumer.message.Value), text_msg) - require.Contains(t, string(consumer.message.Key), key) + require.Contains(t, string(msgs[0].Value), text_msg) + require.Contains(t, string(msgs[0].Key), key) } func TestKafka_withListener(t *testing.T) {