Skip to content

Commit 65190e2

Browse files
authored
feat: add option to set cpu limits (#1384)
1 parent ad8d07f commit 65190e2

8 files changed

Lines changed: 163 additions & 15 deletions

File tree

components/renku_data_services/crc/api.spec.yaml

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,8 @@ components:
15431543
$ref: "#/components/schemas/Ulid"
15441544
platform:
15451545
$ref: "#/components/schemas/RuntimePlatform"
1546+
cpu_limit_factor:
1547+
$ref: "#/components/schemas/CpuLimitFactor"
15461548
required: ["classes", "name", "public", "default"]
15471549
example:
15481550
quota:
@@ -1595,6 +1597,10 @@ components:
15951597
$ref: "#/components/schemas/Ulid"
15961598
platform:
15971599
$ref: "#/components/schemas/RuntimePlatform"
1600+
cpu_limit_factor:
1601+
oneOf:
1602+
- $ref: "#/components/schemas/CpuLimitFactor"
1603+
- $ref: "#/components/schemas/RemoveCpuLimitFactor"
15981604
example:
15991605
quota:
16001606
cpu: 50
@@ -1631,6 +1637,10 @@ components:
16311637
$ref: "#/components/schemas/Ulid"
16321638
platform:
16331639
$ref: "#/components/schemas/RuntimePlatform"
1640+
cpu_limit_factor:
1641+
oneOf:
1642+
- $ref: "#/components/schemas/CpuLimitFactor"
1643+
- $ref: "#/components/schemas/RemoveCpuLimitFactor"
16341644
required: ["classes", "name", "public", "default", "platform"]
16351645
example:
16361646
quota:
@@ -1690,6 +1700,8 @@ components:
16901700
required: ["id"]
16911701
platform:
16921702
$ref: "#/components/schemas/RuntimePlatform"
1703+
cpu_limit_factor:
1704+
$ref: "#/components/schemas/CpuLimitFactor"
16931705
required: ["classes", "name", "id", "public", "default", "platform"]
16941706
example:
16951707
quota:
@@ -1748,6 +1760,8 @@ components:
17481760
$ref: "#/components/schemas/RuntimePlatform"
17491761
credits_used:
17501762
$ref: "#/components/schemas/CreditsUsed"
1763+
cpu_limit_factor:
1764+
$ref: "#/components/schemas/CpuLimitFactor"
17511765
required: ["classes", "name", "id", "public", "default", "platform"]
17521766
example:
17531767
quota:
@@ -2159,7 +2173,7 @@ components:
21592173
properties:
21602174
kind:
21612175
type: string
2162-
enum: [ "firecrest" ]
2176+
enum: ["firecrest"]
21632177
description: Kind of remote resource pool
21642178
example: "firecrest"
21652179
provider_id:
@@ -2200,7 +2214,7 @@ components:
22002214
properties:
22012215
kind:
22022216
type: string
2203-
enum: [ "runai" ]
2217+
enum: ["runai"]
22042218
description: Kind of remote resource pool
22052219
example: "runai"
22062220
base_url:
@@ -2235,7 +2249,7 @@ components:
22352249
properties:
22362250
kind:
22372251
type: string
2238-
enum: [ "firecrest" ]
2252+
enum: ["firecrest"]
22392253
description: Kind of remote resource pool
22402254
example: "firecrest"
22412255
provider_id:
@@ -2254,7 +2268,7 @@ components:
22542268
properties:
22552269
kind:
22562270
type: string
2257-
enum: [ "runai" ]
2271+
enum: ["runai"]
22582272
description: Kind of remote resource pool
22592273
example: "runai"
22602274
base_url:
@@ -2339,7 +2353,7 @@ components:
23392353
description: A list of k8s labels used for tolerations and/or node affinity
23402354
items:
23412355
$ref: "#/components/schemas/NodeAffinity"
2342-
example: [{"key": "test-label-1", "required_during_scheduling": false}]
2356+
example: [{ "key": "test-label-1", "required_during_scheduling": false }]
23432357
uniqueItems: true
23442358
default: []
23452359
minItems: 0
@@ -2348,7 +2362,7 @@ components:
23482362
description: A list of k8s labels used for tolerations and/or node affinity
23492363
items:
23502364
$ref: "#/components/schemas/NodeAffinity"
2351-
example: [{"key": "test-label-1", "required_during_scheduling": false}]
2365+
example: [{ "key": "test-label-1", "required_during_scheduling": false }]
23522366
uniqueItems: true
23532367
default: []
23542368
minItems: 0
@@ -2385,11 +2399,11 @@ components:
23852399
type: string
23862400
minLength: 26
23872401
maxLength: 26
2388-
pattern: "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" # This is case-insensitive
2402+
pattern: "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" # This is case-insensitive
23892403
Protocol:
23902404
description: Allowed Protocol strings
23912405
type: string
2392-
enum: [ "http", "https" ]
2406+
enum: ["http", "https"]
23932407
Port:
23942408
type: integer
23952409
minimum: 0
@@ -2411,6 +2425,18 @@ components:
24112425
IngressAnnotations:
24122426
type: object
24132427
additionalProperties: true
2428+
CpuLimitFactor:
2429+
type: number
2430+
format: float
2431+
minimum: 1
2432+
maximum: 100
2433+
description: The value used to multiply cpu requests to get the limit. If unset there will be no cpu limits.
2434+
RemoveCpuLimitFactor:
2435+
type: number
2436+
minimum: 0
2437+
maximum: 0
2438+
description: Indicates that the cpu limits should not be set and if a limit is set it should be removed
2439+
format: float
24142440
responses:
24152441
Error:
24162442
description: The schema for all 4xx and 5xx responses

components/renku_data_services/crc/apispec.py

Lines changed: 39 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: 2026-06-29T12:51:11+00:00
3+
# timestamp: 2026-07-16T20:59:27+00:00
44

55
from __future__ import annotations
66

@@ -219,6 +219,24 @@ class IngressAnnotations(BaseAPISpec):
219219
)
220220

