Skip to content
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

🔥 conditions et at, replacing with proper cel::Predicates & ::Expressions #396

Merged
merged 20 commits into from
Dec 6, 2024

Conversation

alexsnaps
Copy link
Member

fix #385

@alexsnaps alexsnaps force-pushed the cel_predicates branch 5 times, most recently from 787bf96 to 9edeb4e Compare November 27, 2024 18:32
limitador/src/counter.rs Outdated Show resolved Hide resolved
}

impl Context<'_> {
pub(crate) fn new(limit: &Limit, root: String, values: &HashMap<String, String>) -> Self {
Copy link
Member Author

Choose a reason for hiding this comment

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

So, this root relates to this point from #385

Should the descriptor(s) and their entries be first class citizens and have their own root binding?

Right now, limit binds the limit info (id & name), I was thinking that possibly we could have some root binding for the descriptor entries (e.g. descriptor), which... could then also be a List and hold multiple of them and all their entries: descriptor[0].foo == '1' && descriptor[1].bar != 'bad'... See this issue here, so it'd probably be a List<HashMap<String, String>> as well then...

Whatever the case, who would set that root namespace/binding to something? I'd expect it to be the server code... but that then creates further issues in the (de)serialization paths... Also, the logic of resolving the Limit.variables() would need to account for that (along side what happens in the wasm-shim's code to lazily resolve attributes from the host).

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, doing so would tie releasing this with a release of the kuadrant-operator... right now, this isn't necessary.

Copy link
Member Author

@alexsnaps alexsnaps Dec 2, 2024

Choose a reason for hiding this comment

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

So... thinking about this a little more, and given this comment, here's how I'm proposing to go about this:

  • Have a blanket From<&HashMap<String, String> for Context... which would not add the binding for limit.
  • Change Limit::applies & ::resolve_variables to take a &Context instead of creating it explicitly
  • Have the (Async)RateLimiter::counters_that_apply also take the &Context
  • Have the (Async)RateLimiter APIs expose the Context, instead of &HashMap<String, String>
  • Specialize the Context on resolving Predicates only, so the derived cel_interpreter::Context::Child contains the &limit data
  • Expose some API around Context to set the root(s)
  • Have the (Async)RateLimiter::counters_that_apply also take the &Context
  • Have the (Async)RateLimiter APIs expose the Context, instead of &HashMap<String, String>
  • Wire in the server code

That way, the server code can decide how it wires the RateLimitDescriptors and their .entries from envoy's protocol for external rate limiting.

Copy link
Member Author

Choose a reason for hiding this comment

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

Have the (Async)RateLimiter::counters_that_apply also take the &Context
Have the (Async)RateLimiter APIs expose the Context, instead of &HashMap<String, String>

duh! this obviously doesn't work 🤦 Or at least not while injecting the Limit this operates on.

@alexsnaps alexsnaps marked this pull request as ready for review November 27, 2024 20:14
@alexsnaps alexsnaps requested a review from a team November 27, 2024 20:14
@alexsnaps
Copy link
Member Author

Pretty big PR, but I tried to keep a somewhat meaningful history and broke it down according to the #385
Thanks... and... sorry 🤦

@@ -333,6 +334,15 @@ fn encode_counter_to_key(counter: &Counter) -> Vec<u8> {
}

fn encode_limit_to_key(limit: &Limit) -> Vec<u8> {
let counter = Counter::new(limit.clone(), HashMap::default());
// fixme this is broken!
Copy link
Member Author

Choose a reason for hiding this comment

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

This has always been broken... and will need revisiting! But we can't just fake a the variables values like this (and they were simply left empty before, but this won't work now, as they'd fail resolution. I could "just" use the new Counter::resolved_vars, but that would just hide the problem a little more again.

Comment on lines 151 to 167
let var = "timestamp(ts).getHours()";
let limit = Limit::new(
"",
10,
60,
Vec::default(),
[var.try_into().expect("failed parsing!")],
);
let counter = Counter::new(
limit,
HashMap::from([("ts".to_string(), "2019-10-12T13:20:50.52Z".to_string())]),
)
.expect("failed creating counter");
assert_eq!(
counter.set_variables.get(var),
Some("13".to_string()).as_ref()
);
Copy link
Member Author

Choose a reason for hiding this comment

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

So I guess this is now new...

@alexsnaps alexsnaps marked this pull request as draft December 3, 2024 00:22
@alexsnaps
Copy link
Member Author

As per this, starting to address these issues

@alexsnaps alexsnaps marked this pull request as ready for review December 3, 2024 12:02
@alexsnaps alexsnaps requested a review from eguzki December 3, 2024 12:02
@alexsnaps
Copy link
Member Author

This now fixes #62 as well

@alexsnaps alexsnaps force-pushed the cel_predicates branch 2 times, most recently from 103eddf to 2dde346 Compare December 3, 2024 12:18
@alexsnaps alexsnaps linked an issue Dec 3, 2024 that may be closed by this pull request
Comment on lines +20 to +21
- "descriptors[0]['req.method'] == 'GET'"
- "descriptors[0]['req.path'] == '/json'"
Copy link
Member Author

Choose a reason for hiding this comment

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

This is now a breaking change obviously...
We could tell the server to not do that tho. But this is the only way I can think of to support multiple RateLimitDescriptor coming in from envoy, as well as the fact that each of their entries are String, String... i.e. any valid utf-8 string, which for the key has to be exposed as a Map<String, _> somehow.

For Kuadrant tho, we can now use the limit.id == '3f5e80af-1056-4349-bb84-c8010f34ecbb' if we'd wanted (and wire the id field of the Limit itself in the config)... eventually this semantic could then be optimized for

Copy link
Member

@adam-cattermole adam-cattermole left a comment

Choose a reason for hiding this comment

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

After our discussion the other day and time to go through it, the changes look sound to me, so I'm happy to approve to unblock

@alexsnaps alexsnaps merged commit bf6a26f into main Dec 6, 2024
10 checks passed
@alexsnaps alexsnaps deleted the cel_predicates branch December 6, 2024 11:24
@eguzki
Copy link
Contributor

eguzki commented Dec 9, 2024

Can we tell that this is a breaking change? Just to confirm the breaking-change I just added.

@eguzki
Copy link
Contributor

eguzki commented Dec 9, 2024

Is there any plan to have some documentation here or somewhere else? So, whenever I want to write some limitador configuration, what context do I have available in CEL expressions? How do I make reference to descriptor entries to write predicates?

@eguzki
Copy link
Contributor

eguzki commented Dec 9, 2024

After merging this, is it correct to say that Kuadrant's RatelimitPolicies are broken on HEAD of main ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
3 participants