Skip to content

Commit 8ad211d

Browse files
committed
Integrate querying by user_id into existing usage endpoint
this removes rendundancy
1 parent 5487535 commit 8ad211d

4 files changed

Lines changed: 91 additions & 84 deletions

File tree

components/renku_data_services/resource_usage/api.spec.yaml

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,15 @@ 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
19+
user is used if this is not present. This endpoint requires
20+
admin privileges, if the `user_id` query parameter is not the
21+
current user.
5822
parameters:
5923
- in: path
6024
name: resource_pool_id

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: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -144,53 +144,19 @@ def get_pool_usage(self) -> BlueprintFactoryResponse:
144144

145145
@authenticate(self.authenticator)
146146
@validate_db_ids
147-
async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -> HTTPResponse:
148-
start_date = self._extract_date(req, "start_date")
149-
end_date = self._extract_date(req, "end_date")
150-
result: model.ResourcePoolUsage | None = None
151-
if start_date:
152-
result = await self.rr_svc.get_for_date(resource_pool_id, user.id or "", start_date, end_date)
153-
else:
154-
result = await self.rr_svc.get_running_week(resource_pool_id, user.id or "")
155-
if result:
156-
output = apispec.ResourcePoolUsage(
157-
total_usage=apispec.ResourceUsageSummary(
158-
runtime=result.total_usage.runtime_hours, cost=result.total_usage.cost.value
159-
),
160-
pool_limits=apispec.ResourcePoolLimits(
161-
pool_id=result.pool_limits.pool_id,
162-
total_limit=result.pool_limits.total_limit.value,
163-
user_limit=result.pool_limits.user_limit.value,
164-
),
165-
user_usage=apispec.ResourceUsageSummary(
166-
runtime=result.user_usage.runtime_hours, cost=result.user_usage.cost.value
167-
),
168-
)
169-
return validated_json(
170-
apispec.ResourcePoolUsage,
171-
output,
172-
)
173-
else:
174-
raise errors.MissingResourceError()
175-
176-
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
184147
async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -> HTTPResponse:
185148
start_date = self._extract_date(req, "start_date")
186149
end_date = self._extract_date(req, "end_date")
187150
user_id = req.args.get("user_id")
188-
uid = str(user_id) if user_id else user.id or ""
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+
189155
result: model.ResourcePoolUsage | None = None
190156
if start_date:
191-
result = await self.rr_svc.get_for_date(resource_pool_id, uid, start_date, end_date)
157+
result = await self.rr_svc.get_for_date(resource_pool_id, user_id or "", start_date, end_date)
192158
else:
193-
result = await self.rr_svc.get_running_week(resource_pool_id, uid)
159+
result = await self.rr_svc.get_running_week(resource_pool_id, user_id or "")
194160
if result:
195161
output = apispec.ResourcePoolUsage(
196162
total_usage=apispec.ResourceUsageSummary(
@@ -212,4 +178,4 @@ async def _get(req: Request, user: base_models.APIUser, resource_pool_id: int) -
212178
else:
213179
raise errors.MissingResourceError()
214180

215-
return "/resource_pools/<resource_pool_id>/admin_usage", ["GET"], _get
181+
return "/resource_pools/<resource_pool_id>/usage", ["GET"], _get
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from copy import deepcopy
2+
3+
import pytest
4+
from sanic_testing.testing import SanicASGITestClient
5+
6+
from test.bases.renku_data_services.data_api.utils import create_rp
7+
from test.utils import KindCluster
8+
9+
resource_pool_payload = {
10+
"name": "test-name",
11+
"classes": [
12+
{
13+
"cpu": 1.0,
14+
"memory": 10,
15+
"gpu": 0,
16+
"name": "test-class-name",
17+
"max_storage": 100,
18+
"default_storage": 1,
19+
"default": True,
20+
"node_affinities": [],
21+
"tolerations": [],
22+
}
23+
],
24+
"quota": {"cpu": 100, "memory": 100, "gpu": 0},
25+
"default": False,
26+
"public": True,
27+
"idle_threshold": 86400,
28+
"hibernation_threshold": 99999,
29+
"hibernation_warning_period": 888,
30+
}
31+
32+
33+
async def create_resource_pool(sanic_client: SanicASGITestClient) -> int:
34+
payload = deepcopy(resource_pool_payload)
35+
_, res = await create_rp(payload, sanic_client)
36+
assert res.status_code == 201, res.text
37+
assert res.json is not None
38+
return res.json["id"]
39+
40+
41+
@pytest.mark.asyncio
42+
@pytest.mark.xdist_group("sessions") # Needs to run on the same worker as the rest of the sessions tests
43+
async def test_resource_usage_for_user(
44+
sanic_client: SanicASGITestClient,
45+
cluster: KindCluster,
46+
) -> None:
47+
rp_id = await create_resource_pool(sanic_client)
48+
_, res = await sanic_client.get(
49+
f"/api/data/resource_pools/{rp_id}/usage",
50+
headers={"Authorization": 'Bearer {"is_admin": false}'},
51+
)
52+
53+
assert "total_usage" in res.json
54+
assert "user_usage" in res.json
55+
56+
57+
@pytest.mark.asyncio
58+
@pytest.mark.xdist_group("sessions") # Needs to run on the same worker as the rest of the sessions tests
59+
async def test_resource_usage_for_admin(
60+
sanic_client: SanicASGITestClient,
61+
cluster: KindCluster,
62+
) -> None:
63+
rp_id = await create_resource_pool(sanic_client)
64+
_, res = await sanic_client.get(
65+
f"/api/data/resource_pools/{rp_id}/usage?user_id=123",
66+
headers={"Authorization": 'Bearer {"is_admin": false}'},
67+
)
68+
69+
assert res.status_code == 403, res.text
70+
71+
_, res = await sanic_client.get(
72+
f"/api/data/resource_pools/{rp_id}/usage?user_id=123",
73+
headers={"Authorization": 'Bearer {"is_admin": true}'},
74+
)
75+
76+
assert res.status_code == 200, res.text

0 commit comments

Comments
 (0)