Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.gridsuite.study.server.dto.ComputationType;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.RootNetworkIndexationStatus;
import org.gridsuite.study.server.networkmodificationtree.dto.InsertMode;
import org.gridsuite.study.server.nodeactivity.NodeActivityInfos;
Expand Down Expand Up @@ -43,6 +44,7 @@ public class NotificationService {
public static final String HEADER_ROOT_NETWORKS_UUIDS = "rootNetworksUuids";
public static final String HEADER_STUDY_UUID = "studyUuid";
public static final String HEADER_UPDATE_TYPE = "updateType";
public static final String HEADER_QUOTA_TYPE = "quotaType";
public static final String HEADER_COMPUTATION_TYPE = "computationType";
public static final String HEADER_COMPUTATION_SUBTYPE = "computationSubtype";
public static final String HEADER_RESULT_UUID = "resultUuid";
Expand Down Expand Up @@ -110,6 +112,7 @@ public class NotificationService {
public static final String UPDATE_TYPE_COMPUTATION_PARAMETERS = "computationParametersUpdated";
public static final String UPDATE_COMPUTATION_RESULT_COLUMN_FILTER = "computationResultColumnFilterUpdated";
public static final String UPDATE_COMPUTATION_RESULT_GLOBAL_FILTER = "computationResultGlobalFilterUpdated";
public static final String UPDATE_QUOTA_USAGE = "updateUserQuotaUsage";

public static final String UPDATE_NETWORK_VISUALIZATION_PARAMETERS = "networkVisualizationParametersUpdated";
public static final String UPDATE_NODE_ACTIVITIES = "nodeActivitiesUpdated";
Expand Down Expand Up @@ -561,4 +564,14 @@ public void emitNetworkExportFinished(UUID studyUuid, UUID exportUuid, @NonNull
.setHeader(HEADER_ERROR, error)
);
}

public void emitQuotaChange(String userId, QuotaType quotaType) {
Message<String> message = MessageBuilder.withPayload("")
.setHeader(HEADER_USER_ID, userId)
.setHeader(HEADER_QUOTA_TYPE, quotaType)
.build();

MESSAGE_OUTPUT_LOGGER.debug(MESSAGE_LOG, message);
updatePublisher.send("publishQuotaUpdate-out-0", message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ public void consumeCalculationFailed(Message<String> msg, ComputationType comput

// free quota
if (userId != null && resultUuid != null) {
userAdminService.endOperationWithQuota(userId, QuotaType.mapFromComputationType(computationType), resultUuid);
handleQuotaEnd(computationType, userId, resultUuid);
}

// send notification for failed computation
Expand All @@ -441,6 +441,12 @@ public void consumeCalculationFailed(Message<String> msg, ComputationType comput
}
}

private void handleQuotaEnd(ComputationType computationType, String userId, UUID resultUuid) {
QuotaType quotaType = QuotaType.mapFromComputationType(computationType);
userAdminService.endOperationWithQuota(userId, quotaType, resultUuid);
notificationService.emitQuotaChange(userId, quotaType);
}

public void consumeCalculationStopped(Message<String> msg, ComputationType computationType) {
String receiver = msg.getHeaders().get(HEADER_RECEIVER, String.class);
if (!Strings.isBlank(receiver)) {
Expand All @@ -460,7 +466,7 @@ public void consumeCalculationStopped(Message<String> msg, ComputationType compu
String userId = msg.getHeaders().get(HEADER_USER_ID, String.class);
if (resultId != null && userId != null) {
UUID resultUuid = UUID.fromString(resultId);
userAdminService.endOperationWithQuota(userId, QuotaType.mapFromComputationType(computationType), resultUuid);
handleQuotaEnd(computationType, userId, resultUuid);
}

LOGGER.info("{} stopped for node '{}'", computationType.getLabel(), receiverObj.getNodeUuid());
Expand Down Expand Up @@ -544,7 +550,7 @@ public void consumeCalculationResult(Message<String> msg, ComputationType comput
// free quota
String userId = msg.getHeaders().get(HEADER_USER_ID, String.class);
if (userId != null) {
userAdminService.endOperationWithQuota(userId, QuotaType.mapFromComputationType(computationType), resultUuid);
handleQuotaEnd(computationType, userId, resultUuid);
}

// send notifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package org.gridsuite.study.server.service.common;

import org.gridsuite.study.server.dto.ComputationType;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.error.StudyException;
import org.gridsuite.study.server.notification.NotificationService;
Expand Down Expand Up @@ -121,4 +122,10 @@ protected void emitComputationParametersChanged(UUID studyUuid, String userId,
protected void updateComputationResultUuid(UUID nodeUuid, UUID rootNetworkUuid, UUID computationResultUuid, ComputationType computationType) {
rootNetworkNodeInfoService.updateComputationResultUuid(nodeUuid, rootNetworkUuid, computationResultUuid, computationType);
}

protected void handleQuotaStart(String userId, UUID result, ComputationType computationType) {
QuotaType quotaType = QuotaType.mapFromComputationType(computationType);
userAdminService.startOperationWithQuota(userId, quotaType, result);
notificationService.emitQuotaChange(userId, quotaType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
package org.gridsuite.study.server.service.dynamicmargincalculation;

import lombok.NonNull;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.error.StudyException;
import org.gridsuite.study.server.notification.NotificationService;
Expand Down Expand Up @@ -88,7 +87,7 @@ public UUID runDynamicMarginCalculation(@NonNull UUID studyUuid, @NonNull UUID n

UUID result = handleDynamicMarginCalculationRequest(studyEntity, nodeUuid, rootNetworkUuid, debug, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(DYNAMIC_MARGIN_CALCULATION), result);
handleQuotaStart(userId, result, DYNAMIC_MARGIN_CALCULATION);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
package org.gridsuite.study.server.service.dynamicsecurityanalysis;

import lombok.NonNull;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.dto.dynamicsimulation.DynamicSimulationStatus;
import org.gridsuite.study.server.error.StudyException;
Expand Down Expand Up @@ -90,7 +89,7 @@ public UUID runDynamicSecurityAnalysis(@NonNull UUID studyUuid, @NonNull UUID no

UUID result = handleDynamicSecurityAnalysisRequest(studyEntity, nodeUuid, rootNetworkUuid, debug, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(DYNAMIC_SECURITY_ANALYSIS), result);
handleQuotaStart(userId, result, DYNAMIC_SECURITY_ANALYSIS);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package org.gridsuite.study.server.service.dynamicsimulation;

import lombok.NonNull;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.dto.dynamicsimulation.event.EventInfos;
import org.gridsuite.study.server.error.StudyException;
Expand Down Expand Up @@ -94,7 +93,7 @@ public UUID runDynamicSimulation(@NonNull UUID studyUuid, @NonNull UUID nodeUuid

UUID result = handleDynamicSimulationRequest(studyEntity, nodeUuid, rootNetworkUuid, debug, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(DYNAMIC_SIMULATION), result);
handleQuotaStart(userId, result, DYNAMIC_SIMULATION);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ private void handleLoadflowRequest(StudyEntity studyEntity, UUID nodeUuid, UUID
new LoadFlowRestService.ParametersInfos(lfParametersUuid, withRatioTapChangers, isSecurityNode), lfReportUuid, userId);
rootNetworkNodeInfoService.updateLoadflowResultUuid(nodeUuid, rootNetworkUuid, result, withRatioTapChangers);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(LOAD_FLOW), result);
handleQuotaStart(userId, result, LOAD_FLOW);
notificationService.emitStudyChanged(studyEntity.getId(), nodeUuid, rootNetworkUuid, LOAD_FLOW.getUpdateStatusType());
notificationService.emitElementUpdated(studyEntity.getId(), userId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public UUID runPccMin(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull

UUID result = handlePccMinRequest(studyEntity, nodeUuid, rootNetworkUuid, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(PCC_MIN), result);
handleQuotaStart(userId, result, PCC_MIN);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public UUID runSecurityAnalysis(@NonNull UUID studyUuid, @NonNull UUID nodeUuid,

UUID result = handleSecurityAnalysisRequest(study, nodeUuid, rootNetworkUuid, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(SECURITY_ANALYSIS), result);
handleQuotaStart(userId, result, SECURITY_ANALYSIS);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package org.gridsuite.study.server.service.sensitivityanalysis;

import lombok.NonNull;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.notification.NotificationService;
import org.gridsuite.study.server.repository.StudyEntity;
Expand Down Expand Up @@ -81,7 +80,7 @@ public UUID runSensitivityAnalysis(@NonNull UUID studyUuid, @NonNull UUID nodeUu

UUID result = handleSensitivityAnalysisRequest(study, nodeUuid, rootNetworkUuid, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(SENSITIVITY_ANALYSIS), result);
handleQuotaStart(userId, result, SENSITIVITY_ANALYSIS);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public UUID runShortCircuit(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid,

UUID result = handleShortCircuitRequest(studyEntity, nodeUuid, rootNetworkUuid, busId, debug, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(SHORT_CIRCUIT), result);
handleQuotaStart(userId, result, SHORT_CIRCUIT);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public UUID runStateEstimation(@NonNull UUID studyUuid, @NonNull UUID nodeUuid,

UUID result = handleStateEstimationRequest(studyEntity, nodeUuid, rootNetworkUuid, userId, debug);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(STATE_ESTIMATION), result);
handleQuotaStart(userId, result, STATE_ESTIMATION);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
package org.gridsuite.study.server.service.voltageinit;

import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.ReportInfos;
import org.gridsuite.study.server.dto.UserProfileInfos;
import org.gridsuite.study.server.dto.VariantInfos;
Expand Down Expand Up @@ -75,7 +74,7 @@ public UUID runVoltageInit(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid,

UUID result = handleVoltageInitRequest(studyEntity, nodeUuid, rootNetworkUuid, debug, userId);

userAdminService.startOperationWithQuota(userId, QuotaType.mapFromComputationType(VOLTAGE_INITIALIZATION), result);
handleQuotaStart(userId, result, VOLTAGE_INITIALIZATION);
return result;
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/config/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ spring:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}study.update
publishElementUpdate-out-0:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}element.update
publishQuotaUpdate-out-0:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}quota.update
consumeSaResult-in-0:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}sa.result
group: studySaResultGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.gridsuite.study.server.dto.ComputationType;
import org.gridsuite.study.server.dto.InvalidateNodeInfos;
import org.gridsuite.study.server.dto.InvalidateNodeTreeParameters;
import org.gridsuite.study.server.dto.QuotaType;
import org.gridsuite.study.server.dto.workflow.RerunLoadFlowInfos;
import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus;
import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable;
Expand Down Expand Up @@ -139,6 +140,26 @@ void testRunLoadFlowWithExistingResult() {
verify(studyService, times(1)).assertCanRunOnConstructionNode(eq(studyUuid), eq(nodeUuid), any(), any());
}

@Test
void testSendLoadflowRequestStartsQuotaAndEmitsQuotaChange() {
StudyEntity studyEntity = new StudyEntity();
studyEntity.setId(studyUuid);

when(studyRepository.findById(studyUuid)).thenReturn(Optional.of(studyEntity));
doReturn(false).when(networkModificationTreeService).isSecurityNode(nodeUuid);
doReturn(Map.of()).when(networkModificationTreeService).getComputationReports(nodeUuid, rootNetworkUuid);
doReturn(variantId).when(networkModificationTreeService).getVariantId(nodeUuid, rootNetworkUuid);
when(rootNetworkService.getNetworkUuid(rootNetworkUuid)).thenReturn(networkUuid);
when(loadFlowRestService.runLoadFlow(any(), any(), any(), any(), any(), anyString())).thenReturn(loadflowResultUuid);

loadFlowService.sendLoadflowRequest(studyUuid, nodeUuid, rootNetworkUuid, loadflowResultUuid, false, userId);

verify(userAdminService, times(1)).startOperationWithQuota(userId, QuotaType.LOAD_FLOW, loadflowResultUuid);
verify(notificationService, times(1)).emitQuotaChange(userId, QuotaType.LOAD_FLOW);
verify(notificationService, times(1)).emitStudyChanged(eq(studyUuid), eq(nodeUuid), eq(rootNetworkUuid), anyString());
verify(notificationService, times(1)).emitElementUpdated(studyUuid, userId);
}

@Test
void testRerunLoadFlow() {
testRerunLoadFlow(false, true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.study.server.notification;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.gridsuite.study.server.dto.QuotaType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.Message;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

/**
* @author Ghiles Abdellah {@literal <ghiles.abdellah at rte-france.com>}
*
* Unit tests for {@link NotificationService}, focusing on the quota change notification
* introduced alongside {@link QuotaType}.
*/
@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {
private static final String USER_ID = "userId";

@Mock
private StreamBridge updatePublisher;

private NotificationService notificationService;

@BeforeEach
void setUp() {
notificationService = new NotificationService(updatePublisher, new ObjectMapper());
}

@Test
void testEmitQuotaChange() {
notificationService.emitQuotaChange(USER_ID, QuotaType.SHORT_CIRCUIT);

ArgumentCaptor<Message<String>> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(updatePublisher).send(eq("publishQuotaUpdate-out-0"), messageCaptor.capture());

Message<String> message = messageCaptor.getValue();
assertThat(message.getHeaders().get(NotificationService.HEADER_USER_ID)).isEqualTo(USER_ID);

Check warning on line 52 in src/test/java/org/gridsuite/study/server/notification/NotificationServiceTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use assertThat(actual).containsEntry(key, value) instead.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Astudy-server&issues=AaAeNFGq2LwWTEUKOZDr&open=AaAeNFGq2LwWTEUKOZDr&pullRequest=1071
assertThat(message.getHeaders().get(NotificationService.HEADER_QUOTA_TYPE)).isEqualTo(QuotaType.SHORT_CIRCUIT);

Check warning on line 53 in src/test/java/org/gridsuite/study/server/notification/NotificationServiceTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use assertThat(actual).containsEntry(key, value) instead.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Astudy-server&issues=AaAeNFGq2LwWTEUKOZDs&open=AaAeNFGq2LwWTEUKOZDs&pullRequest=1071
}
}
Loading