[Project Sync] Add follower startup-sync trigger to Orca by daanvdk-mck · Pull Request #10089 · mlrun/mlrun · GitHub
Skip to content

[Project Sync] Add follower startup-sync trigger to Orca - #10089

Merged
daanvdk-mck merged 4 commits into
mlrun:feature/project-sync-followerfrom
daanvdk-mck:feature/ML-12905-startup-sync-resource-blocking
Sep 24, 2026
Merged

daanvdk-mck merged 4 commits into
mlrun:feature/project-sync-followerfrom
daanvdk-mck:feature/ML-12905-startup-sync-resource-blocking

Conversation

@daanvdk-mck

@daanvdk-mck daanvdk-mck commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

📝 Description

Follower Contract HLD checklist item 10 (ML-12905): on boot (enterprise mode, leader=orca), MLRun triggers Orca's leader-side reconciliation sweep for itself (POST /api/v1/projects/followers/sync, MLRun's own SA — not a user-identity relay) and polls the resulting Trackable Action to a terminal state. MLRun never reads a states list or computes its own diff — Orca pushes truth back onto MLRun's existing /follower/projects/* surface (ML-12901) while the sweep runs.

Only the chief triggers the sweep. Workers converge on the same readiness signal by asking the chief (new GET /api/v1/followers-sync-status, mirroring the existing clusterization-spec chief-proxy pattern) instead of each independently triggering their own, redundant sweep against Orca.

Scope note: this PR is the trigger/poll machinery only. It tracks readiness (a threading.Event, is_followers_sync_ready()) but nothing consumes it for resource-creation blocking yet — the gate that reads this signal (checklist item 9, plus the blocking half of item 10) is a separate, follow-up PR, stacked on this branch, to be opened once this merges.

Verified live end-to-end against a real implementation of orca's trigger endpoint (feature/ORIS-3532-trigger) on a real lab: the full chain — chief trigger → real Orca sweep → real callbacks into MLRun's own /follower/projects/* endpoints → poll → chief readiness → worker-proxied readiness — all confirmed working. Two wire-format bugs (explicitly flagged as unverified guesses in the original code) were found and fixed this way: the trigger response field is correlationId (top-level), not opId/status.opId; the real action type is "follower-sync", not "sync-followers".

Note: the ticket text describes startup-sync as a paginated GET /api/v1/project-states pull-and-diff. That endpoint doesn't exist and contradicts the current, decided HLD (same class of stale-ticket-text issue ML-12903 hit) — this PR implements the HLD's actual, current design instead.


🛠️ Changes Made

  • framework/utils/clients/iguazio/v4.py: new Client.trigger_followers_sync() (SA-authenticated POST + poll-to-terminal, mirrors the existing project-CUD trigger/poll pattern) and its supporting _send_self_authenticated_request/_wait_for_followers_sync/_verify_followers_sync_terminal.
  • framework/utils/projects/follower.py: chief-only _start_periodic_followers_sync_trigger() and worker-only _start_periodic_followers_sync_wait_for_chief(), both registered from start() via the existing framework.utils.periodic.run_function_periodically infra (the same mechanism _start_periodic_sync and framework/service/__init__.py's _start_chief_clusterization_spec_sync_loop already use for analogous chief/worker background polling) instead of a hand-rolled threading.Thread + retry loop. Backoff-with-logging on failure comes for free from that infra; each function cancels its own periodic slot once it succeeds. New is_followers_sync_ready() accessor.
  • framework/utils/projects/member.py: is_followers_sync_ready() no-op-true default on the abstract base, so CE/leader mode (and any polymorphic caller that doesn't know which mode is running) is unaffected.
  • framework/utils/clients/chief.py: new is_followers_sync_ready() — asks the chief via the new endpoint.
  • services/api/api/endpoints/followers_sync_status.py (new): GET /followers-sync-status, chief answers locally, worker re-routes to chief. Registered in api.py; added to the verify_api_state allowlists in deps.py alongside clusterization-spec (same "clusterization purposes" category).

✅ Checklist

  • I updated the documentation (if applicable)
  • I have tested the changes in this PR
  • I confirmed whether my changes are covered by system tests
    • If yes, I ran all relevant system tests and ensured they passed before submitting this PR
    • I updated existing system tests and/or added new ones if needed to cover my changes
  • If I introduced a deprecation:
  • Please run Smoke-tests workflow, providing it the PR number as input (upon success, the workflow run will add label "Smoke tests: Pass" to the PR)

🧪 Testing

  • New unit tests: test_iguazio_v4.py (+4: trigger success/SA-auth, missing-correlationId, poll-timeout, action-failed), test_follower_member.py (+8: non-orca-leader-ready-immediately, chief/worker initialize()+start() integration, trigger sets-ready-and-cancels-on-success, trigger leaves-pending-on-failure, wait-for-chief sets-ready-and-cancels-when-ready, wait-for-chief stays-pending-when-not-ready, is_followers_sync_ready reflects the event), test_chief.py (+1: is_followers_sync_ready round-trip).
  • Live end-to-end verification on a real lab against orca's feature/ORIS-3532-trigger: full trigger → sweep → callback → poll → readiness chain confirmed working, including the chief/worker readiness-consistency fix (both report identical readiness, worker via the new chief-proxy endpoint, not an independent check).

🔗 References


🚨 Breaking Changes?

  • Yes (explain below)
  • No

🔍️ Additional Notes

  • Cross-repo dependency, now resolved for testing purposes: Orca's /api/v1/projects/followers/sync endpoint didn't exist on feature/project-sync when this PR was originally opened; it now exists on orca's feature/ORIS-3532-trigger branch and was used for the live verification above. Still not merged to orca's mainline.
  • Found, root-caused, and deliberately deferred a separate bug (already-merged ML-12901/ML-12904 code, out of scope for this PR): commit_delete_project's underlying delete_project call unconditionally blocks up to 300s waiting for Nuclio to confirm a project's deletion whenever nuclio_dashboard_url is configured, regardless of leader mode. In leader=orca, Nuclio is a separate peer follower Orca drives independently — MLRun's own follower-delete hook shouldn't wait on it. To be fixed in a separate session/PR.
  • Follow-up PR: the resource-creation-blocking gate (checklist item 9 + the blocking half of item 10) is stacked on this branch as feature/ML-12905-resource-creation-gate; its mlrun/mlrun PR will be opened once this one merges (GitHub won't let a cross-repo PR base on a fork-only branch).
  • Base branch is feature/project-sync-follower (this epic's integration branch), not development.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Follower Contract HLD checklist item 10 (ML-12905): on boot (leader=orca),
MLRun triggers Orca's leader-side reconciliation sweep for itself via a new
SA-authenticated outbound call, then polls the resulting Trackable Action to
a terminal state. Runs in a background thread with backoff-retry - never
blocks FastAPI startup, since the sweep calls back into MLRun's own
/follower/projects/* routes while it runs.

Nothing consumes the resulting readiness signal yet (see the follow-up
resource-creation-gate PR) - this PR is the trigger/poll machinery only.
@daanvdk-mck
daanvdk-mck force-pushed the feature/ML-12905-startup-sync-resource-blocking branch from 03300a0 to 5ccd3e7 Compare September 9, 2026 08:21
@daanvdk-mck daanvdk-mck changed the title [Project Sync] Add follower startup-sync trigger and resource-creation gate [Project Sync] Add follower startup-sync trigger to Orca Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Tests with the PR synced to feature/project-sync-follower HEAD b8f1c6bc: Fail ❌

Only the chief now triggers Orca's followers-sync sweep and polls it to
completion - workers no longer each independently trigger their own,
redundant sweep. Workers converge on the same readiness signal by asking
the chief (new GET /api/v1/followers-sync-status, mirroring the existing
clusterization-spec chief-proxy pattern) instead, caching the result once
true since readiness is monotonic.

This replaces the previous per-replica-independent design, which had two
real problems: N redundant trigger calls to Orca per boot event, and
inconsistent resource-creation gating across replicas depending on which
one happened to finish its own trigger first.
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Tests with the PR synced to feature/project-sync-follower HEAD b8f1c6bc: Fail ❌

Confirmed live against orca's feature/ORIS-3532-trigger branch: the trigger
response carries `correlationId` at the top level, not `opId`/`status.opId`
as originally guessed, and the published action type is "follower-sync",
not "sync-followers". Both were explicitly-flagged unverified guesses in
the original code/comments - fixed now that a real implementation exists
to check against. Renamed the local variable op_id -> correlation_id
throughout for accuracy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Tests with the PR synced to feature/project-sync-follower HEAD b8f1c6bc: Pass ✅

@daanvdk-mck
daanvdk-mck marked this pull request as ready for review September 14, 2026 08:28
@daanvdk-mck
daanvdk-mck requested review from a team and quaark as code owners September 14, 2026 08:28
…ction

Replace the hand-rolled threading.Thread + while/sleep/try-except retry
loops with framework.utils.periodic.run_function_periodically - the same
infra follower.Member already uses for _start_periodic_sync, and the
pattern framework/service/__init__.py uses for the analogous
worker-polls-chief case (_start_chief_clusterization_spec_sync_loop).

This gets backoff and failure logging for free from the periodic
wrapper instead of duplicating it, moves the trigger registration from
initialize() (no running event loop there) to start() (already on the
event loop, alongside _start_periodic_sync), and lets each function
cancel its own periodic slot once it succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Tests with the PR synced to feature/project-sync-follower HEAD b8f1c6bc: Pass ✅

@github-actions

Copy link
Copy Markdown
Contributor

@daanvdk-mck
daanvdk-mck merged commit ffcc425 into mlrun:feature/project-sync-follower Sep 24, 2026
16 checks passed
daanvdk-mck added a commit that referenced this pull request Sep 24, 2026
…10103)

### 📝 Description
Follower Contract HLD checklist item 9 (ML-12905): while this MLRun
follower's project state isn't trustworthy, block creation of new
logical resources (functions, API gateways) against it. Two conditions
gate creation: the follower's own startup sync with the leader hasn't
reached a terminal state yet (checklist item 10's readiness signal,
`is_followers_sync_ready()`, built in #10089), or the specific project's
own `status.state` is `creating`/`deleting`.

---

### 🛠️ Changes Made
- `framework/utils/projects/follower.py`: new
`Member.ensure_project_open_for_resource_creation()`. Raises
`MLRunPreconditionFailedError` if `is_followers_sync_ready()` is false,
or if the project's `status.state` is `creating`/`deleting`. A project
with no recorded state yet (pre-dates the 2PC follower interface) is
treated as online, not blocked.
- `framework/utils/projects/member.py`: no-op default on the abstract
base — MLRun-as-leader (CE) drives project state directly and
synchronously, so there's no window where a project sits non-online
while a caller races to create resources against it.
- `services/api/api/endpoints/functions.py` / `nuclio.py`: wired the
gate into `store_function`, `build_function`, `start_function`,
`store_api_gateway` (added a `db_session` dependency), and
`deploy_function`.

---

### ✅ Checklist
- [ ] I updated the documentation (if applicable)
- [x] I have tested the changes in this PR
- [ ] I confirmed whether my changes are covered by system tests
- [ ] If yes, I ran all relevant system tests and ensured they passed
before submitting this PR
- [ ] I updated existing system tests and/or added new ones if needed to
cover my changes
- [ ] If I introduced a deprecation:
  - [ ] I followed the [Deprecation Guidelines](./DEPRECATION.md)
  - [ ] I updated the relevant Jira ticket for documentation
- [ ] Please run
[Smoke-tests](https://github.com/mlrun/mlrun/actions/workflows/smoke-tests.yml)
workflow, providing it the PR number as input (upon success, the
workflow run will add label "Smoke tests: Pass" to the PR)

---

### 🧪 Testing
- New unit tests: `test_follower_member.py` (+4:
blocks-while-startup-sync-pending, blocks-non-online-project,
allows-online-project, allows-unset-state), `test_leader_member.py` (+1:
is-noop-for-leader).
- Live end-to-end verification on a lab patched with this branch (at the
time, stacked on #10089's pre-merge branch) against a real Orca
implementation: confirmed the gate blocks resource creation while a
project is mid-sync and clears once state settles.

---

### 🔗 References
- Ticket link: https://ecliptos.atlassian.net/browse/ML-12905
- Design docs links: [Follower Contract
HLD](https://ecliptos.atlassian.net/wiki/spaces/IRG/pages/654737417)
(checklist item 9) · [Followers
page](https://ecliptos.atlassian.net/wiki/spaces/IRG/pages/661979181)
(MLRun §1)

---

### 🚨 Breaking Changes?

- [ ] Yes (explain below)
- [x] No

---

### 🔍️ Additional Notes
- Base branch is `feature/project-sync-follower` (this epic's
integration branch), not `development`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants