Skip to content

Commit e4a80cb

Browse files
committed
Integrate querying by user_id into existing usage endpoint
this removes rendundancy
1 parent ec0af07 commit e4a80cb

4 files changed

Lines changed: 90 additions & 82 deletions

File tree

components/renku_data_services/resource_usage/api.spec.yaml

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,12 @@ servers:
1010
- url: /api/data
1111
paths:
1212
/resource_pools/{resource_pool_id}/usage:
13-
get:
14-
summary: "Get usage and limits of the supplied pool. If a `start_date` is not given, the usage of the current week is returned."
15-
parameters:
16-
- in: path
17-
name: resource_pool_id
18-
required: true
19-
schema:
20-
type: integer
21-
- in: query
22-
name: start_date
23-
required: false
24-
schema:
25-
type: string
26-
format: date
27-
- in: query
28-
name: end_date
29-
required: false
30-
schema:
31-
type: string
32-
format: date
33-
responses:
34-
"200":
35-
description: "Return the pool limits and current usage."
36-
content:
37-
"application/json":
38-
schema:
39-
$ref: "#/components/schemas/ResourcePoolUsage"
40-
"404":
41-
description: "The resource pool doesn't exist."
42-
content:
43-
"application/json":
44-
schema:
45-
$ref: "#/components/schemas/ErrorResponse"
46-
default:
47-
$ref: "#/components/responses/Error"
48-
tags:
49-
- resource-usage
50-
51-
/resource_pools/{resource_pool_id}/admin_usage:
5213
get:
5314
summary: |
5415
Get usage and limits of the supplied pool. If a `start_date`
55-
is not given, the usage of the current week is returned. This
56-
endpoint is only available to admins and it allows to specify
57-
a user_id query parameter to check usages for any user.
16+
is not given, the usage of the current week is returned.
17+
18+
A query parameter `user_id` can be specified and the current user is used if this ismi
5819
parameters:
5920
- in: path
6021
name: resource_pool_id
@@ -96,6 +57,7 @@ paths:
9657
tags:
9758
- resource-usage
9859

60+
9961
/resource_pools/{resource_pool_id}/limits:
10062
get:
10163
summary: "Get resource pool limits"

components/renku_data_services/resource_usage/apispec.py

Lines changed: 2 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-03-04T10:36:46+00:00
3+
# timestamp: 2026-03-13T14:07:21+00:00
44

55
from __future__ import annotations
66

@@ -87,6 +87,7 @@ class Ulid(RootModel[str]):
8787
class ResourcePoolsResourcePoolIdUsageGetParametersQuery(BaseAPISpec):
8888
start_date: Optional[date] = None
8989
end_date: Optional[date] = None
90+
user_id: Optional[str] = None
9091

9192

9293
class ResourcePoolUsage(BaseAPISpec):

components/renku_data_services/resource_usage/blueprints.py

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ def get_pool_usage(self) -> BlueprintFactoryResponse:
147147
async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -> HTTPResponse:
148148
start_date = self._extract_date(req, "start_date")
149149
end_date = self._extract_date(req, "end_date")
150+
user_id = req.args.get("user_id")
151+
user_id = str(user_id) if user_id else user.id
152+
if user_id != user.id and (user.access_token is None or not user.is_admin):
153+
raise errors.ForbiddenError(message="You do not have the required permissions for this operation.")
154+
150155
result: model.ResourcePoolUsage | None = None
151156
if start_date:
152157
result = await self.rr_svc.get_for_date(resource_pool_id, user.id or "", start_date, end_date)
@@ -174,42 +179,3 @@ async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -
174179
raise errors.MissingResourceError()
175180