221221

222+
class CpuLimitFactor(RootModel[float]):
223+
root: float = Field(
224+
...,
225+
description="The value used to multiply cpu requests to get the limit. If unset there will be no cpu limits.",
226+
ge=1.0,
227+
le=100.0,
228+
)
229+
230+
231+
class RemoveCpuLimitFactor(RootModel[float]):
232+
root: float = Field(
233+
...,
234+
description="Indicates that the cpu limits should not be set and if a limit is set it should be removed",
235+
ge=0.0,
236+
le=0.0,
237+
)
238+
239+
222240
class ResourcePoolsParams(BaseAPISpec):
223241
model_config = ConfigDict(
224242
extra="forbid",
@@ -984,6 +1002,7 @@ class ResourcePoolPatch(BaseAPISpec):
9841002
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
9851003
)
9861004
platform: RuntimePlatform | None = None
1005+
cpu_limit_factor: CpuLimitFactor | RemoveCpuLimitFactor | None = None
9871006

9881007

9891008
class ResourcePool(BaseAPISpec):
@@ -1038,6 +1057,12 @@ class ResourcePool(BaseAPISpec):
10381057
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
10391058
)
10401059
platform: RuntimePlatform | None = None
1060+
cpu_limit_factor: float | None = Field(
1061+
None,
1062+
description="The value used to multiply cpu requests to get the limit. If unset there will be no cpu limits.",
1063+
ge=1.0,
1064+
le=100.0,
1065+
)
10411066

10421067

10431068
class ResourcePoolPut(BaseAPISpec):
@@ -1120,6 +1145,7 @@ class ResourcePoolPut(BaseAPISpec):
11201145
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
11211146
)
11221147
platform: RuntimePlatform
1148+
cpu_limit_factor: CpuLimitFactor | RemoveCpuLimitFactor | None = None
11231149

11241150

11251151
class ResourcePoolWithId(BaseAPISpec):
@@ -1174,6 +1200,12 @@ class ResourcePoolWithId(BaseAPISpec):
11741200
)
11751201
cluster: Cluster1 | None = None
11761202
platform: RuntimePlatform
1203+
cpu_limit_factor: float | None = Field(
1204+
None,
1205+
description="The value used to multiply cpu requests to get the limit. If unset there will be no cpu limits.",
1206+
ge=1.0,
1207+
le=100.0,
1208+
)
11771209

11781210

11791211
class ResourcePoolWithIdFiltered(BaseAPISpec):
@@ -1240,6 +1272,12 @@ class ResourcePoolWithIdFiltered(BaseAPISpec):
12401272
examples=[300],
12411273
ge=0,
12421274
)
1275+
cpu_limit_factor: float | None = Field(
1276+
None,
1277+
description="The value used to multiply cpu requests to get the limit. If unset there will be no cpu limits.",
1278+
ge=1.0,
1279+
le=100.0,
1280+
)
12431281

12441282

12451283
class ResourcePoolsWithId(RootModel[list[ResourcePoolWithId]]):

