feat(gateways): iggy gateway for kafka listener - #3519
Conversation
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).
|
Thanks for the PR. It is labeled Slash commands (own line, regular comment) move it around the queue:
See CONTRIBUTING.md for details. |
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
|
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 Thank you for your contribution! |
|
/ready |
atharvalade
left a comment
There was a problem hiding this comment.
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-kafkaThen 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.
|
@ryerraguntla seems like you pushed opensearch connector work to this PR as well? |
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.
and updating the documentation.
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
|
/ready |
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.
|
/author |
…com/ryerraguntla/iggy into feat(gateways)/kafka_to_iggy_listener
…ener" This reverts commit 3caa4c6.
…a_to_iggy_listener
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.
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 atokio-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 withUNSUPPORTED_VERSION(error 35) without dropping the connection. Akafka-message-genhelper tool generates golden wire fixtures used by the regression suite.Key implementation notes:
Bytes— no RecordBatch decode in this layer.BrokerAdvertiseisArc-cloned per connection; no per-connection string allocation.SUPPORTED_RANGESis the single source of truth;ApiVersionsadvertises 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
KafkaServerlistener.Local Execution
All 103 tests pass.
clippy -D warningsclean. Manual smoke-test procedure followed pergateways/kafka/docs/MANUAL_TESTING.md(ApiVersions, Metadata, Produce, Fetch round-trips; version firewall; oversized frame rejection).AI Usage
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 thekafka-toolfixture generator.MANUAL_TESTING.mdusingkcatand thekafka-message-gentool against a live listener. Golden byte fixtures pin exact wire output against known-correct Kafka responses.SUPPORTED_RANGESgovernance model,Arc<BrokerAdvertise>per-connection,varint=0null compact array) are documented inline or inSCOPE.md.