-
Notifications
You must be signed in to change notification settings - Fork 596
Use the TransformationPolicy API directly as rustformation config #12803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Use the TransformationPolicy API directly as rustformation config #12803
Conversation
Signed-off-by: Andy Fong <[email protected]> fixed unit tests Signed-off-by: Andy Fong <[email protected]> mimic existing behavior for set header with empty value Signed-off-by: Andy Fong <[email protected]>
|
I think it was 2 meetings ago or so but there was a statement that we kept our api too close to envoy because now we have multiple sub data planes. Given this I think @ashleywang1 may have opinions on our api structure and how instead of this iteration we should remove this sort of transformation rather than enabling it |
|
|
||
| # Update our deps to make cve toil lower | ||
| # install wget for our default probes | ||
| USER root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added in my last PR but turns out it's not needed, so removing this.
| // in testdata/gateway-attached-transform.yaml, | ||
| // for x-empty, the value is set to "" | ||
| // for x-not-set, the value is not set | ||
| // The behavior for both is removing the existing header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this mean that a header with empty string value is treated the same as the header not being present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, http spec allow empty value in a header. So, ideally, the "not set" case so act as remove (but why not use "remove") and the "empty" case should still set the header with empty value. However, the behavior of the C++ transformation filter is to remove the headers in both cases. So, just doing the same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just some minor questions for my own understanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work for requestion transformations that get run on both on_request_headers and on_request_body? Does it only apply the transformation once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The on_request_body is not implemented yet and will be next. Basically, if there is a body, we will ask envoy to buffer the entire body first because calling us with on_request_body and only do the transformation there and not in on_request_headers. That's kind of the behavior of the C++ filter (except it buffer the body bytes inside the filter instead of having envoy to do that) but it buffer regardless if we use the body or not. We cannot buffer the body ourselves here because the buffer_bytes limit is not exposed by the rust sdk and I will see if we can skip body buffering if not needed. Stay tuned.
| request_headers_setter: Vec<(String, String)>, | ||
| #[serde(default)] | ||
| response_headers_setter: Vec<(String, String)>, | ||
| impl Deref for FilterConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this? Can we just expose calling .transformations() on the FilterConfig?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, technically we don't need this. I don't remember what I was trying to do but end up not doing that. This can be removed later with some minor change as there are only a few places using this.
| if value.is_empty() { | ||
| ops.remove_response_header(key); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to your code- but I think template_from_str can return an error? Can we check that with match and then only do the render if it's not an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch. yeah, I should check all the unwrap() call. I think originally as a POC, we don't care much if it just crash. I will change this.
| } | ||
|
|
||
| // Since PerRouteConfig is the same as the FilterConfig, for now just just a type alias | ||
| pub type PerRouteConfig = FilterConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does FilterConfig have the same envoy error log when it fails to parse the input config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Currently, the control-plane in kgateway actually never use FilterConfig and always use PerRouteConfig.
| use serde::Deserialize; | ||
| use serde_with::serde_as; | ||
| use serde_json::Value; | ||
| type Strng = String; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was this alias for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's another thing originally, I was trying to mimic the config structure of agentgateway's transformation, in there, it uses ArcStr but I ended up not using that because it also require a fork from John's repo for some other lib to work with ArcStr. So, I just alias that to normal String here. It's really not necessary now and I probably will remove it.
| pub add: Vec<(Strng, Strng)>, | ||
| #[serde(default)] | ||
| #[serde_as(as = "serde_with::Map<_, _>")] | ||
| #[serde(deserialize_with = "deserialize_name_value")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the Vec<(String, String)> just always a name and value pair? Can we just define that as a struct and then avoid using the custom deserializer and do something like:
#[derive(Deserialize, Clone)]
struct NameValue {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very good point. I was mimicing agentgateway and was thinking if we have the same interface, we can pull code from there to here and vice versa but that's really not easily feasible, so no reason to do this.
| #[derive(Default, Clone, Deserialize)] | ||
| pub struct BodyTransform { | ||
| #[serde(default)] | ||
| pub parse_as: Strng, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should parse_as be a string or an enum to match the kgateway api fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh... serde can map json string to enum, nice! I will do that.
| } | ||
|
|
||
| impl TransformationOps for EnvoyTransformationOps<'_> { | ||
| fn set_request_header(&mut self, key: &str, value: &[u8]) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to match some envoy style for the transformations? Or can we return an error here with the failure message if it doesn't apply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the envoy rust sdk functions, so have to follow the same function signature. However, we can return error in the main transform_request_header() if this return false. I actually plan to do that because in the trasnformation crate, I intentionally not to have any envoy specific logic and dependency in there, so I don't have access to the envoy_log_*! macro neither. I plan to bubble up the error to the rustformation crate and use envoy_log_*! there.
…rework Signed-off-by: Andy Fong <[email protected]>
Description
The shape of the rustformation config was loosely following the classic transformation filter. From envoy's dynamic module config, it's actually a free form json string. There is no reason to have yet another message format. So, just serialize the yaml from the TransformationPolicy API and pass it down. This is kind of what we do for agentgateway (except in the body transformation, there is no "parseAs" field for agentgateway).
Change Type
/kind cleanup
Changelog
Additional Notes