feat: add ext_proc filter crate with Envoy-compatible config#182
feat: add ext_proc filter crate with Envoy-compatible config#182usize wants to merge 21 commits into
Conversation
|
Experimenting with saving summarized agent session for prs: https://gist.github.com/usize/ebb03b9963383cf01f433bd5bf7719e3 |
1619fd4 to
031cb54
Compare
|
@shaneutt Aside from config parsing there's not much happening here. Except it does establish the structure of the |
|
Converted to draft: required checks failing. |
PR too largeThis PR adds 1199 lines (limit: 500). Large PRs are difficult to review and more likely to introduce subtle bugs. Please break this contribution into smaller, focused PRs that each address a single concern. See our coding conventions for guidance.
|
836a254 to
c2c6d3d
Compare
| ) | ||
| .into()); | ||
| } | ||
| if cfg.message_timeout_ms == 0 { |
There was a problem hiding this comment.
Should this have an upper bound validation?
There was a problem hiding this comment.
As a matter of taste, while there is definitely a reasonable limit on how long a timeout should be... it's more a matter of taste than a lower bound. Should someone have a timeout of 3600 * 1000? Well.. no. Probably not. How about 300 * 1000? Still high, but seems murkier.
| license.workspace = true | ||
| repository.workspace = true | ||
| readme = "../../README.md" | ||
| publish = true |
There was a problem hiding this comment.
| publish = true | |
| publish = false |
| r#" | ||
| target: "http://127.0.0.1:50051" | ||
| message_timeout_ms: 500 | ||
| max_message_timeout_ms: 5000 |
There was a problem hiding this comment.
With the change suggested for from_config() in lib.rs this should validate timeout.
| let yaml: serde_yaml::Value = serde_yaml::from_str( | ||
| r#" | ||
| target: "http://127.0.0.1:50051" | ||
| deferred_close_timeout_ms: 10000 |
There was a problem hiding this comment.
accepts_custom_status_on_error and accepts_custom_deferred_close_timeout only assert filter.name()
These tests verify that certain configs are accepted, but the only assertion is filter.name() == "ext_proc", which is true for any successfully built filter. They're really just "doesn't error" tests. That's a limitation of the private field / Box interface rather than a test bug, but it's worth noting the tests don't actually confirm the values were stored.
There was a problem hiding this comment.
That's true. I think that for a skeleton.. this seems okay? As the implementation proceeds, integration tests will hit the private fields. Or we can revisit and see if it's worth making it possible to inspect here.
| status_on_error: u16, | ||
|
|
||
| /// gRPC endpoint URI (retained for diagnostics). | ||
| target: String, |
There was a problem hiding this comment.
I think it's worth having for logging purposes?
| } | ||
|
|
||
| async fn on_request(&self, _ctx: &mut HttpFilterContext<'_>) -> Result<FilterAction, FilterError> { | ||
| tracing::trace!( |
There was a problem hiding this comment.
Should be warn! instead, so if it ever gets triggered (for some reason) it's seen
| tracing::trace!( | |
| tracing::warn!( |
| license.workspace = true | ||
| repository.workspace = true | ||
| readme = "../../README.md" | ||
| publish = true |
There was a problem hiding this comment.
| publish = true | |
| publish = false |
Introduce `praxis-proxy-ext-proc` as a standalone workspace crate at filter/ext-proc/, alongside filter/proto/. Isolated from the filter builtins to contain the gRPC/tonic/proto dependency surface. The config mirrors Envoy's ExternalProcessor proto shape for easy migration from Envoy deployments: - target, failure_mode, message_timeout_ms - processing_mode (header/body/trailer send modes) - allow_mode_override, observability_mode, disable_immediate_response, max_message_timeout_ms Features not yet implemented are parsed but rejected during validation with a clear error. The filter is not registered in FilterRegistry::with_builtins(); consumers register it explicitly via registry.register() or register_filters!. Opus 4.6 aided in codebase exploration and feature implementation. Signed-off-by: usize <mofoster@redhat.com>
Reject status_on_error values outside the valid HTTP status code range (100..=599) and message_timeout_ms of 0, which would cause immediate gRPC timeouts. Implementation assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com>
Error messages now show the snake_case YAML value the user typed
(e.g. 'buffered') instead of the Rust enum variant ('Buffered').
Implementation assisted by Opus 4.6
Signed-off-by: usize <mofoster@redhat.com>
Use 'filter\/ext-proc' instead of bare 'ext-proc' to avoid matching unrelated workspace members in the future. Implementation assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com>
tonic's Channel requires a tokio reactor during Drop, causing panics when tests share threads without a runtime. Convert all tests that call ExtProcFilter::from_config to #[tokio::test]. Also replace invalid_failure_mode_errors with invalid_target_uri_errors since #[serde(default)] silently defaults unknown enum variants in serde_yaml::from_value, making the original assertion untestable. Implementation assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com>
failure_mode is a pipeline-level concern owned by FilterEntry, not a filter-specific config field. strip_structural_keys() already removes it from the YAML before it reaches from_config, so the field always silently defaulted regardless of user input. Remove the field from both the config struct and the filter struct. The pipeline executor reads failure_mode from FilterEntry and applies it via PipelineFilter, matching all other filters. Also removes the now-unused praxis-core direct dependency. Signed-off-by: usize <mofoster@redhat.com>
Adds tests for validation paths that had no coverage: - response_header_mode: skip rejection - response_trailer_mode: send rejection - all response_body_mode variants (streamed, buffered, buffered_partial, full_duplex_streamed) - allowed_override_modes with non-empty entries Replaces the single-variant body mode tests with comprehensive loops that cover all four BodySendMode variants for both request and response directions. Signed-off-by: usize <mofoster@redhat.com>
Proves that failure_mode flows through the pipeline infrastructure rather than being parsed by the filter itself: - failure_mode in filter YAML is stripped by parse_filter_config - FilterEntry captures failure_mode: open and closed correctly - FilterEntry defaults failure_mode to Closed when omitted - FilterPipeline::build succeeds with ext_proc entries carrying different failure_mode values Signed-off-by: usize <mofoster@redhat.com>
- Remove unused praxis-proto dependency from filter/ext-proc - Update praxis-ext-proc version from 0.2.0 to 0.3.1 in workspace dependencies to match the workspace version Signed-off-by: usize <mofoster@redhat.com>
This is a workspace-internal crate, not intended for crates.io. Signed-off-by: usize <mofoster@redhat.com>
If the unimplemented on_request path ever fires in a real deployment it should be visible in logs, not buried at trace level. Signed-off-by: usize <mofoster@redhat.com>
Describe the rejection condition rather than just naming the field. Signed-off-by: usize <mofoster@redhat.com>
The project convention requires inline format args. Field access expressions are bound to locals first since format! does not support dotted paths directly. Signed-off-by: usize <mofoster@redhat.com>
The input type is inferred; only the return type annotation is needed for the .into() conversion. Signed-off-by: usize <mofoster@redhat.com>
Enables diagnostic formatting for the filter struct. All fields including tonic::Channel implement Debug. Signed-off-by: usize <mofoster@redhat.com>
All fields are Copy enums (HeaderSendMode, BodySendMode), so the containing struct can be Copy too. Signed-off-by: usize <mofoster@redhat.com>
Use explicit "not yet implemented" instead of "(skeleton)" which was unclear to reviewers. Signed-off-by: usize <mofoster@redhat.com>
Make the ExternalProcessorClient relationship explicit so the next contributor knows praxis-proto needs to be added to Cargo.toml when implementing the gRPC callout. Signed-off-by: usize <mofoster@redhat.com>
Reject max_message_timeout_ms of 0 and values less than message_timeout_ms, which would make the upper bound meaningless. Negative values are caught by serde (u64), but a test guards against future type changes. Signed-off-by: usize <mofoster@redhat.com>
The Copy derive on ProcessingModeConfig triggered trivially_copy_pass_by_ref — pass by value instead of reference. Also reformats chained method calls per nightly rustfmt. Signed-off-by: usize <mofoster@redhat.com>
The workspace migrated from serde_yaml to yaml_serde in praxis-proxy#381. Cargo.lock for ext-proc needs to reflect the new package name. Signed-off-by: usize <mofoster@redhat.com>
c97e938 to
2018b23
Compare
Introduce
praxis-proxy-ext-procas a standalone workspace crate at filter/ext-proc/, alongside filter/proto/. Isolated from the filter builtins to contain the gRPC/tonic/proto dependency surface.The config mirrors Envoy's ExternalProcessor proto shape for easy migration from Envoy deployments:
Features not yet implemented are parsed but rejected during validation with a clear error. The filter is not registered in FilterRegistry::with_builtins(); consumers register it explicitly via registry.register() or register_filters!.
Opus 4.6 aided in codebase exploration and feature implementation.