Skip to content

feat: add scheduled and recurring job support - #112

Open
ashtarkb wants to merge 1 commit into
openshift-psap:mainfrom
ashtarkb:feat/scheduled-and-recurring-jobs
Open

feat: add scheduled and recurring job support#112
ashtarkb wants to merge 1 commit into
openshift-psap:mainfrom
ashtarkb:feat/scheduled-and-recurring-jobs

Conversation

@ashtarkb

Copy link
Copy Markdown
Contributor

Summary

  • Adds spec.scheduledStartTime (ISO 8601 datetime) to defer a one-time job execution to a future time. The job enters a new Scheduled phase and automatically transitions to Resolving once the time is reached.
  • Adds spec.schedule (cron expression, e.g. 0 20 * * *) to turn a FournosJob into a recurring template. The operator creates child FournosJob CRs on each cron tick, labeled with fournos.dev/recurring-parent for querying.
  • Both fields are optional and mutually exclusive. When neither is set, existing behavior is completely unchanged.

Changes

File Change
manifests/crd.yaml Added scheduledStartTime, schedule, lastScheduledTime fields; Scheduled/Recurring phases; printer columns
fournos/core/constants.py Added Phase.SCHEDULED, Phase.RECURRING, LABEL_RECURRING_PARENT
fournos/handlers/lifecycle.py Added reconcile_scheduled, reconcile_recurring, scheduling gate in on_create, cron validation
fournos/operator.py Added Scheduled/Recurring to timer filter and reconcile routing
fournos/handlers/__init__.py Exported new handlers
pyproject.toml Added croniter>=5.0 dependency

Example: one-time scheduled job

apiVersion: fournos.dev/v1
kind: FournosJob
metadata:
  generateName: deferred-bench-
spec:
  owner: perf-team
  cluster: cluster-1
  scheduledStartTime: "2026-08-18T15:00:00Z"
  executionEngine:
    forge:
      project: llm-d
      args: [cks]

Example: recurring job (every day at 20:00 UTC)

apiVersion: fournos.dev/v1
kind: FournosJob
metadata:
  name: nightly-llm-bench
spec:
  owner: perf-team
  cluster: cluster-1
  schedule: "0 20 * * *"
  executionEngine:
    forge:
      project: llm-d
      args: [cks]

Query all child runs: kubectl get fjobs -l fournos.dev/recurring-parent=nightly-llm-bench

Test plan

  • Create a FournosJob without scheduledStartTime or schedule — verify existing behavior unchanged
  • Create a FournosJob with scheduledStartTime set 1 minute in the future — verify it enters Scheduled phase and transitions to Resolving after the time passes
  • Create a FournosJob with scheduledStartTime in the past — verify it starts immediately
  • Create a FournosJob with an invalid schedule expression — verify it fails with a clear error
  • Create a FournosJob with both scheduledStartTime and schedule — verify mutual exclusivity validation
  • Create a FournosJob with schedule: "*/1 * * * *" — verify child jobs are created every minute with the fournos.dev/recurring-parent label
  • Delete a recurring parent job — verify no more children are created

Made with Cursor

@openshift-ci

openshift-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign tosokin for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f9ea672-8409-479b-b021-a00d21e9fdf6


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.

@ashtarkb
ashtarkb force-pushed the feat/scheduled-and-recurring-jobs branch 2 times, most recently from 7a705f1 to 0811d13 Compare August 18, 2026 09:26
Comment thread fournos/handlers/lifecycle.py Outdated
CRD_GROUP, CRD_VERSION, namespace, "fournosjobs", child_body,
)
child_name = created["metadata"]["name"]
patch.status["lastScheduledTime"] = now.strftime("%Y-%m-%dT%H:%M:%SZ")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe an annotation, so that we can wipe it easily to retrigger ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

forget, status is better

maybe we can have an annotation triggerNow: false that we can edit to triggerNow: true
when the controller sees it, it triggers the job and changes the annotation back to triggerNow: false

Comment thread fournos/handlers/lifecycle.py Outdated
child_spec.pop("schedule", None)
child_spec.pop("scheduledStartTime", None)

safe_name = name[:40]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why 40 chars? 🤔

Comment thread fournos/handlers/lifecycle.py Outdated
Comment on lines +156 to +162
try:
ts = datetime.fromisoformat(raw)
if ts.tzinfo is None:
ts = ts.replace(tzinfo=UTC)
return ts
except (ValueError, TypeError):
return None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not sure about this silent catch 🤔
should make the fjob FAILED if the timestamp is invalid

@ashtarkb
ashtarkb force-pushed the feat/scheduled-and-recurring-jobs branch 3 times, most recently from 25632e0 to afe62f5 Compare August 18, 2026 10:06
@ashtarkb

Copy link
Copy Markdown
Contributor Author

/test ?

@ashtarkb

Copy link
Copy Markdown
Contributor Author

/test deploy-fournos-wip

1 similar comment
@ashtarkb

Copy link
Copy Markdown
Contributor Author

/test deploy-fournos-wip

Add two new optional fields to the FournosJob CRD:

- `spec.scheduledStartTime` (ISO 8601): defers job execution until a
  specific time. The job enters a new `Scheduled` phase and the operator
  transitions it to `Resolving` once the time is reached. When omitted,
  the job starts immediately (existing behavior unchanged).

- `spec.schedule` (cron expression): turns the FournosJob into a
  recurring template in `Recurring` phase. The operator creates child
  FournosJob CRs on each cron tick, labeled with
  `fournos.dev/recurring-parent` for easy querying. Requires the
  `croniter` dependency.

The two fields are mutually exclusive. Both features build on the
existing 5-second kopf timer loop — scheduled jobs do a lightweight
datetime comparison, recurring jobs use croniter to determine the next
fire time.

New CRD additions:
- spec.scheduledStartTime, spec.schedule
- status.lastScheduledTime (for recurring jobs)
- Scheduled and Recurring phases
- Printer columns for scheduled time and cron expression

Co-authored-by: Cursor <cursoragent@cursor.com>
@ashtarkb
ashtarkb force-pushed the feat/scheduled-and-recurring-jobs branch from afe62f5 to 2fe3aaf Compare August 18, 2026 15:42
@ashtarkb

Copy link
Copy Markdown
Contributor Author

/test deploy-fournos-wip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants