Skip to content

drenv: Add dependency-based addon scheduler#2412

Draft
nirs wants to merge 2 commits intoRamenDR:mainfrom
nirs:addon-scheduler
Draft

drenv: Add dependency-based addon scheduler#2412
nirs wants to merge 2 commits intoRamenDR:mainfrom
nirs:addon-scheduler

Conversation

@nirs
Copy link
Member

@nirs nirs commented Feb 8, 2026

Dependency-based addon scheduler

Replace the rigid worker-group model with dependency-based scheduling.
Each cluster gets its own scheduler that runs addons in parallel based
on their dependencies, with cross-cluster notifications handled by a
simple pubsub mechanism. This eliminates wasted worker slots, enables
accurate timeouts, and lets addons start as soon as their dependencies
are satisfied.

PubSub

Simple thread-safe publish-subscribe for decoupled notifications.
Subscribers register callbacks for specific keys, and publishers post
keys to notify all subscribers. Used to signal cross-cluster addon
completion without schedulers knowing about each other.

Scheduler

Runs tasks based on dependencies with configurable concurrency. Tasks
with no unmet dependencies run in parallel up to max_workers. When a
task completes, newly unblocked tasks are scheduled automatically.

Tasks carry a Key(context, name) for identification and logging, a
set of dependency keys, and opaque data passed to the run function.
Validation catches empty input, unknown dependencies, and cycles at
construction time.

How they work together

Each cluster gets its own scheduler with max_workers matching the
cluster's capacity. Cross-cluster dependencies (e.g. dr1/ocm-cluster
requires hub/ocm-hub) are handled by pubsub:

  1. Before starting schedulers, inspect task lists for external deps
    and subscribe to the relevant pubsub keys.
  2. Each scheduler has a watcher thread that sleeps on a queue.
  3. When an addon completes, it posts to pubsub. Subscribers receive
    the key via their queue.
  4. The watcher wakes up, its future completes, and the scheduler
    picks up the newly ready task.

This keeps each scheduler simple -- it just checks _completed for
all deps. External deps arrive via notifications instead of local
task completion, but the scheduler doesn't know the difference.

Example: waiting for a cluster

Currently, addons like ocm-cluster and submariner call
cluster.wait_until_ready() to poll another cluster's status,
occupying a worker slot while sleeping. With the scheduler, this
becomes a dependency on a "ready" key posted when cluster startup
completes:

# dr1 tasks
- name: ocm-cluster
  requires:
    - ["hub", "ready"]

The scheduler won't start dr1/ocm-cluster until hub/ready is
posted via pubsub. No polling, no wasted slots -- the task stays
pending until the hub cluster is actually ready.

Integration with current code

The current model uses nested ThreadPoolExecutors with rigid worker
groups. The main executor starts clusters in parallel, each cluster
runs its own per-cluster executor with addon groups.

The new model replaces per-cluster executors with schedulers:

main executor (clusters in parallel)
  └─ start_cluster(profile):
       provider.start(profile)
       provider.configure(profile)
       scheduler = Scheduler(context, tasks, max_workers=...)
       scheduler.run(run_task)

This preserves the executor-per-cluster model while replacing rigid
worker groups with dependency-based scheduling.

Next steps

  • Add context parameter to scheduler for detecting external deps
    and simplifying validation and logging.
  • Add watcher thread to scheduler for waking up when external deps
    are satisfied via pubsub notifications.
  • Express addon dependencies in per-addon YAML files.
  • Update environment YAMLs -- replace worker lists with a single
    list of addons per profile.
  • Replace per-cluster executors with schedulers in __main__.py.
  • Move global workers (rbd-mirror, volsync) to the hub scheduler.
    They depend on addons from other clusters and start as soon as their
    dependencies are satisfied via pubsub.
  • Handle error propagation across schedulers.
  • Similar treatment for do_stop (reverse dependency order).

@nirs nirs force-pushed the addon-scheduler branch 7 times, most recently from c1d7570 to 17af995 Compare February 10, 2026 18:51
nirs added 2 commits February 10, 2026 21:31
When using per-cluster schedulers, addons on one cluster may depend
on addons completing on another cluster (e.g. ocm-cluster on dr1
requires ocm-hub on hub). The PubSub class provides a simple
notification mechanism for this: schedulers subscribe to external
dependency keys, and when an addon completes, it posts a
notification to all subscribers.

Callbacks are invoked outside the lock, so subscribers can use
queue.put() to wake up a blocking watcher thread without holding
the lock.

Assisted-by: Cursor/Claude Opus 4.6
Signed-off-by: Nir Soffer <[email protected]>
Add a simple task scheduler that runs tasks based on dependencies.
Tasks with no unmet dependencies run in parallel, up to a configurable
concurrency limit (max_workers). When a task completes, newly unblocked
tasks are scheduled automatically.

The scheduler is generic - it does not know about addons or clusters.
It works with Task objects that carry a Key (context + name), a list
of dependency keys, and opaque data passed to the run function.

Tasks from different contexts (e.g. hub, dr1) can be mixed in the
same task list and depend on each other. Per-cluster separation and
cross-cluster notifications are handled externally using pubsub.

The key types:

    Key(context, name)  - unique task identifier, context used for
                          log message prefixes
    Task(key, requires, data) - unit of work with dependencies

Internal state uses three stages: pending (ordered dict), running
(mapped by future), completed (set of keys). The _validate method
builds the pending map and checks for empty input, unknown
dependencies, and cycles.

Assisted-by: Cursor/Claude Opus 4.6
Signed-off-by: Nir Soffer <[email protected]>
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.

1 participant