[Limiter] Add admin rule book endpoints and push to local cache #4691
[Limiter] Add admin rule book endpoints and push to local cache #4691tillrohrmann wants to merge 0 commit intorestatedev:mainfrom
Conversation
00caa79 to
22184cd
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22184cda80
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "422".to_string(), | ||
| RefOr::Ref(Ref::from_response_name("UnprocessableEntity")), |
There was a problem hiding this comment.
Define the referenced 422 response component
RulesApiError::responses now returns a $ref to UnprocessableEntity for HTTP 422, but the OpenAPI components list only registers BadRequest, NotFound, MethodNotAllowed, Conflict, and InternalServerError (crates/admin/src/rest_api/mod.rs). This creates a dangling response reference in the generated spec, which can break OpenAPI validation/codegen and hide the 422 contract for /rules endpoints; define and register a real UnprocessableEntity response (or inline it) before referencing it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is a good catch. Will update it.
91fee1a to
925e1fd
Compare
| /// Patch a rule. | ||
| /// | ||
| /// JSON Merge Patch on `limits.action_concurrency`/`reason` (omit to | ||
| /// keep, `null` to clear, value to set) and `disabled` (omit to keep, | ||
| /// boolean to set). Pattern cannot be changed — delete and recreate. | ||
| #[utoipa::path( | ||
| patch, | ||
| path = "/limits/rules/{rule_id}", | ||
| operation_id = "update_rule", | ||
| tag = "rule", | ||
| params( | ||
| ("rule_id" = String, Path, description = "Rule identifier (rul_…)"), | ||
| ), | ||
| request_body = PatchRuleRequest, | ||
| responses( | ||
| (status = 200, description = "Rule updated", body = RuleResponse), | ||
| RulesApiError, | ||
| ) | ||
| )] | ||
| pub async fn update_rule<Metadata, Discovery, Telemetry, Invocations, Transport>( |
There was a problem hiding this comment.
I implemented the modification of rules using the PATCH method by providing a patch request. A different approach could have been to use the PUT method with providing the whole rule value.
For the latter approach we probably wouldn't have needed the RuleId. This would have left only the DELETE method as an open question for how to reference the rule that the user wants to delete.
There was a problem hiding this comment.
This has changed. Now we are only offering a PUT handler which accepts a set of rules and preconditions.
|
We settled on removing the |
925e1fd to
10da756
Compare
10da756 to
b98b0d4
Compare
|
@nikrooz and @slinkydeveloper it would be great if you could give this PR a review. The important bit would be the shape of the REST API. Thanks a lot! |
6fb5197 to
8844c0b
Compare
There was a problem hiding this comment.
Thanks Till, lgtm.
My only API-shape comment is that it might be nice to add a GET /limits/rules as the read side of these endpoints, so the REST API is complete. Otherwise clients that create/update/delete rules through REST have to discover current rules via the sys_rules SQL table, which feels a bit split-brain for a basic CRUD surface.
One small API feedback, when I first read this, I expected each rule to have an id that I’d use to update or delete it. It took me a bit to realise that pattern is the id. Looking at the comments, I think we had an id and decided to remove it for good reasons, so I don’t want to reopen that discussion, but I thought it was worth mentioning.
|
Thanks for the review @nikrooz.
We can certainly add this. Initially I thought that most of the read queries in the UI go through SQL and it gives more expressive filtering and sorting options. If the GET
Fair point. Initially we did have an explicit rule id. The reason why we removed it was because the pattern uniquely identifies the rule (there was a 1:1 mapping between the pattern and the rule id). Moreover, rules have a maximum size of 108 characters (usually they are a lot shorter). That's why we thought that the additional indirection of a rule id was not worth it. Open to discuss this decision if it makes a difference for how the UI interacts with the rules. |
|
Thanks Till. For the UI the SQL table is preferred, my comment was more about the admin API as a standalone interface. |
|
Ah ok, then I'll defer this to when people are asking for it. |
8844c0b to
1332bfa
Compare
Two batch endpoints write the cluster-global rule book through
read_modify_write, mirroring
RuleBook::apply_changesso a requesteither commits in full or leaves the book untouched:
PUT /limits/rules— body is a list ofUpsertRuleRequests.Each entry carries a fully-specified rule body plus an optional
Precondition(internally-tagged enum, defaults tonone):{ "type": "none" }→ unconditional upsert,{ "type": "matches", "version": v }→ reject unless therule's current version is
v,{ "type": "does_not_exist" }→ strict insert.Returns the post-batch state of every entry so callers chaining
Matches(v)get the new versions without a second read.POST /limits/rules/bulk-delete— body is a list of{ pattern, expected_version: Option<u32> }. Missing version isan unconditional, idempotent delete; present version maps to
Precondition::Matches(v). Returns the patterns this batchactually removed (idempotent no-ops are omitted).
Preconditionis consumed directly by the request DTOs. To make thatpossible, this commit also opts the limiter's
serdeandschemafeatures into deriving Serialize/Deserialize/ToSchema on
Precondition, and givesrestate_types::Versiona transparentutoipa::ToSchemaderive (#[schema(value_type = u32)]) under theexisting
utoipa-schemafeature, so the OpenAPI spec sees Version asa plain integer.
When a worker role runs in the same process, both handlers also push
the freshly written book into the local
RuleBookCachevia afire-and-forget observer threaded down from the node wiring, shaving
the metadata-store poll latency.
This PR is based on #4690