Skip to content

feat(defrag): EtcdDefragPolicy — scheduled EtcdDefrag runs - #363

Open
Andrey Kolkov (androndo) wants to merge 2 commits into
feat/defrag-and-capacity-metricsfrom
feat/etcd-defrag-policy
Open

feat(defrag): EtcdDefragPolicy — scheduled EtcdDefrag runs#363
Andrey Kolkov (androndo) wants to merge 2 commits into
feat/defrag-and-capacity-metricsfrom
feat/etcd-defrag-policy

Conversation

@androndo

Copy link
Copy Markdown
Collaborator

Adds a CronJob-style EtcdDefragPolicy so the operator drives recurring defragmentation itself, instead of relying on an external CronJob/GitOps cron to create EtcdDefrag objects. This is the follow-up flagged as planned in the EtcdDefrag docs.

Stacked on #361 (the EtcdDefrag controller) — base is feat/defrag-and-capacity-metrics. The diff here is the policy kind + its controller; the runs it stamps are the EtcdDefrag from #361. Merge #361 first; GitHub will retarget this to main.

What it does

Reconciles an EtcdDefragPolicy as a scheduler that stamps out EtcdDefrag runs on a cron cadence:

  • Schedule — standard five-field cron, evaluated in UTC (a CRON_TZ=<zone> prefix is honoured). On each due tick it creates one EtcdDefrag, owned by the policy (cascades on delete) and labelled with the policy name.
  • The stamped run carries the policy's rule and ttlSecondsAfterFinished, then follows all of feat(defrag): EtcdDefrag controller #361's safety rules — per-cluster serialization, the health gate (incl. NOSPACE handling), followers-before-leader. The policy only triggers; it never defragments directly.
  • concurrencyPolicyForbid (default: skip a tick while a stamped run is still active) or Allow (stamp anyway; EtcdDefrag's own serialization queues it).
  • suspend pauses stamping (missed ticks are not backfilled); startingDeadlineSeconds skips a tick already older than the deadline (e.g. after downtime) rather than starting it late; historyLimit trims retained finished runs (the oldest go first), complementing per-run TTL.
  • A long backlog after the operator was down is collapsed into a single run, not replayed slot-by-slot.

status.lastScheduleTime anchors the next tick (so a tick is never acted on twice); status.lastSuccessfulTime records the last Complete; status.active lists in-flight runs; an Active condition carries Suspended/InvalidSchedule reasons.

Adds the API type + generated CRD/deepcopy/RBAC; the controller (wired in main.go, watches its owned EtcdDefrags); github.com/robfig/cron/v3 (what k8s CronJob uses). No changes to EtcdCluster/EtcdDefrag behavior.

Usage

apiVersion: etcd-operator.cozystack.io/v1alpha2
kind: EtcdDefragPolicy
metadata:
  name: nightly
  namespace: team-a
spec:
  clusterRef:
    name: etcd
  schedule: "0 3 * * *"        # UTC
  concurrencyPolicy: Forbid
  ttlSecondsAfterFinished: 3600
  historyLimit: 3
  rule:
    freeSpaceAbove: 200Mi
    quotaUsageAbove: 80%
    minReclaim: 32Mi

Tests

  • Unit: schedule parsing (UTC forcing, invalid input) and the next-tick math (not-yet-due, most-recent-boundary, backlog collapse).
  • Controller-integration (fake client, injected clock): stamps a run when due (owner ref + labels + rule/ttl propagated, lastScheduleTime set); not-due requeues without stamping; suspend reports Suspended; an unparseable schedule reports InvalidSchedule; Forbid skips while a run is active; Allow stamps concurrently; historyLimit GCs the oldest finished runs.

go build / go vet / go test ./... / gofmt green; CRD/RBAC/deepcopy regenerated (codegen-drift clean).

Refs #221.

🤖 Generated with Claude Code

Adds a CronJob-style `EtcdDefragPolicy` so the operator drives recurring
defragmentation itself, instead of relying on an external CronJob to create
`EtcdDefrag` objects (the follow-up flagged as "planned" in the EtcdDefrag docs).

The policy reconciler stamps out `EtcdDefrag` runs on a five-field cron schedule
(evaluated in UTC; a CRON_TZ prefix is honoured). Each stamped run is owned by
the policy (cascades on delete) and labelled with the policy name, and carries
the policy's `rule` and `ttlSecondsAfterFinished` — the run then follows all of
EtcdDefrag's existing safety rules (per-cluster serialization, health gate,
followers-before-leader), so the policy only triggers runs and never
defragments directly.

Spec: `schedule`, `clusterRef`, `rule`, `ttlSecondsAfterFinished`, `suspend`,
`concurrencyPolicy` (Forbid default / Allow), `startingDeadlineSeconds`, and
`historyLimit`. Status: `lastScheduleTime` (anchors the next tick so one is
never acted on twice), `lastSuccessfulTime`, `active`, and an `Active` condition
carrying Suspended / InvalidSchedule reasons. A long backlog after downtime is
collapsed into a single run rather than replayed.

Adds the API type + generated CRD/deepcopy/RBAC, the controller (wired in
main.go, watches its owned EtcdDefrags), robfig/cron/v3, unit + controller
tests (schedule parsing/next-tick math, stamp-when-due, not-due, suspend,
invalid schedule, Forbid/Allow concurrency, history GC), and docs.

Stacked on the EtcdDefrag controller PR.

Refs #221.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
@github-actions github-actions Bot added api-change controllers documentation Improvements or additions to documentation feature New feature or request labels Aug 20, 2026
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7d97ff0-e878-4e11-b4cd-6701e901f6fe

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The controller skips a tick older than StartingDeadlineSeconds — emitting
MissedSchedule and consuming the tick without stamping a run — but the case
was untested. Add a missed-deadline case (tick past the window: no run, tick
still consumed) and a within-deadline mirror (run stamped normally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-change controllers documentation Improvements or additions to documentation feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant