Skip to content

feat(gateways): iggy gateway for kafka listener - #3519

Open
ryerraguntla wants to merge 78 commits into
apache:masterfrom
ryerraguntla:feat(gateways)/kafka_to_iggy_listener
Open

feat(gateways): iggy gateway for kafka listener#3519
ryerraguntla wants to merge 78 commits into
apache:masterfrom
ryerraguntla:feat(gateways)/kafka_to_iggy_listener

Conversation

@ryerraguntla

@ryerraguntla ryerraguntla commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #3421

Rationale

Existing Kafka clients cannot talk to Iggy without a protocol translation layer. Issue #3421 calls for a foundation-layer TCP listener on the Kafka wire port (9093) so that any Kafka-compatible client can eventually route messages into Iggy streams without any client-side changes. This PR delivers that foundation: decode, version-firewall, and stub responses with no Iggy backend integration yet.

What changed?

Before this PR, Iggy had no surface that spoke the Kafka binary protocol. Kafka clients connecting on port 9093 would receive no response or a connection reset.

This PR adds a new gateways/kafka/ workspace crate (iggy-gateway-kafka) with a tokio-based TCP listener that accepts Kafka wire connections, auto-detects request header v1/v2, enforces a version-firewall (SUPPORTED_RANGES), decodes and stub-encodes six API keys (ApiVersions 18, Metadata 3, Produce 0, Fetch 1, ListOffsets 2, CreateTopics 19), and rejects unsupported keys or versions with UNSUPPORTED_VERSION (error 35) without dropping the connection. A kafka-message-gen helper tool generates golden wire fixtures used by the regression suite.

Key implementation notes:

  • Produce hot path keeps RecordBatch as opaque Bytes — no RecordBatch decode in this layer.
  • BrokerAdvertise is Arc-cloned per connection; no per-connection string allocation.
  • Frame body read: bounded read() chunks, not read_buf — avoids stealing pipelined frame bytes; chunks zero-filled via resize.
  • SUPPORTED_RANGES is the single source of truth; ApiVersions advertises exactly what the firewall allows.

103 regression tests across 12 suites cover: primitive codec round-trips, adversarial wire inputs, header v1/v2 parsing, version boundary matrix, per-API golden byte fixtures, stub error codes, and full TCP round-trips via a real KafkaServer listener.

Local Execution

  • Passed
  • Pre-commit hooks ran
# Fixture generation (required for decode_validation_tests)
cargo run -p kafka-message-gen -- generate \
  --output gateways/kafka/tools/kafka-tool/kafka_messages \
  --api-key 0    --api-key 1 --api-key 2 --api-key 19

# Regression suite
cargo test -p iggy-gateway-kafka

# Lint
cargo clippy -p iggy-gateway-kafka --all-targets -- -D warnings
cargo fmt --all --check

All 103 tests pass. clippy -D warnings clean. Manual smoke-test procedure followed per gateways/kafka/docs/MANUAL_TESTING.md (ApiVersions, Metadata, Produce, Fetch round-trips; version firewall; oversized frame rejection).

AI Usage

  1. Tools: Claude Sonnet 4.6 (Cursor Cloud Agent)
  2. Scope: Protocol codec implementation (requests.rs, responses.rs, codec.rs, header.rs), server framing (server.rs), error type (error.rs), regression test suites, documentation (SCOPE.md, TEST_SUITE.md, MANUAL_TESTING.md, kafka_api_keys_reference.md), and the kafka-tool fixture generator.
  3. Verification: Every generated file was reviewed line by line. The 103-test regression suite was run locally against the implementation. Manual testing followed the procedure in MANUAL_TESTING.md using kcat and the kafka-message-gen tool against a live listener. Golden byte fixtures pin exact wire output against known-correct Kafka responses.
  4. Explainability: Yes — can explain the code. The codec follows the Kafka protocol specification field-by-field; non-obvious decisions (opaque RecordBatch, SUPPORTED_RANGES governance model, Arc<BrokerAdvertise> per-connection, varint=0 null compact array) are documented inline or in SCOPE.md.
  5. Some files(SCOPE.md and TEST_SUITE.md) in the docs folder are temporary. After the implementation of the complete feature, these files will be removed or merged with other documents.

ryerraguntla and others added 4 commits June 14, 2026 00:20
Mirror elasticsearch_source architecture using the opensearch crate
v2.4.0 (rustls-tls) for OpenSearch wire-protocol compatibility.
Register as a new workspace member.
Mirror elasticsearch_source documentation, adapted for the
OpenSearch wire protocol and state storage type naming.
Add the four canonical source-state unit tests, plus integration
fixtures (opensearchproject/opensearch:2.19.1 container) and
end-to-end tests covering happy paths (poll, empty index, bulk,
restart state persistence) and a negative path (missing index
surfaces ConnectorStatus::Error via the runtime API).
@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Jun 21, 2026
@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.76%. Comparing base (4eb879e) to head (e803716).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3519      +/-   ##
============================================
- Coverage     82.78%   82.76%   -0.03%     
  Complexity     1299     1299              
============================================
  Files          1199     1199              
  Lines        161885   161887       +2     
  Branches     131360   131464     +104     
============================================
- Hits         134019   133985      -34     
+ Misses        24322    24243      -79     
- Partials       3544     3659     +115     
Components Coverage Δ
Rust Core 83.32% <ø> (+0.04%) ⬆️
Java SDK 66.06% <ø> (ø)
C# SDK 74.77% <ø> (-1.21%) ⬇️
Python SDK 89.98% <ø> (ø)
PHP SDK 84.26% <ø> (ø)
Node SDK 96.21% <ø> (ø)
Go SDK 68.53% <ø> (ø)
see 70 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ryerraguntla ryerraguntla changed the title feat(gateways):iggy gateway for kafka - listener feat(gateways): iggy gateway for kafka listener Jun 21, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs.

If you need a review, please ensure CI is green and the PR is rebased on the latest master. Don't hesitate to ping the maintainers - either @core on Discord or by mentioning them directly here on the PR.

Thank you for your contribution!

@github-actions github-actions Bot added S-stale Inactive issue or pull request and removed S-stale Inactive issue or pull request labels Jul 1, 2026
@ryerraguntla

Copy link
Copy Markdown
Contributor Author

/ready

@atharvalade atharvalade left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I ran this locally to verify the behavior before commenting, sharing the setup so it's easy to reproduce.

Started the gateway:

cargo build -p iggy-gateway-kafka
KAFKA_BIND_ADDR=127.0.0.1:19093 ./target/debug/iggy-gateway-kafka

Then sent raw frames with this script:

import socket, struct, time

def frame(p): return struct.pack('>i', len(p)) + p

def send(payload, timeout=3.0):
    s = socket.create_connection(('127.0.0.1', 19093), timeout=timeout)
    s.sendall(frame(payload)); s.settimeout(timeout)
    try: return s.recv(65536)
    except socket.timeout: return None

# 1. Produce v3 with acks=0, empty topics. Spec says the broker must not respond.
hdr = struct.pack('>hhih', 0, 3, 42, -1)
body = struct.pack('>hhii', -1, 0, 1000, 0)
resp = send(hdr + body)
print('acks=0 Produce:', resp.hex() if resp else 'no response')

# 2. ListOffsets v0, below the firewall min, so the error path encodes at v0.
hdr = struct.pack('>hhih', 2, 0, 7, -1)
body = struct.pack('>i', -1) + struct.pack('>i', 1) + struct.pack('>h', 1) + b't' \
     + struct.pack('>i', 1) + struct.pack('>iqi', 0, -1, 1)
resp = send(hdr + body)
print('ListOffsets v0:', resp.hex())

# 3. Metadata v1 asking for topic "orders".
hdr = struct.pack('>hhih', 3, 1, 9, -1)
body = struct.pack('>i', 1) + struct.pack('>h', 6) + b'orders'
resp = send(hdr + body)
print('Metadata has "orders":', b'orders' in resp, '| has "unknown-topic":', b'unknown-topic' in resp)

# 4. Open a connection and send nothing, measure when the server closes it.
s = socket.create_connection(('127.0.0.1', 19093), timeout=20)
start = time.time()
s.recv(1)
print(f'idle connection closed after {time.time()-start:.1f}s')

Output I got:

acks=0 Produce: 0000000c0000002a0000000000000000
ListOffsets v0: 0000001c00000007000000010000000000010000000000230000000000000000
Metadata has "orders": False | has "unknown-topic": True
idle connection closed after 15.0s

So: acks=0 gets a 16 byte response when the spec says silence, the ListOffsets v0 error response leaves 4 unparsed bytes for a v0 client (bare i64 where the old_style_offsets array should be), Metadata never echoes the requested topic name, and idle connections are dropped after 15s. Details in the inline comments.

Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/protocol/responses.rs Outdated
Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/server.rs
Comment thread gateways/kafka/tools/kafka-tool/src/main.rs Outdated
Comment thread gateways/kafka/src/server.rs Outdated
Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/server.rs Outdated
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Jul 2, 2026
@slbotbm

