The project tree is the only place concurrent use is capped: quotas are written on the project (quota, member_quota over the subtree) and compiled into the limit plugin, which becomes internal. Queues keep per-job ceilings only; host slot caps are supply. Project path is the identity and the only submit form; members are users and user groups, never sub-projects. Adds the limits-to-owners backlog item. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1023 lines
61 KiB
Markdown
1023 lines
61 KiB
Markdown
# Brief: application-profile
|
|
|
|
(Reshaped from `job-execution-mode` on 2026-09-29, by user direction. Execution
|
|
mode is an attribute of the job, determined by the **application** the user runs,
|
|
so it is carried by a named application profile rather than by a bare mode
|
|
option.)
|
|
|
|
**Update 2026-10-09 (`/kiro-discovery`, configuration layering; user
|
|
decisions):**
|
|
- Configuration is layered LSF-style: **queue** = scheduling policy only (no
|
|
resreq; queue `res_req` is deprecated), **application profile** =
|
|
application execution constraints *including the job's resource
|
|
requirement* (select / rusage / `span[...]` / same) and task limits
|
|
(min / default / max, LSF TASKLIMIT), **project** = resource ownership
|
|
(`project-config`).
|
|
- Every attribute has one owning level (`.kiro/steering/job-config-layering.md`).
|
|
How the levels interact is owned by **`job-config-merge`**; queue
|
|
ceilings and project quotas only check, never change, the job's values.
|
|
(The job-vs-profile rule of this update, *default*/*fixed* markers, is
|
|
replaced by the additive rule of 2026-10-12 below.)
|
|
- **Pre/post-exec moves here from the queue** (queue `pre_exec`/`post_exec`
|
|
deprecated). The profile's pre/post-exec is admin-defined and always runs
|
|
as the outer layer; the job's own pre/post-exec is the inner layer, allowed
|
|
or forbidden by the profile. Order: profile pre → job pre → job → job post
|
|
→ profile post (the worker's existing two-layer structure,
|
|
`xcecd_worker.c` `queue_pre_exec()`/`queue_post_exec()`).
|
|
- **The merge is *live*** (replaces the earlier "resolution at submit time",
|
|
removed from this brief 2026-10-12). The WAL
|
|
keeps the job's own values plus the profile name; the controller and
|
|
apiserver validate at submit, and the scheduler computes effective values
|
|
and recomputes them when a profile changes (a pending job that no longer
|
|
satisfies its profile pends with a reason; running jobs are unaffected).
|
|
Execution-side values (mode, options, launch type, env, pre/post-exec) are
|
|
still taken from the profile at dispatch time and frozen on the dispatch
|
|
record (decided 2026-10-12, "Dispatch record" below).
|
|
|
|
**Update 2026-10-10 (`/kiro-discovery`, long-running services):** a profile
|
|
may declare **kind `service`** with a service section: restart policy
|
|
(always / on-failure / never, backoff, give-up), liveness and readiness
|
|
probes (command / TCP / HTTP), named ports, replica range, spread rule and
|
|
rollout settings (how these follow the additive rule: "Essentials and the
|
|
additive rule" below). The section is defined with this spec and
|
|
consumed by `service-supervision` (worker probes and restarts),
|
|
`service-replica-set` (replicas, scaling, rolling update) and
|
|
`service-endpoint` (ports, endpoints).
|
|
|
|
**Update 2026-10-12 (discussion; user decisions):**
|
|
- **Split: execution modes move to `job-sandbox-execution`.** This spec is
|
|
the profile core: config, access and submit, job state (name, dispatch
|
|
record), the outer pre/post-exec layer. Its schema has `mode:` but
|
|
accepts only `direct`. The `sandbox` mode with its options, I/O
|
|
directories, worker capability advertisement and scheduler placement by
|
|
mode belong to `job-sandbox-execution`, since nothing uses a non-`direct`
|
|
mode before the sandbox exists. The worker's clear launch error for a
|
|
missing `direct` exec user stays here.
|
|
- **Order with `job-config-merge`:** requirements and design of this spec
|
|
first (it fixes the profile schema), then `job-config-merge`'s design;
|
|
implementation tasks interleave (schema/config, merge library, submit
|
|
validation and dispatch record).
|
|
- **Own configuration file.** Profiles live in a new
|
|
`src/configs/applications.yaml` with its own JSON schema, mapped into
|
|
`ClusterConfig` and `Conf4Sched` like queues and limits. See "Profile
|
|
configuration" below.
|
|
- **Cluster default** is set by `default_application` in `xce.yaml`. A name
|
|
that is not defined is a configuration error everywhere (`xceadmin`, live
|
|
upgrade, controller start), never a silent fallback. See "Default profile"
|
|
below.
|
|
- **This spec owns the profile's attribute schema** (which attributes exist
|
|
and what each holds). `job-config-merge` consumes that schema and owns the
|
|
merge and live re-evaluation.
|
|
- **A profile holds only essentials; convenience lives in client-side
|
|
presets** (user decision, 2026-10-12). "The profile holds what the job must
|
|
have; the preset holds what the user would like." A profile value is
|
|
something the application needs or the admin enforces, and it binds every
|
|
job of that profile. The *default*/*fixed* markers of 2026-10-09 are
|
|
dropped: there are no markers, and no per-term markers or ranges. See
|
|
"Essentials and the additive rule" below.
|
|
- **Additive rule:** the job may add to the profile's values, never change
|
|
or remove them. A conflict is rejected at submit with an error naming the
|
|
profile value, never resolved silently.
|
|
- **Exception: default reservations.** The profile's structured
|
|
`rusage` entries hold per-resource defaults the job may override (see
|
|
"Essentials and the additive rule"), so REST/portal users without
|
|
presets still reserve sensibly.
|
|
- **Other convenience defaults** (tuning such as `span[ptile=...]`, task
|
|
count, extra env, different `rusage` values) go into **presets**, handled
|
|
fully by the client (`xcecli-presets`). The server only shares
|
|
admin-deployed presets read-only; presets never reach the controller,
|
|
WAL or scheduler and carry no privilege.
|
|
- **Rule of thumb for admins:** something every job of the application must
|
|
have (required host properties, licenses, launch type, mode,
|
|
pre/post-exec, limits) goes into the profile; an optional variant or
|
|
tuning shortcut goes into a site preset, typically one with the profile's
|
|
name.
|
|
- **Recommendations are advisory** (user decision): usage-based
|
|
suggestions (requested vs. used, OOM kills, runtime by variant) are
|
|
reported to the admin, who changes site presets (or a profile, for an
|
|
essential); nothing tunes itself. Backlog: `profile-usage-insight`.
|
|
- Job details show the effective resreq with each term's origin (profile
|
|
or job).
|
|
- Deferred: a submit-time hook run for the application (LSF `esub`
|
|
style) that inspects or adjusts each submission; roadmap backlog
|
|
`app-submit-hook` (it cannot run in the controller, would add latency
|
|
and a failure mode to every apiserver submit, and bypasses the merge
|
|
rules; `params`, annotations, the job starter and pre-exec cover most
|
|
needs).
|
|
- Deferred: a privileged `app:override` for one-off exceptions (until
|
|
`job-modify` exists, an admin kills and resubmits, or uses a variant
|
|
profile; recurring variants are presets), and profile inheritance
|
|
(`base:`), no longer needed for variants.
|
|
- **Later sections are added by their consumers**: `service:`
|
|
(`service-supervision`), I/O claims (resource-model PR-6), relationship
|
|
and compound templates (PR-14/15), `container:` (R6). This spec only makes
|
|
the schema open to new top-level sections.
|
|
|
|
## Problem
|
|
XCE users run many kinds of applications: trusted in-house codes, untrusted
|
|
scripts, vendor binaries, MPI solvers. Each needs a different way of running:
|
|
- directly as the submitter, or jailed in a sandbox under a separate execution
|
|
identity (and later in a container or vm);
|
|
- a particular launch type (serial, `openmpi`, `mpich`);
|
|
- particular environment and pre/post-exec steps;
|
|
- resource requirements it cannot run without, and limits.
|
|
|
|
Today every one of these must be set per job by hand, and several (execution
|
|
mode, sandbox options) cannot be set at all. Many users log in through the
|
|
portal or REST API (JWT; `config_file`/PAM, later LDAP or OAuth2) and have **no
|
|
Unix account on the compute nodes**. They can only run jobs if their
|
|
applications run sandboxed. Admins have no way to say "this application runs
|
|
like this, and these users may run it".
|
|
|
|
This spec introduces **application profiles** and is the submission/policy half
|
|
of the job-sandbox initiative (roadmap "Job Sandbox Execution"). There is
|
|
precedent in LSF application profiles (`lsb.applications`, `bsub -app`), which
|
|
fits XCE's LSF-style job model.
|
|
|
|
## Current State
|
|
- **Identity.** A job's identity is only the JWT `username`
|
|
(`controllers/auth.go:26-37`). `SubmitUser` comes from the token
|
|
(`controllers/jobs.go:1379`, `:1459`), and `UserGroup` is taken from the client
|
|
request unchecked (`:1465`).
|
|
- **Execution user.** It is always the submitter: `resolveExecUser`
|
|
(`backend/convert_pure.go:162`, bug-1780).
|
|
- **Per-job settings that exist today** (`src/include/xce/types.h`):
|
|
- `job_startinfo_t` (:240-251): `execuser`, `exechome`, `execcwd`, `cmdline`,
|
|
`exdata`, `launch_type`, and pre/post-exec commands.
|
|
- `exdata` is a JSON object carrying the environment (`sub_env_vars`) and
|
|
the worker cgroup limits parsed by `parse_limits` (MemoryMax, CPUQuota,
|
|
TasksMax, RuntimeMax, ...). Since bug-1936 the apiserver sends it
|
|
(`backend/convert_pure.go:388`, capped at 16 KB by `maxExdataLen`); the
|
|
controller stores it opaquely in `start_info` (WAL submit fact,
|
|
snapshot) and the worker parses it, ignoring unknown top-level keys.
|
|
Remote tasks get the same exdata (`worker/src/controller_connector.c:764`).
|
|
- The resource requirement is `resreq` on the submit data.
|
|
- Remote tasks use `xce_taskhost_t` (:696-705).
|
|
- **Missing today:**
|
|
- any profile concept, any execution-mode field, any input/output directory
|
|
field;
|
|
- any way for workers to advertise execution capabilities to the scheduler.
|
|
- **Controller.** It validates only that the user is not empty
|
|
(`controller/job.c:2447`). The submit data, including `start_info`, is
|
|
persisted in the WAL submit fact and in snapshots.
|
|
- **Ownership.** RBAC ownership, scheduler per-user limits and the user table all
|
|
key on the **submit user**, not the exec user (`controllers/jobs.go:1010`,
|
|
`:2542`, `output.go:95`; `schedobj.c:121`; `plugins/limit.c`). So a separate
|
|
execution identity breaks none of them.
|
|
- **Access control.** RBAC is `config/rbac.yaml` plus `RequirePermission`
|
|
(`controllers/rbac_check.go`).
|
|
|
|
## Desired Outcome
|
|
- **Admins define named application profiles** in the cluster configuration. The
|
|
first version contains the following (user decision; the format must be
|
|
extensible for "maybe others"):
|
|
1. **Execution mode.** The `mode:` field, accepting only `direct` in this
|
|
spec; `job-sandbox-execution` adds `sandbox` with its options (allowed
|
|
input/output prefixes, isolation extras), later `container`/`vm`.
|
|
2. **Launch type.** Serial, `openmpi` or `mpich`.
|
|
3. **Environment and hooks.** Environment variables the application
|
|
needs, and the worker-run hooks `pre/post_job_exec` (first host) and
|
|
`pre/post_host_exec` (every host), see "Hooks and starters".
|
|
4. **Resource requirement and limits.** A structured resreq: the binding
|
|
terms the application cannot run without (`select`, and `span` /
|
|
`same` / `order` when set) and default reservations (`rusage`
|
|
entries), its task range (min / max), and per-job execution limits as
|
|
maximums (memory, runtime, CPU, tasks), mapped onto the existing worker
|
|
cgroup limits.
|
|
5. **Job starter and task starter** (decided 2026-10-12, user): two
|
|
wrappers, both frozen on the dispatch record.
|
|
- `job_starter` wraps the **full job**: the job command, started once
|
|
on the first host (for an MPI job, the launcher such as `mpirun`),
|
|
e.g. `module load openfoam &&`.
|
|
- `task_starter` wraps **every task startup** that XCE's workers
|
|
perform, on every host including remote tasks (`xce_taskhost_t`),
|
|
e.g. `apptainer exec img` or a binding/affinity wrapper.
|
|
- A serial (single-task) job's command is both the job and its task:
|
|
it is wrapped by both, `job_starter` outermost (design confirms).
|
|
- For MPI jobs XCE inserts the task starter into the `mpirun` line it
|
|
builds, so every rank starts through it (see "Hooks and starters").
|
|
6. **Application parameters (`params`)** (decided 2026-10-12): settings
|
|
XCE carries but never interprets, handled by the application, e.g.
|
|
|
|
```yaml
|
|
params:
|
|
- { name: solver, default: pimpleFoam, allowed: [pimpleFoam, simpleFoam] }
|
|
- { name: decompose, default: scotch }
|
|
- { name: case_dir, pattern: "^/shared/cases/.*" } # no default
|
|
```
|
|
- Each is delivered to the job as `XCE_APP_PARAM_<NAME>`, with the
|
|
profile env (separate from the job env, frozen on the dispatch
|
|
record), so the job, the job starter and the outer pre/post-exec can
|
|
read it.
|
|
- The job overrides with `--app-param name=value` (also in presets);
|
|
the job's value wins, like `rusage` defaults. Only declared names are
|
|
accepted (a typo is rejected at submit). XCE checks only the shape
|
|
(`allowed` list or `pattern`), never the meaning. A parameter with no
|
|
default and no job value is not set.
|
|
- The job's values are stored in its exdata (`app_params`), already
|
|
persisted; no new job field.
|
|
- The portal can build an application's submit form from the declared
|
|
parameters.
|
|
7. **Annotations** (decided 2026-10-12): free-form key/value facts about
|
|
the application (owner, version, docs URL, licence server name), like
|
|
Kubernetes annotations. Shown to tools (profile listing, portal, job
|
|
details) **and** exported to the job as `XCE_APP_ANN_<KEY>`, so an
|
|
admin writes a value once for both. The job cannot change them.
|
|
- Name conversion: keys may be Kubernetes-style
|
|
(`cfd.org/solver-family`); the env name is uppercased with every
|
|
non-alphanumeric character replaced by `_`
|
|
(`XCE_APP_ANN_CFD_ORG_SOLVER_FAMILY`). `xceadmin` rejects two
|
|
annotation keys (or two params) that convert to the same name.
|
|
- Limits on count and size, so a profile cannot bloat every job's env.
|
|
- Visible to every user who can view profiles: no secrets (the secret-name
|
|
warning applies). All are exported in v1; an opt-out only if
|
|
needed.
|
|
- `env` vs annotations vs params: `env` is a variable the application
|
|
expects under its own name; annotations are facts for tools and the
|
|
job; params are the user's choices. `XCE_APP_*` names are set by XCE
|
|
only; the reserved `XCE_` prefix keeps jobs and profiles from setting
|
|
them directly, so a job cannot fake one.
|
|
All of these are essentials under the additive rule, except the `rusage`
|
|
entries and `params`, which are defaults the job may override.
|
|
- **A profile attribute ships with its job feature** (user, 2026-10-12): a
|
|
profile attribute is added by the spec that builds the job-level feature
|
|
it configures, never ahead of it, so no profile setting is dead config.
|
|
None of the other candidates exists as a job feature today, so they are
|
|
deferred:
|
|
- requeue exit values and success exit values: `job-requeue` (backlog);
|
|
- checkpoint method/period/directory, and a warning signal before the run
|
|
limit: their own future specs;
|
|
- output path template: the PR-6/PR-7 I/O track (output options are
|
|
rejected at submit today);
|
|
- runtime estimate: not a profile attribute at all (it describes one run:
|
|
a job option or preset; the profile's runtime limit is the maximum).
|
|
- Found on the way: the REST submit accepts several of these options and
|
|
silently drops them (bug-1970).
|
|
- **Users pick a profile at submit** (REST, `xcecli jobs submit --app <name>`,
|
|
harness, libxce). A **cluster default profile** applies when none is given.
|
|
- **Jobs add, never override** (except `rusage` defaults). A job may add to
|
|
its profile's values (more `select` terms, other resources, its own env
|
|
variables, lower limits) and override a profile `rusage` default, but not
|
|
change or remove anything else. Anything else is rejected with a clear
|
|
error.
|
|
- **Access control is deferred** (user decision, 2026-10-12): v1 has no
|
|
per-profile permission. Any user who may submit (`queue:submit`) may use
|
|
any profile; listing and showing profiles reuses the existing
|
|
`cluster:view` permission (profiles are cluster configuration). The
|
|
design recorded below moves to the backlog spec `app-access-control`,
|
|
kept here so it is not lost:
|
|
- permissions `app:use` (submit with a profile) and `app:view` (list and
|
|
inspect profiles), a new `app` scope type whose values are profile
|
|
names or `all`, following the named-instance pattern of `queue:submit`
|
|
(`config/rbac.yaml`, `GetRequireResourcePermission`,
|
|
`controllers/jobs.go:1402-1409`);
|
|
- checks on job create (alongside, and independent of, `queue:submit`),
|
|
on job restart (re-check for the job's profile) and on listing;
|
|
- `rbac.yaml` defaults (`basic_user` → `app:use` on the default profile,
|
|
`app:view` on `all`); the current default profile always usable with
|
|
`queue:submit` (implicit `app:use`), so plain submits never break when
|
|
the default is repointed; aliases checked against the target profile;
|
|
- role API accepts the new scope type; `cluster_admin` keeps the single
|
|
existing bypass; API-made grants depend on `rbac-persistence`.
|
|
- **Queues and profiles are orthogonal** (user decision, 2026-09-29).
|
|
- **Queues** own scheduling policy: which jobs are considered and in what
|
|
order, priority, scheduling limits, and host partitioning.
|
|
- **Application profiles** own application-related configuration: how the
|
|
application is executed and what it needs to run.
|
|
- A queue does not list or restrict profiles, and a profile does not select or
|
|
constrain queues.
|
|
- A profile carries no scheduling-policy attributes (no priority, queue choice,
|
|
fairshare or preemption).
|
|
- Any combination of an allowed queue and a profile is valid. Queues keep
|
|
their access rules; per-profile access control comes later
|
|
(`app-access-control`).
|
|
- Resource values in a profile describe what the application needs (its
|
|
essential resource requirement and per-job execution limits). They are
|
|
not scheduling policy. Queue per-job ceilings and project quotas check
|
|
the merged result on top (no queue aggregate caps, 2026-10-12).
|
|
- **Live merge** (`job-config-merge`). The job records its profile name and
|
|
its own values, not the effective values. The scheduler computes the
|
|
effective values and recomputes them when the profile changes: a pending job
|
|
that no longer satisfies its profile pends with a reason, and running jobs
|
|
are unaffected. Job details show the profile name and the effective mode.
|
|
- **The apiserver never checks node accounts** (user decision, 2026-09-29).
|
|
- Submit-time validation covers profile existence, the additive rule and
|
|
paths only (no per-profile permission in v1).
|
|
- The execution account is the worker's concern. If a `direct` job's execution
|
|
user doesn't exist on the node, the worker fails the job at launch with a
|
|
clear, user-visible reason, instead of today's opaque systemd failure.
|
|
- Account-less users are served by profiles whose mode is `sandbox`.
|
|
- **Placement by mode** (moved to `job-sandbox-execution`): a job is placed
|
|
only on workers whose advertised capabilities support its mode (sandbox
|
|
needs systemd 242 or later and `nss-systemd`).
|
|
- **Ownership.** The submitter remains the owner for RBAC, limits and accounting
|
|
in every mode.
|
|
|
|
## Approach
|
|
- **Profile definitions** live in their own cluster configuration file (see
|
|
"Profile configuration" below), with xceadmin validation and live-upgrade
|
|
replication.
|
|
- **The controller validates the profile at submit**: the named profile
|
|
exists, and the job only adds to the profile's values (additive rule).
|
|
This makes it authoritative for every submit path (REST, libxce, harness).
|
|
It stores the profile name and the job's own values; the effective values
|
|
come from the live merge (`job-config-merge`).
|
|
- **The apiserver checks that the named profile exists** (user direction,
|
|
2026-09-29).
|
|
- At submit, before anything goes downstream (`preCheckJobSubmitRequest`), the
|
|
apiserver rejects a job that names a profile which does not exist, with a
|
|
clear client error naming the unknown profile.
|
|
- The apiserver already receives the cluster configuration from the controller
|
|
(`backend/convert.go:265`), so it knows the current set of profiles and keeps
|
|
it current across live config upgrades.
|
|
- The controller still re-validates at submit, because it is
|
|
authoritative for non-REST submit paths and for a profile removed between the
|
|
two checks. If the profile has gone by then, the job is rejected, never run
|
|
with a fallback profile.
|
|
- **The apiserver additionally checks the additive rule early**
|
|
(`preCheckJobSubmitRequest`); the middleware chain is unchanged. The
|
|
profile listing route uses `RequirePermission("cluster:view")`.
|
|
- **The job's own values stay in its existing fields** (`launch_type`,
|
|
pre/post exec, exdata env and limits, resreq). New fields (all additive
|
|
Cap'n Proto; WAL submit/dispatch facts and snapshot carry them with field
|
|
completeness):
|
|
- on the submit data: the profile name;
|
|
- on `start_info` (what the worker gets): the job starter, the task
|
|
starter (also on each `xce_taskhost_t` entry for remote tasks) and the
|
|
profile's env, kept **separate** from the job's env (see "Environment of
|
|
the outer layer"); the queue-named `qPreExec` / `qPostExec` carry the
|
|
profile's outer pre/post-exec (renamed in design);
|
|
- the dispatch record payload ("Dispatch record").
|
|
- **exdata is read by the merge** (owned by `job-config-merge`): the job's
|
|
env and limits live inside the exdata JSON (`sub_env_vars`, `limits`), so
|
|
the shared `src/lib` merge parses it to check the additive env rule, the
|
|
reserved `XCE_` prefix and "limits: the job may lower", and at dispatch
|
|
the controller writes the effective limits back into the exdata sent to
|
|
the worker, preserving every other key (including `meta`, used by
|
|
presets). Execution-side values (mode and options,
|
|
launch type, env, profile pre/post-exec) are read from the profile at
|
|
dispatch, as queue pre/post-exec is today (`get_queue_exec()`,
|
|
`controller/queue.c:199`).
|
|
- **Workers advertise their supported execution modes** and the scheduler
|
|
filters hosts by mode: `job-sandbox-execution` (the design picks the
|
|
mechanism, e.g. a host attribute or boolean resource).
|
|
|
|
### Profile configuration
|
|
- **One file per entity type**, as for hosts, queues, resources, limits,
|
|
users and scaler:
|
|
- `src/configs/applications.yaml`, top-level key `applications:` (named
|
|
after LSF `lsb.applications`; the user flag is `--app`);
|
|
- `src/configs/schema/applications.schema.json`, picked up by xceadmin's
|
|
file-to-schema naming rule (`xcecommon/validation.py:68`);
|
|
- `xcecommon/validation.py`: duplicate names rejected; cross-file
|
|
references checked (`rusage` entry names against `resources.yaml`,
|
|
`per:` values); aliases (no clashes, no chains); `XCE_*` env rejected;
|
|
a warning for env names that look like secrets;
|
|
- `src/schema/controller.capnp`: `applications: List(AppProfileConf)` in
|
|
`ClusterConfig` and in `Conf4Sched` (the scheduler runs the live merge).
|
|
`Conf4Worker` does not need it: the worker gets execution values through
|
|
`start_info` at dispatch.
|
|
- The apiserver already receives `ClusterConfig`
|
|
(`backend/convert.go:265`), so it gets the profiles with no new channel.
|
|
- **Shared config, per `config-design.md`:** controller, scheduler and
|
|
apiserver all need the same view (independence rule), and profiles change
|
|
at the rate of queues, as occasional admin edits (frequency rule).
|
|
- **The file is optional.** Without it there is one built-in profile
|
|
`default` (mode `direct`, no attributes), which is today's behaviour. A
|
|
profile named `default` in the file **replaces** the built-in one, so
|
|
admins can customise the default without setting `default_application`.
|
|
- **No secrets in profiles.** Profile env values and pre/post-exec paths
|
|
are visible to every user who can view profiles (`cluster:view`) and in
|
|
job details;
|
|
`xceadmin` warns on env names that look like secrets (`*TOKEN*`,
|
|
`*SECRET*`, `*PASSWORD*`, `*_KEY`), as for presets.
|
|
- **No override markers.** Every profile value binds the job (additive
|
|
rule), except the `rusage` entries' defaults, which the job may override.
|
|
Whether the job's own pre/post-exec is allowed is a profile setting
|
|
(`job_pre_post_exec: allow | forbid`).
|
|
- **Open to new sections.** Later specs add top-level sections to a profile
|
|
(`service:`, I/O claims, relationship templates, `container:`); the schema
|
|
must allow that without reshaping existing sections.
|
|
- Sketch (format decided in design):
|
|
|
|
```yaml
|
|
applications:
|
|
- name: openfoam
|
|
description: "OpenFOAM, MPI"
|
|
mode: direct
|
|
launch_type: openmpi
|
|
resreq:
|
|
select: "ib" # binding
|
|
rusage:
|
|
- { name: license_foam, per: job, default: 1 } # default reservation
|
|
- { name: mem, default: 4000 } # per task by default
|
|
tasks: { min: 32, max: 512 }
|
|
limits: { runtime: 24h, memory: 128G } # maximums
|
|
env: { FOAM_INST_DIR: /opt/openfoam }
|
|
job_starter: "module load openfoam &&"
|
|
task_starter: "/opt/xce/app/openfoam/bind.sh"
|
|
pre_job_exec: /opt/xce/app/openfoam/licence-out.sh
|
|
post_job_exec: /opt/xce/app/openfoam/licence-in.sh
|
|
pre_host_exec: /opt/xce/app/openfoam/scratch-setup.sh
|
|
post_host_exec: /opt/xce/app/openfoam/scratch-clean.sh
|
|
job_pre_post_exec: allow
|
|
|
|
# mode: sandbox and its options come with job-sandbox-execution;
|
|
# this spec accepts only mode: direct
|
|
- name: untrusted-python
|
|
mode: sandbox
|
|
sandbox: { inputs: ["/shared/in/"], outputs: ["/shared/out/"] }
|
|
job_pre_post_exec: forbid
|
|
```
|
|
|
|
Default reservations live in the profile's `rusage` entries (the job may
|
|
override them); other tuning such as `span[ptile=32]` and a task count of
|
|
64 is not in the profile: the admin publishes it as a site preset named
|
|
`openfoam`.
|
|
|
|
### Essentials and the additive rule
|
|
How each profile attribute combines with the job's own value (owned by
|
|
`job-config-merge`, which implements it):
|
|
|
|
| Profile attribute | Holds | The job may |
|
|
|---|---|---|
|
|
| resreq `select` | required terms (`ib`, GPU model, OS) | add terms (ANDed) |
|
|
| resreq `rusage` | entries with optional `per:` and `default` | add resources; override a profile default (job's term wins; unit must match, see below) |
|
|
| resreq `span` / `same` / `order` | only if essential (e.g. a code that runs on one host only) | set them if the profile does not; otherwise rejected |
|
|
| task range | `min` / `max` | choose within it; no `-n` means `min` |
|
|
| env | variables the application needs | add others; not redefine these |
|
|
| launch type, mode, sandbox options, job starter, task starter | fixed values | nothing |
|
|
| `job_pre_post_exec` | `allow` / `forbid` | use its own pre/post-exec only if `allow` |
|
|
| limits | maximums | ask for less (checked like a ceiling) |
|
|
| pre/post-exec | outer layer, always runs | add its own inner layer if the profile allows |
|
|
|
|
**Structured profile resreq; `rusage` defaults, the job's term wins** (user
|
|
decisions 2026-10-12). A profile's resreq is not one string but separate
|
|
parts, so each part's rule is visible from its shape:
|
|
|
|
```yaml
|
|
resreq:
|
|
select: "ib && os == rhel9" # binding: ANDed with the job's select
|
|
rusage: # defaults: the job may override per resource
|
|
- { name: license_foam, per: job, default: 1 }
|
|
- { name: mem, default: 4000 } # per: optional, defaults to task
|
|
- { name: gpu } # unit only: nothing reserved by default
|
|
span: "ptile=32" # binding if set
|
|
same: [switch]
|
|
order: "r15s"
|
|
```
|
|
|
|
| Part | Merge with the job's `-R` (an ordinary resreq string, parsed into terms) |
|
|
|---|---|
|
|
| `select` | ANDed with the job's |
|
|
| `rusage` entry with `default` | reserved at that value unless the job names the resource; then the job's term wins **whole** (value and `duration`/`decay`), in either direction |
|
|
| `rusage` entry without `default` | nothing reserved unless the job asks; it only fixes the unit |
|
|
| `per:` (optional, default `task`) | the unit for that resource: a job term without a per-type gets it; a job term with a **different** per-type is rejected at submit ("resource mem is per task in profile openfoam"), never silently reinterpreted (16000/job read as per task multiplies by the task count) |
|
|
| a resource only the job names | the job's term as written |
|
|
| `span` / `same` / `order` | the profile's if set (binding), otherwise the job's |
|
|
|
|
- `per:` defaults to `task`, today's behaviour: `PER_DEFAULT` takes the
|
|
caller's per-type (`lib/resreq.c:842`) and the controller passes
|
|
`PER_TASK` (`controller/job.c:3909`); `resources.yaml` has no per-type
|
|
field. A per-resource default there may come later.
|
|
- **A 0-valued term is removed by the merge** (decided 2026-10-12, user):
|
|
after combining profile and job, every `rusage` term whose value is 0 is
|
|
dropped, so the scheduler sees exactly "no term". `name=0` is how a job
|
|
drops a profile default. Needed because the scheduler does not treat 0 as
|
|
absent today: a 0 term still becomes a wanted entry
|
|
(`calc_wanted_host_resvals()`, `lib/resreq.c`), and `unsatisfy_()`
|
|
(`scheduler/src/resource.c:164`, `total < inuse + uncomit + wanted`)
|
|
rejects a host where that resource is already over-committed
|
|
(`inuse > total`, possible because memory is charged
|
|
`max(reserved, actual)`), and the term shows in decision-trace rows and
|
|
pend reasons; with no term the resource is not checked at all (fast path
|
|
at `resource.c:351`). Applies to every 0 term (profile, job, preset, or a
|
|
job without a profile). Job details still show "license: 0 (overrides the
|
|
profile default)".
|
|
- `xceadmin` validates each `rusage` entry against `resources.yaml` (name
|
|
exists) instead of parsing a string; later per-resource fields
|
|
(`duration`, a `min:` floor for counted resources such as licences, if
|
|
under-reservation proves a problem) slot into the entry without new
|
|
grammar.
|
|
- Consequence: `rusage` is the one profile part that holds **defaults**; a
|
|
profile cannot enforce a minimum reservation. Hard protection of shared
|
|
resources comes from project quotas (`limits.yaml` is retired, 2026-10-12).
|
|
- Side effects to handle in requirements: measured resources (memory) are
|
|
charged `max(reserved, actual)` once usage is reported
|
|
(`resource-accounting.md`), so lowering them mostly self-corrects, but
|
|
counted resources (licences) do not; xcecli and `--dry-run` warn when a
|
|
job lowers a profile default, and when a preset restates one (it would
|
|
pin the job against later profile changes); job details label each
|
|
`rusage` term as profile default or job override; queue ceilings and
|
|
project quotas check the merged value and the error names the job's
|
|
override; a profile `rusage` change changes the content hash, so
|
|
in-flight dispatches re-pend and pending jobs re-merge even if they
|
|
override that resource (accepted).
|
|
- A job with no `-R` gets the profile's binding terms and its `rusage`
|
|
defaults; the scheduler's existing empty-resreq default (`type == any`)
|
|
still applies to what neither sets.
|
|
- **Profile kind is checked** (decided 2026-10-12): a batch job naming a
|
|
`kind: service` profile is rejected ("profile X is a service profile");
|
|
the services specs reject the reverse.
|
|
- **A `select` no host matches** (decided 2026-10-12): a warning, not a
|
|
rejection, at submit and from `xceadmin` against `hosts.yaml`, since
|
|
host attributes can change (e.g. IB nodes added later).
|
|
- **The `service:` section is configured in the profile** (user,
|
|
2026-10-12; defined with this spec, consumed by `service-supervision`,
|
|
`service-replica-set`, `service-endpoint`) and follows the same rule:
|
|
- restart policy (always / on-failure / never, backoff, give-up),
|
|
liveness and readiness probes, and named ports are the application's
|
|
definition: profile values, the service cannot change them;
|
|
- replicas work like the task range: the profile may set `min` / `max`,
|
|
and the service chooses its count within them (from its own spec or a
|
|
preset);
|
|
- spread rule and rollout settings (max unavailable / surge): if the
|
|
profile sets them they bind; otherwise the service sets them.
|
|
- Other later sections (I/O claims, relationship templates, `container:`)
|
|
state in their own specs how they follow the additive rule.
|
|
- **Compound and alternative resreqs** (decided 2026-10-12, user,
|
|
simplified). Parts are unnamed today: only the profile's `select` applies,
|
|
ANDed into every part and every `||` alternative. If the profile has any
|
|
`rusage` entry with a `default`, or sets `span` / `same` / `order`, a
|
|
compound or alternative job resreq is **rejected** with an error naming
|
|
the part, until PR-15 named parts let a profile target a part by role.
|
|
`rusage` entries without a default only fix a unit and do not block.
|
|
Admin guidance (not enforceable): no part-specific property (e.g. a GPU
|
|
model only one role needs) in a profile's `select`.
|
|
|
|
### Hooks and starters
|
|
Decided 2026-10-12 (user). Run by XCE's worker, never by the job:
|
|
|
|
| Item | Runs | Typical use |
|
|
|---|---|---|
|
|
| `pre_job_exec` / `post_job_exec` | once, on the first host, before the job / after all tasks end | licence checkout and check-in, staging |
|
|
| `pre_host_exec` / `post_host_exec` | once per host, before its first task / after its last | scratch, local cache, cleanup (LSF `HOST_PRE_EXEC` / `HOST_POST_EXEC`) |
|
|
| `job_starter` | wraps the job command once (the `mpirun` line for MPI) | `module load ... &&` |
|
|
| `task_starter` | wraps every task | `apptainer exec img`, binding wrappers |
|
|
|
|
- **No per-task pre/post hooks.** With N tasks per host they would run N
|
|
times concurrently: setup needs locking, and "the last task cleans up"
|
|
needs reference counting that a SIGKILL breaks. For MPI jobs XCE does
|
|
not start the ranks (it builds one `mpirun` line and sets `ntasks = 1`,
|
|
`worker/src/controller_connector.c:993-1010`), so it could not run hooks
|
|
around them, and a wrapper there runs in the job's env. A task starter
|
|
can do per-task work itself (it is a wrapper).
|
|
- **Task starter reach (correction):** for serial and non-MPI parallel jobs
|
|
the worker wraps each process it starts (`nranks` per host, remote hosts
|
|
through `xce_taskhost_t`); for MPI jobs XCE inserts it into the `mpirun`
|
|
command line it builds (`create_mpi_cmdline()`: `mpirun <opts>
|
|
<task_starter> <app>`), so the MPI runtime starts every rank through it.
|
|
Only an application that calls its own launcher inside the job script
|
|
bypasses it. A serial job's command is wrapped by both starters,
|
|
`job_starter` outermost (design confirms).
|
|
- **Order on a multi-host job:**
|
|
```
|
|
first host: pre_job_exec → job's own pre-exec → job_starter(job command) … job's own post-exec → post_job_exec
|
|
every host: pre_host_exec → task_starter(task) … (all its tasks end) → post_host_exec
|
|
```
|
|
`pre_job_exec` finishes before any host runs `pre_host_exec`;
|
|
`post_job_exec` runs after every host's `post_host_exec`. The job's own
|
|
pre/post-exec stays job-level (inner layer, allowed by
|
|
`job_pre_post_exec`).
|
|
- **Clean environment** for all four hooks: system values, the profile env,
|
|
`XCE_APP_*`; never the job's env ("Environment of the outer layer").
|
|
- **Per-host helpers:** XCE exports `XCE_JOB_HOST_DIR` (a per-job, per-host
|
|
directory the worker creates before `pre_host_exec` and removes after
|
|
`post_host_exec`) and `XCE_HOST_NTASKS` / `XCE_HOST_TASK_INDEX`, so
|
|
starters and the application know their host-local position without
|
|
MPI-specific variables. A task starter may use a lock in
|
|
`XCE_JOB_HOST_DIR` for best-effort once-per-host work (e.g. a cache
|
|
warm-up), never for cleanup that must happen.
|
|
- **Failures** (decided, proposal 4): `pre_job_exec` fails → nothing else
|
|
starts, `post_job_exec` still runs, the job goes to EXIT with a reason
|
|
naming the hook; `pre_host_exec` fails on a host → its tasks do not
|
|
start, its `post_host_exec` still runs, the job fails. Requeue on hook
|
|
failure belongs to `job-requeue`. (Today `queue_pre_exec()` just exits:
|
|
`TODO: Handle pre-exec failure`.)
|
|
- **The post hooks must survive every way a job ends.** Checked
|
|
2026-10-12: a graceful kill is SIGINT → SIGTERM → SIGKILL with a grace
|
|
period (`worker/src/job_signal.c:52-55`); a forced kill is SIGKILL to the
|
|
job's processes; the run limit is systemd `RuntimeMaxUSec` on the unit
|
|
(`worker/src/xcecd_cgroups.c:753-766`), which stops the unit with
|
|
`KillMode=control-group`, `KillSignal=SIGTERM` (`:195-230`), the worker
|
|
included. The worker's `signal_term()` (`worker/src/xcecd_worker.c:293`)
|
|
forwards SIGTERM to the job and keeps running, so post-exec runs today,
|
|
but only until systemd's `TimeoutStopUSec` (unset by XCE, system default,
|
|
typically 90 s) SIGKILLs everything left in the unit. Requirements: the
|
|
unit gets an explicit stop timeout covering the post hooks (with a
|
|
configurable maximum hook duration), and design checks how the worker
|
|
survives an OOM kill inside its own unit.
|
|
- **Migration:** design checks on which hosts today's queue
|
|
`pre_exec`/`post_exec` runs (in `xcecd_worker`) and maps it to the
|
|
matching hook, so behaviour does not change.
|
|
|
|
### Environment of the outer layer and reserved names
|
|
Decided 2026-10-12 (user), replacing the idea of an env denylist:
|
|
- **A. The profile's pre/post-exec runs in a clean environment**: system
|
|
values (`XCE_*`, `HOME`, a fixed `PATH`) plus the profile's own env, never
|
|
the job's. The job's env applies after it, to the job's pre-exec, the job
|
|
and the job's post-exec. An admin script that needs job facts reads
|
|
`XCE_*`. Today the job env is applied first (`apply_env_vars()`,
|
|
`worker/src/xcecd_worker.c:835`, then `queue_pre_exec()` :843), so a job's
|
|
`PATH` or `BASH_ENV` can subvert the admin's script. Worker change in this
|
|
spec: the outer layer gets an explicitly built env (`uv_proc_pre_exec`
|
|
takes none today). It also fixes today's queue pre/post-exec.
|
|
- **The profile env travels separately from the job env** (decided
|
|
2026-10-12, review). Today all env is one `exdata.sub_env_vars` object,
|
|
also sent to remote tasks; if the profile env were merged into it, the
|
|
worker could not build the clean environment of A. The profile env goes
|
|
in its own `start_info` field (or an `exdata` key, decided in design), to
|
|
the head and every remote task; the job's env stays in `sub_env_vars`.
|
|
The job process gets both (profile first, the job's additions on top,
|
|
never redefining a profile variable).
|
|
- **B. The `XCE_` prefix is reserved**: neither a job nor a profile may set
|
|
`XCE_*` variables (rejected at submit by apiserver and controller, and by
|
|
`xceadmin` for profiles).
|
|
- Why no denylist: the whole job unit runs as the execution user
|
|
(`User=` on the transient unit, `worker/src/xcecd_cgroups.c:299-312`), in
|
|
sandbox mode inside the sandbox, so `LD_PRELOAD` and similar are not an
|
|
escalation; the real risks were the admin's outer layer and XCE's own
|
|
variables, which A and B close at the source. Sandbox-specific env
|
|
exposure stays with `job-sandbox-execution`.
|
|
|
|
### Presets (client side, `xcecli-presets`)
|
|
Not part of this spec, recorded here because they hold the convenience
|
|
defaults a profile does not (everything but default reservations):
|
|
- A preset is a set of job options (`--app`, `--queue`, `-R`, `-n`, env,
|
|
limits). The client expands it; a command-line flag replaces the preset's
|
|
value for that option as a whole. The submit carries only the expanded
|
|
values.
|
|
- Personal presets live one file per preset under `~/.config/xce/presets/`.
|
|
- The apiserver **only shares**: admin-deployed preset files served
|
|
read-only (`GET /xce/v1/presets`, `GET /xce/v1/presets/{name}` or
|
|
`{ns}/{name}`) from `<TopDir>/config/presets/`. Pulling copies a preset
|
|
into its own local file, so a server preset being removed never affects
|
|
users. `preset diff` / `pull --force` are user-initiated.
|
|
- User-to-user sharing by `preset export` / `import` (file, mail, git).
|
|
- Presets have no access control of their own (user, 2026-10-12): the
|
|
preset routes need no RBAC permission, and a submit made from a preset is
|
|
checked like any other submit. Presets carry no privilege.
|
|
|
|
### Dispatch record
|
|
Decided 2026-10-12 (user): what a job was scheduled and started with is
|
|
**frozen at dispatch**. A pending job follows the current profile; a
|
|
dispatched job uses only its dispatch record until it is requeued or
|
|
restarted, and the scheduler no longer re-merges it.
|
|
- The record holds both sides:
|
|
- scheduling: the effective resreq (`select`, `rusage`, `span`, `same`,
|
|
`order`) and task count, so a running job's reservations (e.g.
|
|
`rusage[license=1]`) do not change when the profile changes, and are
|
|
rebuilt identically after a scheduler failover or restart;
|
|
- execution: mode, mode options, launch type, job starter, task
|
|
starter, the
|
|
profile's env and the job's env (separately), outer pre/post-exec, the
|
|
`job_pre_post_exec` setting, effective limits;
|
|
- the resolved profile name (after aliases) and the content hash the
|
|
record was built from.
|
|
- At each dispatch the controller puts the execution values into the job's
|
|
`start_info` for the worker, and the whole record into an additive
|
|
payload on the WAL dispatch fact
|
|
(`persist_submit_job_dispatch_fact`, `controller/persist.c:8167`) and the
|
|
snapshot. Both apply seams decode it; `/xce-audit-invariants` checks the
|
|
round trip.
|
|
- Today the queue's pre/post-exec copied at dispatch (`controller/job.c`
|
|
1825-1897) is kept in memory only, so after a failover the new primary
|
|
does not know what a running job was given. The frozen record closes that
|
|
gap as pre/post-exec moves to the profile.
|
|
- A running or finished job's details show the frozen values, and say
|
|
"profile changed since dispatch" when the current profile differs.
|
|
- **Requeue or restart re-reads the profile** at the next dispatch, the same
|
|
as the live merge does for pending jobs.
|
|
- Running jobs therefore need nothing from the live profile: post-exec at
|
|
the end, later remote tasks or a resize, and a service replica's probes
|
|
and in-place restarts all use the record.
|
|
- Why: for sandboxed jobs the mode and mounts a job actually ran with is a
|
|
security/audit fact, and `job-sandbox-execution`'s mandatory cleanup
|
|
depends on it; `persistence.md` does not allow memory-only state without a
|
|
documented scope decision.
|
|
|
|
### Profile removed while jobs use it
|
|
Decided 2026-10-12 (user): **a job whose profile no longer exists pends**,
|
|
with the reason "application profile X is not defined".
|
|
- This applies to every job that reads the profile: pending jobs, requeued
|
|
or restarted jobs, and new service replicas (replacements, scale-up),
|
|
which are new jobs. Running jobs are unaffected (dispatch record); a
|
|
service whose profile is gone keeps its running replicas but shows as
|
|
degraded when one cannot be replaced.
|
|
- Putting the profile back releases the jobs. A rename is remove plus add.
|
|
- The removal is never refused (`xceadmin config build` works offline and
|
|
cannot know live jobs) and jobs never exit because of it.
|
|
- The controller's live-upgrade reply lists removed profiles still named by
|
|
pending jobs or services, with counts; `xceadmin config upgrade` prints
|
|
it. Needs a new scheduler pend reason (profile missing from
|
|
`Conf4Sched`).
|
|
- **Between the scheduler's decision and the controller's dispatch**
|
|
(decided 2026-10-12, user). The scheduler merges with its copy of the
|
|
profile (`Conf4Sched`); the controller reads the profile again when it
|
|
builds the dispatch record. If the profile is gone or has changed in
|
|
between, the **dispatch fails and the job goes back to PEND through the
|
|
requeue path** (re-reading the profile, as any requeue does): gone → it
|
|
pends with "application profile X is not defined"; changed → the
|
|
scheduler re-merges with the new profile. Never EXIT.
|
|
- New dispatch failure site (e.g. `XCE_DISPATCH_FAIL_SITE_APP_PROFILE`),
|
|
classified retryable, unlike today's terminal
|
|
`XCE_DISPATCH_FAIL_SITE_QUEUE_EXEC` (`controller/job.c`). Nothing is
|
|
committed on this path (`j->job.allocs` is committed only after a
|
|
successful `xce_startjob_req()`). Requirements decide that it does not
|
|
count as a run or against requeue limits.
|
|
- A scheduler still on the old config may re-dispatch and be declined
|
|
again until the controller's config push reaches it; the existing
|
|
dispatch retry backoff bounds that.
|
|
- After the controller has built the record, profile changes no longer
|
|
matter: the worker starts from `start_info`.
|
|
- **Profile content hash** (decided 2026-10-12, user): an internal version
|
|
of each profile.
|
|
- Computed, never stored as state: one `src/lib` function hashes the
|
|
canonical Cap'n Proto encoding of the profile, excluding
|
|
`description`. Controller, scheduler and apiserver get the same value
|
|
from the same profile, also after failover or restart.
|
|
- The scheduler's dispatch decision carries the hash it merged with
|
|
(additive field); the controller compares it with the current
|
|
profile's hash at dispatch. A scheduler that sends no hash (rolling
|
|
upgrade) skips the check.
|
|
- The dispatch record stores it; "profile changed since dispatch" in job
|
|
details is a hash comparison; job details show it short
|
|
(`openfoam@3f2a9c`). Admins never set it.
|
|
- Base for profile history and audit (`config-history-audit`, backlog).
|
|
- **Moving pending jobs to another profile is not in this spec**
|
|
(decided 2026-10-12, user). XCE has no job modification today
|
|
(`JOB_MODIFY` is only an enum, `include/xce/types.h:620`; the apiserver's
|
|
`"modify"` action is commented out, `controllers/jobs.go:2446`). A general
|
|
modify path (apiserver action, controller opcode, WAL fact, scheduler
|
|
re-merge) is its own backlog spec, `job-modify` (LSF `bmod`), with `--app`
|
|
among its first attributes. Until then: restore the profile, use an
|
|
alias, or kill and resubmit.
|
|
|
|
### Profile aliases
|
|
Decided 2026-10-12 (user), so a rename never strands jobs:
|
|
- A profile may list `aliases: [old-name, ...]`. An alias resolves to the
|
|
profile everywhere: merge, dispatch record, content hash (the target's),
|
|
and, once `app-access-control` lands, its permission check (against the
|
|
target profile's name).
|
|
- Jobs keep the name they recorded; the scheduler and controller resolve it
|
|
through the alias. New submits that name an alias are accepted and record
|
|
the **target's** name, so the alias can be removed once old jobs drain.
|
|
- Job details show the resolution, e.g. `openfoam (recorded as
|
|
openfoam-old)`.
|
|
- `xceadmin` rejects an alias that clashes with a profile name or another
|
|
alias, and alias chains (an alias naming an alias). `default_application`
|
|
must name a real profile, not an alias.
|
|
- Removing an alias while jobs still name it is the same as removing a
|
|
profile: they pend, and the live-upgrade warning lists them.
|
|
- Rename workflow: rename the profile and add the old name as an alias;
|
|
drop the alias when no pending job uses it (the upgrade warning shows
|
|
when that is).
|
|
|
|
### Default profile
|
|
- **Set by `default_application: <name>` in `xce.yaml`**, a cluster
|
|
parameter (as LSF keeps `DEFAULT_APPLICATION` in `lsb.params`), not a flag
|
|
on one profile.
|
|
- **Recorded at submit.** A job submitted without `--app` gets the current
|
|
default's *name* written on its record, so changing the default later does
|
|
not move pending jobs to another profile.
|
|
- **An empty profile name means "no profile"**, never "the current
|
|
default": jobs submitted before profiles existed (old WAL records and
|
|
snapshots) keep today's behaviour and are never silently given
|
|
`default_application`. Only new submits record the default's name.
|
|
- **Restart carries the profile name.** `POST /:jobids/restart` rebuilds the
|
|
new job field by field from the old job's record (bug-1968 shows what is
|
|
lost otherwise), so it must copy the profile name (or alias); it then
|
|
re-reads the profile like any new dispatch (and, once
|
|
`app-access-control` lands, re-checks `app:use`).
|
|
- **Array jobs** carry one profile name on the array's submit data; every
|
|
element uses it.
|
|
- **A name that is not defined is a configuration error, never a silent
|
|
fallback** (user decision, 2026-10-12):
|
|
|
|
| `default_application` | Behaviour |
|
|
|---|---|
|
|
| absent | built-in `default` profile (mode `direct`, no attributes), today's behaviour |
|
|
| names a defined profile | that profile is used for jobs submitted without `--app` |
|
|
| names an undefined profile | the configuration is rejected |
|
|
|
|
- `xceadmin config build` fails with an error naming the missing profile.
|
|
This is an error, not one of the warnings `validation.py` gives for
|
|
unknown host or user references: a dangling default would break every
|
|
submit without `--app`.
|
|
- The controller re-checks on live upgrade and at start, because config can
|
|
reach it without xceadmin. A live upgrade with a dangling default,
|
|
including one that removes or renames the current default profile, is
|
|
refused and the current config stays in force. At start the controller
|
|
refuses to run and logs the reason.
|
|
- Profiles and the default are in the same configuration, checked
|
|
atomically, so the default cannot become dangling at run time.
|
|
- **Queue deprecations surface here.** `xceadmin config build` warns when
|
|
`queues.yaml` still sets `res_req` or `pre_exec`/`post_exec` (the shipped
|
|
`night` queue does) and suggests moving them into a profile. The
|
|
deprecation itself is `job-config-merge`'s; the message needs
|
|
`applications.yaml` to exist.
|
|
|
|
## Scope
|
|
- **In**:
|
|
- The profile schema (essentials, additive rule, aliases) and
|
|
`applications.yaml`, and the default profile (`default_application`).
|
|
- Submit-time validation of the profile and the additive rule.
|
|
- Submit surfaces: REST, `xcecli --app`, harness, libxce.
|
|
- The profile name and the job's own values persisted on the job.
|
|
- The `mode:` schema field (only `direct`), the structured resreq schema
|
|
(`select`, `rusage` entries with `per:` / `default`, `span`, `same`,
|
|
`order`), the job and task starters, `params` (`--app-param`) and
|
|
annotations,
|
|
exported as `XCE_APP_PARAM_*` / `XCE_APP_ANN_*`.
|
|
- New `start_info` fields (job starter, task starter, profile env
|
|
separate from the job
|
|
env) and exdata handling with `job-config-merge`.
|
|
- Profile content hash (shared `src/lib` function) and its field on the
|
|
scheduler's dispatch decision.
|
|
- Restart and array submits carrying the profile name.
|
|
- A worker launch error when a `direct` exec user is missing.
|
|
- Dispatch record (scheduling and execution values, content hash) and the
|
|
retryable dispatch failure for a missing or changed profile.
|
|
- Profile pre/post-exec as the outer layer (moved from the queue), with
|
|
a clean environment; the reserved `XCE_` prefix.
|
|
- Aliases, live-upgrade warning, pend reason for a missing profile.
|
|
- Profile name and mode shown in job details.
|
|
- Listing available profiles (REST and xcecli).
|
|
- Profile `--dry-run` support: the effective values a job would get
|
|
(shared with `job-config-merge`).
|
|
- Tests.
|
|
- **Open for requirements / design**:
|
|
- Hooks and starters (decided, "Hooks and starters"); design confirms
|
|
the serial-job wrapping order, which hosts today's queue pre/post-exec
|
|
runs on, the unit stop timeout and maximum hook duration, and how the
|
|
worker survives an OOM kill.
|
|
- Confirm a dispatch declined for a missing or changed profile does not
|
|
count as a run or against requeue limits.
|
|
- ~~How resource defaults combine with a job's own resreq~~ — settled
|
|
2026-10-09, owned by `job-config-merge`.
|
|
- Settled 2026-10-12: scheduling and execution values frozen at
|
|
dispatch, re-read on requeue ("Dispatch record"); a removed profile
|
|
makes its jobs pend ("Profile removed while jobs use it"); clean env for
|
|
the outer layer and reserved `XCE_` instead of an env denylist; moving
|
|
pending jobs deferred to `job-modify`, profile aliases in v1; override granularity (no markers; additive rule),
|
|
limits a job may lower (profile limits are maximums), personal presets
|
|
(client side), `app:override` and inheritance (deferred); execution modes
|
|
split into `job-sandbox-execution`; v1 attributes are the four plus the
|
|
job starter (plus a task starter for every task startup, decided
|
|
2026-10-12), others ship with their job features; profile history and
|
|
change audit deferred to the roadmap (`config-history-audit`): the
|
|
content hash and the dispatch record already show what each run used;
|
|
no job labels needed here (presets record themselves in `exdata.meta`;
|
|
general labels are the `job-labels` backlog item); compound and
|
|
alternative resreqs (only the profile `select` applies); structured
|
|
resreq with `per:` default `task`; separate profile env; empty profile
|
|
name = no profile; a file profile named `default` replaces the built-in;
|
|
0-valued `rusage` terms removed by the merge; `params` and annotations
|
|
(exported to env) in v1; submit hook deferred (`app-submit-hook`).
|
|
- **Out**:
|
|
- The `sandbox` mode and its options, I/O directories, capability
|
|
advertisement, placement by mode and the worker sandbox mechanics
|
|
(`job-sandbox-execution`).
|
|
- Multi-node sandbox (`job-sandbox-multinode`).
|
|
- Container or vm execution itself (only room for them in the schema).
|
|
- Auth backends and JWT claims.
|
|
- Per-profile access control (`app:use` / `app:view`): deferred to
|
|
`app-access-control` (user decision, 2026-10-12).
|
|
|
|
## Boundary Candidates
|
|
- The profile model and config (schema, validation, live upgrade).
|
|
- Submit-time validation of the profile and the additive rule (controller).
|
|
- Early validation (apiserver).
|
|
- Submit and listing surfaces (REST, xcecli, harness, libxce).
|
|
- Persisting the profile name and the job's own values (wire, WAL submit
|
|
fact, snapshot).
|
|
|
|
## Out of Boundary
|
|
- Worker execution backends and sandbox mechanics.
|
|
- Output file location and retrieval inside the sandbox.
|
|
- Scheduler policy beyond capability filtering and the profile's essential
|
|
resreq.
|
|
- Presets (`xcecli-presets`): client side; the read-only preset listing on
|
|
the apiserver belongs to that spec.
|
|
|
|
## Upstream / Downstream
|
|
- **Upstream**:
|
|
- The existing submit path.
|
|
- The WAL submit fact (`ha-wal-replication`, the `raft-apply-decoupling` apply
|
|
seam).
|
|
- The config pipeline (`clusterconfig-schema-split`, `config-upgrade-snapshot`,
|
|
`admin-config-tool`).
|
|
- RBAC (`rbac_check.go`), only for the reused `cluster:view` on the
|
|
listing route.
|
|
- The scheduler-to-controller dispatch decision (gains the profile
|
|
content hash; `scheduler-dispatch-commit`).
|
|
- The restart path (`RestartJobByID`, bug-1968).
|
|
- **Downstream**:
|
|
- `job-sandbox-execution` adds the `sandbox` mode, its options and I/O
|
|
directories, capability advertisement and placement, on top of this
|
|
spec's `mode:` field and dispatch record.
|
|
- `job-sandbox-multinode`.
|
|
- Future `container`/`vm` modes, which add profile attributes.
|
|
- `job-status-detail-display`, which can show the profile and mode.
|
|
- `job-modify` (moves pending jobs between profiles), `job-requeue`
|
|
(requeue/success exit values as profile attributes),
|
|
`config-history-audit` (profile history), `xcecli-presets` (reads
|
|
profiles for warnings; `exdata.meta`).
|
|
|
|
## Existing Spec Touchpoints
|
|
- **Extends**: none directly.
|
|
- **Depends on**: `job-config-merge` (merge rules and live re-evaluation).
|
|
- **Feeds**: `parjob-placement` (the profile's `span[...]`).
|
|
- **Adjacent**:
|
|
- `job-status-detail-display`
|
|
- `controller-job-cleanup`
|
|
- `resreq-maxtasks-wiring` (task range)
|
|
- `xcecli-presets` (convenience defaults, client side)
|
|
- `profile-usage-insight` (advisory recommendations)
|
|
- `xce-node-manager`
|
|
|
|
## Constraints
|
|
- Middleware chain unchanged: WhitelistURL → WhitelistIP → RateLimit → JWT → CORS
|
|
→ RBAC.
|
|
- All wire changes are additive Cap'n Proto. Old WAL records and snapshots
|
|
decode as "no profile, mode `direct`", which is today's behaviour; an
|
|
empty profile name is never resolved to `default_application`.
|
|
- Save/restore field completeness (`persistence.md`): the profile name, the
|
|
job's own values, the new `start_info` fields and the dispatch record
|
|
round trip through the WAL submit and dispatch facts and the snapshot
|
|
(`/xce-audit-invariants controller`). The scheduler's own state for
|
|
dispatched jobs (frozen, no longer re-merged) and its `Conf4Sched`
|
|
profiles round trip through the scheduler snapshot and replay recordings
|
|
(`/xce-audit-invariants scheduler`).
|
|
- Input validation (`security.md`): the job's additions and paths are validated
|
|
server-side. No path traversal, and mount prefixes are restricted by the
|
|
profile.
|
|
- Size: still large after the split (config, access and submit, job state,
|
|
outer layer); one coherent feature, testable end to end with `direct`.
|
|
- HA/persistence spec: use the full 3-phase workflow.
|
|
|
|
## Future Discussion
|
|
Everything left to discuss, collected 2026-10-12 at the end of the brief
|
|
discussion. Nothing here blocks requirements.
|
|
|
|
### For this spec's design phase
|
|
1. **Serial job wrapping:** both starters wrap a single-task job's command;
|
|
confirm `job_starter` is outermost.
|
|
2. **Today's queue pre/post-exec:** on which hosts it runs (in
|
|
`xcecd_worker`), so it maps to `*_job_exec` or `*_host_exec` without a
|
|
behaviour change.
|
|
3. **Kill paths:** the unit's explicit stop timeout and a configurable
|
|
maximum hook duration (today post-exec is bounded only by systemd's
|
|
unset `TimeoutStopUSec`); how the worker survives an OOM kill inside its
|
|
own unit.
|
|
4. **Declined dispatch:** confirm a dispatch declined for a missing or
|
|
changed profile counts neither as a run nor against requeue limits.
|
|
5. **Field placement:** the profile env (a `start_info` field or an exdata
|
|
key), the task starter on each `xce_taskhost_t`, renaming the queue-named
|
|
`qPreExec` / `qPostExec`, and the dispatch-record payload layout.
|
|
6. **Task starter reach:** which launch types start tasks through the
|
|
worker and which through the MPI runtime; inserting the starter into
|
|
`create_mpi_cmdline()` for `openmpi` and `mpich`.
|
|
7. **Content hash:** canonical encoding, algorithm and length; which fields
|
|
besides `description` are excluded.
|
|
8. **params and annotations:** pattern syntax, count and size limits, env
|
|
name conversion edge cases; an opt-out for exporting annotations only if
|
|
someone needs it.
|
|
9. **Re-merge cost:** with the live merge, a profile change re-merges every
|
|
pending job of that profile in the scheduler; check the cost for large
|
|
pending queues and whether re-merging is batched or lazy.
|
|
10. **Messages:** the live-upgrade warning format, the new pend reason
|
|
("application profile X is not defined"), the `xceadmin` secret-name
|
|
patterns, and the warning when no host matches a profile's `select`.
|
|
|
|
### Deferred features (roadmap backlog)
|
|
| Item | What | Revisit when |
|
|
|---|---|---|
|
|
| `app-access-control` | per-profile `app:use` / `app:view` (design kept above) | profiles must be restricted to some users |
|
|
| `job-modify` | modify pending jobs (`--app`, `-R`, `-n`, queue; bulk `--app-from`) | renames or profile moves become common |
|
|
| `job-requeue` | requeue/success exit values (profile attributes), requeue on hook failure | an application needs automatic requeue |
|
|
| checkpoint, warning-before-kill | their own specs, each adding its profile attribute | an application needs them |
|
|
| output path template | PR-6/PR-7 I/O track | output options are supported |
|
|
| `job-labels` | server-side labels, filtering, summaries; reuses REST `tags` (bug-1970) | team-wide comparison or services need labels |
|
|
| `config-history-audit` | profile history, change audit, lookup by content hash | admins need to see past profiles |
|
|
| `app-submit-hook` | `esub`-style submit hook; client or apiserver | params, annotations, starters and hooks are not enough |
|
|
| `profile-usage-insight` | advisory suggestions for site presets and profile defaults | usage data is available per profile |
|
|
| `xcecli-presets` | client-side presets (brief exists, no `spec.json`) | after this spec's requirements |
|
|
| `app:override` | privileged one-off override of profile values | `job-modify` exists and admins still need it |
|
|
| profile inheritance (`base:`) | variants of a profile | presets prove insufficient for variants |
|
|
| counted-resource protection | a `min:` floor on a profile `rusage` entry, or a "counted" marker in `resources.yaml` | licence under-reservation is a real problem |
|
|
| per-resource default per-type | a per-type field in `resources.yaml` | a resource is almost always per host or per job |
|
|
| per-part profile terms | profile `rusage` / `span` / `same` / `order` for compound jobs | PR-15 named parts land |
|
|
| per-host hooks for the job | the job's own per-host pre/post-exec | users ask for it |
|
|
| writable server-side presets | publish presets from the portal | the portal needs it, after `rbac-persistence` |
|
|
| portal | preset storage, submit forms from `params` | portal work starts |
|
|
|
|
### Housekeeping
|
|
- Regenerate `job-config-merge`'s `requirements.md` after this spec's
|
|
design (it predates the structured resreq, the `rusage` rule, `params`,
|
|
exdata handling, frozen dispatched jobs and the `min`/`max` task range).
|
|
- Mirror bug-1970 to Gitea (`/gitea-sync`).
|
|
- `spec.json` for `xcecli-presets` and the backlog items when they are
|
|
promoted (gitea-sync skips briefs without one).
|
|
- Next step: `/kiro-spec-init application-profile`, then
|
|
`/kiro-spec-requirements application-profile`.
|