Add RetryPolicy CRUD service, client, and armadactl support#5000
Add RetryPolicy CRUD service, client, and armadactl support#5000dejanzele wants to merge 2 commits into
Conversation
Greptile SummaryThis PR implements the
Confidence Score: 5/5Safe to merge; all mutating paths are authorized and validated, the acknowledged TOCTOU gap in delete degrades gracefully (scheduler falls back to no-policy rather than corrupting state), and the migration is non-destructive. The service, repository, engine, and CLI are each individually well-tested. The only known correctness gap (TOCTOU between the queue-reference check and the delete) is explicitly documented in the code and degrades safely. No new issues were found beyond what was already discussed in previous review threads. No files require special attention; previous review findings on internal/server/retrypolicy/service.go (empty-name guard ordering, TOCTOU) are documented and either addressed or intentionally deferred. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant CLI as armadactl
participant Server as RetryPolicyService
participant Auth as Authorizer
participant RPRepo as RetryPolicyRepo (Postgres)
participant QRepo as QueueRepo (Postgres)
participant Scheduler as ApiPolicyCache
CLI->>Server: CreateRetryPolicy(policy)
Server->>Auth: AuthorizeAction(create_retry_policy)
Auth-->>Server: ok
Server->>Server: ValidatePolicy(policy)
Server->>RPRepo: CreateRetryPolicy(policy)
RPRepo-->>Server: ok / AlreadyExists
Server-->>CLI: Empty / gRPC status
CLI->>Server: DeleteRetryPolicy(name)
Server->>Auth: AuthorizeAction(delete_retry_policy)
Auth-->>Server: ok
Server->>QRepo: GetAllQueues()
QRepo-->>Server: []Queue
Server->>Server: filter queues referencing name
alt queues reference policy
Server-->>CLI: FailedPrecondition
else no references
Server->>RPRepo: DeleteRetryPolicy(name)
RPRepo-->>Server: ok
Server-->>CLI: Empty
end
loop every updateFrequency
Scheduler->>Server: GetRetryPolicies()
Server->>RPRepo: GetAllRetryPolicies()
RPRepo-->>Server: []RetryPolicy
Server-->>Scheduler: RetryPolicyList
Scheduler->>Scheduler: ConvertPolicy each to compiled map
Scheduler->>Scheduler: "atomic.Store(&compiled)"
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant CLI as armadactl
participant Server as RetryPolicyService
participant Auth as Authorizer
participant RPRepo as RetryPolicyRepo (Postgres)
participant QRepo as QueueRepo (Postgres)
participant Scheduler as ApiPolicyCache
CLI->>Server: CreateRetryPolicy(policy)
Server->>Auth: AuthorizeAction(create_retry_policy)
Auth-->>Server: ok
Server->>Server: ValidatePolicy(policy)
Server->>RPRepo: CreateRetryPolicy(policy)
RPRepo-->>Server: ok / AlreadyExists
Server-->>CLI: Empty / gRPC status
CLI->>Server: DeleteRetryPolicy(name)
Server->>Auth: AuthorizeAction(delete_retry_policy)
Auth-->>Server: ok
Server->>QRepo: GetAllQueues()
QRepo-->>Server: []Queue
Server->>Server: filter queues referencing name
alt queues reference policy
Server-->>CLI: FailedPrecondition
else no references
Server->>RPRepo: DeleteRetryPolicy(name)
RPRepo-->>Server: ok
Server-->>CLI: Empty
end
loop every updateFrequency
Scheduler->>Server: GetRetryPolicies()
Server->>RPRepo: GetAllRetryPolicies()
RPRepo-->>Server: []RetryPolicy
Server-->>Scheduler: RetryPolicyList
Scheduler->>Scheduler: ConvertPolicy each to compiled map
Scheduler->>Scheduler: "atomic.Store(&compiled)"
end
Reviews (2): Last reviewed commit: "Add RetryPolicy CRUD service, client, an..." | Re-trigger Greptile |
| } | ||
|
|
||
| // Deleting a policy that queues still reference would leave those queues | ||
| // pointing at nothing, silently disabling their retries. Force the | ||
| // operator to detach the policy from all queues first. | ||
| referencing, err := s.queuesReferencingPolicy(ctx, req.Name) | ||
| if err != nil { | ||
| return nil, status.Errorf(codes.Unavailable, "error checking queues referencing retry policy %s: %s", req.Name, err) | ||
| } | ||
| if len(referencing) > 0 { | ||
| shown := referencing | ||
| if len(shown) > maxReportedReferencingQueues { | ||
| shown = shown[:maxReportedReferencingQueues] | ||
| } | ||
| return nil, status.Errorf( | ||
| codes.FailedPrecondition, | ||
| "retry policy %s is still referenced by %d queue(s), including: %s", | ||
| req.Name, len(referencing), strings.Join(shown, ", "), | ||
| ) | ||
| } | ||
|
|
||
| err = s.repository.DeleteRetryPolicy(ctx, req.Name) | ||
| if err != nil { | ||
| return nil, status.Errorf(codes.Unavailable, "error deleting retry policy %s: %s", req.Name, err) | ||
| } | ||
| return &types.Empty{}, nil | ||
| } |
There was a problem hiding this comment.
TOCTOU race: queue reference check and delete are not atomic
queuesReferencingPolicy reads the current queue list and then, in a separate operation, DeleteRetryPolicy removes the row. Between these two calls another process could create or update a queue that references the policy, and the delete would proceed, leaving that queue pointing at a non-existent policy. The scheduler cache would then return (nil, false) for that queue's policy name, silently disabling its retry rules. Making the guard transactional (or at minimum adding a database-level foreign key) would close this window.
Signed-off-by: Dejan Zele Pejchev <pejcev.dejan@gmail.com>
Signed-off-by: Dejan Zele Pejchev <pejcev.dejan@gmail.com>
bd38227 to
003f623
Compare
Stacked on #4998. This PR targets
masterfor review, so its diff currently includes #4998's commit as well. The changes that belong to this PR are the most recent commit; the isolated diff is at dejanzele/armada@retry-policy-engine...retry-policy-api. Please merge #4998 first.Implements the
RetryPolicyServicewhose proto was defined in #4998: create, get, update, and delete for retry policies, backed by a Postgres repository and a new lookout schema migration for theretry_policytable. Deletion is rejected while any queue still references the policy.Adds the client package and armadactl support:
armadactl create/get/update/delete retry-policy, and a--retry-policyflag onarmadactl create queueandarmadactl update queueto attach a policy to a queue. Addscreate_retry_policy,update_retry_policy, anddelete_retry_policypermissions.The one piece of churn worth calling out:
requireGrpcCode, a test helper that three existing server test files each defined privately, is moved into the sharedservertestpackage so the new retry policy service test can reuse it. The three existing tests (executor,node,queue) now import it from there instead of defining their own copies.