Skip to content

WIP: feat: add computation quotas check#1013

Open
GhilesA wants to merge 3 commits into
mainfrom
feat/add_quotas_on_computation
Open

WIP: feat: add computation quotas check#1013
GhilesA wants to merge 3 commits into
mainfrom
feat/add_quotas_on_computation

Conversation

@GhilesA

@GhilesA GhilesA commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

  • send error when max quota is reached
  • expose quota check status (can be disabled if necessary, default quota check is on)

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces an operation-based quota system. A new OperationType enum maps ComputationType values to quota categories. UserProfileInfos replaces its single build-limit field with a per-operation quota map. UserAdminService gains REST methods to query and start/end quotas, wired into StudyService (enforcement and start) and ConsumerService (release). A feature flag controls enforcement, with a new business error code and corresponding test updates.

Changes

Operation quota tracking

Layer / File(s) Summary
OperationType enum and mapping
src/main/java/org/gridsuite/study/server/dto/OperationType.java, src/test/java/org/gridsuite/study/server/dto/OperationTypeTest.java
New OperationType enum with a switch-based mapFromComputationType conversion, plus a unit test validating the mapping.
Profile DTO quota map and new error code
src/main/java/org/gridsuite/study/server/dto/UserProfileInfos.java, src/main/java/org/gridsuite/study/server/error/StudyBusinessErrorCode.java
maxAllowedBuilds is replaced with Map<OperationType, Integer> maxComputationQuotas; a new MAX_OPERATION_TYPE_EXCEEDED error code is added.
UserAdminService quota REST endpoints
src/main/java/org/gridsuite/study/server/service/UserAdminService.java, src/test/java/org/gridsuite/study/server/service/UserAdminServiceTest.java
Adds getUserMaxQuota, getUserCurrentQuota, startOperationWithQuota, endOperationWithQuota REST calls, removing getUserMaxAllowedBuilds; new tests cover each method.
StudyService quota enforcement and start wiring
src/main/java/org/gridsuite/study/server/service/StudyService.java, src/test/java/org/gridsuite/study/server/StudyServiceTest.java
Adds shouldCheckOperationQuotas flag, assertOnQuotasAvailability, getOperationQuotaStatus, reworks build-limit logic to use quota maps, and starts quota tracking across all computation entrypoints; tests updated/added accordingly.
ConsumerService quota release on completion
src/main/java/org/gridsuite/study/server/service/ConsumerService.java
Injects UserAdminService and calls endOperationWithQuota on computation failure, stop, debug, and result events.
Quota feature flag configuration
src/main/resources/config/application.yaml, src/test/resources/application-default.yml
Adds study.enable-operation-quotas configuration (default true in main config, false in test defaults).
Test mocks and WireMock stubs updated for quota API
src/test/java/org/gridsuite/study/server/NetworkModificationTest.java, NodeSequenceTest.java, ShortCircuitTest.java, StateEstimationTest.java, VoltageInitTest.java, StudyControllerDynamic*Test.java, rootnetworks/ModificationToExcludeTest.java, service/StudyServiceDynamicSimulationTest.java, utils/TestUtils.java, utils/wiremock/UserAdminServerStubs.java
Updates mocks/stubs to the new quota endpoints and Map-based data, adds UserAdminService mocked beans where needed, and filters quota start/end requests out of generic request assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StudyService
  participant UserAdminService
  participant ConsumerService

  Client->>StudyService: run computation
  StudyService->>StudyService: assertOnQuotasAvailability(computationType, userId)
  StudyService->>UserAdminService: getUserCurrentQuota / getUserMaxQuota
  UserAdminService-->>StudyService: quota values
  StudyService->>UserAdminService: startOperationWithQuota(userId, operationType, resultUuid)
  StudyService-->>Client: computation result UUID
  Note over ConsumerService: computation completes asynchronously
  ConsumerService->>ConsumerService: read userId, resultUuid from message headers
  ConsumerService->>UserAdminService: endOperationWithQuota(userId, operationType, resultUuid)
  ConsumerService-->>Client: notify result/failure/stop
Loading

Suggested reviewers: AbdelHedhili, thangqp

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is clearly related to the main change: adding computation quota checks.
Description check ✅ Passed The description matches the changeset by describing quota errors and quota check status exposure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
src/main/java/org/gridsuite/study/server/service/StudyService.java

ast-grep timed out on this file

src/main/java/org/gridsuite/study/server/service/UserAdminService.java

ast-grep retry budget exhausted before isolating this batch

src/main/resources/config/application.yaml

ast-grep retry budget exhausted before isolating this batch

  • 13 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@GhilesA GhilesA force-pushed the feat/add_quotas_on_computation branch from 03724ef to 05ec476 Compare July 6, 2026 13:44
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/test/java/org/gridsuite/study/server/utils/TestUtils.java (2)

239-264: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated removeServeEventsMatching pair across the two teardown helpers.

Consider extracting a small private helper (e.g. removeQuotaServeEvents(WireMockServer)) to avoid repeating the same two lines in assertWiremockServerRequestsEmptyThenShutdown and assertWiremockServerRequestsEmptyThenClear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/org/gridsuite/study/server/utils/TestUtils.java` around lines
239 - 264, The two teardown helpers,
assertWiremockServerRequestsEmptyThenShutdown and
assertWiremockServerRequestsEmptyThenClear, repeat the same
removeServeEventsMatching calls for the quota start/end URLs. Extract that
duplicated logic into a small private helper such as
removeQuotaServeEvents(WireMockServer) and call it from both methods so the
quota-event cleanup is centralized and easier to maintain.

112-140: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Narrow the quota-skip regex to avoid masking legitimate GET /quota/max verification.

"/v1/users/.*/quota/.*" also matches a GET .../quota/max request, not just the start/end tracking calls it's meant to filter out. This differs from the precise UserAdminServerStubs.QUOTA_START_URL_PATTERN/QUOTA_END_URL_PATTERN (which require a /start or /end suffix) already imported in this file. If a future MockWebServer-based test needs to verify the max-quota check call, this broader filter would silently skip it.

♻️ Suggested fix reusing the precise patterns
             do {
                 recordedRequest = Objects.requireNonNull(server.takeRequest(TIMEOUT, TimeUnit.MILLISECONDS));
                 // skip quota start/end requests that are auto-handled and not part of test assertions
-            } while (recordedRequest.getPath() != null && recordedRequest.getPath().matches("/v1/users/.*/quota/.*"));
+            } while (recordedRequest.getPath() != null
+                    && (recordedRequest.getPath().matches(UserAdminServerStubs.QUOTA_START_URL_PATTERN)
+                        || recordedRequest.getPath().matches(UserAdminServerStubs.QUOTA_END_URL_PATTERN)));

Apply the same change in getRequestsDone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/org/gridsuite/study/server/utils/TestUtils.java` around lines
112 - 140, The quota-request filtering in TestUtils.assertRequestMatches and
getRequestsDone is too broad and can accidentally skip legitimate GET /quota/max
calls. Replace the generic "/v1/users/.*/quota/.*" match with the precise quota
start/end patterns already available in this file via
UserAdminServerStubs.QUOTA_START_URL_PATTERN and
UserAdminServerStubs.QUOTA_END_URL_PATTERN, and apply the same narrowing in both
helper methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/gridsuite/study/server/service/ConsumerService.java`:
- Around line 402-405: The quota release path in ConsumerService’s handler only
checks userId before calling userAdminService.endOperationWithQuota, so it can
pass a null resultUuid and build a malformed quota URL. Update the free-quota
block to require both userId and resultUuid before invoking
endOperationWithQuota, matching the guard already used in the stopped handler,
and keep the call to OperationType.mapFromComputationType(computationType)
unchanged.

---

Nitpick comments:
In `@src/test/java/org/gridsuite/study/server/utils/TestUtils.java`:
- Around line 239-264: The two teardown helpers,
assertWiremockServerRequestsEmptyThenShutdown and
assertWiremockServerRequestsEmptyThenClear, repeat the same
removeServeEventsMatching calls for the quota start/end URLs. Extract that
duplicated logic into a small private helper such as
removeQuotaServeEvents(WireMockServer) and call it from both methods so the
quota-event cleanup is centralized and easier to maintain.
- Around line 112-140: The quota-request filtering in
TestUtils.assertRequestMatches and getRequestsDone is too broad and can
accidentally skip legitimate GET /quota/max calls. Replace the generic
"/v1/users/.*/quota/.*" match with the precise quota start/end patterns already
available in this file via UserAdminServerStubs.QUOTA_START_URL_PATTERN and
UserAdminServerStubs.QUOTA_END_URL_PATTERN, and apply the same narrowing in both
helper methods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd8f74b1-33af-4c41-a8f8-f55735d4b2f6

📥 Commits

Reviewing files that changed from the base of the PR and between a76a79c and 2e6c5ac.

📒 Files selected for processing (24)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/OperationType.java
  • src/main/java/org/gridsuite/study/server/dto/UserProfileInfos.java
  • src/main/java/org/gridsuite/study/server/error/StudyBusinessErrorCode.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/java/org/gridsuite/study/server/service/UserAdminService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/NodeSequenceTest.java
  • src/test/java/org/gridsuite/study/server/ShortCircuitTest.java
  • src/test/java/org/gridsuite/study/server/StateEstimationTest.java
  • src/test/java/org/gridsuite/study/server/StudyControllerDynamicMarginCalculationTest.java
  • src/test/java/org/gridsuite/study/server/StudyControllerDynamicSecurityAnalysisTest.java
  • src/test/java/org/gridsuite/study/server/StudyControllerDynamicSimulationTest.java
  • src/test/java/org/gridsuite/study/server/StudyServiceTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/dto/OperationTypeTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/service/StudyServiceDynamicSimulationTest.java
  • src/test/java/org/gridsuite/study/server/service/UserAdminServiceTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
  • src/test/java/org/gridsuite/study/server/utils/wiremock/UserAdminServerStubs.java
  • src/test/resources/application-default.yml

Comment on lines +402 to +405
// free quota
if (userId != null) {
userAdminService.endOperationWithQuota(userId, OperationType.mapFromComputationType(computationType), resultUuid);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Also guard on resultUuid != null before freeing quota.

Here resultUuid can be null (the resultUuid header may be absent on a failure message — it's only assigned when resultId != null). With only the userId != null check, endOperationWithQuota is called with a null operation id, producing a malformed URL such as /quota/LOAD_FLOW/null/end. The stopped handler at Line 431 correctly guards both. Align this handler:

🐛 Proposed fix
-                    // free quota
-                    if (userId != null) {
+                    // free quota
+                    if (userId != null && resultUuid != null) {
                         userAdminService.endOperationWithQuota(userId, OperationType.mapFromComputationType(computationType), resultUuid);
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// free quota
if (userId != null) {
userAdminService.endOperationWithQuota(userId, OperationType.mapFromComputationType(computationType), resultUuid);
}
// free quota
if (userId != null && resultUuid != null) {
userAdminService.endOperationWithQuota(userId, OperationType.mapFromComputationType(computationType), resultUuid);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/ConsumerService.java` around
lines 402 - 405, The quota release path in ConsumerService’s handler only checks
userId before calling userAdminService.endOperationWithQuota, so it can pass a
null resultUuid and build a malformed quota URL. Update the free-quota block to
require both userId and resultUuid before invoking endOperationWithQuota,
matching the guard already used in the stopped handler, and keep the call to
OperationType.mapFromComputationType(computationType) unchanged.

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.

1 participant