From d531a4ede9b079e1578bad95c8371b12dd92b540 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:12:24 +0100 Subject: [PATCH 01/12] add debug mode - only used for modification application Signed-off-by: LE SAULNIER Kevin --- .../monitor/commons/ProcessRunMessage.java | 3 +- .../monitor/commons/utils/S3PathUtils.java | 28 +++++++++ .../server/controllers/MonitorController.java | 3 +- .../entities/ProcessExecutionEntity.java | 6 ++ .../server/services/MonitorService.java | 9 ++- .../server/services/NotificationService.java | 4 +- .../changesets/changelog_20260212T082157Z.xml | 13 +++++ .../db/changelog/db.changelog-master.yaml | 4 +- .../server/services/MonitorServiceTest.java | 3 +- .../services/NotificationServiceTest.java | 5 +- monitor-worker-server/pom.xml | 6 ++ .../worker/server/config/S3Configuration.java | 33 +++++++++++ .../server/core/AbstractProcessStep.java | 8 +++ .../server/core/ProcessExecutionContext.java | 4 +- .../core/ProcessStepExecutionContext.java | 8 +++ .../worker/server/dto/S3InputStreamInfos.java | 14 +++++ .../commons/steps/ApplyModificationsStep.java | 43 +++++++++++++- .../SecurityAnalysisProcess.java | 1 + .../services/ProcessExecutionService.java | 1 + .../worker/server/services/S3Service.java | 58 +++++++++++++++++++ .../worker/server/utils/FileUtils.java | 26 +++++++++ .../src/main/resources/application-local.yaml | 4 ++ .../core/ProcessExecutionContextTest.java | 6 +- .../steps/ApplyModificationsStepTest.java | 6 +- .../services/ProcessExecutionServiceTest.java | 11 ++-- 25 files changed, 286 insertions(+), 21 deletions(-) create mode 100644 monitor-commons/src/main/java/org/gridsuite/monitor/commons/utils/S3PathUtils.java create mode 100644 monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml create mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java create mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java create mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java create mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java diff --git a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessRunMessage.java b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessRunMessage.java index 95d5c71..8783488 100644 --- a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessRunMessage.java +++ b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessRunMessage.java @@ -21,7 +21,8 @@ public record ProcessRunMessage( @JsonSubTypes({ @JsonSubTypes.Type(value = SecurityAnalysisConfig.class, name = "SECURITY_ANALYSIS") }) - T config + T config, + boolean isDebug ) { public ProcessType processType() { return config.processType(); diff --git a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/utils/S3PathUtils.java b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/utils/S3PathUtils.java new file mode 100644 index 0000000..fa819a5 --- /dev/null +++ b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/utils/S3PathUtils.java @@ -0,0 +1,28 @@ +/** + * 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.monitor.commons.utils; + +import java.util.UUID; + +/** + * @author Kevin Le Saulnier + */ +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") +public class S3PathUtils { + public static final String S3_DELIMITER = "/"; + + /** + * Builds root path used to build debug file location + * @param executionEnvName + * @param processType + * @param executionId + * @return {executionEnvName}_debug/process/{processType}/{executionId} + */ + public static String toDebugLocation(String executionEnvName, String processType, UUID executionId) { + return String.join(S3_DELIMITER, executionEnvName + "_debug", "process", processType, executionId.toString()); + } +} diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java index 65ec8bc..26c766c 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java @@ -45,8 +45,9 @@ public MonitorController(MonitorService monitorService) { public ResponseEntity executeSecurityAnalysis( @RequestParam UUID caseUuid, @RequestBody SecurityAnalysisConfig securityAnalysisConfig, + @RequestBody boolean isDebug, @RequestHeader(HEADER_USER_ID) String userId) { - UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); + UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, isDebug); return ResponseEntity.ok(executionId); } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java index eaaeaa1..7ef13f4 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java @@ -55,6 +55,12 @@ public class ProcessExecutionEntity { @Column private String userId; + @Column + private boolean isDebug; + + @Column + private String debugFileLocation; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "execution_id", foreignKey = @ForeignKey(name = "processExecutionStep_processExecution_fk")) @OrderBy("stepOrder ASC") diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index d0722e5..fbf5949 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -11,6 +11,7 @@ import org.gridsuite.monitor.commons.ProcessStatus; import org.gridsuite.monitor.commons.ProcessType; import org.gridsuite.monitor.commons.ResultInfos; +import org.gridsuite.monitor.commons.utils.S3PathUtils; import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.ReportPage; import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; @@ -49,17 +50,18 @@ public MonitorService(ProcessExecutionRepository executionRepository, } @Transactional - public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig) { + public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig, boolean isDebug) { ProcessExecutionEntity execution = ProcessExecutionEntity.builder() .type(processConfig.processType().name()) .caseUuid(caseUuid) .status(ProcessStatus.SCHEDULED) .scheduledAt(Instant.now()) .userId(userId) + .isDebug(isDebug) .build(); executionRepository.save(execution); - notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId()); + notificationService.sendProcessRunMessage(caseUuid, processConfig, isDebug, execution.getId()); return execution.getId(); } @@ -77,6 +79,9 @@ public void updateExecutionStatus(UUID executionId, ProcessStatus status, String if (completedAt != null) { execution.setCompletedAt(completedAt); } + if (execution.isDebug()) { + execution.setDebugFileLocation(S3PathUtils.toDebugLocation(executionEnvName, execution.getType(), executionId)); + } executionRepository.save(execution); }); } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java index a6ca955..d7ce14d 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java @@ -23,11 +23,11 @@ public class NotificationService { private final StreamBridge publisher; - public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, UUID executionId) { + public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, boolean isDebug, UUID executionId) { String bindingName = switch (processConfig.processType()) { case SECURITY_ANALYSIS -> "publishRunSecurityAnalysis-out-0"; }; - ProcessRunMessage message = new ProcessRunMessage<>(executionId, caseUuid, processConfig); + ProcessRunMessage message = new ProcessRunMessage<>(executionId, caseUuid, processConfig, isDebug); publisher.send(bindingName, message); } } diff --git a/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml new file mode 100644 index 0000000..b2fb5d1 --- /dev/null +++ b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml b/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml index 44dd608..e8f62f7 100644 --- a/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml @@ -8,4 +8,6 @@ databaseChangeLog: - include: file: changesets/changelog_20260130T160426Z.xml relativeToChangelogFile: true - + - include: + file: changesets/changelog_20260212T082157Z.xml + relativeToChangelogFile: true diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java index fd1c712..90a7a47 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java @@ -79,7 +79,7 @@ void executeProcessCreateExecutionAndSendNotification() { return entity; }); - UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); + UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, false); assertThat(result).isNotNull(); verify(executionRepository).save(argThat(execution -> @@ -94,6 +94,7 @@ void executeProcessCreateExecutionAndSendNotification() { verify(notificationService).sendProcessRunMessage( caseUuid, securityAnalysisConfig, + false, result ); } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java index daed261..f2abc8f 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java @@ -55,14 +55,15 @@ void setUp() { @Test void sendProcessRunMessage() { - notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, executionId); + notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, false, executionId); verify(publisher).send( eq("publishRunSecurityAnalysis-out-0"), argThat((ProcessRunMessage message) -> message.executionId().equals(executionId) && message.caseUuid().equals(caseUuid) && - message.config().equals(securityAnalysisConfig)) + message.config().equals(securityAnalysisConfig) && + !message.isDebug()) ); } } diff --git a/monitor-worker-server/pom.xml b/monitor-worker-server/pom.xml index 512af36..659926c 100644 --- a/monitor-worker-server/pom.xml +++ b/monitor-worker-server/pom.xml @@ -14,6 +14,7 @@ 0.61.0 + 3.3.1 @@ -108,6 +109,11 @@ com.powsybl powsybl-case-datasource-client + + io.awspring.cloud + spring-cloud-aws-starter-s3 + ${spring-cloud-aws-starter-s3} + diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java new file mode 100644 index 0000000..2820ee2 --- /dev/null +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2025, 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.monitor.worker.server.config; + +import org.gridsuite.monitor.worker.server.services.S3Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.services.s3.S3Client; + +/** + * @author Thang PHAM + */ +@Configuration +public class S3Configuration { + private static final Logger LOGGER = LoggerFactory.getLogger(S3Configuration.class); + @Value("${spring.cloud.aws.bucket:ws-bucket}") + private String bucketName; + + @SuppressWarnings("checkstyle:MethodName") + @Bean + public S3Service S3Service(S3Client s3Client) { + LOGGER.info("Configuring S3Service with bucket: {}", bucketName); + return new S3Service(s3Client, bucketName); + } +} diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java index 2671026..09eea6e 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java @@ -8,6 +8,7 @@ import lombok.Getter; import org.gridsuite.monitor.commons.ProcessConfig; +import org.gridsuite.monitor.commons.utils.S3PathUtils; import java.util.UUID; @@ -24,4 +25,11 @@ protected AbstractProcessStep(ProcessStepType type) { this.type = type; this.id = UUID.randomUUID(); } + + protected String getDebugFilePath(ProcessStepExecutionContext context, String fileName) { + return String.join(S3PathUtils.S3_DELIMITER, + S3PathUtils.toDebugLocation(context.getExecutionEnvironment(), context.getConfig().processType().name(), context.getProcessExecutionId()), + type.getName() + "_" + context.getStepOrder(), + fileName); + } } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContext.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContext.java index e4c64f3..04a17cd 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContext.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContext.java @@ -22,14 +22,16 @@ public class ProcessExecutionContext { private final UUID executionId; private final UUID caseUuid; private final C config; + private final boolean isDebug; @Setter private Network network; private final String executionEnvName; - public ProcessExecutionContext(UUID executionId, UUID caseUuid, C config, String executionEnvName) { + public ProcessExecutionContext(UUID executionId, UUID caseUuid, C config, boolean isDebug, String executionEnvName) { this.executionId = executionId; this.caseUuid = caseUuid; this.config = config; + this.isDebug = isDebug; this.executionEnvName = executionEnvName; } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessStepExecutionContext.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessStepExecutionContext.java index 29e4481..eac1a8e 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessStepExecutionContext.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/ProcessStepExecutionContext.java @@ -67,6 +67,14 @@ public Network getNetwork() { return processContext.getNetwork(); } + public String getExecutionEnvironment() { + return processContext.getExecutionEnvName(); + } + + public boolean isDebug() { + return processContext.isDebug(); + } + public void setNetwork(Network network) { processContext.setNetwork(network); } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java new file mode 100644 index 0000000..6f8366e --- /dev/null +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java @@ -0,0 +1,14 @@ +package org.gridsuite.monitor.worker.server.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.io.InputStream; + +@Builder +@Getter +public class S3InputStreamInfos { + InputStream inputStream; + String fileName; + Long fileLength; +} diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index 8c09d9a..e7fe99c 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -6,6 +6,8 @@ */ package org.gridsuite.monitor.worker.server.processes.commons.steps; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.datasource.DataSource; import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.Network; import org.apache.commons.collections4.CollectionUtils; @@ -16,10 +18,18 @@ import org.gridsuite.monitor.worker.server.services.FilterService; import org.gridsuite.monitor.worker.server.services.NetworkModificationRestService; import org.gridsuite.monitor.worker.server.services.NetworkModificationService; +import org.gridsuite.monitor.worker.server.services.S3Service; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.UUID; +import java.util.zip.GZIPOutputStream; /** * @author Antoine Bouhours @@ -29,14 +39,19 @@ public class ApplyModificationsStep extends AbstractPro private final NetworkModificationService networkModificationService; private final NetworkModificationRestService networkModificationRestService; - + private final S3Service s3Service; private final FilterService filterService; - public ApplyModificationsStep(NetworkModificationService networkModificationService, NetworkModificationRestService networkModificationRestService, + private final String DEBUG_FILENAME = "debug.xiidm.gz"; + + public ApplyModificationsStep(NetworkModificationService networkModificationService, + NetworkModificationRestService networkModificationRestService, + S3Service s3Service, FilterService filterService) { super(CommonStepType.APPLY_MODIFICATIONS); this.networkModificationService = networkModificationService; this.networkModificationRestService = networkModificationRestService; + this.s3Service = s3Service; this.filterService = filterService; } @@ -47,6 +62,30 @@ public void execute(ProcessStepExecutionContext context) { if (CollectionUtils.isNotEmpty(modificationIds)) { applyModifications(modificationIds, network, context.getReportInfos().reportNode()); } + if (context.isDebug()) { + try { + exportUpdatedNetworkToS3(context); + } catch (IOException e) { + throw new PowsyblException("An error occurred while saving debug file", e); + } + } + } + + private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) throws IOException { + Path tmp = Files.createTempFile("debug", ".xiidm"); + + DataSource ds = DataSource.fromPath(tmp); + context.getNetwork().write("XIIDM", null, ds); + + Path gzPath = Paths.get(DEBUG_FILENAME); + try (InputStream in = Files.newInputStream(tmp); + OutputStream out = new GZIPOutputStream(Files.newOutputStream(gzPath))) { + in.transferTo(out); + } + + Files.deleteIfExists(tmp); + + s3Service.uploadFile(gzPath, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); } private void applyModifications(List modificationIds, Network network, ReportNode reportNode) { diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java index 0e80ed7..00ca3e3 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java @@ -44,6 +44,7 @@ public List> defineSteps() { return List.of( loadNetworkStep, applyModificationsStep, + applyModificationsStep, runComputationStep ); } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java index 38b8c78..d1668d4 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java @@ -56,6 +56,7 @@ public void executeProcess(ProcessRunMessage runMes runMessage.executionId(), runMessage.caseUuid(), runMessage.config(), + runMessage.isDebug(), executionEnvName ); diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java new file mode 100644 index 0000000..4d65eb7 --- /dev/null +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java @@ -0,0 +1,58 @@ +package org.gridsuite.monitor.worker.server.services; + +import org.gridsuite.monitor.worker.server.dto.S3InputStreamInfos; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +public class S3Service { + public static final String METADATA_FILE_NAME = "file-name"; + + private final S3Client s3Client; + + private final String bucketName; + + public S3Service(S3Client s3Client, String bucketName) { + this.s3Client = s3Client; + this.bucketName = bucketName; + } + + public void uploadFile(Path filePath, String s3Key, String fileName) throws IOException { + try { + PutObjectRequest putRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .metadata(Map.of(METADATA_FILE_NAME, fileName)) + .build(); + s3Client.putObject(putRequest, RequestBody.fromFile(filePath)); + } catch (SdkException e) { + throw new IOException("Error occurred while uploading file to S3: " + e.getMessage()); + } + } + + public S3InputStreamInfos downloadFile(String s3Key) throws IOException { + try { + GetObjectRequest getRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .build(); + ResponseInputStream inputStream = s3Client.getObject(getRequest); + return S3InputStreamInfos.builder() + .inputStream(inputStream) + .fileName(inputStream.response().metadata().get(METADATA_FILE_NAME)) + .fileLength(inputStream.response().contentLength()) + .build(); + } catch (SdkException e) { + throw new IOException("Error occurred while downloading file from S3: " + e.getMessage()); + } + } +} diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java new file mode 100644 index 0000000..9560315 --- /dev/null +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java @@ -0,0 +1,26 @@ +package org.gridsuite.monitor.worker.server.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class FileUtils { + public static Path createZipFile(Path tempDir, String fileOrNetworkName, Set fileNames) throws IOException { + Path zipFile = tempDir.resolve(fileOrNetworkName + ".zip"); + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile))) { + for (String fileName : fileNames) { + Path sourceFile = tempDir.resolve(fileName); + zos.putNextEntry(new ZipEntry(fileName)); + try (InputStream is = Files.newInputStream(sourceFile)) { + is.transferTo(zos); + } + zos.closeEntry(); + } + } + return zipFile; + } +} diff --git a/monitor-worker-server/src/main/resources/application-local.yaml b/monitor-worker-server/src/main/resources/application-local.yaml index 54f57ba..1f0fcee 100644 --- a/monitor-worker-server/src/main/resources/application-local.yaml +++ b/monitor-worker-server/src/main/resources/application-local.yaml @@ -4,6 +4,10 @@ server: spring: rabbitmq: addresses: localhost + cloud: + aws: + s3: + endpoint: http://localhost:19000 powsybl: services: diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContextTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContextTest.java index 0bc3782..aca34c6 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContextTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/core/ProcessExecutionContextTest.java @@ -37,7 +37,7 @@ void shouldInitializeCorrectly() { UUID caseUuid = UUID.randomUUID(); String envName = "test-env"; - ProcessExecutionContext processContext = new ProcessExecutionContext<>(executionId, caseUuid, config, envName); + ProcessExecutionContext processContext = new ProcessExecutionContext<>(executionId, caseUuid, config, false, envName); assertThat(processContext.getConfig()).isEqualTo(config); assertThat(processContext.getExecutionId()).isEqualTo(executionId); @@ -48,7 +48,7 @@ void shouldInitializeCorrectly() { @Test void shouldSetAndGetNetwork() { - ProcessExecutionContext processContext = new ProcessExecutionContext<>(UUID.randomUUID(), UUID.randomUUID(), config, "test-env"); + ProcessExecutionContext processContext = new ProcessExecutionContext<>(UUID.randomUUID(), UUID.randomUUID(), config, false, "test-env"); processContext.setNetwork(network); @@ -57,7 +57,7 @@ void shouldSetAndGetNetwork() { @Test void shouldCreateStepContext() { - ProcessExecutionContext processContext = new ProcessExecutionContext<>(UUID.randomUUID(), UUID.randomUUID(), config, "test-env"); + ProcessExecutionContext processContext = new ProcessExecutionContext<>(UUID.randomUUID(), UUID.randomUUID(), config, false, "test-env"); ProcessStep step = mock(ProcessStep.class); ProcessStepType stepType = mock(ProcessStepType.class); when(stepType.getName()).thenReturn("test-step"); diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java index 1316d04..1ff850b 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java @@ -19,6 +19,7 @@ import org.gridsuite.monitor.worker.server.services.FilterService; import org.gridsuite.monitor.worker.server.services.NetworkModificationRestService; import org.gridsuite.monitor.worker.server.services.NetworkModificationService; +import org.gridsuite.monitor.worker.server.services.S3Service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -49,6 +50,9 @@ class ApplyModificationsStepTest { @Mock private FilterService filterService; + @Mock + private S3Service s3Service ; + @Mock private ProcessConfig config; @@ -62,7 +66,7 @@ class ApplyModificationsStepTest { @BeforeEach void setUp() { - applyModificationsStep = new ApplyModificationsStep<>(networkModificationService, networkModificationRestService, filterService); + applyModificationsStep = new ApplyModificationsStep<>(networkModificationService, networkModificationRestService, s3Service, filterService); when(config.modificationUuids()).thenReturn(List.of(MODIFICATION_UUID)); when(stepContext.getConfig()).thenReturn(config); ReportInfos reportInfos = new ReportInfos(REPORT_UUID, ReportNode.newRootReportNode() diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionServiceTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionServiceTest.java index c4cc51a..0071794 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionServiceTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionServiceTest.java @@ -59,6 +59,9 @@ class ProcessExecutionServiceTest { @Mock private FilterService filterService; + @Mock + private S3Service s3Service; + @Mock private SecurityAnalysisService securityAnalysisService; @@ -80,7 +83,7 @@ void setUp() { processExecutionService = new ProcessExecutionService(processList, notificationService, EXECUTION_ENV_NAME); loadNetworkStep = new LoadNetworkStep<>(networkConversionService); - applyModificationsStep = new ApplyModificationsStep<>(networkModificationService, networkModificationRestService, filterService); + applyModificationsStep = new ApplyModificationsStep<>(networkModificationService, networkModificationRestService, s3Service, filterService); runComputationStep = new SecurityAnalysisRunComputationStep(securityAnalysisService); } @@ -90,7 +93,7 @@ void executeProcessShouldCompleteSuccessfullyWhenProcessExecutesWithoutError() { UUID caseUuid = UUID.randomUUID(); when(processConfig.processType()).thenReturn(ProcessType.SECURITY_ANALYSIS); doNothing().when(process).execute(any(ProcessExecutionContext.class)); - ProcessRunMessage runMessage = new ProcessRunMessage<>(executionId, caseUuid, processConfig); + ProcessRunMessage runMessage = new ProcessRunMessage<>(executionId, caseUuid, processConfig, false); when(process.defineSteps()).thenReturn((List) List.of(loadNetworkStep, applyModificationsStep, runComputationStep)); processExecutionService.executeProcess(runMessage); @@ -139,7 +142,7 @@ void executeProcessShouldSendFailedStatusWhenProcessThrowsException() { when(processConfig.processType()).thenReturn(ProcessType.SECURITY_ANALYSIS); RuntimeException processException = new RuntimeException("Process execution failed"); doThrow(processException).when(process).execute(any(ProcessExecutionContext.class)); - ProcessRunMessage runMessage = new ProcessRunMessage<>(executionId, caseUuid, processConfig); + ProcessRunMessage runMessage = new ProcessRunMessage<>(executionId, caseUuid, processConfig, false); assertThrows(RuntimeException.class, () -> processExecutionService.executeProcess(runMessage)); @@ -158,7 +161,7 @@ void executeProcessShouldSendFailedStatusWhenProcessThrowsException() { @Test void executeProcessShouldThrowIllegalArgumentExceptionWhenProcessTypeNotFound() { when(processConfig.processType()).thenReturn(null); - ProcessRunMessage runMessage = new ProcessRunMessage<>(UUID.randomUUID(), UUID.randomUUID(), processConfig); + ProcessRunMessage runMessage = new ProcessRunMessage<>(UUID.randomUUID(), UUID.randomUUID(), processConfig, false); assertThatThrownBy(() -> processExecutionService.executeProcess(runMessage)) .isInstanceOf(IllegalArgumentException.class) From c424ca01b9d4686f3aa3aa768d5968c850d6222d Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:19:54 +0100 Subject: [PATCH 02/12] improve naming and comments Signed-off-by: LE SAULNIER Kevin --- .../monitor/worker/server/core/AbstractProcessStep.java | 2 +- .../processes/commons/steps/ApplyModificationsStep.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java index 09eea6e..ad6b21f 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/core/AbstractProcessStep.java @@ -29,7 +29,7 @@ protected AbstractProcessStep(ProcessStepType type) { protected String getDebugFilePath(ProcessStepExecutionContext context, String fileName) { return String.join(S3PathUtils.S3_DELIMITER, S3PathUtils.toDebugLocation(context.getExecutionEnvironment(), context.getConfig().processType().name(), context.getProcessExecutionId()), - type.getName() + "_" + context.getStepOrder(), + context.getProcessStepType().getName() + "_" + context.getStepOrder(), fileName); } } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index e7fe99c..2a4fa72 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -32,7 +32,10 @@ import java.util.zip.GZIPOutputStream; /** - * @author Antoine Bouhours + * Apply modifications passed in context to network passed in context
+ * If debug is enabled, resulting network will be saved into S3 + * + * @author Antoine Bouhours */ @Component public class ApplyModificationsStep extends AbstractProcessStep { @@ -72,7 +75,7 @@ public void execute(ProcessStepExecutionContext context) { } private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) throws IOException { - Path tmp = Files.createTempFile("debug", ".xiidm"); + Path tmp = Files.createTempFile("debug-temp", ".xiidm"); DataSource ds = DataSource.fromPath(tmp); context.getNetwork().write("XIIDM", null, ds); From d52722d264674ac602c9dd8fb8409b8062f3adc9 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:23:27 +0100 Subject: [PATCH 03/12] fix checkstyle Signed-off-by: LE SAULNIER Kevin --- .../commons/steps/ApplyModificationsStep.java | 2 +- .../worker/server/services/S3Service.java | 1 - .../worker/server/utils/FileUtils.java | 26 ------------------- .../steps/ApplyModificationsStepTest.java | 2 +- 4 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index 2a4fa72..ab4579b 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -45,7 +45,7 @@ public class ApplyModificationsStep extends AbstractPro private final S3Service s3Service; private final FilterService filterService; - private final String DEBUG_FILENAME = "debug.xiidm.gz"; + private static final String DEBUG_FILENAME = "debug.xiidm.gz"; public ApplyModificationsStep(NetworkModificationService networkModificationService, NetworkModificationRestService networkModificationRestService, diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java index 4d65eb7..0feef72 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java @@ -1,7 +1,6 @@ package org.gridsuite.monitor.worker.server.services; import org.gridsuite.monitor.worker.server.dto.S3InputStreamInfos; -import org.springframework.stereotype.Service; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.exception.SdkException; import software.amazon.awssdk.core.sync.RequestBody; diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java deleted file mode 100644 index 9560315..0000000 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/utils/FileUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.gridsuite.monitor.worker.server.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -public class FileUtils { - public static Path createZipFile(Path tempDir, String fileOrNetworkName, Set fileNames) throws IOException { - Path zipFile = tempDir.resolve(fileOrNetworkName + ".zip"); - try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile))) { - for (String fileName : fileNames) { - Path sourceFile = tempDir.resolve(fileName); - zos.putNextEntry(new ZipEntry(fileName)); - try (InputStream is = Files.newInputStream(sourceFile)) { - is.transferTo(zos); - } - zos.closeEntry(); - } - } - return zipFile; - } -} diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java index 1ff850b..bb1e10d 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java @@ -51,7 +51,7 @@ class ApplyModificationsStepTest { private FilterService filterService; @Mock - private S3Service s3Service ; + private S3Service s3Service; @Mock private ProcessConfig config; From 4ccd728bf9135dd2f82e91864858b7517ec1ce10 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:31:45 +0100 Subject: [PATCH 04/12] fix tests + copyright/author Signed-off-by: LE SAULNIER Kevin --- .../gridsuite/monitor/server/MonitorIntegrationTest.java | 2 +- .../server/controllers/MonitorControllerTest.java | 4 ++-- .../monitor/worker/server/config/S3Configuration.java | 5 ++--- .../monitor/worker/server/dto/S3InputStreamInfos.java | 9 +++++++++ .../monitor/worker/server/services/S3Service.java | 9 +++++++++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java index 3111c7c..8760e27 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java @@ -102,7 +102,7 @@ void securityAnalysisProcessIT() throws Exception { UUID.randomUUID(), List.of("contingency1", "contingency2"), List.of(UUID.randomUUID())); - UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); + UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, false); // Verify message was published Message sentMessage = outputDestination.receive(1000, PROCESS_SA_RUN_DESTINATION); diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java index 18c6a10..49427e7 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java @@ -66,7 +66,7 @@ void executeSecurityAnalysisShouldReturnExecutionId() throws Exception { List.of(modificationUuid) ); - when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class))) + when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class), eq(false))) .thenReturn(executionId); mockMvc.perform(post("/v1/execute/security-analysis") @@ -78,7 +78,7 @@ void executeSecurityAnalysisShouldReturnExecutionId() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$").value(executionId.toString())); - verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class)); + verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class), eq(false)); } @Test diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java index 2820ee2..6213b5d 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java @@ -1,10 +1,9 @@ /** - * Copyright (c) 2025, RTE (http://www.rte-france.com) + * 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.monitor.worker.server.config; import org.gridsuite.monitor.worker.server.services.S3Service; @@ -16,7 +15,7 @@ import software.amazon.awssdk.services.s3.S3Client; /** - * @author Thang PHAM + * @author Kevin Le Saulnier */ @Configuration public class S3Configuration { diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java index 6f8366e..d6a203d 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/dto/S3InputStreamInfos.java @@ -1,3 +1,9 @@ +/** + * 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.monitor.worker.server.dto; import lombok.Builder; @@ -5,6 +11,9 @@ import java.io.InputStream; +/** + * @author Kevin Le Saulnier + */ @Builder @Getter public class S3InputStreamInfos { diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java index 0feef72..88f11ed 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java @@ -1,3 +1,9 @@ +/** + * 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.monitor.worker.server.services; import org.gridsuite.monitor.worker.server.dto.S3InputStreamInfos; @@ -13,6 +19,9 @@ import java.nio.file.Path; import java.util.Map; +/** + * @author Kevin Le Saulnier + */ public class S3Service { public static final String METADATA_FILE_NAME = "file-name"; From 76f4b47bf2563768de64eab0e9e846004a8506de Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:44:01 +0100 Subject: [PATCH 05/12] pr remarks Signed-off-by: LE SAULNIER Kevin --- .../server/services/MonitorService.java | 3 ++- .../changesets/changelog_20260212T082157Z.xml | 4 +++- .../commons/steps/ApplyModificationsStep.java | 23 ++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index fbf5949..89a34b5 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -79,7 +79,8 @@ public void updateExecutionStatus(UUID executionId, ProcessStatus status, String if (completedAt != null) { execution.setCompletedAt(completedAt); } - if (execution.isDebug()) { + if (execution.isDebug() && executionEnvName != null + && (status.equals(ProcessStatus.COMPLETED) || status.equals(ProcessStatus.FAILED))) { execution.setDebugFileLocation(S3PathUtils.toDebugLocation(executionEnvName, execution.getType(), executionId)); } executionRepository.save(execution); diff --git a/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml index b2fb5d1..5559b76 100644 --- a/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml +++ b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260212T082157Z.xml @@ -7,7 +7,9 @@ - + + + diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index ab4579b..ac2d7e3 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -76,19 +76,20 @@ public void execute(ProcessStepExecutionContext context) { private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) throws IOException { Path tmp = Files.createTempFile("debug-temp", ".xiidm"); + Path gzPath = Files.createTempFile("debug-temp", ".xiidm.gz"); + try { + DataSource ds = DataSource.fromPath(tmp); + context.getNetwork().write("XIIDM", null, ds); + try (InputStream in = Files.newInputStream(tmp); + OutputStream out = new GZIPOutputStream(Files.newOutputStream(gzPath))) { + in.transferTo(out); + } - DataSource ds = DataSource.fromPath(tmp); - context.getNetwork().write("XIIDM", null, ds); - - Path gzPath = Paths.get(DEBUG_FILENAME); - try (InputStream in = Files.newInputStream(tmp); - OutputStream out = new GZIPOutputStream(Files.newOutputStream(gzPath))) { - in.transferTo(out); + s3Service.uploadFile(gzPath, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); + } finally { + Files.deleteIfExists(tmp); + Files.deleteIfExists(gzPath); } - - Files.deleteIfExists(tmp); - - s3Service.uploadFile(gzPath, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); } private void applyModifications(List modificationIds, Network network, ReportNode reportNode) { From 1656f57757c7467b76c61c7b364f1cfd330e08e5 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:46:32 +0100 Subject: [PATCH 06/12] fix checkstyle Signed-off-by: LE SAULNIER Kevin --- .../server/processes/commons/steps/ApplyModificationsStep.java | 1 - 1 file changed, 1 deletion(-) diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index ac2d7e3..178cb07 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -26,7 +26,6 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.UUID; import java.util.zip.GZIPOutputStream; From ffd04df2f8efeef88cd04bb9b3c25ed0b477b012 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:52:14 +0100 Subject: [PATCH 07/12] fix test Signed-off-by: LE SAULNIER Kevin --- .../gridsuite/monitor/server/controllers/MonitorController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java index 26c766c..bb56fdd 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java @@ -44,8 +44,8 @@ public MonitorController(MonitorService monitorService) { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis execution has been started")}) public ResponseEntity executeSecurityAnalysis( @RequestParam UUID caseUuid, + @RequestParam(required = false, defaultValue = "false") boolean isDebug, @RequestBody SecurityAnalysisConfig securityAnalysisConfig, - @RequestBody boolean isDebug, @RequestHeader(HEADER_USER_ID) String userId) { UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, isDebug); return ResponseEntity.ok(executionId); From be0f7d69a4f1a2eb9432550bf7de7b981226c2f5 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 11:56:48 +0100 Subject: [PATCH 08/12] fix tests Signed-off-by: LE SAULNIER Kevin --- .../processes/securityanalysis/SecurityAnalysisProcess.java | 1 - .../monitor/worker/server/services/ConsumerServiceTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java index 00ca3e3..0e80ed7 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/securityanalysis/SecurityAnalysisProcess.java @@ -44,7 +44,6 @@ public List> defineSteps() { return List.of( loadNetworkStep, applyModificationsStep, - applyModificationsStep, runComputationStep ); } diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ConsumerServiceTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ConsumerServiceTest.java index c03685a..f28bee3 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ConsumerServiceTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/ConsumerServiceTest.java @@ -41,7 +41,7 @@ void setUp() { @Test void consumeRun() { - ProcessRunMessage runMessage = new ProcessRunMessage<>(UUID.randomUUID(), UUID.randomUUID(), processConfig); + ProcessRunMessage runMessage = new ProcessRunMessage<>(UUID.randomUUID(), UUID.randomUUID(), processConfig, false); Message> message = MessageBuilder.withPayload(runMessage).build(); var consumer = consumerService.consumeRun(); From 37eaf0c5af16c1cdabaaad256297be9ebd4f9f3a Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 12:05:33 +0100 Subject: [PATCH 09/12] fix sonar issues regarding temp files Signed-off-by: LE SAULNIER Kevin --- .../commons/steps/ApplyModificationsStep.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index 178cb07..7b89fbe 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -26,7 +26,11 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.zip.GZIPOutputStream; @@ -74,20 +78,24 @@ public void execute(ProcessStepExecutionContext context) { } private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) throws IOException { - Path tmp = Files.createTempFile("debug-temp", ".xiidm"); - Path gzPath = Files.createTempFile("debug-temp", ".xiidm.gz"); + FileAttribute> tempDirectoryAttributes = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")); + Path tempDirectory = Files.createTempDirectory("process-debug", tempDirectoryAttributes); + + Path tempDebugFile = Files.createTempFile(tempDirectory, "debug", ".xiidm"); + Path tempGzFile = Files.createTempFile(tempDirectory, "debug", ".xiidm.gz"); try { - DataSource ds = DataSource.fromPath(tmp); + DataSource ds = DataSource.fromPath(tempDebugFile); context.getNetwork().write("XIIDM", null, ds); - try (InputStream in = Files.newInputStream(tmp); - OutputStream out = new GZIPOutputStream(Files.newOutputStream(gzPath))) { + try (InputStream in = Files.newInputStream(tempDebugFile); + OutputStream out = new GZIPOutputStream(Files.newOutputStream(tempGzFile))) { in.transferTo(out); } - s3Service.uploadFile(gzPath, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); + s3Service.uploadFile(tempGzFile, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); } finally { - Files.deleteIfExists(tmp); - Files.deleteIfExists(gzPath); + Files.deleteIfExists(tempDebugFile); + Files.deleteIfExists(tempGzFile); + Files.deleteIfExists(tempDirectory); } } From 17f173356403e7d081a4ce523c8938f797154c88 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 12:36:13 +0100 Subject: [PATCH 10/12] export file compression to specific service Signed-off-by: LE SAULNIER Kevin --- .../worker/server/config/S3Configuration.java | 6 +- .../commons/steps/ApplyModificationsStep.java | 39 ++------ .../worker/server/services/S3RestService.java | 66 +++++++++++++ .../worker/server/services/S3Service.java | 94 +++++++++---------- .../steps/ApplyModificationsStepTest.java | 5 +- 5 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3RestService.java diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java index 6213b5d..a72d056 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/config/S3Configuration.java @@ -6,7 +6,7 @@ */ package org.gridsuite.monitor.worker.server.config; -import org.gridsuite.monitor.worker.server.services.S3Service; +import org.gridsuite.monitor.worker.server.services.S3RestService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -25,8 +25,8 @@ public class S3Configuration { @SuppressWarnings("checkstyle:MethodName") @Bean - public S3Service S3Service(S3Client s3Client) { + public S3RestService S3Service(S3Client s3Client) { LOGGER.info("Configuring S3Service with bucket: {}", bucketName); - return new S3Service(s3Client, bucketName); + return new S3RestService(s3Client, bucketName); } } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index 7b89fbe..63d0d7c 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -7,7 +7,6 @@ package org.gridsuite.monitor.worker.server.processes.commons.steps; import com.powsybl.commons.PowsyblException; -import com.powsybl.commons.datasource.DataSource; import com.powsybl.commons.report.ReportNode; import com.powsybl.iidm.network.Network; import org.apache.commons.collections4.CollectionUtils; @@ -15,24 +14,12 @@ import org.gridsuite.monitor.commons.ProcessConfig; import org.gridsuite.monitor.worker.server.core.AbstractProcessStep; import org.gridsuite.monitor.worker.server.core.ProcessStepExecutionContext; -import org.gridsuite.monitor.worker.server.services.FilterService; -import org.gridsuite.monitor.worker.server.services.NetworkModificationRestService; -import org.gridsuite.monitor.worker.server.services.NetworkModificationService; -import org.gridsuite.monitor.worker.server.services.S3Service; +import org.gridsuite.monitor.worker.server.services.*; import org.springframework.stereotype.Component; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; import java.util.List; -import java.util.Set; import java.util.UUID; -import java.util.zip.GZIPOutputStream; /** * Apply modifications passed in context to network passed in context
@@ -78,25 +65,11 @@ public void execute(ProcessStepExecutionContext context) { } private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) throws IOException { - FileAttribute> tempDirectoryAttributes = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")); - Path tempDirectory = Files.createTempDirectory("process-debug", tempDirectoryAttributes); - - Path tempDebugFile = Files.createTempFile(tempDirectory, "debug", ".xiidm"); - Path tempGzFile = Files.createTempFile(tempDirectory, "debug", ".xiidm.gz"); - try { - DataSource ds = DataSource.fromPath(tempDebugFile); - context.getNetwork().write("XIIDM", null, ds); - try (InputStream in = Files.newInputStream(tempDebugFile); - OutputStream out = new GZIPOutputStream(Files.newOutputStream(tempGzFile))) { - in.transferTo(out); - } - - s3Service.uploadFile(tempGzFile, getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME); - } finally { - Files.deleteIfExists(tempDebugFile); - Files.deleteIfExists(tempGzFile); - Files.deleteIfExists(tempDirectory); - } + s3Service.exportCompressedToS3( + getDebugFilePath(context, DEBUG_FILENAME), + DEBUG_FILENAME, + dataSource -> context.getNetwork().write("XIIDM", null, dataSource) + ); } private void applyModifications(List modificationIds, Network network, ReportNode reportNode) { diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3RestService.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3RestService.java new file mode 100644 index 0000000..90a99b2 --- /dev/null +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3RestService.java @@ -0,0 +1,66 @@ +/** + * 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.monitor.worker.server.services; + +import org.gridsuite.monitor.worker.server.dto.S3InputStreamInfos; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +/** + * @author Kevin Le Saulnier + */ +public class S3RestService { + public static final String METADATA_FILE_NAME = "file-name"; + + private final S3Client s3Client; + + private final String bucketName; + + public S3RestService(S3Client s3Client, String bucketName) { + this.s3Client = s3Client; + this.bucketName = bucketName; + } + + public void uploadFile(Path filePath, String s3Key, String fileName) throws IOException { + try { + PutObjectRequest putRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .metadata(Map.of(METADATA_FILE_NAME, fileName)) + .build(); + s3Client.putObject(putRequest, RequestBody.fromFile(filePath)); + } catch (SdkException e) { + throw new IOException("Error occurred while uploading file to S3: " + e.getMessage()); + } + } + + public S3InputStreamInfos downloadFile(String s3Key) throws IOException { + try { + GetObjectRequest getRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .build(); + ResponseInputStream inputStream = s3Client.getObject(getRequest); + return S3InputStreamInfos.builder() + .inputStream(inputStream) + .fileName(inputStream.response().metadata().get(METADATA_FILE_NAME)) + .fileLength(inputStream.response().contentLength()) + .build(); + } catch (SdkException e) { + throw new IOException("Error occurred while downloading file from S3: " + e.getMessage()); + } + } +} diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java index 88f11ed..8066737 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/S3Service.java @@ -1,66 +1,58 @@ -/** - * 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.monitor.worker.server.services; -import org.gridsuite.monitor.worker.server.dto.S3InputStreamInfos; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.exception.SdkException; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import org.springframework.stereotype.Service; +import org.springframework.util.function.ThrowingConsumer; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.zip.GZIPOutputStream; -/** - * @author Kevin Le Saulnier - */ +@Service public class S3Service { - public static final String METADATA_FILE_NAME = "file-name"; + private final S3RestService s3RestService; - private final S3Client s3Client; - - private final String bucketName; - - public S3Service(S3Client s3Client, String bucketName) { - this.s3Client = s3Client; - this.bucketName = bucketName; + public S3Service(S3RestService s3RestService) { + this.s3RestService = s3RestService; } - public void uploadFile(Path filePath, String s3Key, String fileName) throws IOException { - try { - PutObjectRequest putRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(s3Key) - .metadata(Map.of(METADATA_FILE_NAME, fileName)) - .build(); - s3Client.putObject(putRequest, RequestBody.fromFile(filePath)); - } catch (SdkException e) { - throw new IOException("Error occurred while uploading file to S3: " + e.getMessage()); - } - } + public void exportCompressedToS3(String s3Key, String fileName, ThrowingConsumer writer) throws IOException { + FileAttribute> attrs = + PosixFilePermissions.asFileAttribute( + PosixFilePermissions.fromString("rwx------")); + + Path tempDir = Files.createTempDirectory("process-debug", attrs); + Path debugFile = null; + Path compressedDebugFile = null; - public S3InputStreamInfos downloadFile(String s3Key) throws IOException { try { - GetObjectRequest getRequest = GetObjectRequest.builder() - .bucket(bucketName) - .key(s3Key) - .build(); - ResponseInputStream inputStream = s3Client.getObject(getRequest); - return S3InputStreamInfos.builder() - .inputStream(inputStream) - .fileName(inputStream.response().metadata().get(METADATA_FILE_NAME)) - .fileLength(inputStream.response().contentLength()) - .build(); - } catch (SdkException e) { - throw new IOException("Error occurred while downloading file from S3: " + e.getMessage()); + debugFile = Files.createTempFile(tempDir, fileName, ".tmp"); + compressedDebugFile = Files.createTempFile(tempDir, fileName, ".gz"); + + writer.accept(debugFile); + + try (InputStream in = Files.newInputStream(debugFile); + OutputStream out = new GZIPOutputStream(Files.newOutputStream(compressedDebugFile))) { + in.transferTo(out); + } + + s3RestService.uploadFile(compressedDebugFile, s3Key, fileName); + } finally { + if (debugFile != null) { + Files.deleteIfExists(debugFile); + } + if (compressedDebugFile != null) { + Files.deleteIfExists(compressedDebugFile); + } + if (tempDir != null) { + Files.deleteIfExists(tempDir); + } } } } diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java index bb1e10d..f903ca8 100644 --- a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStepTest.java @@ -16,10 +16,7 @@ import org.gridsuite.monitor.commons.ProcessConfig; import org.gridsuite.monitor.worker.server.core.ProcessStepExecutionContext; import org.gridsuite.monitor.worker.server.dto.ReportInfos; -import org.gridsuite.monitor.worker.server.services.FilterService; -import org.gridsuite.monitor.worker.server.services.NetworkModificationRestService; -import org.gridsuite.monitor.worker.server.services.NetworkModificationService; -import org.gridsuite.monitor.worker.server.services.S3Service; +import org.gridsuite.monitor.worker.server.services.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; From a0d8419f3b5bf9f189dbec762f1a421ab983d6b8 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Thu, 12 Feb 2026 13:48:37 +0100 Subject: [PATCH 11/12] increase test coverage Signed-off-by: LE SAULNIER Kevin --- .../controllers/MonitorControllerTest.java | 37 ++++++---- .../server/services/MonitorServiceTest.java | 69 +++++++++++++++++-- .../services/NotificationServiceTest.java | 12 ++-- .../commons/steps/ApplyModificationsStep.java | 2 +- 4 files changed, 98 insertions(+), 22 deletions(-) diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java index 49427e7..6b6bbe3 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java @@ -18,11 +18,15 @@ import org.gridsuite.monitor.server.dto.Severity; import org.gridsuite.monitor.server.services.MonitorService; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import java.time.Instant; import java.util.List; @@ -54,8 +58,10 @@ class MonitorControllerTest { @MockitoBean private MonitorService monitorService; - @Test - void executeSecurityAnalysisShouldReturnExecutionId() throws Exception { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @NullSource + void executeSecurityAnalysisShouldReturnExecutionId(Boolean isDebug) throws Exception { UUID caseUuid = UUID.randomUUID(); UUID parametersUuid = UUID.randomUUID(); UUID modificationUuid = UUID.randomUUID(); @@ -65,20 +71,27 @@ void executeSecurityAnalysisShouldReturnExecutionId() throws Exception { List.of("contingency1", "contingency2"), List.of(modificationUuid) ); + boolean expectedDebugValue = Boolean.TRUE.equals(isDebug); - when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class), eq(false))) + when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class), eq(expectedDebugValue))) .thenReturn(executionId); - mockMvc.perform(post("/v1/execute/security-analysis") - .param("caseUuid", caseUuid.toString()) - .header("userId", "user1") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(config))) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$").value(executionId.toString())); + MockHttpServletRequestBuilder request = post("/v1/execute/security-analysis") + .param("caseUuid", caseUuid.toString()) + .header("userId", "user1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(config)); + + if (isDebug != null) { + request.param("isDebug", isDebug.toString()); + } + + mockMvc.perform(request) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").value(executionId.toString())); - verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class), eq(false)); + verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class), eq(expectedDebugValue)); } @Test diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java index 90a7a47..baddb4c 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java @@ -7,6 +7,7 @@ package org.gridsuite.monitor.server.services; import org.gridsuite.monitor.commons.*; +import org.gridsuite.monitor.commons.utils.S3PathUtils; import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.ReportLog; import org.gridsuite.monitor.server.dto.ReportPage; @@ -17,6 +18,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -69,8 +73,9 @@ void setUp() { ); } - @Test - void executeProcessCreateExecutionAndSendNotification() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void executeProcessCreateExecutionAndSendNotification(boolean isDebug) { UUID expectedExecutionId = UUID.randomUUID(); when(executionRepository.save(any(ProcessExecutionEntity.class))) .thenAnswer(invocation -> { @@ -79,7 +84,7 @@ void executeProcessCreateExecutionAndSendNotification() { return entity; }); - UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, false); + UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, isDebug); assertThat(result).isNotNull(); verify(executionRepository).save(argThat(execution -> @@ -94,7 +99,7 @@ void executeProcessCreateExecutionAndSendNotification() { verify(notificationService).sendProcessRunMessage( caseUuid, securityAnalysisConfig, - false, + isDebug, result ); } @@ -146,6 +151,62 @@ void updateExecutionStatusShouldUpdateAllFields() { verify(executionRepository).save(execution); } + @ParameterizedTest + @EnumSource(value = ProcessStatus.class, names = {"COMPLETED", "FAILED"}) + void updateExecutionStatusWithDebugOnAndProcessStatusDoneShouldUpdateDebugLocation(ProcessStatus processStatus) { + ProcessExecutionEntity execution = ProcessExecutionEntity.builder() + .id(executionId) + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(caseUuid) + .userId(userId) + .status(ProcessStatus.RUNNING) + .scheduledAt(Instant.now()) + .isDebug(true) + .build(); + when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); + String envName = "production-env"; + Instant startedAt = Instant.now().minusSeconds(60); + Instant completedAt = Instant.now(); + + monitorService.updateExecutionStatus(executionId, processStatus, envName, startedAt, completedAt); + + verify(executionRepository).findById(executionId); + // assert standard fields + assertThat(execution.getStatus()).isEqualTo(processStatus); + assertThat(execution.getExecutionEnvName()).isEqualTo(envName); + assertThat(execution.getStartedAt()).isEqualTo(startedAt); + assertThat(execution.getCompletedAt()).isEqualTo(completedAt); + + // assert debug location field + assertThat(execution.getDebugFileLocation()).isEqualTo(S3PathUtils.toDebugLocation(envName, ProcessType.SECURITY_ANALYSIS.name(), executionId)); + verify(executionRepository).save(execution); + } + + @ParameterizedTest + @EnumSource(value = ProcessStatus.class, names = {"RUNNING", "SCHEDULED"}) + void updateExecutionStatusWithDebugOnAndProcessStatusNotDoneShouldNotUpdateDebugLocation(ProcessStatus processStatus) { + ProcessExecutionEntity execution = ProcessExecutionEntity.builder() + .id(executionId) + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(caseUuid) + .userId(userId) + .status(ProcessStatus.RUNNING) + .scheduledAt(Instant.now()) + .isDebug(true) + .build(); + when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); + String envName = "production-env"; + Instant startedAt = Instant.now().minusSeconds(60); + Instant completedAt = Instant.now(); + + monitorService.updateExecutionStatus(executionId, processStatus, envName, startedAt, completedAt); + + verify(executionRepository).findById(executionId); + // assert debug location field + assertThat(execution.getDebugFileLocation()).isNull(); + verify(executionRepository).save(execution); + } + @Test void updateExecutionStatusShouldHandleExecutionNotFound() { when(executionRepository.findById(executionId)).thenReturn(Optional.empty()); diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java index f2abc8f..0f3af21 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/NotificationServiceTest.java @@ -9,8 +9,9 @@ import org.gridsuite.monitor.commons.ProcessRunMessage; import org.gridsuite.monitor.commons.SecurityAnalysisConfig; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -53,9 +54,10 @@ void setUp() { ); } - @Test - void sendProcessRunMessage() { - notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, false, executionId); + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void sendProcessRunMessage(boolean isDebug) { + notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, isDebug, executionId); verify(publisher).send( eq("publishRunSecurityAnalysis-out-0"), @@ -63,7 +65,7 @@ void sendProcessRunMessage() { message.executionId().equals(executionId) && message.caseUuid().equals(caseUuid) && message.config().equals(securityAnalysisConfig) && - !message.isDebug()) + message.isDebug() == isDebug) ); } } diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java index 63d0d7c..8957709 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/processes/commons/steps/ApplyModificationsStep.java @@ -68,7 +68,7 @@ private void exportUpdatedNetworkToS3(ProcessStepExecutionContext context) th s3Service.exportCompressedToS3( getDebugFilePath(context, DEBUG_FILENAME), DEBUG_FILENAME, - dataSource -> context.getNetwork().write("XIIDM", null, dataSource) + networkFile -> context.getNetwork().write("XIIDM", null, networkFile) ); } From ed55f034adda6fc7b85a1ac7f9a7026f9299b7ee Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Fri, 13 Feb 2026 11:45:05 +0100 Subject: [PATCH 12/12] test coverage Signed-off-by: LE SAULNIER Kevin --- .../server/services/S3RestServiceTest.java | 81 ++++++++++++++++ .../worker/server/services/S3ServiceTest.java | 95 +++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3RestServiceTest.java create mode 100644 monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3ServiceTest.java diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3RestServiceTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3RestServiceTest.java new file mode 100644 index 0000000..6e598b5 --- /dev/null +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3RestServiceTest.java @@ -0,0 +1,81 @@ +package org.gridsuite.monitor.worker.server.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class S3RestServiceTest { + @Mock + S3Client s3Client; + + S3RestService s3RestService; + + @TempDir + Path testDir; + + @BeforeEach + void setup() { + s3RestService = new S3RestService(s3Client, "my-bucket"); + } + + @Test + void testUploadFileToS3() throws Exception { + Path tempFile = Files.createTempFile(testDir, "test", ".txt"); + Files.writeString(tempFile, "dataToUpload"); + + s3RestService.uploadFile(tempFile, "testS3Key", "fileToUploadName"); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(PutObjectRequest.class); + + verify(s3Client).putObject( + requestCaptor.capture(), + any(RequestBody.class) + ); + + PutObjectRequest request = requestCaptor.getValue(); + + assertThat(request.bucket()).isEqualTo("my-bucket"); + assertThat(request.key()).isEqualTo("testS3Key"); + assertThat(request.metadata()).containsEntry("file-name", "fileToUploadName"); + } + + @Test + void testUpdateFileToS3Error() throws Exception { + + Path fileToUpload = Files.createTempFile(testDir, "test", ".txt"); + Files.writeString(fileToUpload, "dataToUpload"); + + doThrow(SdkException.builder().message("sdkError").build()) + .when(s3Client) + .putObject(any(PutObjectRequest.class), any(RequestBody.class)); + + assertThatThrownBy(() -> + s3RestService.uploadFile(fileToUpload, "key", "file.txt") + ) + .isInstanceOf(IOException.class) + .hasMessageContaining("Error occurred while uploading file to S3") + .hasMessageContaining("sdkError"); + + verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + } +} diff --git a/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3ServiceTest.java b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3ServiceTest.java new file mode 100644 index 0000000..dc3df45 --- /dev/null +++ b/monitor-worker-server/src/test/java/org/gridsuite/monitor/worker/server/services/S3ServiceTest.java @@ -0,0 +1,95 @@ +package org.gridsuite.monitor.worker.server.services; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.util.function.ThrowingConsumer; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPInputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class S3ServiceTest { + @Mock + S3RestService s3RestService; + + @InjectMocks + S3Service s3Service; + + @TempDir + Path testDir; + + @Test + void testPathIsCompressedBeforeUploading() throws Exception { + // --- Inputs --- + String s3Key = "s3Key"; + String fileName = "fileName"; + String dataToCompress = "dataToCompress"; + ThrowingConsumer writer = + path -> Files.writeString(path, dataToCompress); + + // Since temp files are deleted after "uploadFile", make a copy of the compressed file to assert its content + Path capturedCopy = Files.createTempFile(testDir, "test-copy", ".gz"); + doAnswer(invocation -> { + Path uploaded = invocation.getArgument(0); + Files.copy(uploaded, capturedCopy, StandardCopyOption.REPLACE_EXISTING); + return null; + }).when(s3RestService) + .uploadFile(any(Path.class), anyString(), anyString()); + + // --- Method invocation --- + s3Service.exportCompressedToS3(s3Key, fileName, writer); + + // --- Assertions --- + verify(s3RestService).uploadFile(any(), eq(s3Key), eq(fileName)); + try (InputStream in = new GZIPInputStream(Files.newInputStream(capturedCopy))) { + String uncompressedContent = new String(in.readAllBytes(), UTF_8); + assertThat(uncompressedContent).isEqualTo(dataToCompress); + } + } + + @Test + void testTempFilesAreDeletedAfterExecution() throws Exception { + // --- Inputs --- + AtomicReference debugFileToUplad = new AtomicReference<>(); + AtomicReference debugTempDir = new AtomicReference<>(); + String s3Key = "s3Key"; + String fileName = "fileName"; + + ThrowingConsumer writer = path -> { + debugFileToUplad.set(path); + debugTempDir.set(path.getParent()); + Files.writeString(path, "hello"); + }; + + // --- Method invocation --- + s3Service.exportCompressedToS3(s3Key, fileName, writer); + + // --- Assertions --- + ArgumentCaptor compressedDebugFileToUploadCaptor = ArgumentCaptor.forClass(Path.class); + verify(s3RestService).uploadFile( + compressedDebugFileToUploadCaptor.capture(), + eq(s3Key), + eq(fileName) + ); + Path compressedDebugFileToUpload = compressedDebugFileToUploadCaptor.getValue(); + + assertThat(Files.exists(compressedDebugFileToUpload)).isFalse(); + assertThat(Files.exists(debugFileToUplad.get())).isFalse(); + assertThat(Files.exists(debugTempDir.get())).isFalse(); + } +}