myxce/.kiro/steering/resource-questions.md
2026-10-12 04:28:06 +00:00

22 KiB
Raw Blame History

Resource Design: Decisions, Proposals and Open Questions

The persistent discussion log for the unified resource model (resource-model.md) and the provisioning work (resource-provisioner, provisioning-demand, elastic-*). Specs must not settle an item here silently.

How to use:

  • Status values: open (not discussed), discussing (user input recorded, not final), leaning (direction agreed, details open), decided, deferred (later, with a reason).
  • Changes: update an item in place and add a dated line under "History". When an item is decided, also update the documents it names.
  • IDs: R = resource model, P = provisioner, N = new questions raised in discussion, PR = proposals.

Proposals under discussion (user, 2026-10-11)

PR-1: one resource map for all supply, including provisioned supply (status: leaning)

  • User: should resource-distribution-mapping be made generic, so it also handles resource provisioning?
  • Analysis (2026-10-11): yes, for the data model.
    • The resource map becomes the single record of supply: for each resource, which locations (host, host group, pool, storage) hold how much.
    • Each entry has a source (static config, reporter, provisioner) and a state (requested, available, draining, releasing).
    • Provisioning only changes map entries over time, e.g. a license pool's base plus borrowed amount, or a new dataset copy.
    • The provisioning mechanism stays in the provisioner: policy, provider calls, leases, epochs, orphan scans. The map is the state; the provisioner is the actuator. This is the Kubernetes split too (drivers publish ResourceSlices; autoscalers and provisioners act).
    • The scheduler keeps reading only the map's cached snapshot.
  • Affects: resource-distribution-mapping, elastic-shared-resources, resource-provisioner, provisioning-demand.
  • Open: see N1, N2.

PR-2: a resource provider proxy between the controller and upstream providers (status: leaning)

  • User: introduce a resource provider that proxies requests and states between the XCE controller and upstream resource providers.
  • Analysis (2026-10-11): yes. It generalizes the resource-provisioner daemon. The proxy:
    • connects to the controller as one role over libxce/Cap'n Proto;
    • speaks each upstream provider's protocol: the JSON script contract, a persistent JSON-lines mode, later CSI or vendor APIs;
    • holds provider sessions, credentials and rate limits, and translates provider states into map entries and claim states;
    • publishes inventory and values (it is the external reporter of R2);
    • carries out provisioning and claim operations with op_id, an epoch and owner tags.
  • Why it helps: the controller never blocks on or depends on an outside system, and credentials never reach the controller.
  • Where node-local work runs: GPU binding, mounts and scratch stay on the worker, possibly behind the same contract.
  • Affects: resource-provisioner (rename or generalize), custom-resource-reporting (external reporters), resource-claims (storage providers), R4/C3 (the CSI adapter would live here).
  • Open: see N2, N4.

PR-3: datasets in the resource map; staging as data provisioning (status: discussing)

  • User: could data instances be handled by the resource distribution map, and data staging by provisioning the data to a new location?
  • Analysis (2026-10-11): this fits PR-1 well.
    • Datasets: a dataset is a resource whose map entries are the locations holding a copy. Locality is an L1 attribute the scheduler can use.
    • Staging: staging is provisioning a copy at a new location, with a lead time like any provider.
    • Eviction: evicting a copy is a release.
    • Shortage: "dataset X needed at host group Y" can become provisioning demand.
    • Locality, caching and stage-in become one mechanism.
  • Caveats:
    • Job-private data (stage-out results, scratch, a job's own outputs) is still a per-job claim (resource-claims), not shared supply.
    • Datasets need an identity and version, capacity accounting on the storage holding a copy, an eviction policy, and consistency rules (read-only vs updated).
  • Affects: resource-distribution-mapping, custom-resource-reporting (string-set locality may become map entries instead), resource-claims (scope shrinks to job-private data), R9.
  • Open: see N3.

PR-4: hosts as supply entries; the worker connection only confirms them (status: discussing)

  • User: hosts get into the controller directly today; how could they be unified with the rest of the supply?
  • Today: a worker connects to the controller and is accepted only if its name matches a host config entry (find_matched_hostconfig, src/controller/join.c:~76). The host record is created by that first join (init_worker). elastic-hosts plans "expected host" records matched at join.
  • Proposal: separate who decides a host should exist (control plane) from the worker's connection (data plane):
    • Host supply entries: a host is an entry in the supply map with a source and a lifecycle state:

      • config: a static hosts.yaml entry or pattern;
      • provisioned: created by a proxy as an expected host, with instance id, template and deadline;
      • discovered: an unknown host that joins, admitted only if a policy allows it.

      Lifecycle: expected → joining → available → draining → released, plus lost.

    • Join confirms: the worker still connects directly. Job control, heartbeats and resync stay on that long-lived connection. A join confirms an existing entry (or is admitted by policy) instead of being the only way a host appears.

    • Host resources: slots, mem and so on stay L2 counters at the host's location, as today.

    • Bring-up: xce-node-manager can act as the node-side provider that starts the worker on a provisioned machine.

  • Gains: one place for static, elastic (cloud) and discovered hosts. Orphan rules apply uniformly (expected but never joined; joined but unknown). provisioning-demand sees hosts and other resources the same way.
  • Affects: elastic-hosts (its expected-host records become supply entries), resource-distribution-mapping (PR-1), xce-node-manager, the controller join path.

PR-5: simplified dataset model, v1 (status: discussing)

  • User: give a simplified dataset model first.
  • Proposal:
    • Identity: a dataset is name@version and is immutable; a change is a new version. No update consistency problem.
    • Copies: supply-map entries of the dataset at a location (host, host group or storage pool) with a state staging → available → evicting and a size. The size is charged as an L2 counter on the location's cache or scratch capacity.
    • Jobs: a job names datasets, e.g. data[imagenet@3]. The scheduler prefers hosts with an available copy, as a locality attribute from its cached snapshot. v1 does not make placement wait for a copy.
    • Staging: in v1 staging is explicit. A user or admin asks for a copy at a location (xcecli data stage); a proxy provider copies it and reports available. Eviction is explicit too.
    • Later: scheduler-driven staging (shortfall as provisioning demand), automatic LRU eviction, dataset quotas, and writable datasets.
  • Open: whether v1 job placement should require a copy (a hard constraint) or only prefer one; and what the dataset registry is (config, REST object, or both).

PR-6: job input/output through provisioned volumes (status: discussing)

  • User (2026-10-11): use volume provisioning for job input and output.
  • Context:
    • bug-1936 rejects -i/-o/-e, out_dir and transfer_files at submit, because no field carries them to the job and the worker never stages files.
    • bug-1937 defers stage-in/out to a future spec.
    • Worker file fetch is locked to its own output path (bug-1508).
  • Proposal: job I/O as claims (resource-claims):
    • Input claims (read-only): from a dataset copy (PR-5), for large or shared inputs; from files uploaded at submit, which a provider puts into a per-job input volume (the new home of transfer_files/-f); or from an existing standalone claim (model weights, a previous job's output).
    • Output claim (read-write): per job (template) or a named standalone claim.
      • -o/-e and out_dir resolve inside it.
      • Results are fetched through the API server from the provider, not the worker (this removes the bug-1508 restriction).
      • Retention policy: keep until fetched, or N days.
      • Optional export after the job (stage-out) to another volume, object storage or the user's home directory.
    • Placement in the job: the sandbox mounts the claims at fixed points (/xce/in, /xce/out) or at profile-allowed paths. Direct-mode jobs get the paths through environment variables.
    • v1 provider: a directory provider on a shared filesystem. Create is mkdir with an owner and a quota, publish is a bind mount or path, delete is removal. CSI, cloud block and burst-buffer volumes plug into the same contract later.
    • Default: jobs without I/O options keep today's behaviour.
  • Refinement (user, 2026-10-11): a shared volume with per-job subdirectories is the default (status: leaning). Jobs do not each get a volume; several jobs share one: shared_volume/<user>/<jobid>/{in,out,err}, array elements <jobid>/<index>/....
    • The shared volume: a long-lived, admin-provisioned standalone claim (read-write-many, retain). Its capacity is a pool counter in the supply map; the hosts that mount it are an L1 attribute, so placement only uses hosts that reach it.
    • A job's directory: a lightweight template claim of kind "subdirectory". Create is mkdir plus ownership, with no storage call; release is retention or cleanup.
    • Per-job volumes (burst buffer, large scratch) stay possible when a profile asks for one.
    • Settles: N9 (read-write-many, so multi-host jobs work) and N10 (a subdirectory per element). Reframes N8 as per-user or per-project quota on the shared volume (filesystem quota, or usage charged to the pool counter after the fact, consistent with max(reserved, actual)).
    • Prior art: Kubernetes subPath and the nfs-subdir provisioner; the shared output trees common at LSF and Slurm sites.
  • Affects:
    • resource-claims (adds job I/O as its first use, ahead of checkpoints);
    • job-sandbox-execution (mount points);
    • the apiserver submit/fetch path (lifts the bug-1936 rejections once claims exist);
    • application-profile (default I/O claims, allowed paths);
    • PR-5 (inputs from datasets).

CSI notes (2026-10-11, for C3 and R4)

Source: the CSI specification, from general knowledge; to be checked against the current spec version during design.

  • What it is: the Container Storage Interface, a CNCF specification: a gRPC API between a container orchestrator and a storage driver. It is orchestrator-neutral (used by Kubernetes, Nomad, Mesos and Cloud Foundry), though most drivers are written, packaged and tested for Kubernetes.
  • Three services:
    • Identity (every plugin): GetPluginInfo, GetPluginCapabilities, Probe.
    • Controller (runs once, central): CreateVolume/DeleteVolume, ControllerPublishVolume/Unpublish (attach to a node), ValidateVolumeCapabilities, ListVolumes, GetCapacity, CreateSnapshot/DeleteSnapshot/ListSnapshots, ControllerExpandVolume, ControllerGetVolume.
    • Node (one per host, privileged): NodeStageVolume/Unstage (mount once per node at a staging path), NodePublishVolume/Unpublish (bind-mount into the workload's path), NodeGetInfo (node id, max volumes, accessible topology), NodeGetCapabilities, NodeExpandVolume, NodeGetVolumeStats.
  • Key concepts:
    • Volume capability: block or filesystem, plus fs type and mount flags.
    • Access modes: single-node writer or reader, multi-node reader-only, single-writer or multi-writer.
    • Topology: driver-reported segments such as zone=a or rack=r1; CreateVolume takes requisite and preferred topology.
    • Opaque parameters (a storage class); per-call secrets; volume context returned by create.
  • How Kubernetes runs it:
    • The controller plugin runs as a Deployment, with sidecars (external-provisioner, attacher, snapshotter, resizer) that turn PVCs into CSI calls.
    • The node plugin runs as a privileged DaemonSet, with node-driver-registrar, over a unix socket.
    • The sidecars are Kubernetes-specific; the CSI calls themselves are not.
  • Mapping to XCE:
    • Controller side → PR-2 proxy: CreateVolume when a claim or dataset copy is created; ControllerPublish when it is bound to hosts; Delete on release; GetCapacity → pool counters in the map; snapshots → checkpoint snapshots; secrets stay in the proxy.
    • Node side → worker/xcecd: NodeStage on first use on a host; NodePublish into the job's sandbox (or container) path; unpublish and unstage at job end. NodeGetInfo topology → host attributes (L1).
    • Timing: CSI topology plus "create after placement" matches the rule that the scheduler only selects (Kubernetes WaitForFirstConsumer).
    • Access modes map one-to-one onto claim access modes.
  • Costs:
    • gRPC clients: natural in Go (the proxy; P2), awkward in C (the worker). A small Go node helper, started with posix_spawn or run under xce-node-manager, could make the node calls.
    • The node plugin is a privileged daemon on every host, needing mount propagation.
    • Some drivers assume Kubernetes APIs or container packaging, so each target driver needs testing outside Kubernetes.
  • Relevant drivers:
    • HPC parallel filesystems: IBM Spectrum Scale/GPFS CSI, BeeGFS CSI, Lustre (e.g. AWS FSx, DDN), WEKA, VAST.
    • General storage: NFS, Ceph RBD/CephFS, NetApp Trident, Portworx, cloud disks (EBS, GCE PD, Azure Disk), local path.
    • Object storage: S3 via Mountpoint for S3 or JuiceFS.
    • Dataset caching: Fluid (Alluxio/JuiceFS), a Kubernetes dataset-cache project relevant to PR-5.
  • Recommendation:
    • Make XCE's own claim and provider contract CSI-shaped: the same verbs (create, publish, stage, node publish, capacity, snapshot, expand), capabilities, access modes, topology and per-call secrets.
    • v1 ships simple native JSON providers (local scratch, NFS or parallel-filesystem directories).
    • Add a CSI adapter later as one provider type in the proxy, plus a node helper for the worker.
    • That keeps CSI a cheap "must later" rather than a v1 cost. If containers (R6) or cloud block storage become early targets, CSI moves up.

Resource model questions (from resource-model.md)

ID Question Status User input / notes
R1 Phase 1 scope: shared pools only (option 2) with runtime values split out, or option 3 in one spec? discussing Reframed by PR-1: phase 1 may become the generic supply map. Decide after PR-1.
R2 Who reports runtime custom values: worker scripts, a reporter role, or both? leaning Leaning both: worker scripts (elim-like, posix_spawn) for host values; the PR-2 proxy for external and cluster values.
R3 Are claims first-class objects owned by a project from v1? Do they count against quotas? deferred User 2026-10-11: RBAC is handled in the API server; the backend only provides configuration and state persistence. Discuss later (with N4).
R4 Own JSON driver contract, or CSI and device-plugin wire compatibility? leaning User 2026-10-11: CSI compatibility is better. Still to decide whether it is nice-to-have or a must in future (C3). Either way, model the operations on CSI so an adapter in the PR-2 proxy is mechanical.
R5 Topology depth: host plus switch block, or per-GPU/NIC graphs? leaning User 2026-10-11: build GPU/topology into the scheduler first, with the capability to keep improving it. Means resource-instances delivers an extensible inventory model plus an allocator plugin interface, starting simple.
R6 OCI containers as an execution mode? leaning User 2026-10-11: possible through the application profile (it already reserves a container mode). Profile defines image, runtime, mounts and device passthrough.
R7 Who changes MIG layouts: a provider or an admin? open
R8 Honour rusage duration/decay? decided User 2026-10-11: must-have features, handled in a future spec (not the current phase).
R9 Where does data staging live? discussing PR-3: shared datasets are provisioned copies in the map; job-private staging is a claim.

Provisioner questions (from .kiro/specs/resource-provisioner/research.md)

ID Question Status Notes
P1 Specify the persistent-provider JSON-lines format now (not in v1)? open Leaning yes, so v1 schemas carry over.
P2 Is Go via CGO into libxce acceptable for the provisioner/proxy daemon? open Leaning yes (apiserver pattern; cloud SDKs native).
P3 Must peer authentication (per-role opcode allowlist) ship before the provisioner? open Related to N4: component authentication is separate from user RBAC.
P4 Fix the wire byte order and publish the protocol as stable? open
P5 Are cloud accounts dedicated to the cluster, or shared? open If shared, tagged-but-unknown instances stay report-only.
P6 Automatic return of never-joined instances in v1? open Leaning yes, after a join deadline.
P7 Offer stop (quarantine) as well as terminate? open
P8 Settle window after failover: fixed time, or until expected workers rejoin? open
P9 License server vs XCE in-use counts: which wins? open

New questions raised in discussion

ID Question Status Notes
N1 Does the supply map (PR-1) also cover hosts provisioned by elastic-hosts, or only resources on hosts and pools? discussing User 2026-10-11: hosts join the controller directly today; how could they be unified? Proposal PR-4 below.
N2 One proxy (PR-2) for every provider kind, or one per kind (cloud, license, storage)? How does it map onto the provisioner's per-provider leases? leaning User 2026-10-11: there may be multiple proxies. Matches the provisioner brief: several instances, each provider owned by exactly one active proxy at a time through a controller lease, with standbys taking over. Still open: N2a, N2b.
N2a How does a proxy declare which providers it serves: config pushed by the controller, or self-declared at join (and checked against config)? open
N2b Can one proxy serve several kinds (cloud and storage), or one kind each? open Leaning: any mix; the lease is per provider, not per kind.
N3 Dataset model (PR-3): identity and versions, read-only vs updatable, capacity accounting at each location, eviction policy, who triggers staging (scheduler demand, user, admin)? discussing User 2026-10-11: start with a simplified model. Proposal PR-5 below.
N4 User RBAC in the apiserver vs component authorization in the controller (which daemon roles may call which opcodes): confirm both layers exist and where each check lives. leaning User 2026-10-11: XCE delegates access control to the API server, and the controller trusts it. Implication: the trust is only sound if the controller knows a request really comes from the apiserver (or another legitimate component). Today validate_host() accepts every peer (src/controller/join.c:13-16), so anything that reaches the controller port bypasses RBAC. Component authentication with a per-role opcode allowlist (peer-authentication-hardening, P3) is what makes the delegation safe, especially once proxies can spend money.
N6 PR-6: with I/O claims, should -o/-e/-i and out_dir be accepted again even when no output claim is configured (e.g. a default per-job output directory claim), or stay rejected? open
N7 PR-6: where do uploaded inputs live between submit and job start (provider volume, apiserver staging area), and is there a size limit? open Large inputs should be datasets (PR-5).
N8 PR-6: retention and quota defaults for output claims; who is charged (pool counter per project)? discussing With the shared volume: per-user or per-project quota on that volume (filesystem quota, or usage charged to the pool counter after the fact). Retention: see N14.
N9 PR-6: multi-host jobs: a read-write-many output (shared filesystem), or per-host outputs collected after the job? leaning Read-write-many shared volume (PR-6 refinement).
N10 PR-6: job arrays: one output claim with a subdirectory per element, or one claim per element? leaning Subdirectory per element: <jobid>/<index>/... (PR-6 refinement).
N11 PR-6: requeue and restart: reuse the same output claim or create a new one? How does it relate to checkpoint claims? open
N12 PR-6 shared volume: permissions and creation. <user> 0700 and user-owned under a root-owned top? Is the job directory created by the worker (as root, before dropping privileges) or by a provider? Path-safe encoding of array IDs with brackets? open
N13 PR-6 shared volume: a requeued job keeps its ID. Reuse <jobid>/ or add an attempt subdirectory (<jobid>/attempt-2)? (Refines N11.) open
N14 PR-6 shared volume: retention per job directory (TTL, after fetch), the cleaner (a provider task), and orphan directories whose job no longer exists. open
N15 PR-6 shared volume: with several shared volumes, which one a job uses: by application profile, queue or project? open
C3 CSI compatibility: nice-to-have or a must? If a must, which side: the controller plugin through the proxy, the node plugin on the worker, or both? discussing Background and recommendation in "CSI notes" below (2026-10-11).

History

  • 2026-10-11: user proposed a shared volume with per-user/per-job subdirectories (shared_volume/<user>/<jobid>/{in,out,err}) instead of a volume per job → PR-6 refinement (leaning). N9 and N10 leaning; N8 reframed; N12–N15 added.
  • 2026-10-11: user asked whether volume provisioning can carry job input and output → PR-6; questions N6–N11 added.
  • 2026-10-11: user input on N1 (hosts join directly; asked how to unify → PR-4), N2 (multiple proxies → leaning), N3 (a simplified model first → PR-5), N4 (access control delegated to the API server, the controller trusts it → leaning, with the component-authentication implication), C3 (asked for more on CSI → CSI notes). N2a and N2b added.
  • 2026-10-11: created from the resource-model discussion. User input recorded for PR-1, PR-2 and PR-3 (proposals) and for R3, R4, R5, R6 and R8. Questions N1–N4 and C3 added.