slbotbm commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@ryerraguntla seems like you pushed opensearch connector work to this PR as well?

ryerraguntla and others added 13 commits July 2, 2026 20:02
TCP listener on 9093 with scoped API decode/validation and stub
responses for apache#3421. Includes kafka-message-gen test tool.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add tokio-util dependency and implement BrokerAdvertise to advertise host/port from server bind address. Replace dynamic supported_api_ranges with a static table and pass BrokerAdvertise into handler so metadata responses include the advertised broker. Harden codec primitives: add MAX_COLLECTION_LEN, checked read_i32_array_count and read_compact_array_count, tagged-field bounds checks, better varint validation, and string-length checks (write_nullable_string returns Result). Update response encoders to take references and use checked conversions for counts. Improve server: use tokio_util::TaskTracker for graceful shutdown, handle transient accept errors, return error-only response for unsupported request header versions, extract correlation id, and add safer frame read/write size checks. Update docs and tests to reflect compact/flexible encoding and new APIs.
Critical protocol correctness:
- Metadata non-flexible: controller_id now written before topics (v1+);
  add rack (v1+), cluster_id (v2+), is_internal (v1+) to match spec
- Metadata flexible (v9): is_internal now written before partitions array
- ListOffsets: replace hardcoded 1_700_000_000_000 stub timestamp with -1
  (Kafka "not available" sentinel)
- read_compact_array_count: treat varint=0 as Ok(0) (null compact array)
  instead of Err; fixes Fetch v7+ with null forgotten_topics from
  librdkafka/kafka-go

Performance:
- read_frame: replace vec![0u8; frame_len] with BytesMut + read_buf loop
  (no zero-initialization)
- handle_connection: single BytesMut alloc for length+header+body via
  new send_response() helper and ResponseHeader::encode_into()
- BrokerAdvertise: wrap in Arc, clone Arc per connection instead of
  String-copying the struct

Codec hardening:
- read_nullable_string / read_compact_nullable_string: single alloc via
  str::from_utf8 on borrowed slice + to_owned()
- read_tagged_fields: remove dead usize::try_from (always succeeds on
  64-bit); compare directly as u64
- write_nullable_bytes: add debug_assert for i32::MAX overflow

API cleanliness:
- Remove 11 out-of-scope API_KEY_* constants (dead code, not referenced)
- requests.rs: null topic name now returns Err(NullTopicName) instead of
  silently mapping to ""
- error.rs: add NullTopicName variant
- header.rs: add encode_into() and encoded_size() for zero-copy framing

Tests: update golden Metadata v0 fixture and api_handler test to match
corrected field order (no controller_id in v0).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Foundation for the Kafka gateway (apache#3421): add manual testing and test-suite docs, a full API key reference, and update README/SCOPE to reflect bind/config notes and test coverage. Implement various protocol, header, codec and server tweaks (src/protocol/*, server.rs, lib.rs, main.rs, error.rs) and add/extend many tests and test helpers (103 regression tests across new and updated suites). Add kafka-tool response helper and fixtures tooling updates to support decode/validation tests and manual end-to-end checks. These changes bundle documentation, test infra, and protocol-level fixes required to validate wire decoding, version firewalling, and stub responses for the gateway.
Add/relax several clippy allow attributes in the Kafka codec module; simplify i32->usize conversion for array length by using a direct cast with a safety comment (avoids unnecessary try_from and map_err), and add #[must_use] to Encoder::freeze to prevent accidental discards. Also add a TODO comment in the Produce response placeholder for populating the topic name.
Rename the crate/binary to iggy-gateway-kafka and update README and Cargo.toml accordingly. Introduce DEFAULT_KAFKA_PORT and refactor the server to accept a pre-bound TcpListener (eliminates bind TOCTOU) and use listener.local_addr() to compute advertised broker information, returning clearer config errors. Harden protocol handling: add Encoder helpers (unchecked nullable strings, write_null_bytes), handle metadata parsing errors safely, downgrade some decode errors to warnings, and adjust metadata encoding for flexible versions. Improve read_frame (single deadline for length+body, chunked reservation to avoid large upfront allocs, truncate extra bytes, and transient accept backoff). Update tests and CI: add rust-gateway component and include gateways in PR-title workflow.
Multiple fixes and improvements to the Kafka gateway:

- Docs: document KAFKA_* env vars in README and normalize package name references from `iggy_gateway_kafka` to `iggy-gateway-kafka` across MANUAL_TESTING and TEST_SUITE.
- Protocol: add MAX_SUPPORTED_METADATA_VERSION and clamp metadata responses to the highest implemented version; treat empty or malformed metadata request bodies as a 0-topic response with appropriate per-topic error instead of failing; remove unused KafkaProtocolError import.
- Server: validate KAFKA_ADVERTISED_HOST length against Kafka nullable string limit and return a config error if exceeded; log a warn when setting TCP_NODELAY fails.
- I/O: rewrite read_frame to use bounded read() slices (avoid allocator over-reads via read_buf) so pipelined frames are not consumed accidentally.
- Tests: add tests for advertised-host length, corrupt metadata partial-body behavior returning zero topics, and that read_frame does not consume pipelined bytes; adjust existing tests/docs to match crate name changes.

These changes fix metadata decoding semantics, prevent frame bleed between pipelined requests, harden config validation, and align documentation/test commands with the package name.
Adjust trailing newline/whitespace in .github/config/publish.yml and .github/dependabot.yml. These are non-functional formatting fixes to normalize end-of-file newlines and do not change any configuration values.
@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Aug 3, 2026
Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/protocol/api.rs Outdated
Comment thread gateways/kafka/src/server.rs Outdated
Comment thread gateways/kafka/src/protocol/codec.rs Outdated
Comment thread gateways/kafka/src/protocol/requests.rs Outdated
Comment thread gateways/kafka/tools/kafka-tool/src/main.rs
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Aug 8, 2026
cursoragent and others added 8 commits August 8, 2026 21:28
Close connections when a request version is above the encoder max so
clients cannot parse a clamped UNSUPPORTED_VERSION body. Fully decode
ApiVersions v3+ and Metadata bodies (reject empty/malformed Metadata),
split nullable vs required compact arrays, honor KIP-464 assignments on
CreateTopics v2/v3, cancel idle connections on shutdown, and cap
kafka-tool response frame allocation.

Co-authored-by: ryerraguntla <ryerraguntla@users.noreply.github.com>
…nary

Request decoders for Produce/Fetch/ListOffsets/CreateTopics now fail on
trailing body bytes, matching Metadata/ApiVersions. A fixtures canary
fails under KAFKA_FIXTURES_REQUIRED=1 when the wire fixture directory is
empty so CI cannot pass green-but-empty skip suites.

Co-authored-by: ryerraguntla <ryerraguntla@users.noreply.github.com>
Tightens gateway safety and protocol behavior across config loading, decoding, and response handling. This adds validated IGGY_KAFKA_* parsing (including max frame/read/write timeouts), enforces a per-request decode element budget plus compact-string size limits, and simplifies outcomes so unknown/out-of-scope API keys now close the connection instead of sending an unparseable error body. Produce decode failures now distinguish pre-acks failures (silent to avoid acks=0 stream desync) from post-acks failures (INVALID_REQUEST response). It also updates metadata/version helpers, response write framing, docs, and related regression tests.
…com/ryerraguntla/iggy into feat(gateways)/kafka_to_iggy_listener
@ryerraguntla

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Aug 9, 2026
ryerraguntla and others added 2 commits August 12, 2026 05:46
Replaced the gateway’s hand-rolled Kafka wire codec, request decoders, and header encode/decode logic with `kafka_protocol` (broker feature only), and removed the old `protocol/codec.rs` and `protocol/requests.rs` modules. Response encoding now goes through shared `Encodable` paths with explicit malformed/encode error mapping, and unsupported versions that cannot be encoded at the requested wire version now close the connection instead of emitting downgraded responses.

This also updates Produce handling around decode failures and pre-v3 compatibility (including acks=0 silence behavior), switches header version selection to `ApiKey` metadata from `kafka_protocol`, and rewrites tests to use a test-only local wire helper codec plus new expectations for close/silent paths under the version firewall.
@ryerraguntla

Copy link
Copy Markdown
Contributor Author

/author

@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Aug 12, 2026
ryerraguntla and others added 10 commits August 12, 2026 06:39
…com/ryerraguntla/iggy into feat(gateways)/kafka_to_iggy_listener
…ner"

This reverts commit 7cee839, reversing
changes made to caf878e.
Remove stale lockfile entries for the deleted Kafka gateway, Kafka message generator, and OpenSearch source connector crates, along with their now-unused transitive dependencies.
added a new line
Somewhere the opensource search connector wip branch got merged. This commit takes out those changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kafka gateway: Build Gateway to Iggy for existing Kafka users - Create a TCP Listener for Kafka users

8 participants