components/renku_data_services/crc/core.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ def validate_resource_pool_put_or_patch(
235235
hibernation_warning_period = validate_hibernation_warning_period(
236236
body.hibernation_threshold, body.hibernation_warning_period
237237
)
238+
239+
cpu_limit_factorrr: None | ResetType | float = None
240+
match method, body.cpu_limit_factor:
241+
case _, apispec.RemoveCpuLimitFactor():
242+
cpu_limit_factorrr = RESET
243+
case "PUT", None:
244+
cpu_limit_factorrr = RESET
245+
case _, apispec.CpuLimitFactor():
246+
cpu_limit_factorrr = body.cpu_limit_factor.root
247+
238248
return models.ResourcePoolPatch(
239249
name=body.name,
240250
classes=classes,
@@ -247,6 +257,7 @@ def validate_resource_pool_put_or_patch(
247257
remote=remote,
248258
cluster_id=ULID.from_str(body.cluster_id) if body.cluster_id else None,
249259
platform=platform,
260+
cpu_limit_factor=cpu_limit_factorrr,
250261
)
251262

252263

components/renku_data_services/crc/db.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,10 @@ async def update_resource_pool(
622622
rp.hibernation_warning_period = update.hibernation_warning_period
623623
if update.platform is not None:
624624
rp.platform = update.platform
625+
if update.cpu_limit_factor == 0 or update.cpu_limit_factor is RESET:
626+
rp.cpu_limit_factor = None
627+
elif update.cpu_limit_factor is not None:
628+
rp.cpu_limit_factor = update.cpu_limit_factor
625629

626630
match (update.cluster_id, rp.cluster_id):
627631
case ResetType.Reset, x if x is not None:

components/renku_data_services/crc/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ class UnsavedResourcePool:
333333
remote: RemoteConfigurationFirecrest | RemoteConfigurationRunai | None = None
334334
cluster_id: ClusterId | None = None
335335
platform: RuntimePlatform
336+
cpu_limit_factor: float | None = None
337+
"""Used to assign cpu limits based on the cpu value in the resource classes in the pool.
338+
If the value is zero or unset then cpu limits are not set.
339+
"""
336340

337341

338342
@dataclass(frozen=True, eq=True, kw_only=True)
@@ -352,6 +356,10 @@ class ResourcePool:
352356
cluster: SavedClusterSettings | None = None
353357
platform: RuntimePlatform
354358
credits_used: int | None = None
359+
cpu_limit_factor: float | None = None
360+
"""Used to assign cpu limits based on the cpu value in the resource classes in the pool.
361+
If the value is zero or unset then cpu limits are not set.
362+
"""
355363

356364
def get_resource_class(self, resource_class_id: int) -> ResourceClass | None:
357365
"""Find a specific resource class in the resource pool by the resource class id."""
@@ -389,6 +397,7 @@ class ResourcePoolPatch:
389397
remote: RemoteConfigurationPatch | None = None
390398
cluster_id: ClusterId | ResetType | None = None
391399
platform: RuntimePlatform | None = None
400+
cpu_limit_factor: float | None | ResetType = None
392401

393402

394403
class RemoteConfigurationKind(StrEnum):

components/renku_data_services/crc/orm.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CheckConstraint,
1111
Column,
1212
Enum,
13+
Float,
1314
Identity,
1415
Integer,
1516
MetaData,
@@ -254,6 +255,12 @@ class ResourcePoolORM(BaseORM):
254255
"""Resource pool specifies a set of resource classes, users that can access them and a quota."""
255256

256257
__tablename__ = "resource_pools"
258+
__table_args__ = (
259+
CheckConstraint(
260+
"cpu_limit_factor IS NULL OR cpu_limit_factor >= 1.0",
261+
name="chk_cpu_limit_factor_gte_1",
262+
),
263+
)
257264
name: Mapped[str] = mapped_column(String(40), index=True)
258265
quota: Mapped[Optional[str]] = mapped_column(String(63), index=True, default=None)
259266
users: Mapped[list[UserORM]] = relationship(
@@ -298,6 +305,11 @@ class ResourcePoolORM(BaseORM):
298305
Enum(models.RuntimePlatform, name="build_platform"), default=None, server_default=literal("linux_amd64")
299306
)
300307

308+
cpu_limit_factor: Mapped[Optional[float]] = mapped_column("cpu_limit_factor", Float, default=None, nullable=True)
309+
"""Used to assign cpu limits based on the cpu value in the resource classes in the pool.
310+
If the value is zero or unset then cpu limits are not set.
311+
"""
312+
301313
@classmethod
302314
def from_unsaved_model(
303315
cls,
@@ -329,6 +341,7 @@ def from_unsaved_model(
329341
remote_json=remote_json,
330342
cluster_id=cluster.id if cluster else None,
331343
platform=new_resource_pool.platform,
344+
cpu_limit_factor=new_resource_pool.cpu_limit_factor,
332345
)
333346

334347
def dump(
@@ -366,6 +379,7 @@ def dump(
366379
cluster=cluster,
367380
platform=self.platform,
368381
credits_used=credits_used.value if credits_used else None,
382+
cpu_limit_factor=self.cpu_limit_factor,
369383
)
370384

371385
def _dump_remote(self) -> models.RemoteConfigurationFirecrest | models.RemoteConfigurationRunai | None:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""add cpu limit factor
2+
3+
Revision ID: 5cb5a8d5663b
4+
Revises: 0a6cee40fe0d
5+
Create Date: 2026-07-13 13:14:07.765045
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "5cb5a8d5663b"
14+
down_revision = "0a6cee40fe0d"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column("resource_pools", sa.Column("cpu_limit_factor", sa.Float(), nullable=True), schema="resource_pools")
22+
# ### end Alembic commands ###
23+
op.create_check_constraint(
24+
"chk_cpu_limit_factor_gte_1",
25+
"resource_pools",
26+
"cpu_limit_factor IS NULL OR cpu_limit_factor >= 1.0",
27+
schema="resource_pools",
28+
)
29+
30+
31+
def downgrade() -> None:
32+
op.drop_constraint("chk_cpu_limit_factor_gte_1", "resource_pools", schema="resource_pools", type_="check")
33+
# ### commands auto generated by Alembic - please adjust! ###
34+
op.drop_column("resource_pools", "cpu_limit_factor", schema="resource_pools")
35+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)