Skip to content

fix(backend): fail fast on unreachable kafka brokers and add e2e cove… - #169

Open
harshitg927 wants to merge 1 commit into
kubescape:mainfrom
harshitg927:phase2/kafka/improvements
Open

fix(backend): fail fast on unreachable kafka brokers and add e2e cove…#169
harshitg927 wants to merge 1 commit into
kubescape:mainfrom
harshitg927:phase2/kafka/improvements

Conversation

@harshitg927

@harshitg927 harshitg927 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

…rage

Overview

Completes Phase 2 of the pluggable message queue proposal, closing the remaining gaps in the Kafka backend merged in #166 / #167.

Current behavior: kgo.NewClient connects lazily, so a synchronizer server configured with a wrong broker address, bad SASL credentials, or a failed TLS handshake starts successfully, serves /healthz 200, and then silently never syncs. The Pulsar client connects eagerly and fails fast, so the Kafka path regressed this.

New behavior: the Kafka factory pings both clients before returning, retrying on a budget that mirrors the Pulsar client (20 attempts at 3s). On failure the server exits with a fatal error instead of running healthy but dead. Ping issues a Metadata request, so it covers TCP, TLS and SASL - note it does not validate that topics exist.

Also adds the test coverage the proposal's Phase 2 testing strategy called for, plus a local Kafka development path:

  • Broker restart resilience (TestKafkaMessageReader_SurvivesBrokerRestart) - the TC09 equivalent.
  • Multi-pod E2E (TestKafkaE2E_TwoPodsRouteToConnectedClients) - two server instances, each with its own consumer group and a websocket-connected cluster; asserts every pod consumes the whole inbound topic but routes only what is addressed to a cluster connected to it, and that cluster-originated events reach .out with the {account}/{cluster} partition key and expected headers.
  • SASL credential rejection (TestKafkaSecurity_BadCredentialsFailStartup).
  • scripts/kafka.sh + configuration/server-kafka/config.json for running the server against Kafka locally.

Additional Information

A pre-existing data race is visible under -race. The new E2E test exercises Adapter.Start and Adapter.IsRelated concurrently, as the real server does when a cluster connects while the reader dispatches. That trips a bug in goradd/maps, where SafeMap.Load() reads m.items before taking RLock while Set() writes it under the write lock.

This is not introduced here: go test -race ./core/... already fails on main with several races in untouched code. CI is unaffected - pr-created.yaml sets CGO_ENABLED: 0 and the reusable workflow gates its -race step on CGO_ENABLED == 1, so that step is skipped. Bumping goradd/maps does not help (v1.3.0 has the identical unsynchronized read); the real fix is replacing SafeMap in adapter.go, which is out of scope here. Happy to open a separate issue.

Why the restart test replaces the broker instead of restarting it. An in-place Stop/Start of the testcontainers Redpanda module hangs forever: rpk rewrites /etc/redpanda/redpanda.yaml on first boot, stripping the # Injected by testcontainers marker its entrypoint waits for. The container is therefore rebuilt on a pinned host port - the ephemeral port also changes across a restart, which would break the advertised listener and leave clients unable to reconnect.

The test asserts recovery, not redelivery. The .in reader is at-most-once by design (AtEnd, no commits, throwaway per-pod group), so messages produced during an outage are not expected to survive it.

Why the E2E lives in the root module. The tests/ module embeds the real armosec/event-ingester-service, which is Pulsar-only, so a Kafka E2E cannot live there until Phase 3. A plain franz-go consumer stands in for the ingester on .out.

go.mod: docker/docker and docker/go-connections move from indirect to direct - needed to pin the broker's host port in the restart test.

Still out of scope: Phase 3 (ingester Kafka support) and Phase 4 (Helm + docs). The full TC01TC13 matrix against Kafka remains blocked on Phase 3.

How to Test

# unit + integration (requires Docker)
go test ./adapters/backend/v1/... -timeout 15m

# full root-module run, exactly as CI does it
go test -v $(go list ./... | grep -v /e2e)

# confirm the Pulsar path is unaffected
go test ./adapters/backend/v1/... -run Pulsar

Manual end-to-end against a local broker:

./scripts/kafka.sh
CONFIG=./configuration/server-kafka go run cmd/server/main.go

To exercise the fail-fast path, point bootstrapServers in configuration/server-kafka/config.json at a dead address and confirm the server logs retry warnings and then exits with a fatal error, rather than starting and serving /healthz.

