Skip to content

[gateway] Add the Gateway runtime, common HTTP layer, lifecycle, and test infrastructure - #3963

Open
beryllw wants to merge 16 commits into
apache:mainfrom
beryllw:gateway-g1-foundation
Open

[gateway] Add the Gateway runtime, common HTTP layer, lifecycle, and test infrastructure#3963
beryllw wants to merge 16 commits into
apache:mainfrom
beryllw:gateway-g1-foundation

Conversation

@beryllw

@beryllw beryllw commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Purpose

Linked issue: close #3958

First PR of FIP-49: the Gateway runtime and HTTP foundation. Authentication, the Fluss backend and the data
APIs come in follow-up issues.

Distilled from the FIP-49 PoC; the foundation was written by @gstamatakis95 and is credited via Co-authored-by.

Brief change log

  • fluss-gateway/ as an independent Cargo workspace, library plus executable.
  • gateway.yaml configuration: flat dotted keys, env and CLI overrides, validation, stable exit codes.
  • Common HTTP layer: one error envelope, request IDs, 404/405/413/504.
  • GET /health returning {status, uptime_ms}.
  • Lifecycle: REST and Prometheus listeners, task supervision, SIGTERM draining.
  • Metrics: the FIP-49 families that need no backend.
  • OpenAPI 3.1 generated from the router and checked in, with CI drift and lint gates.
  • CI: build, test, clippy, fmt, rustdoc, dependency licences, MSRV.

Tests

66 tests: unit tests, the HTTP contract over a real listener, and the compiled binary's startup, health, SIGTERM and exit codes. A --features integration_tests suite drives the gateway against a dockerized Fluss cluster on demand; it is not a pull-request gate yet.

API and Format

New endpoints GET /health and GET /v1/openapi.json. No change to existing APIs or storage formats.

Documentation

Module-level rustdoc, enforced by cargo doc -D warnings.

@beryllw
beryllw force-pushed the gateway-g1-foundation branch 3 times, most recently from fc487d0 to f6e94e7 Compare August 12, 2026 12:11
@beryllw
beryllw marked this pull request as ready for review August 12, 2026 14:32
@beryllw

beryllw commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@fresh-borzoni This is the first PR for FIP-49 — could you help review it when you have time? Thanks a lot! 🙏

@beryllw

beryllw commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Copilot AI 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.

Pull request overview

This PR introduces the initial Rust-based Fluss Gateway module (fluss-gateway/) as a standalone Cargo workspace, providing the runtime/lifecycle foundation, a shared HTTP layer (error envelope, request IDs, limits/timeouts), baseline observability (logging + Prometheus metrics), and integration test infrastructure for future gateway capabilities described in FIP-49 / issue #3958.

Changes:

  • Adds gateway runtime + lifecycle (listener binding, readiness/draining, SIGTERM handling, stable exit codes).
  • Adds REST foundation with GET /health, GET /v1/openapi.json, shared error envelope, request IDs, body limits, and request deadlines.
  • Adds gateway-focused CI jobs (build/test, fmt/clippy/rustdoc, OpenAPI drift + lint, MSRV gate) and supporting test suites.

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
fluss-gateway/tests/support/mod.rs Shared HTTP + process helpers for gateway integration tests.
fluss-gateway/tests/process.rs End-to-end tests for the compiled binary (exit codes, SIGTERM drain, bind failures).
fluss-gateway/tests/http_api.rs End-to-end HTTP contract tests against a real listener (health, OpenAPI, envelopes, limits).
fluss-gateway/tests/e2e_cluster.rs Feature-gated dockerized cluster smoke test wiring (integration scaffolding).
fluss-gateway/src/protocol/rest/openapi.rs Router-derived OpenAPI generation + serving and drift checks.
fluss-gateway/src/protocol/rest/mod.rs REST router assembly and middleware (request IDs, envelopes, deadlines, body limits, acceptance guard).
fluss-gateway/src/protocol/rest/health.rs GET /health endpoint and schema.
fluss-gateway/src/protocol/mod.rs Protocol module root exporting REST surface.
fluss-gateway/src/observability.rs Logging + Prometheus metrics inventory and runtime/process metric sampling.
fluss-gateway/src/main.rs Binary entrypoint (CLI, config load/validate, init logging, lifecycle run + exit codes).
fluss-gateway/src/lifecycle.rs Listener/task supervision, readiness state machine, graceful shutdown/draining.
fluss-gateway/src/lib.rs Gateway library crate root (internal implementation modules).
fluss-gateway/src/error.rs Error taxonomy + REST error envelope and OpenAPI-friendly schema generation.
fluss-gateway/src/config.rs Gateway configuration loading (CLI/env/YAML precedence), parsing, validation, and warnings.
fluss-gateway/rustfmt.toml Gateway-local rustfmt configuration.
fluss-gateway/rust-toolchain.toml Gateway-local toolchain selection (stable + components).
fluss-gateway/openapi.yaml Checked-in generated OpenAPI document.
fluss-gateway/justfile Developer recipes for build/test/fmt/clippy/doc/openapi/licenses.
fluss-gateway/deny.toml cargo-deny license allowlist/config.
fluss-gateway/copyright.txt License header template for tooling.
fluss-gateway/clippy.toml Clippy configuration (cognitive complexity threshold).
fluss-gateway/Cargo.toml Standalone workspace/package definition, deps, features, tests, MSRV.
fluss-gateway/.licenserc.yaml License header checking configuration (with generated-file exclusions).
fluss-gateway/.gitignore Gateway-local ignore rules.
.github/workflows/rust-license-and-format.yml Extends Rust license/format workflow to include gateway checks.
.github/workflows/rust-build-and-test.yml Adds gateway build/test/OpenAPI/MSRV/E2E jobs and triggers.
.github/workflows/license-check.yml Excludes gateway from the generic license-check workflow (gateway has its own checks).
.github/workflows/ci.yaml Excludes gateway-only changes from the main CI workflow (gateway has dedicated Rust workflows).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread fluss-gateway/src/protocol/rest/mod.rs
Comment thread fluss-gateway/src/protocol/rest/mod.rs
@beryllw

beryllw commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Split out the module scaffolding (workspace setup, toolchain/lint/license configs, placeholder targets, and the dedicated gateway CI gates) into #4002 for a fast first merge. This PR keeps all of the gateway runtime, HTTP layer, lifecycle, and test suites, and will be rebased once #4002 lands.

beryllw and others added 9 commits August 14, 2026 21:21
Introduces fluss-gateway as an independent Cargo workspace with library and executable entry points: strict configuration with stable exit codes, the shared error envelope, request-id/body-size/deadline middleware, the FIP-49 GET /health endpoint, REST and metrics listeners, task supervision with SIGTERM draining, and the OpenAPI 3.1 document generated from the typed router.

Co-authored-by: gstamatakis95 <126914070+gstamatakis95@users.noreply.github.com>
…uster harness

Covers the shared HTTP contract over a real listener, the compiled binary's startup, health, SIGTERM draining and exit codes, and a self-test of the fixed-version dockerized Fluss cluster harness that later capabilities reuse for their end-to-end suites.

Co-authored-by: gstamatakis95 <126914070+gstamatakis95@users.noreply.github.com>
Runs the gateway workspace's build, unit tests, license headers, formatting, clippy and rustdoc, checks the checked-in OpenAPI document for drift and validates it externally, and runs the end-to-end suite in a job that fails rather than skipping silently when a selected scenario cannot start.

Co-authored-by: gstamatakis95 <126914070+gstamatakis95@users.noreply.github.com>
… rename the harness job

Co-authored-by: gstamatakis95 <126914070+gstamatakis95@users.noreply.github.com>
@beryllw
beryllw force-pushed the gateway-g1-foundation branch from 795c831 to cbd4ab8 Compare August 14, 2026 13:23
…e rust workflows

Move the gateway jobs out of rust-build-and-test.yml and
rust-license-and-format.yml into the dedicated gateway-ci.yml introduced in
apache#4002, and install protoc in every compiling job: the gateway now depends on
fluss-rs, whose proto codegen needs protoc, which the scaffold-era workflow
steps did not install. The rust workflows are restored to their upstream
state so gateway changes never build the fluss-rust workspace and vice
versa.

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@beryllw Thank you for the PR!
I looked quickly with a limited time I had, left some quick comments, PTAL

Comment thread fluss-gateway/src/lifecycle.rs Outdated
Comment thread fluss-gateway/src/config.rs Outdated
Comment thread fluss-gateway/src/protocol/rest/mod.rs
Comment thread fluss-gateway/src/protocol/rest/health.rs
@beryllw
beryllw requested a review from fresh-borzoni August 17, 2026 01:50

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@beryllw Thank you, but the thing I'd look at again is dropping axum::serve to get at the builder.

