Skip to content

feat: update apispec for polymorphic resource class definition - #1388

Open
SalimKayal wants to merge 49 commits into
mainfrom
salimkayal/feat/expend-resource-class-apispec-for-remote-sessions
Open

feat: update apispec for polymorphic resource class definition#1388
SalimKayal wants to merge 49 commits into
mainfrom
salimkayal/feat/expend-resource-class-apispec-for-remote-sessions

Conversation

@SalimKayal

@SalimKayal SalimKayal commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a kind and optional FirecREST remote override to resource classes so that FirecREST-backed
classes can target specific Slurm systems/partitions and optionally let the HPC grid choose CPU,
memory, and GPU. A single resource pool can now contain classes with different Slurm target
configurations.

Motivation and context

We originally targeted only CSCS, where the smallest allocation is a full node, so it was enough to
to let Slurm set the defaults for CPUS_PER_TASK, GPUS, RAM. As we expand to other grids
(Idiap) we need to tell Amalthea exactly how much of a node to request. We also want a single
resource pool to support multiple class-level target configurations and optionally which
system_name/partition to use, rather than forcing one pool per Slurm target.

Behavior

  • Resource classes now have a kind (local, firecrest, runai). The kind must match the parent
    pool's remote kind; firecrest classes require integer CPU and may only carry remote overrides
    when the pool is FirecREST.
  • FirecREST classes support per-class remote overrides for system_name and partition, falling
    back to the pool defaults when omitted. This lets a single pool hold multiple classes targeting
    different Slurm systems or partitions.
  • FirecREST classes can set ignore_resource_class_values to tell Amalthea to ignore the CRD CPU,
    memory, and GPU values and let the HPC grid choose them. The class still stores and reports the
    values for display and quota matching.
  • On session launch, FirecREST classes emit RSC_FIRECREST_SYSTEM_NAME, RSC_FIRECREST_PARTITION
    (when set), and RSC_FIRECREST_IGNORE_RESOURCE_CLASS_VALUES (when true). CPU, memory, and GPU
    continue to be passed through the session CRD resources as before.
  • Changing a pool's remote kind auto-converts existing classes to the new kind. The update is
    rejected if any class still has FirecREST remote overrides when moving away from FirecREST, or if
    any class has non-integer CPU when moving to FirecREST.
  • The API remains backward compatible: resource class schemas default kind to local, so existing
    clients and legacy classes keep working.

Changes

  • Updated crc/api.spec.yaml and regenerated crc/apispec.py:

    • ResourceClass/ResourceClassWithId now include kind: RemoteKind (default local) and an
      optional remote: RemoteClassConfigurationFirecrest.
    • RemoteClassConfigurationFirecrest gains ignore_resource_class_values (default false).
    • Added if/then/else schema rules so remote is forbidden when kind != firecrest and cpu
      must be an integer when kind == firecrest.
  • Extended crc/models.py with FirecrestClassRemote and added kind/remote to
    UnsavedResourceClass, ResourceClass, and ResourceClassPatch.

  • Extended crc/orm.py with a remote_json column for class-level FirecREST overrides. Class
    kind is derived at read time from the parent pool's remote_json.kind.

  • Added an Alembic migration that adds resource_classes.remote_json.

  • Updated crc/core.py validators to:

    • Enforce pool-kind/class-kind matching.
    • Require integer CPU for FirecREST classes.
    • Reject remote overrides on non-FirecREST classes.
    • Derive kind correctly on PUT/PATCH when omitted.
    • Validate class compatibility when a pool's remote kind changes.
  • Updated crc/db.py to persist remote_json, reject kind changes, and apply pool remote changes
    before class updates so class conversion sees the new pool kind.

  • Updated crc/blueprints.py to pass the existing class kind into standalone class PUT/PATCH
    validators.

  • Updated notebooks/core_sessions.py to compute effective FirecREST env vars from class overrides
    and emit RSC_FIRECREST_IGNORE_RESOURCE_CLASS_VALUES when requested.

  • Added unit tests for the new validators and updated FirecREST resource-pool and session-launch
    tests.

Notes

  • Resource class kind is not stored on the resource_classes table; it is derived at read time
    from the parent pool's remote_json.kind (defaulting to local). This keeps the migration
    minimal (only remote_json is added) while still making existing FirecREST/Run:AI classes appear
    with the correct kind.
  • The UI can still read/write pool-level system_name/partition for now; they serve as the
    defaults for classes without per-class overrides.

@SalimKayal
SalimKayal force-pushed the salimkayal/feat/expend-resource-classes-for-remote-sessions branch from 7dbc2f8 to d76ebb5 Compare August 4, 2026 09:49
@SalimKayal
SalimKayal force-pushed the salimkayal/feat/expend-resource-class-apispec-for-remote-sessions branch from 225f912 to 0646568 Compare August 4, 2026 10:11
@coveralls

coveralls commented Aug 4, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 31506944335

Coverage at 85.878% (no base build to compare)

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 8 uncovered changes across 4 files (129 of 137 lines covered, 94.16%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
components/renku_data_services/crc/db.py 22 18 81.82%
components/renku_data_services/crc/core.py 47 45 95.74%
components/renku_data_services/crc/models.py 18 17 94.44%
components/renku_data_services/notebooks/core_sessions.py 15 14 93.33%
Total (8 files) 137 129 94.16%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 32410
Covered Lines: 27833
Line Coverage: 85.88%
Coverage Strength: 1.49 hits per line

💛 - Coveralls

@SalimKayal
SalimKayal force-pushed the salimkayal/feat/expend-resource-class-apispec-for-remote-sessions branch 2 times, most recently from df18a6a to 964aeaf Compare August 4, 2026 14:33
@SalimKayal
SalimKayal marked this pull request as ready for review August 5, 2026 09:34
@SalimKayal
SalimKayal requested review from a team and sgaist as code owners August 5, 2026 09:34
@SalimKayal
SalimKayal requested review from leafty and olevski August 5, 2026 09:35
@SalimKayal
SalimKayal changed the base branch from salimkayal/feat/expend-resource-classes-for-remote-sessions to main August 5, 2026 09:41
$ref: "#/components/schemas/NodeAffinityList"
quota_enforced:
$ref: "#/components/schemas/QuotaEnforced"
kind:

@leafty leafty Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand why we repeat kind in the resource class when it cannot be different from the parent resource pool.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it allows the conditional in the api spec. I hesitated a lot with this because datamodel code generator does not support this. I can change that if you think it's more trouble than otherwise

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

kind is gone, per #1404

then:
properties:
cpu:
$ref: "#/components/schemas/CpuInteger"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we add this complexity in the API spec? I would rather have this handled by a math.floor() inside the remote session controller on the amalthea side than make a complex API spec.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

because floor of 0.5 is not what we want ? but again this can be validated elsewhere (i.e. in core.py)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean, we can have:

cpu_int = max(math.floor(cpu), 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

to have a minimum of 1 and then truncating if set to a non-whole value

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

out of the api spec per #1404

Comment thread components/renku_data_services/crc/api.spec.yaml Outdated

@SalimKayal SalimKayal left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So basically all your remarks are linked to the kind in resource class. should I remove this and manage this aspect differently ?

$ref: "#/components/schemas/NodeAffinityList"
quota_enforced:
$ref: "#/components/schemas/QuotaEnforced"
kind:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it allows the conditional in the api spec. I hesitated a lot with this because datamodel code generator does not support this. I can change that if you think it's more trouble than otherwise

then:
properties:
cpu:
$ref: "#/components/schemas/CpuInteger"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

because floor of 0.5 is not what we want ? but again this can be validated elsewhere (i.e. in core.py)

Comment thread components/renku_data_services/crc/api.spec.yaml Outdated
$ref: "#/components/schemas/RemoteConfigurationFirecrestSystemName"
partition:
$ref: "#/components/schemas/RemoteConfigurationFirecrestPartition"
ignore_resource_class_values:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make more sense to move this field to the resource class? It seems that all the classes in a remote HPC pool would set this to the same value.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I wondered a bit about this. Here's why I went this way.

If we keep the resource pools/classes as is it works because the resource pool's values are used.

if we change the values in the resource class, it overrides the pool's defaults

this means that if we do not change anything in the configs it should still work, if we do we can have a single resource pool per firecrest api endpoint. i.e. CSCS Alps with Daint/Eiger/Bristen/Clariden Systems and debug/normal partitions. I can change that if you think it's poor design.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would be more likely that an admin forgets about this field when updating some classes. If we have forward_resource_values at the resource pool level, then it is less likely to be mis-configured (only needs to be set once per pool).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the default for the forward_resource_values is false. we may want to have some classes with defaults and some with config. Also we could have a default in the pool and an override in the class ? Or do we disregard this feature for admins and simplify it at the pool level ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a case where a resource pool would have a mix bag of forward: true and forward: false for classes? It seems to me that the setting could be left at the pool level.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

At CSCS they were talking about allowing partial node allocation because some people were complaining that 4xGB200 is a bit much for some tasks.

Comment thread components/renku_data_services/crc/core.py Outdated
Comment thread components/renku_data_services/crc/core.py
Comment thread components/renku_data_services/crc/core.py Outdated

@leafty leafty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment: without a platform override (amd64 | arm64), it is not possible to merge clusters in a single resource pool.

@SalimKayal

Copy link
Copy Markdown
Collaborator Author

Comment: without a platform override (amd64 | arm64), it is not possible to merge clusters in a single resource pool.

Should I add it while I'm at it ?

@leafty

leafty commented Aug 11, 2026

Copy link
Copy Markdown
Member

Comment: without a platform override (amd64 | arm64), it is not possible to merge clusters in a single resource pool.

Should I add it while I'm at it ?

Sure.

@SalimKayal

Copy link
Copy Markdown
Collaborator Author

Comment: without a platform override (amd64 | arm64), it is not possible to merge clusters in a single resource pool.

Should I add it while I'm at it ?

Sure.

this is a bigger redesign as we need to use it to validate the image builds. do I simply wire it in for now then add the ui/image-building stuff in a follow up PR or is all of this a follow up pr ?

@leafty

leafty commented Aug 11, 2026

Copy link
Copy Markdown
Member

Comment: without a platform override (amd64 | arm64), it is not possible to merge clusters in a single resource pool.

Should I add it while I'm at it ?

Sure.

this is a bigger redesign as we need to use it to validate the image builds. do I simply wire it in for now then add the ui/image-building stuff in a follow up PR or is all of this a follow up pr ?

We should leave it out then.

@SalimKayal

Copy link
Copy Markdown
Collaborator Author

Info: ignore_resource_class_values renamed to forward_resource_value

SalimKayal and others added 22 commits August 11, 2026 17:11
* feat: remove redundant kind field from resource-class API spec

* refactor: derive resource class local/remote from parent pool

* fix: components/renku_data_services/crc/apispec.py

* test: update resource pool API tests for removed kind field

* refactor: reset QuotaEnforced defaults at apispec level
@SalimKayal
SalimKayal force-pushed the salimkayal/feat/expend-resource-class-apispec-for-remote-sessions branch from b5fcd65 to 0464e41 Compare August 11, 2026 15:25

@leafty leafty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good now, we should deploy and test.

$ref: "#/components/schemas/RemoteConfigurationFirecrestSystemName"
partition:
$ref: "#/components/schemas/RemoteConfigurationFirecrestPartition"
forward_resource_value:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
forward_resource_value:
forward_resource_values:

# FirecREST requires integer CPU.
cpu = rc.cpu if rc.cpu is not None else (existing.cpu if existing is not None else None)
if new_pool_kind == models.RemoteConfigurationKind.firecrest and cpu is not None and not cpu.is_integer():
raise errors.ValidationError(message="FirecREST resource classes require an integer value for cpu.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we not do ceil() instead? This could be annoying to deal with. (Or the admin would have to delete and re-create).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The check only fires on writes (PUT/PATCH of the pool, POST of a class). GETs don't validate, so existing fractional-cpu classes still read back fine and don't need migrating.

Failing at write time is actually the right moment to be explicit: the admin is already making a change, so a clear error tells them to set an integer cpu in the same request rather than silently coercing a value they typed.

For the case where fractional data does slip through (legacy rows, etc.), I've switched the launch-time coercion in core_sessions.py from int() (truncated down) to ceil() (rounds up). That achieves the "never under-provision" result without hiding the write-time mismatch behind a silent fix.

Co-authored-by: Flora Thiebaut <flora@leafty.dev>
leafty
leafty previously approved these changes Aug 12, 2026
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.

3 participants