Related issues/PRs

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have commented on my code, particularly in hard-to-understand areas
  • I have performed a self-review of my code
  • If it is a core feature, I have added thorough tests.
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features
    • Added Kafka server configuration with topics, consumer settings, compression, security, and copy strategies.
    • Added a setup script that starts a local Kafka-compatible broker, waits for readiness, and creates required topics.
  • Bug Fixes
    • Kafka startup now verifies broker connectivity and reports clear errors when brokers are unreachable or credentials are invalid.
    • Improved resilience when brokers restart, helping message delivery continue after recovery.
  • Tests
    • Added coverage for multi-pod message routing, broker failures, restarts, and authentication errors.

…rage

Signed-off-by: Harshit Gandhi <gandhiharshit716@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Kafka startup now validates broker reachability with retries and cleanup on failure. New tests cover unreachable brokers, invalid SASL credentials, two-pod routing, and broker restarts. The change also adds Kafka configuration, Docker dependencies, and a local Redpanda startup script.

Changes

Kafka backend validation and integration

Layer / File(s) Summary
Kafka startup connectivity validation
adapters/backend/v1/kafka.go, adapters/backend/v1/factory_test.go, adapters/backend/v1/kafka_security_integration_test.go
Kafka clients are pinged with bounded retries during initialization. Failed connectivity closes both clients and returns an error. Tests cover unreachable brokers and invalid SASL credentials.
Two-pod Kafka routing verification
adapters/backend/v1/kafka_e2e_test.go
The end-to-end test verifies consumer-group separation, cluster-specific command routing, outbound event metadata, and filtering of unrelated records.
Broker lifecycle and recovery tests
adapters/backend/v1/kafka_integration_test.go, go.mod
Redpanda setup supports custom options and pinned ports. The restart test verifies message delivery before and after broker replacement. Docker dependencies are declared directly.
Kafka server configuration and local broker bootstrap
configuration/server-kafka/config.json, scripts/kafka.sh
The server configuration defines Kafka and resource-copy settings. The script starts Redpanda, waits for readiness, creates synchronizer topics, and lists topics.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: release

Suggested reviewers: matthyx

Sequence Diagram(s)

sequenceDiagram
  participant TwoPodKafkaTest
  participant kafkaTestPod
  participant KafkaBroker
  participant recordingClientAdapter
  TwoPodKafkaTest->>kafkaTestPod: create two connected pods
  kafkaTestPod->>KafkaBroker: publish and consume synchronizer records
  TwoPodKafkaTest->>KafkaBroker: publish cluster-specific command
  KafkaBroker-->>kafkaTestPod: deliver record to matching consumer group
  kafkaTestPod->>recordingClientAdapter: report received object
  kafkaTestPod->>KafkaBroker: publish outbound cluster event
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Kafka startup validation and end-to-end test coverage changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@harshitg927

Copy link
Copy Markdown
Contributor Author

@matthyx , PTAL

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
adapters/backend/v1/kafka_e2e_test.go (1)

73-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the error from client.Start/server.Start for easier CI debugging.

Both goroutines discard the return value of Start. If the synchronizer client or server fails internally, the test only surfaces this later as a generic timeout (for example, at the require.Eventually on line 83-85, or at a later channel wait), without the actual root cause. Logging the error with t.Log helps diagnose flaky CI failures faster.

♻️ Proposed improvement
-	go func() { _ = client.Start(podCtx) }()
-	go func() { _ = server.Start(podCtx) }()
+	go func() {
+		if err := client.Start(podCtx); err != nil {
+			t.Logf("kafka test client stopped: %v", err)
+		}
+	}()
+	go func() {
+		if err := server.Start(podCtx); err != nil {
+			t.Logf("kafka test server stopped: %v", err)
+		}
+	}()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/backend/v1/kafka_e2e_test.go` around lines 73 - 81, Update the
goroutines invoking client.Start and server.Start to capture their returned
errors and log them with t.Log, while preserving the existing asynchronous
startup behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@adapters/backend/v1/kafka_e2e_test.go`:
- Around line 73-81: Update the goroutines invoking client.Start and
server.Start to capture their returned errors and log them with t.Log, while
preserving the existing asynchronous startup behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9797f704-003f-40bf-bc7f-0e547c22c2f6

📥 Commits

Reviewing files that changed from the base of the PR and between cd11d8e and 147d9f2.

📒 Files selected for processing (8)
  • adapters/backend/v1/factory_test.go
  • adapters/backend/v1/kafka.go
  • adapters/backend/v1/kafka_e2e_test.go
  • adapters/backend/v1/kafka_integration_test.go
  • adapters/backend/v1/kafka_security_integration_test.go
  • configuration/server-kafka/config.json
  • go.mod
  • scripts/kafka.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant