Skip to content

Commit 89752a0

Browse files
authored
feat: describe runtime platform on resource pools (#1116)
⚠️ Breaking API change on `PUT /resource_pools/{resource_pool_id}`: a new `platform` field is required. Add a `platform` field to resource pools. The `platform` field describes the platform which will run the sessions in the resource pool, e.g `linux/amd64` or `linux/arm64`. The platform information can be checked against the image information to know if the image is compatible with the resource pool.
1 parent be5aeca commit 89752a0

File tree

14 files changed

+206
-30
lines changed

14 files changed

+206
-30
lines changed

bases/renku_data_services/data_api/dependencies.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from renku_data_services.authz.authz import Authz
2727
from renku_data_services.connected_services.db import ConnectedServicesRepository
2828
from renku_data_services.crc import models as crc_models
29+
from renku_data_services.crc.constants import DEFAULT_RUNTIME_PLATFORM
2930
from renku_data_services.crc.db import ClusterRepository, ResourcePoolRepository, UserRepository
3031
from renku_data_services.crc.server_options import (
3132
ServerOptions,
@@ -101,6 +102,7 @@
101102
quota=None,
102103
public=True,
103104
default=True,
105+
platform=DEFAULT_RUNTIME_PLATFORM,
104106
)
105107

106108

components/renku_data_services/crc/api.spec.yaml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,8 @@ components:
12941294
$ref: "#/components/schemas/HibernationThreshold"
12951295
cluster_id:
12961296
$ref: "#/components/schemas/Ulid"
1297+
platform:
1298+
$ref: "#/components/schemas/RuntimePlatform"
12971299
required: ["classes", "name", "public", "default"]
12981300
example:
12991301
quota:
@@ -1342,6 +1344,8 @@ components:
13421344
$ref: "#/components/schemas/HibernationThreshold"
13431345
cluster_id:
13441346
$ref: "#/components/schemas/Ulid"
1347+
platform:
1348+
$ref: "#/components/schemas/RuntimePlatform"
13451349
example:
13461350
quota:
13471351
cpu: 50
@@ -1374,7 +1378,9 @@ components:
13741378
$ref: "#/components/schemas/HibernationThreshold"
13751379
cluster_id:
13761380
$ref: "#/components/schemas/Ulid"
1377-
required: ["classes", "name", "public", "default"]
1381+
platform:
1382+
$ref: "#/components/schemas/RuntimePlatform"
1383+
required: ["classes", "name", "public", "default", "platform"]
13781384
example:
13791385
quota:
13801386
cpu: 50
@@ -1429,7 +1435,9 @@ components:
14291435
id:
14301436
$ref: "#/components/schemas/Ulid"
14311437
required: ["id"]
1432-
required: ["classes", "name", "id", "public", "default"]
1438+
platform:
1439+
$ref: "#/components/schemas/RuntimePlatform"
1440+
required: ["classes", "name", "id", "public", "default", "platform"]
14331441
example:
14341442
quota:
14351443
cpu: 50
@@ -1481,7 +1489,9 @@ components:
14811489
$ref: "#/components/schemas/HibernationThreshold"
14821490
cluster_id:
14831491
$ref: "#/components/schemas/Ulid"
1484-
required: ["classes", "name", "id", "public", "default"]
1492+
platform:
1493+
$ref: "#/components/schemas/RuntimePlatform"
1494+
required: ["classes", "name", "id", "public", "default", "platform"]
14851495
example:
14861496
quota:
14871497
cpu: 50
@@ -1871,6 +1881,11 @@ components:
18711881
uniqueItems: true
18721882
default: []
18731883
minItems: 0
1884+
RuntimePlatform:
1885+
type: string
1886+
description: |
1887+
A runtime platform, e.g. "linux/amd64" or "linux/arm64".
1888+
enum: ["linux/amd64", "linux/arm64"]
18741889
ErrorResponse:
18751890
type: object
18761891
properties:

components/renku_data_services/crc/apispec.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: api.spec.yaml
3-
# timestamp: 2025-10-13T09:06:36+00:00
3+
# timestamp: 2025-11-27T12:13:24+00:00
44

55
from __future__ import annotations
66

@@ -123,6 +123,11 @@ class NodeAffinityListResponse(RootModel[List[NodeAffinity]]):
123123
)
124124

125125

126+
class RuntimePlatform(Enum):
127+
linux_amd64 = "linux/amd64"
128+
linux_arm64 = "linux/arm64"
129+
130+
126131
class Error(BaseAPISpec):
127132
code: int = Field(..., examples=[1404], gt=0)
128133
detail: Optional[str] = Field(
@@ -824,6 +829,7 @@ class ResourcePoolPatch(BaseAPISpec):
824829
min_length=26,
825830
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
826831
)
832+
platform: Optional[RuntimePlatform] = None
827833

828834

829835
class ResourcePool(BaseAPISpec):
@@ -871,6 +877,7 @@ class ResourcePool(BaseAPISpec):
871877
min_length=26,
872878
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
873879
)
880+
platform: Optional[RuntimePlatform] = None
874881

875882

876883
class ResourcePoolPut(BaseAPISpec):
@@ -944,6 +951,7 @@ class ResourcePoolPut(BaseAPISpec):
944951
min_length=26,
945952
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
946953
)
954+
platform: RuntimePlatform
947955

948956

949957
class ResourcePoolWithId(BaseAPISpec):
@@ -991,6 +999,7 @@ class ResourcePoolWithId(BaseAPISpec):
991999
le=2147483647,
9921000
)
9931001
cluster: Optional[Cluster1] = None
1002+
platform: RuntimePlatform
9941003

9951004

9961005
class ResourcePoolWithIdFiltered(BaseAPISpec):
@@ -1044,6 +1053,7 @@ class ResourcePoolWithIdFiltered(BaseAPISpec):
10441053
min_length=26,
10451054
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
10461055
)
1056+
platform: RuntimePlatform
10471057

10481058

10491059
class ResourcePoolsWithId(RootModel[List[ResourcePoolWithId]]):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Constant values used for resource pools and resource classes."""
2+
3+
from typing import Final
4+
5+
from renku_data_services.crc import models
6+
7+
DEFAULT_RUNTIME_PLATFORM: Final[models.RuntimePlatform] = models.RuntimePlatform.linux_amd64
8+
"""The default runtime platform used by resource pools, "linux/amd64"."""

components/renku_data_services/crc/core.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def validate_resource_pool(body: apispec.ResourcePool) -> models.UnsavedResource
179179
raise errors.ValidationError(message="One default class is required in each resource pool.")
180180

181181
remote = validate_remote(body=body.remote) if body.remote else None
182+
platform = __validate_runtime_platform(body=body.platform)
182183

183184
return models.UnsavedResourcePool(
184185
name=body.name,
@@ -190,6 +191,7 @@ def validate_resource_pool(body: apispec.ResourcePool) -> models.UnsavedResource
190191
public=body.public,
191192
remote=remote,
192193
cluster_id=ULID.from_str(body.cluster_id) if body.cluster_id else None,
194+
platform=platform,
193195
)
194196

195197

@@ -200,6 +202,7 @@ def validate_resource_pool_patch(body: apispec.ResourcePoolPatch) -> models.Reso
200202
)
201203
quota = validate_quota_put_patch(body=body.quota) if body.quota else None
202204
remote = validate_remote_patch(body=body.remote) if body.remote else None
205+
platform = __validate_runtime_platform(body=body.platform) if body.platform else None
203206
return models.ResourcePoolPatch(
204207
name=body.name,
205208
classes=classes,
@@ -210,6 +213,7 @@ def validate_resource_pool_patch(body: apispec.ResourcePoolPatch) -> models.Reso
210213
public=body.public,
211214
remote=remote,
212215
cluster_id=ULID.from_str(body.cluster_id) if body.cluster_id else None,
216+
platform=platform,
213217
)
214218

215219

@@ -221,6 +225,7 @@ def validate_resource_pool_put(body: apispec.ResourcePoolPut) -> models.Resource
221225
)
222226
quota = validate_quota_put_patch(body=body.quota) if body.quota else RESET
223227
remote = validate_remote_put(body=body.remote)
228+
platform = __validate_runtime_platform(body=body.platform) if body.platform else RESET
224229
return models.ResourcePoolPatch(
225230
name=body.name,
226231
classes=classes,
@@ -231,6 +236,7 @@ def validate_resource_pool_put(body: apispec.ResourcePoolPut) -> models.Resource
231236
public=body.public,
232237
remote=remote,
233238
cluster_id=ULID.from_str(body.cluster_id) if body.cluster_id else RESET,
239+
platform=platform,
234240
)
235241

236242

@@ -460,3 +466,18 @@ def validate_firecrest_api_url(url: str) -> None:
460466
message=f"The scheme for the firecrest api url {url} is not valid, expected one of {accepted_schemes}",
461467
quiet=True,
462468
)
469+
470+
471+
def __validate_runtime_platform(body: apispec.RuntimePlatform | None) -> models.RuntimePlatform:
472+
"""Validate the platform field for resource pools."""
473+
platform_str: str = models.RuntimePlatform.linux_amd64
474+
if body:
475+
platform_str = body.value
476+
if platform_str not in models.RuntimePlatform:
477+
raise errors.ValidationError(
478+
message=(
479+
f"Invalid value for the field 'platform': {body}: "
480+
f"Valid values are {[e.value for e in models.RuntimePlatform]}"
481+
)
482+
)
483+
return models.RuntimePlatform(platform_str)

components/renku_data_services/crc/db.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ async def update_resource_pool(
423423
rp.hibernation_threshold = None
424424
elif isinstance(update.hibernation_threshold, int):
425425
rp.hibernation_threshold = update.hibernation_threshold
426+
if update.platform is not None:
427+
rp.platform = update.platform
426428

427429
if update.cluster_id is RESET:
428430
rp.cluster_id = None

components/renku_data_services/crc/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,13 @@ class SavedClusterSettings(ClusterSettings):
283283
id: ClusterId
284284

285285

286+
class RuntimePlatform(StrEnum):
287+
"""Represents a runtime platform."""
288+
289+
linux_amd64 = "linux/amd64"
290+
linux_arm64 = "linux/arm64"
291+
292+
286293
@dataclass(frozen=True, eq=True, kw_only=True)
287294
class UnsavedResourcePool:
288295
"""Model for a resource pool yet to be saved."""
@@ -296,6 +303,7 @@ class UnsavedResourcePool:
296303
public: bool = False
297304
remote: RemoteConfigurationFirecrest | None = None
298305
cluster_id: ClusterId | None = None
306+
platform: RuntimePlatform
299307

300308

301309
@dataclass(frozen=True, eq=True, kw_only=True)
@@ -312,6 +320,7 @@ class ResourcePool:
312320
public: bool = False
313321
remote: RemoteConfigurationFirecrest | None = None
314322
cluster: SavedClusterSettings | None = None
323+
platform: RuntimePlatform
315324

316325
def get_resource_class(self, resource_class_id: int) -> ResourceClass | None:
317326
"""Find a specific resource class in the resource pool by the resource class id."""
@@ -341,6 +350,7 @@ class ResourcePoolPatch:
341350
public: bool | None = None
342351
remote: RemoteConfigurationPatch | None = None
343352
cluster_id: ClusterId | ResetType | None = None
353+
platform: RuntimePlatform | None = None
344354

345355

346356
class RemoteConfigurationKind(StrEnum):

components/renku_data_services/crc/orm.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,7 @@
44

55
from typing import Any, Optional
66

7-
from sqlalchemy import (
8-
JSON,
9-
BigInteger,
10-
Column,
11-
Identity,
12-
Integer,
13-
MetaData,
14-
String,
15-
Table,
16-
)
7+
from sqlalchemy import JSON, BigInteger, Column, Enum, Identity, Integer, MetaData, String, Table, literal
178
from sqlalchemy.dialects.postgresql import JSONB
189
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column, relationship
1910
from sqlalchemy.schema import ForeignKey
@@ -262,6 +253,11 @@ class ResourcePoolORM(BaseORM):
262253
)
263254
cluster: Mapped[Optional[ClusterORM]] = relationship(viewonly=True, default=None, lazy="selectin", init=False)
264255

256+
# NOTE: we reuse the "build_platform" enum type here
257+
platform: Mapped[models.RuntimePlatform] = mapped_column(
258+
Enum(models.RuntimePlatform, name="build_platform"), default=None, server_default=literal("linux_amd64")
259+
)
260+
265261
@classmethod
266262
def from_unsaved_model(
267263
cls,
@@ -291,6 +287,7 @@ def from_unsaved_model(
291287
remote_provider_id=remote_provider_id,
292288
remote_json=remote_json,
293289
cluster_id=cluster.id if cluster else None,
290+
platform=new_resource_pool.platform,
294291
)
295292

296293
def dump(
@@ -324,6 +321,7 @@ def dump(
324321
default=self.default,
325322
remote=remote,
326323
cluster=cluster,
324+
platform=self.platform,
327325
)
328326

329327
def _dump_remote(self) -> models.RemoteConfigurationFirecrest | None:

components/renku_data_services/crc/server_options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydantic import BaseModel, ByteSize, Field, validator
1212

1313
from renku_data_services.crc import models
14+
from renku_data_services.crc.constants import DEFAULT_RUNTIME_PLATFORM
1415

1516

1617
def _check_greater_than_zero(cls: Any, v: int | float) -> int | float:
@@ -152,4 +153,5 @@ def generate_default_resource_pool(
152153
default=True,
153154
public=True,
154155
name="default",
156+
platform=DEFAULT_RUNTIME_PLATFORM,
155157
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""feat: add platform field to resource pools.
2+
3+
Revision ID: 328803606473
4+
Revises: df2c0e65612a
5+
Create Date: 2025-11-17 12:50:15.907221
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "328803606473"
14+
down_revision = "df2c0e65612a"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
op.add_column(
21+
"resource_pools",
22+
sa.Column(
23+
"platform",
24+
sa.Enum("linux_amd64", "linux_arm64", name="build_platform"),
25+
server_default=sa.literal("linux_amd64"),
26+
nullable=False,
27+
),
28+
schema="resource_pools",
29+
)
30+
31+
32+
def downgrade() -> None:
33+
op.drop_column("resource_pools", "platform", schema="resource_pools")

0 commit comments

Comments
 (0)