auto first checks whether the connection opens with the HTTP/2 preface, and that read has no timer on it, so a socket that sends literally nothing is still parked (half a request head does get closed now). Pulling in server-auto also flips on hyper/http2, so anything that does send that preface now gets an HTTP/2 connection, and if it then just sits there hyper's graceful shutdown waits on it, my SIGTERM sat for the full drain and exited 1. Then the accept arm returns Err, so one EMFILE (tried it with ulimit -n 40) and run() takes the whole process down, where axum just sleeps a second and carries on.

Both the parked socket and the HTTP/2 surface can go away with hyper::server::conn::http1::Builder instead of auto, the timer starts at setup there and there's no HTTP/2 to serve. Needs the non-upgradeable Connection, graceful.watch won't take the other one. For accept, could we copy what axum does in handle_accept_error (serve/listener.rs), skip the connection-level errors and sleep a second on the rest instead of returning?

WDYT?

@beryllw
beryllw requested a review from fresh-borzoni August 17, 2026 07:53
@beryllw

beryllw commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Hey @leekeiabstraction — would you be interested in helping review this PR (or any of the follow-ups in #3957)? It's part of the FIP-49 gateway implementation, which we're targeting for 1.0. The timeline is a bit tight, so any early feedback would really help. Thanks! 🙏

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@beryllw Thank you, I think it's good overall, left some minors, but I approve. Let me know when you wish to merge it.

Comment thread fluss-gateway/src/lifecycle.rs Outdated
Comment thread fluss-gateway/Cargo.toml Outdated
Comment thread fluss-gateway/src/lifecycle.rs Outdated
Connection errors all logged at debug, so a client sending the HTTP/2
preface looked like a dead gateway from both ends; that case now warns
and names the misconfiguration.

hyper-util's http1 feature gates the GracefulConnection impl that
graceful.watch needs, and it was only enabled because axum asks for it,
so it is now declared here.

The accept backoff moved inside the future the serve loop selects, so a
shutdown signal cancels the wait instead of having to outlast it.
HTTP/2 arrives through Cargo feature unification rather than a code
change, so deny.toml bans the h2 crate and CI checks it; tokio-util's
unused rt feature goes with it.

A panicking handler dropped the connection, leaving the caller without
an envelope and the access log and counters without the request. A
panic guard inside the access-log layer answers 500 instead, logging
the payload with the request id and never returning it.
@beryllw
beryllw requested a review from fresh-borzoni August 17, 2026 16:05
The previous run failed before any code ran: 'Gateway License and
Formatting Check' could not download taiki-e/install-action (HTTP 429
after three attempts), 'Java 11 / core' failed in 'Set up job', and
'Java 11 / spark3-scala213' failed in 'Checkout code'. Other branches,
including main, hit the same setup failures at that time.

Empty commit, no change to the tree.

@luoyuxia luoyuxia 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.

The overall foundation is thoughtfully designed, with strong coverage around configuration, the HTTP error contract, OpenAPI generation, and lifecycle behavior. I left two suggestions inline.

// The non-upgradeable connection is what `graceful.watch` accepts; the gateway has no
// upgrade-based protocol.
let connection = graceful.watch(builder.serve_connection(TokioIo::new(socket), service));
tokio::spawn(async move {

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.

The JoinHandle returned here is discarded, so this connection task is not owned by the process-level JoinSet. When drain_tasks() reaches its deadline and calls abort_all(), it can abort only the listener task running serve(); connection tasks spawned here may keep running independently. As a result, RunningGateway::shutdown() can return while a timed-out handler is still executing in the same runtime. Once write APIs are added, work with backend side effects could continue after shutdown has reported completion.

Could we keep lifecycle ownership of these connection tasks and abort + join them when the drain deadline expires? A regression test with a handler that outlives the drain timeout could also assert that the handler is dropped before shutdown() returns.


- name: OpenAPI drift check
run: |
cargo test --lib protocol::rest::openapi::tests::export_checked_in_document -- --ignored --exact

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.

The Cargo validation commands in this workflow currently run without --locked. If Cargo.toml is changed without the corresponding Cargo.lock update, Cargo may rewrite the lockfile in the CI workspace and still pass, and the workflow does not check that diff. Similarly, the checked-in DEPENDENCIES.rust.tsv has a manual generation recipe but no drift check.

Could we run the relevant validation commands with --locked and add an inventory regeneration plus git diff --exit-code, similar to the OpenAPI drift check? That would ensure the committed lockfile and license inventory describe the dependency graph CI actually validated.

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.

[FIP-49][gateway] Add the Gateway runtime, common HTTP layer, lifecycle, and test infrastructure

4 participants