176181
return "/resource_pools/<resource_pool_id>/usage", ["GET"], _get
177-
178-
def get_admin_pool_usage(self) -> BlueprintFactoryResponse:
179-
"""Get usage of a pool."""
180-
181-
@authenticate(self.authenticator)
182-
@only_admins
183-
@validate_db_ids
184-
async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -> HTTPResponse:
185-
start_date = self._extract_date(req, "start_date")
186-
end_date = self._extract_date(req, "end_date")
187-
user_id = req.args.get("user_id")
188-
uid = str(user_id) if user_id else user.id or ""
189-
result: model.ResourcePoolUsage | None = None
190-
if start_date:
191-
result = await self.rr_svc.get_for_date(resource_pool_id, uid, start_date, end_date)
192-
else:
193-
result = await self.rr_svc.get_running_week(resource_pool_id, uid)
194-
if result:
195-
output = apispec.ResourcePoolUsage(
196-
total_usage=apispec.ResourceUsageSummary(
197-
runtime=result.total_usage.runtime_hours, cost=result.total_usage.cost.value
198-
),
199-
pool_limits=apispec.ResourcePoolLimits(
200-
pool_id=result.pool_limits.pool_id,
201-
total_limit=result.pool_limits.total_limit.value,
202-
user_limit=result.pool_limits.user_limit.value,
203-
),
204-
user_usage=apispec.ResourceUsageSummary(
205-
runtime=result.user_usage.runtime_hours, cost=result.user_usage.cost.value
206-
),
207-
)
208-
return validated_json(
209-
apispec.ResourcePoolUsage,
210-
output,
211-
)
212-
else:
213-
raise errors.MissingResourceError()
214-
215-
return "/resource_pools/<resource_pool_id>/admin_usage", ["GET"], _get
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import json
2+
from copy import deepcopy
3+
from pathlib import Path
4+
from typing import Any
5+
6+
import pytest
7+
from sanic_testing.testing import SanicASGITestClient
8+
9+
from test.bases.renku_data_services.data_api.utils import create_rp
10+
from test.utils import KindCluster
11+
12+
resource_pool_payload = {
13+
"name": "test-name",
14+
"classes": [
15+
{
16+
"cpu": 1.0,
17+
"memory": 10,
18+
"gpu": 0,
19+
"name": "test-class-name",
20+
"max_storage": 100,
21+
"default_storage": 1,
22+
"default": True,
23+
"node_affinities": [],
24+
"tolerations": [],
25+
}
26+
],
27+
"quota": {"cpu": 100, "memory": 100, "gpu": 0},
28+
"default": False,
29+
"public": True,
30+
"idle_threshold": 86400,
31+
"hibernation_threshold": 99999,
32+
"hibernation_warning_period": 888,
33+
}
34+
35+
36+
async def create_resource_pool(sanic_client: SanicASGITestClient) -> int:
37+
payload = deepcopy(resource_pool_payload)
38+
_, res = await create_rp(payload, sanic_client)
39+
assert res.status_code == 201, res.text
40+
assert res.json is not None
41+
return res.json["id"]
42+
43+
44+
@pytest.mark.asyncio
45+
@pytest.mark.xdist_group("sessions") # Needs to run on the same worker as the rest of the sessions tests
46+
async def test_resource_usage_for_user(
47+
sanic_client: SanicASGITestClient,
48+
cluster: KindCluster,
49+
) -> None:
50+
rp_id = await create_resource_pool(sanic_client)
51+
_, res = await sanic_client.get(
52+
f"/api/data/resource_pools/{rp_id}/usage",
53+
headers={"Authorization": 'Bearer {"is_admin": false}'},
54+
)
55+
56+
assert "total_usage" in res.json
57+
assert "user_usage" in res.json
58+
59+
60+
@pytest.mark.asyncio
61+
@pytest.mark.xdist_group("sessions") # Needs to run on the same worker as the rest of the sessions tests
62+
async def test_resource_usage_for_admin(
63+
sanic_client: SanicASGITestClient,
64+
cluster: KindCluster,
65+
) -> None:
66+
rp_id = await create_resource_pool(sanic_client)
67+
_, res = await sanic_client.get(
68+
f"/api/data/resource_pools/{rp_id}/usage?user_id=123",
69+
headers={"Authorization": 'Bearer {"is_admin": false}'},
70+
)
71+
72+
assert res.status_code == 403, res.text
73+
74+
_, res = await sanic_client.get(
75+
f"/api/data/resource_pools/{rp_id}/usage?user_id=123",
76+
headers={"Authorization": 'Bearer {"is_admin": true}'},
77+
)
78+
79+
assert res.status_code == 200, res.text

0 commit comments

Comments
 (0)