From ed6bbdebf5e9532835e62e6d53191cf090b45396 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 20 May 2025 14:41:44 +0200 Subject: [PATCH 01/11] Debug computation mode (migration with ws-commons) Signed-off-by: Thang PHAM --- pom.xml | 3 + .../DynamicSecurityAnalysisWorkerService.java | 56 +------------------ ...DynamicSecurityAnalysisControllerTest.java | 2 +- 3 files changed, 7 insertions(+), 54 deletions(-) diff --git a/pom.xml b/pom.xml index cac1b02..0e9b626 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,8 @@ org.gridsuite.dynamicsecurityanalysis.server gridsuite org.gridsuite:dynamic-security-analysis-server + + 1.25.0-SNAPSHOT @@ -130,6 +132,7 @@ com.powsybl powsybl-ws-commons + ${powsybl-ws-commons.version} com.powsybl diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java index 3ad6938..f901cd7 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java @@ -7,10 +7,8 @@ package org.gridsuite.dynamicsecurityanalysis.server.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.powsybl.commons.io.FileUtil; import com.powsybl.commons.report.ReportNode; import com.powsybl.commons.report.TypedValue; -import com.powsybl.computation.ComputationManager; import com.powsybl.contingency.ContingenciesProvider; import com.powsybl.contingency.Contingency; import com.powsybl.dynamicsimulation.DynamicModelsSupplier; @@ -45,8 +43,6 @@ import org.springframework.messaging.Message; import org.springframework.stereotype.Service; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -58,9 +54,8 @@ import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException.Type.CONTINGENCIES_NOT_FOUND; -import static org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException.Type.DUMP_FILE_ERROR; import static org.gridsuite.dynamicsecurityanalysis.server.service.DynamicSecurityAnalysisService.COMPUTATION_TYPE; -import static org.gridsuite.dynamicsecurityanalysis.server.utils.Utils.*; +import static org.gridsuite.dynamicsecurityanalysis.server.utils.Utils.getReportNode; /** * @author Thang PHAM @@ -91,15 +86,6 @@ public DynamicSecurityAnalysisWorkerService(NetworkStoreService networkStoreServ this.parametersService = Objects.requireNonNull(parametersService); } - /** - * Use this method to mock with DockerLocalComputationManager in case of integration tests with test container - * - * @return a computation manager - */ - public ComputationManager getComputationManager() { - return executionService.getComputationManager(); - } - @Override protected DynamicSecurityAnalysisResultContext fromMessage(Message message) { return DynamicSecurityAnalysisResultContext.fromMessage(message, objectMapper); @@ -181,12 +167,8 @@ public void preRun(DynamicSecurityAnalysisRunContext runContext) { runContext.setDynamicModelContent(dynamicModel); runContext.setDynamicSecurityAnalysisParameters(parameters); - // create a working folder for this run - Path workDir; - workDir = createWorkingDirectory(); - runContext.setWorkDir(workDir); - // enrich dump parameters + Path workDir = runContext.getComputationManager().getLocalDir(); parametersService.setupDumpParameters(workDir, parameters.getDynamicSimulationParameters(), dynamicSimulationZippedOutputState); } @@ -207,7 +189,7 @@ public CompletableFuture getCompletableFuture(DynamicSec parameters.getDynamicContingenciesParameters().getContingenciesStartTime()); DynamicSecurityAnalysisRunParameters runParameters = new DynamicSecurityAnalysisRunParameters() - .setComputationManager(getComputationManager()) + .setComputationManager(runContext.getComputationManager()) .setDynamicSecurityAnalysisParameters(parameters) .setReportNode(runContext.getReportNode()); @@ -246,38 +228,6 @@ public Consumer> consumeCancel() { return super.consumeCancel(); } - @Override - protected void clean(AbstractResultContext resultContext) { - super.clean(resultContext); - // clean working directory - Path workDir = resultContext.getRunContext().getWorkDir(); - removeWorkingDirectory(workDir); - } - - private Path createWorkingDirectory() { - Path workDir; - Path localDir = getComputationManager().getLocalDir(); - try { - workDir = Files.createTempDirectory(localDir, "dynamic_security_analysis_"); - } catch (IOException e) { - throw new DynamicSecurityAnalysisException(DUMP_FILE_ERROR, String.format("Error occurred while creating a working directory inside the local directory %s", - localDir.toAbsolutePath())); - } - return workDir; - } - - private void removeWorkingDirectory(Path workDir) { - if (workDir != null) { - try { - FileUtil.removeDir(workDir); - } catch (IOException e) { - LOGGER.error(String.format("%s: Error occurred while cleaning working directory at %s", getComputationType(), workDir.toAbsolutePath()), e); - } - } else { - LOGGER.info("{}: No working directory to clean", getComputationType()); - } - } - // --- TODO remove these reports when powsybl-dynawo implements --- // private static void enrichContingenciesTimelineReport(SecurityAnalysisReport securityAnalysisReport, ReportNode reportNode) { for (PostContingencyResult postContingencyResult : securityAnalysisReport.getResult().getPostContingencyResults()) { diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java index c5766cc..7239fe0 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java @@ -140,7 +140,7 @@ public void tearDown() { private void initDynamicSecurityAnalysisWorkerServiceSpy() { // setup spy bean - when(dynamicSecurityAnalysisWorkerService.getComputationManager()).thenReturn(computationManager); + when(dynamicSecurityAnalysisWorkerService.createComputationManager()).thenReturn(computationManager); } // --- utility methods --- // From cd14251358514fbbf4745d30418c360a7b43b013 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 5 Jun 2025 09:19:50 +0200 Subject: [PATCH 02/11] support download debug file for DSA Signed-off-by: Thang PHAM --- pom.xml | 30 ++++++++- .../DynamicSecurityAnalysisController.java | 14 +++- .../DynamicSecurityAnalysisResultEntity.java | 6 +- ...namicSecurityAnalysisResultRepository.java | 7 ++ .../DynamicSecurityAnalysisResultService.java | 19 +++++- .../DynamicSecurityAnalysisService.java | 4 +- .../DynamicSecurityAnalysisWorkerService.java | 64 ++++++++++++++++++- .../server/service/ParametersService.java | 11 ++-- .../DynamicSecurityAnalysisResultContext.java | 2 + .../DynamicSecurityAnalysisRunContext.java | 4 +- src/main/resources/application-local.yml | 4 ++ src/main/resources/config/application.yaml | 15 +++++ .../changesets/changelog_20250604T143939Z.xml | 8 +++ .../db/changelog/db.changelog-master.yaml | 4 ++ ...DynamicSecurityAnalysisControllerTest.java | 2 +- ...amicSecurityAnalysisResultServiceTest.java | 6 +- 16 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 src/main/resources/db/changelog/changesets/changelog_20250604T143939Z.xml diff --git a/pom.xml b/pom.xml index 0e9b626..c9e686b 100644 --- a/pom.xml +++ b/pom.xml @@ -46,9 +46,10 @@ powsybl/java-dynawo:3.0.0 org.gridsuite.dynamicsecurityanalysis.server gridsuite + 3.2.0 org.gridsuite:dynamic-security-analysis-server - 1.25.0-SNAPSHOT + 1.26.0-SNAPSHOT @@ -88,7 +89,26 @@ - + + com.powsybl + powsybl-computation-local + 6.8.0-SNAPSHOT + + + com.powsybl + powsybl-computation + 6.8.0-SNAPSHOT + + + com.powsybl + powsybl-iidm-api + 6.8.0-SNAPSHOT + + + com.powsybl + powsybl-dynamic-security-analysis + 6.8.0-SNAPSHOT + org.gridsuite @@ -116,6 +136,11 @@ org.springframework.cloud spring-cloud-stream + + io.awspring.cloud + spring-cloud-aws-starter-s3 + ${spring-cloud-aws.version} + org.springdoc springdoc-openapi-starter-webmvc-ui @@ -141,6 +166,7 @@ com.powsybl powsybl-dynawo-security-analysis + 2.9.0-SNAPSHOT diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java index 67988a6..4696559 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java @@ -18,6 +18,7 @@ import org.gridsuite.dynamicsecurityanalysis.server.service.DynamicSecurityAnalysisService; import org.gridsuite.dynamicsecurityanalysis.server.service.ParametersService; import org.gridsuite.dynamicsecurityanalysis.server.service.contexts.DynamicSecurityAnalysisRunContext; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -61,6 +62,7 @@ public ResponseEntity run(@PathVariable("networkUuid") UUID networkUuid, @RequestParam(name = REPORTER_ID_HEADER, required = false) String reportName, @RequestParam(name = REPORT_TYPE_HEADER, required = false, defaultValue = "DynamicSecurityAnalysis") String reportType, @RequestParam(name = HEADER_PROVIDER, required = false) String provider, + @RequestParam(name = "debug", required = false, defaultValue = "false") boolean debug, @RequestParam(name = "dynamicSimulationResultUuid") UUID dynamicSimulationResultUuid, @RequestParam(name = "parametersUuid") UUID parametersUuid, @RequestHeader(HEADER_USER_ID) String userId) { @@ -73,7 +75,8 @@ public ResponseEntity run(@PathVariable("networkUuid") UUID networkUuid, ReportInfos.builder().reportUuid(reportId).reporterId(reportName).computationType(reportType).build(), userId, dynamicSimulationResultUuid, - parametersUuid); + parametersUuid, + debug); UUID resultUuid = dynamicSecurityAnalysisService.runAndSaveResult(dynamicSecurityAnalysisRunContext); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(resultUuid); @@ -138,4 +141,13 @@ public ResponseEntity> getProviders() { public ResponseEntity getDefaultProvider() { return ResponseEntity.ok().body(dynamicSecurityAnalysisService.getDefaultProvider()); } + + @GetMapping(value = "/results/{resultUuid}/download/debug-file", produces = "application/json") + @Operation(summary = "Download a dynamic security analysis debug file") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic security analysis debug file"), + @ApiResponse(responseCode = "404", description = "Dynamic security analysis debug file has not been found")}) + public ResponseEntity downloadDebugFile(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + return dynamicSecurityAnalysisService.downloadDebugFile(resultUuid); + } + } diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java index bdd00a5..d47081e 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java @@ -25,9 +25,10 @@ @Entity public class DynamicSecurityAnalysisResultEntity { - public DynamicSecurityAnalysisResultEntity(UUID id, DynamicSecurityAnalysisStatus status) { + public DynamicSecurityAnalysisResultEntity(UUID id, DynamicSecurityAnalysisStatus status, String debugFilePath) { this.id = id; this.status = status; + this.debugFileLocation = debugFilePath; } @Id @@ -38,4 +39,7 @@ public DynamicSecurityAnalysisResultEntity(UUID id, DynamicSecurityAnalysisStatu @Enumerated(EnumType.STRING) private DynamicSecurityAnalysisStatus status; + @Column(name = "debugFileLocation") + private String debugFileLocation; + } diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/repositories/DynamicSecurityAnalysisResultRepository.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/repositories/DynamicSecurityAnalysisResultRepository.java index 235c2f5..24cb13f 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/repositories/DynamicSecurityAnalysisResultRepository.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/repositories/DynamicSecurityAnalysisResultRepository.java @@ -9,6 +9,9 @@ import org.gridsuite.dynamicsecurityanalysis.server.entities.DynamicSecurityAnalysisResultEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.UUID; @@ -18,4 +21,8 @@ */ @Repository public interface DynamicSecurityAnalysisResultRepository extends JpaRepository { + @Modifying + @Query("UPDATE DynamicSecurityAnalysisResultEntity r SET r.debugFileLocation = :debugFileLocation WHERE r.id = :resultUuid") + int updateDebugFileLocation(@Param("resultUuid") UUID resultUuid, @Param("debugFileLocation") String debugFileLocation); + } diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java index e10a41e..ef2978c 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java @@ -43,7 +43,7 @@ public DynamicSecurityAnalysisResultService(DynamicSecurityAnalysisResultReposit public void insertStatus(List resultUuids, DynamicSecurityAnalysisStatus status) { Objects.requireNonNull(resultUuids); resultRepository.saveAll(resultUuids.stream() - .map(uuid -> new DynamicSecurityAnalysisResultEntity(uuid, status)).toList()); + .map(uuid -> new DynamicSecurityAnalysisResultEntity(uuid, status, null)).toList()); } @Transactional @@ -64,6 +64,15 @@ public void updateResult(UUID resultUuid, DynamicSecurityAnalysisStatus status) resultEntity.setStatus(status); } + @Override + @org.springframework.transaction.annotation.Transactional + public void updateDebugFileLocation(UUID resultUuid, String debugFilePath) { + resultRepository.findById(resultUuid).ifPresentOrElse( + (var resultEntity) -> resultRepository.updateDebugFileLocation(resultUuid, debugFilePath), + () -> resultRepository.save(new DynamicSecurityAnalysisResultEntity(resultUuid, DynamicSecurityAnalysisStatus.NOT_DONE, debugFilePath)) + ); + } + @Override public void delete(UUID resultUuid) { Objects.requireNonNull(resultUuid); @@ -83,4 +92,12 @@ public DynamicSecurityAnalysisStatus findStatus(UUID resultUuid) { .map(DynamicSecurityAnalysisResultEntity::getStatus) .orElse(null); } + + @Override + public String findDebugFileLocation(UUID resultUuid) { + Objects.requireNonNull(resultUuid); + return resultRepository.findById(resultUuid) + .map(DynamicSecurityAnalysisResultEntity::getDebugFileLocation) + .orElse(null); + } } diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisService.java index b2256a6..4b5d944 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisService.java @@ -12,6 +12,7 @@ import com.powsybl.ws.commons.computation.service.AbstractComputationService; import com.powsybl.ws.commons.computation.service.NotificationService; import com.powsybl.ws.commons.computation.service.UuidGeneratorService; +import com.powsybl.ws.commons.s3.S3Service; import org.gridsuite.dynamicsecurityanalysis.server.dto.DynamicSecurityAnalysisStatus; import org.gridsuite.dynamicsecurityanalysis.server.service.contexts.DynamicSecurityAnalysisResultContext; import org.gridsuite.dynamicsecurityanalysis.server.service.contexts.DynamicSecurityAnalysisRunContext; @@ -36,8 +37,9 @@ public DynamicSecurityAnalysisService( ObjectMapper objectMapper, UuidGeneratorService uuidGeneratorService, DynamicSecurityAnalysisResultService dynamicSecurityAnalysisResultService, + S3Service s3Service, @Value("${dynamic-security-analysis.default-provider}") String defaultProvider) { - super(notificationService, dynamicSecurityAnalysisResultService, objectMapper, uuidGeneratorService, defaultProvider); + super(notificationService, dynamicSecurityAnalysisResultService, s3Service, objectMapper, uuidGeneratorService, defaultProvider); } @Override diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java index f901cd7..db3c584 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java @@ -7,8 +7,10 @@ package org.gridsuite.dynamicsecurityanalysis.server.service; import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.io.FileUtil; import com.powsybl.commons.report.ReportNode; import com.powsybl.commons.report.TypedValue; +import com.powsybl.computation.ComputationManager; import com.powsybl.contingency.ContingenciesProvider; import com.powsybl.contingency.Contingency; import com.powsybl.dynamicsimulation.DynamicModelsSupplier; @@ -27,6 +29,7 @@ import com.powsybl.security.dynamic.DynamicSecurityAnalysisRunParameters; import com.powsybl.security.results.PostContingencyResult; import com.powsybl.ws.commons.computation.service.*; +import com.powsybl.ws.commons.s3.S3Service; import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException; import org.gridsuite.dynamicsecurityanalysis.server.dto.DynamicSecurityAnalysisStatus; @@ -43,6 +46,8 @@ import org.springframework.messaging.Message; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -54,6 +59,7 @@ import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException.Type.CONTINGENCIES_NOT_FOUND; +import static org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException.Type.DUMP_FILE_ERROR; import static org.gridsuite.dynamicsecurityanalysis.server.service.DynamicSecurityAnalysisService.COMPUTATION_TYPE; import static org.gridsuite.dynamicsecurityanalysis.server.utils.Utils.getReportNode; @@ -77,15 +83,25 @@ public DynamicSecurityAnalysisWorkerService(NetworkStoreService networkStoreServ DynamicSecurityAnalysisObserver observer, ObjectMapper objectMapper, DynamicSecurityAnalysisResultService dynamicSecurityAnalysisResultService, + S3Service s3Service, DynamicSimulationClient dynamicSimulationClient, ActionsClient actionsClient, ParametersService parametersService) { - super(networkStoreService, notificationService, reportService, dynamicSecurityAnalysisResultService, executionService, observer, objectMapper); + super(networkStoreService, notificationService, reportService, dynamicSecurityAnalysisResultService, s3Service, executionService, observer, objectMapper); this.dynamicSimulationClient = Objects.requireNonNull(dynamicSimulationClient); this.actionsClient = Objects.requireNonNull(actionsClient); this.parametersService = Objects.requireNonNull(parametersService); } + /** + * Use this method to mock with DockerLocalComputationManager in case of integration tests with test container + * + * @return a computation manager + */ + public ComputationManager getComputationManager() { + return executionService.getComputationManager(); + } + @Override protected DynamicSecurityAnalysisResultContext fromMessage(Message message) { return DynamicSecurityAnalysisResultContext.fromMessage(message, objectMapper); @@ -153,6 +169,9 @@ public void preRun(DynamicSecurityAnalysisRunContext runContext) { // create a new dynamic security analysis parameters DynamicSecurityAnalysisParameters parameters = new DynamicSecurityAnalysisParameters(); + if (runContext.getDebugDir() != null) { + parameters.setDebugDir(runContext.getDebugDir().toString()); + } parameters.setDynamicSimulationParameters(dynamicSimulationParameters); // set start and stop times @@ -167,8 +186,12 @@ public void preRun(DynamicSecurityAnalysisRunContext runContext) { runContext.setDynamicModelContent(dynamicModel); runContext.setDynamicSecurityAnalysisParameters(parameters); + // create a working folder for this run + Path workDir; + workDir = createWorkingDirectory(); + runContext.setWorkDir(workDir); + // enrich dump parameters - Path workDir = runContext.getComputationManager().getLocalDir(); parametersService.setupDumpParameters(workDir, parameters.getDynamicSimulationParameters(), dynamicSimulationZippedOutputState); } @@ -189,7 +212,7 @@ public CompletableFuture getCompletableFuture(DynamicSec parameters.getDynamicContingenciesParameters().getContingenciesStartTime()); DynamicSecurityAnalysisRunParameters runParameters = new DynamicSecurityAnalysisRunParameters() - .setComputationManager(runContext.getComputationManager()) + .setComputationManager(getComputationManager()) .setDynamicSecurityAnalysisParameters(parameters) .setReportNode(runContext.getReportNode()); @@ -228,6 +251,41 @@ public Consumer> consumeCancel() { return super.consumeCancel(); } + @Override + protected void clean(AbstractResultContext resultContext) { + super.clean(resultContext); + // clean working directory + Path workDir = resultContext.getRunContext().getWorkDir(); + removeDirectory(workDir); + } + + @Override + protected void processDebug(AbstractResultContext resultContext) { + // copy all content in working directory into debug directory + DynamicSecurityAnalysisRunContext runContext = resultContext.getRunContext(); + if (runContext.getWorkDir() != null && runContext.getDebugDir() != null) { + try { + FileUtil.copyDir(runContext.getWorkDir(), runContext.getDebugDir()); + } catch (IOException e) { + LOGGER.error("{}: Error occurred while copying directory {} to directory {} => {}", + getComputationType(), runContext.getWorkDir().toAbsolutePath(), runContext.getDebugDir().toAbsolutePath(), e.getMessage()); + } + } + super.processDebug(resultContext); + } + + private Path createWorkingDirectory() { + Path workDir; + Path localDir = getComputationManager().getLocalDir(); + try { + workDir = Files.createTempDirectory(localDir, buildComputationDirPrefix()); + } catch (IOException e) { + throw new DynamicSecurityAnalysisException(DUMP_FILE_ERROR, String.format("Error occurred while creating a working directory inside the local directory %s", + localDir.toAbsolutePath())); + } + return workDir; + } + // --- TODO remove these reports when powsybl-dynawo implements --- // private static void enrichContingenciesTimelineReport(SecurityAnalysisReport securityAnalysisReport, ReportNode reportNode) { for (PostContingencyResult postContingencyResult : securityAnalysisReport.getResult().getPostContingencyResults()) { diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/ParametersService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/ParametersService.java index 50ad261..9fe7922 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/ParametersService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/ParametersService.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.powsybl.commons.io.FileUtil; import com.powsybl.dynamicsimulation.DynamicSimulationParameters; import com.powsybl.dynamicsimulation.DynamicSimulationProvider; import com.powsybl.dynawo.DumpFileParameters; @@ -57,7 +56,8 @@ public ParametersService(@Value("${dynamic-security-analysis.default-provider}") public DynamicSecurityAnalysisRunContext createRunContext(UUID networkUuid, String variantId, String receiver, String provider, ReportInfos reportInfos, String userId, UUID dynamicSimulationResultUuid, - UUID dynamicSecurityAnalysisParametersUuid) { + UUID dynamicSecurityAnalysisParametersUuid, + boolean debug) { // get parameters from the local database DynamicSecurityAnalysisParametersInfos dynamicSecurityAnalysisParametersInfos = getParameters(dynamicSecurityAnalysisParametersUuid); @@ -70,6 +70,7 @@ public DynamicSecurityAnalysisRunContext createRunContext(UUID networkUuid, Stri .reportInfos(reportInfos) .userId(userId) .parameters(dynamicSecurityAnalysisParametersInfos) + .debug(debug) .build(); runContext.setDynamicSimulationResultUuid(dynamicSimulationResultUuid); @@ -93,11 +94,9 @@ public DynamicSecurityAnalysisRunContext createRunContext(UUID networkUuid, Stri // --- Dynamic simulation result related methods --- // public void setupDumpParameters(Path workDir, DynamicSimulationParameters dynamicSimulationParameters, byte[] zippedOutputState) { - Path dumpDir = workDir.resolve("dump"); - FileUtil.createDirectory(dumpDir); - Path dumpFile = unZipDumpFile(dumpDir, zippedOutputState); + Path dumpFile = unZipDumpFile(workDir, zippedOutputState); DynawoSimulationParameters dynawoSimulationParameters = dynamicSimulationParameters.getExtension(DynawoSimulationParameters.class); - dynawoSimulationParameters.setDumpFileParameters(DumpFileParameters.createImportDumpFileParameters(dumpDir, dumpFile.getFileName().toString())); + dynawoSimulationParameters.setDumpFileParameters(DumpFileParameters.createImportDumpFileParameters(workDir, dumpFile.getFileName().toString())); } private Path unZipDumpFile(Path dumpDir, byte[] zippedOutputState) { diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java index 726af78..65dee20 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java @@ -55,6 +55,7 @@ public static DynamicSecurityAnalysisResultContext fromMessage(Message m String reporterId = (String) headers.get(REPORTER_ID_HEADER); String reportType = (String) headers.get(REPORT_TYPE_HEADER); String userId = (String) headers.get(HEADER_USER_ID); + Boolean debug = (Boolean) headers.get(DEBUG_HEADER); DynamicSecurityAnalysisRunContext runContext = DynamicSecurityAnalysisRunContext.builder() .networkUuid(networkUuid) @@ -64,6 +65,7 @@ public static DynamicSecurityAnalysisResultContext fromMessage(Message m .reportInfos(ReportInfos.builder().reportUuid(reportUuid).reporterId(reporterId).computationType(reportType).build()) .userId(userId) .parameters(parametersInfos) + .debug(debug) .build(); // specific headers for dynamic simulation diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisRunContext.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisRunContext.java index e440c3b..8a14ec6 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisRunContext.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisRunContext.java @@ -38,8 +38,8 @@ public class DynamicSecurityAnalysisRunContext extends AbstractComputationRunCon @Builder public DynamicSecurityAnalysisRunContext(UUID networkUuid, String variantId, String receiver, String provider, - ReportInfos reportInfos, String userId, DynamicSecurityAnalysisParametersInfos parameters) { - super(networkUuid, variantId, receiver, reportInfos, userId, provider, parameters); + ReportInfos reportInfos, String userId, DynamicSecurityAnalysisParametersInfos parameters, Boolean debug) { + super(networkUuid, variantId, receiver, reportInfos, userId, provider, parameters, debug); } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6ea2a17..6658419 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -4,6 +4,10 @@ server: spring: rabbitmq: addresses: localhost + cloud: + aws: + endpoint: http://localhost:19000 + bucket: bucket-gridsuite powsybl-ws: database: diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 1fa5e09..08e605b 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -38,6 +38,21 @@ spring: enabled: true delivery-limit: 2 + aws: + s3: + path-style-access-enabled: true + enabled: true + # classic minio port, useful default for exploring + endpoint: http://s3-storage:9000 + region: + profile: + name: default + static: test + bucket: bucket-gridsuite + credentials: + access-key: minioadmin + secret-key: minioadmin + powsybl-ws: database: name: dynamicsecurityanalysis diff --git a/src/main/resources/db/changelog/changesets/changelog_20250604T143939Z.xml b/src/main/resources/db/changelog/changesets/changelog_20250604T143939Z.xml new file mode 100644 index 0000000..e965e08 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20250604T143939Z.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 2c05c00..6728e47 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -7,3 +7,7 @@ databaseChangeLog: - include: file: changesets/changelog_20241219T225523Z.xml relativeToChangelogFile: true + + - include: + file: changesets/changelog_20250604T143939Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java index 7239fe0..c5766cc 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java @@ -140,7 +140,7 @@ public void tearDown() { private void initDynamicSecurityAnalysisWorkerServiceSpy() { // setup spy bean - when(dynamicSecurityAnalysisWorkerService.createComputationManager()).thenReturn(computationManager); + when(dynamicSecurityAnalysisWorkerService.getComputationManager()).thenReturn(computationManager); } // --- utility methods --- // diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java index 35432e8..687cb19 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java @@ -38,7 +38,7 @@ class DynamicSecurityAnalysisResultServiceTest { DynamicSecurityAnalysisResultService dynamicSecurityAnalysisResultService; @AfterEach - public void cleanDB() { + void cleanDB() { resultRepository.deleteAll(); } @@ -89,8 +89,8 @@ void testCrud() { // --- delete all --- // LOGGER.info("Test delete all results"); resultRepository.saveAllAndFlush(List.of( - new DynamicSecurityAnalysisResultEntity(UUID.randomUUID(), DynamicSecurityAnalysisStatus.RUNNING), - new DynamicSecurityAnalysisResultEntity(UUID.randomUUID(), DynamicSecurityAnalysisStatus.RUNNING) + new DynamicSecurityAnalysisResultEntity(UUID.randomUUID(), DynamicSecurityAnalysisStatus.RUNNING, null), + new DynamicSecurityAnalysisResultEntity(UUID.randomUUID(), DynamicSecurityAnalysisStatus.RUNNING, null) )); dynamicSecurityAnalysisResultService.deleteAll(); From cf77b1f92c438ef6bd0cff8173cc15f05711843d Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 11 Jun 2025 10:02:16 +0200 Subject: [PATCH 03/11] add test for debug feature Signed-off-by: Thang PHAM --- ...cSecurityAnalysisControllerIEEE14Test.java | 17 +-- ...DynamicSecurityAnalysisControllerTest.java | 144 +++++++++++------- ...amicSecurityAnalysisResultServiceTest.java | 16 ++ 3 files changed, 113 insertions(+), 64 deletions(-) diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerIEEE14Test.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerIEEE14Test.java index e73a025..2069d8a 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerIEEE14Test.java @@ -150,15 +150,14 @@ void test01IEEE14() throws Exception { //run the dynamic security analysis (on a specific variant with variantId=" + VARIANT_1_ID + ") MvcResult result = mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID, - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isOk()) - .andReturn(); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) + .andReturn(); UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java index 7af13e9..f9026ad 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java @@ -32,11 +32,21 @@ import org.springframework.cloud.stream.binder.test.OutputDestination; import org.springframework.messaging.Message; import org.springframework.test.web.servlet.MvcResult; - +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.AbortableInputStream; +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 software.amazon.awssdk.services.s3.model.PutObjectResponse; + +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -47,6 +57,7 @@ import static com.powsybl.network.store.model.NetworkStoreApi.VERSION; import static com.powsybl.ws.commons.computation.service.AbstractResultContext.VARIANT_ID_HEADER; import static com.powsybl.ws.commons.computation.service.NotificationService.*; +import static com.powsybl.ws.commons.s3.S3Service.METADATA_FILE_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.gridsuite.dynamicsecurityanalysis.server.service.DynamicSecurityAnalysisService.COMPUTATION_TYPE; import static org.gridsuite.dynamicsecurityanalysis.server.utils.Utils.RESOURCE_PATH_DELIMITER; @@ -87,6 +98,9 @@ public class DynamicSecurityAnalysisControllerTest extends AbstractDynamicSecuri @SpyBean private NotificationService notificationService; + @SpyBean + private S3Client s3Client; + @Override public OutputDestination getOutputDestination() { return output; @@ -162,35 +176,59 @@ protected void initParametersServiceSpy() { @Test void testResult() throws Exception { + // mock DynamicSecurityAnalysisWorkerService doReturn(CompletableFuture.completedFuture(new SecurityAnalysisReport(SecurityAnalysisResult.empty()))) .when(dynamicSecurityAnalysisWorkerService).getCompletableFuture(any(), any(), any()); - //run the dynamic security analysis on a specific variant + // mock s3 client for run with debug + doReturn(PutObjectResponse.builder().build()).when(s3Client).putObject(eq(PutObjectRequest.builder().build()), any(RequestBody.class)); + doReturn(new ResponseInputStream<>( + GetObjectResponse.builder() + .metadata(Map.of(METADATA_FILE_NAME, "debugFile")) + .contentLength(100L).build(), + AbortableInputStream.create(new ByteArrayInputStream("s3 debug file content".getBytes())) + )).when(s3Client).getObject(any(GetObjectRequest.class)); + + //run the dynamic security analysis on a specific variant with debug MvcResult result = mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID, - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isOk()) + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .param(HEADER_DEBUG, "true") + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) .andReturn(); UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); + // check notification of result Message messageSwitch = output.receive(1000 * 10, dsaResultDestination); assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + // check notification of debug + messageSwitch = output.receive(1000 * 10, dsaResultDestination); + assertThat(messageSwitch.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()) + .containsEntry(HEADER_DEBUG, true); + + // download debug zip file is ok + mockMvc.perform(get("/v1/results/{resultUuid}/download/debug-file", runUuid)) + .andExpect(status().isOk()); + + // check interaction with s3 client + verify(s3Client, times(1)).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); + //run the dynamic security analysis on the implicit default variant result = mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID, - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isOk()) - .andReturn(); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) + .andReturn(); runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); messageSwitch = output.receive(1000 * 10, dsaResultDestination); @@ -253,26 +291,24 @@ void testResult() throws Exception { void testRunWithSynchronousExceptions() throws Exception { //run the dynamic security analysis on a non-exiting provider mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&provider=notFoundProvider" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID, - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isNotFound()); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(HEADER_PROVIDER, "notFoundProvider") + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isNotFound()); //run the dynamic security analysis on a non-exiting parameters mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + UUID.randomUUID(), - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isNotFound()); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", UUID.randomUUID().toString()) + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isNotFound()); } @@ -310,17 +346,16 @@ void testRunWithReport() throws Exception { .when(dynamicSecurityAnalysisWorkerService).getCompletableFuture(any(), any(), any()); MvcResult result = mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID - + "&reportUuid=" + UUID.randomUUID() - + "&reporterId=dsa", - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isOk()) - .andReturn(); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .param("reportUuid", UUID.randomUUID().toString()) + .param("reporterId", "dsa") + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) + .andReturn(); UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); @@ -358,15 +393,14 @@ private void mockSendRunMessage(Supplier> runAsyncMock) { private UUID runAndCancel(CountDownLatch cancelLatch, int cancelDelay) throws Exception { //run the dynamic simulation on a specific variant MvcResult result = mockMvc.perform( - post("/v1/networks/{networkUuid}/run?" - + "&" + VARIANT_ID_HEADER + "=" + VARIANT_1_ID - + "&dynamicSimulationResultUuid=" + DYNAMIC_SIMULATION_RESULT_UUID - + "¶metersUuid=" + PARAMETERS_UUID, - NETWORK_UUID.toString()) - .contentType(APPLICATION_JSON) - .header(HEADER_USER_ID, "testUserId")) - .andExpect(status().isOk()) - .andReturn(); + post("/v1/networks/{networkUuid}/run", NETWORK_UUID.toString()) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) + .param("dynamicSimulationResultUuid", DYNAMIC_SIMULATION_RESULT_UUID.toString()) + .param("parametersUuid", PARAMETERS_UUID.toString()) + .contentType(APPLICATION_JSON) + .header(HEADER_USER_ID, "testUserId")) + .andExpect(status().isOk()) + .andReturn(); UUID runUuid = objectMapper.readValue(result.getResponse().getContentAsString(), UUID.class); assertResultStatus(runUuid, DynamicSecurityAnalysisStatus.RUNNING); diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java index 687cb19..4b9f8d7 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java @@ -75,6 +75,22 @@ void testCrud() { LOGGER.info("Actual updated result status = {}", updatedResultEntityOpt.get().getStatus()); assertThat(updatedResultEntityOpt.get().getStatus()).isSameAs(DynamicSecurityAnalysisStatus.NOT_DONE); + // --- update the result with debugFileLocation + dynamicSecurityAnalysisResultService.updateDebugFileLocation(resultUuid, "/debug/s3key"); + + // new debugFileLocation must be inserted + updatedResultEntityOpt = resultRepository.findById(resultUuid); + assertThat(updatedResultEntityOpt.get().getDebugFileLocation()).isSameAs("/debug/s3key"); + + // --- update the result with debugFileLocation, if entity uuid does not exist, inject a new one + UUID noneExistEntityUuid = UUID.randomUUID(); + dynamicSecurityAnalysisResultService.updateDebugFileLocation(noneExistEntityUuid, "/debug/s3key2"); + + // new debugFileLocation must be inserted + updatedResultEntityOpt = resultRepository.findById(noneExistEntityUuid); + assertThat(updatedResultEntityOpt.get().getStatus()).isSameAs(DynamicSecurityAnalysisStatus.NOT_DONE); + assertThat(updatedResultEntityOpt.get().getDebugFileLocation()).isSameAs("/debug/s3key2"); + // --- delete result --- // LOGGER.info("Test delete a result"); dynamicSecurityAnalysisResultService.delete(resultUuid); From 9e155379ee765a5ec050e459841704bb0e46f022 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 18 Jun 2025 17:01:09 +0200 Subject: [PATCH 04/11] s3 debug subpath now configurable Signed-off-by: Thang PHAM --- src/main/resources/config/application.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 08e605b..7babdbf 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -53,6 +53,8 @@ spring: access-key: minioadmin secret-key: minioadmin +debug-subpath: debug + powsybl-ws: database: name: dynamicsecurityanalysis From 6cf7fd5c6ffcec7a6cd751dbec3762bdf28e49f8 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 19 Jun 2025 14:58:33 +0200 Subject: [PATCH 05/11] rectify endpoint path to download-debug-file Signed-off-by: Thang PHAM --- .../server/controller/DynamicSecurityAnalysisController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java index 4696559..f534b0d 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisController.java @@ -142,7 +142,7 @@ public ResponseEntity getDefaultProvider() { return ResponseEntity.ok().body(dynamicSecurityAnalysisService.getDefaultProvider()); } - @GetMapping(value = "/results/{resultUuid}/download/debug-file", produces = "application/json") + @GetMapping(value = "/results/{resultUuid}/download-debug-file", produces = "application/json") @Operation(summary = "Download a dynamic security analysis debug file") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Dynamic security analysis debug file"), @ApiResponse(responseCode = "404", description = "Dynamic security analysis debug file has not been found")}) From 8e12f4d0c937ab4dbd4a5c43fbc9f31296a6228c Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 19 Jun 2025 15:04:06 +0200 Subject: [PATCH 06/11] correct test for endpoint download-debug-file Signed-off-by: Thang PHAM --- .../controller/DynamicSecurityAnalysisControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java index f9026ad..073f363 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java @@ -213,7 +213,7 @@ void testResult() throws Exception { .containsEntry(HEADER_DEBUG, true); // download debug zip file is ok - mockMvc.perform(get("/v1/results/{resultUuid}/download/debug-file", runUuid)) + mockMvc.perform(get("/v1/results/{resultUuid}/download-debug-file", runUuid)) .andExpect(status().isOk()); // check interaction with s3 client From 52c0ad22b667d9ce31e74681f5df0112c1910ea2 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 19 Jun 2025 17:50:43 +0200 Subject: [PATCH 07/11] using HEADER_DEBUG of NotificationService Signed-off-by: Thang PHAM --- .../service/contexts/DynamicSecurityAnalysisResultContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java index 65dee20..86f8d92 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/contexts/DynamicSecurityAnalysisResultContext.java @@ -55,7 +55,7 @@ public static DynamicSecurityAnalysisResultContext fromMessage(Message m String reporterId = (String) headers.get(REPORTER_ID_HEADER); String reportType = (String) headers.get(REPORT_TYPE_HEADER); String userId = (String) headers.get(HEADER_USER_ID); - Boolean debug = (Boolean) headers.get(DEBUG_HEADER); + Boolean debug = (Boolean) headers.get(HEADER_DEBUG); DynamicSecurityAnalysisRunContext runContext = DynamicSecurityAnalysisRunContext.builder() .networkUuid(networkUuid) From 13341da5bdd7afc5cc1e5ddb7c647c66e3738ae9 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 19 Jun 2025 23:50:17 +0200 Subject: [PATCH 08/11] rename updateDebugFileLocation to saveDebugFileLocation Signed-off-by: Thang PHAM --- .../service/DynamicSecurityAnalysisResultService.java | 9 ++++++--- .../DynamicSecurityAnalysisResultServiceTest.java | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java index ef2978c..2651c4e 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultService.java @@ -8,7 +8,6 @@ package org.gridsuite.dynamicsecurityanalysis.server.service; import com.powsybl.ws.commons.computation.service.AbstractComputationResultService; -import jakarta.transaction.Transactional; import org.gridsuite.dynamicsecurityanalysis.server.DynamicSecurityAnalysisException; import org.gridsuite.dynamicsecurityanalysis.server.dto.DynamicSecurityAnalysisStatus; import org.gridsuite.dynamicsecurityanalysis.server.entities.DynamicSecurityAnalysisResultEntity; @@ -16,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Objects; @@ -65,8 +65,8 @@ public void updateResult(UUID resultUuid, DynamicSecurityAnalysisStatus status) } @Override - @org.springframework.transaction.annotation.Transactional - public void updateDebugFileLocation(UUID resultUuid, String debugFilePath) { + @Transactional + public void saveDebugFileLocation(UUID resultUuid, String debugFilePath) { resultRepository.findById(resultUuid).ifPresentOrElse( (var resultEntity) -> resultRepository.updateDebugFileLocation(resultUuid, debugFilePath), () -> resultRepository.save(new DynamicSecurityAnalysisResultEntity(resultUuid, DynamicSecurityAnalysisStatus.NOT_DONE, debugFilePath)) @@ -74,6 +74,7 @@ public void updateDebugFileLocation(UUID resultUuid, String debugFilePath) { } @Override + @Transactional public void delete(UUID resultUuid) { Objects.requireNonNull(resultUuid); resultRepository.deleteById(resultUuid); @@ -86,6 +87,7 @@ public void deleteAll() { } @Override + @Transactional(readOnly = true) public DynamicSecurityAnalysisStatus findStatus(UUID resultUuid) { Objects.requireNonNull(resultUuid); return resultRepository.findById(resultUuid) @@ -94,6 +96,7 @@ public DynamicSecurityAnalysisStatus findStatus(UUID resultUuid) { } @Override + @Transactional(readOnly = true) public String findDebugFileLocation(UUID resultUuid) { Objects.requireNonNull(resultUuid); return resultRepository.findById(resultUuid) diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java index 4b9f8d7..85837d7 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisResultServiceTest.java @@ -76,7 +76,7 @@ void testCrud() { assertThat(updatedResultEntityOpt.get().getStatus()).isSameAs(DynamicSecurityAnalysisStatus.NOT_DONE); // --- update the result with debugFileLocation - dynamicSecurityAnalysisResultService.updateDebugFileLocation(resultUuid, "/debug/s3key"); + dynamicSecurityAnalysisResultService.saveDebugFileLocation(resultUuid, "/debug/s3key"); // new debugFileLocation must be inserted updatedResultEntityOpt = resultRepository.findById(resultUuid); @@ -84,7 +84,7 @@ void testCrud() { // --- update the result with debugFileLocation, if entity uuid does not exist, inject a new one UUID noneExistEntityUuid = UUID.randomUUID(); - dynamicSecurityAnalysisResultService.updateDebugFileLocation(noneExistEntityUuid, "/debug/s3key2"); + dynamicSecurityAnalysisResultService.saveDebugFileLocation(noneExistEntityUuid, "/debug/s3key2"); // new debugFileLocation must be inserted updatedResultEntityOpt = resultRepository.findById(noneExistEntityUuid); From 8b2c7c100417a1e973c2afd37b837f0e24c995a9 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 20 Jun 2025 00:08:06 +0200 Subject: [PATCH 09/11] correct for comments of Mathieu Signed-off-by: Thang PHAM --- .../entities/DynamicSecurityAnalysisResultEntity.java | 8 ++------ .../service/DynamicSecurityAnalysisWorkerService.java | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java index d47081e..7d9ffe8 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/entities/DynamicSecurityAnalysisResultEntity.java @@ -8,6 +8,7 @@ package org.gridsuite.dynamicsecurityanalysis.server.entities; import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -22,15 +23,10 @@ @Setter @Table(name = "dynamic_security_analysis_result") @NoArgsConstructor +@AllArgsConstructor @Entity public class DynamicSecurityAnalysisResultEntity { - public DynamicSecurityAnalysisResultEntity(UUID id, DynamicSecurityAnalysisStatus status, String debugFilePath) { - this.id = id; - this.status = status; - this.debugFileLocation = debugFilePath; - } - @Id @Column(name = "result_uuid") private UUID id; diff --git a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java index db3c584..5215f44 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java @@ -261,7 +261,7 @@ protected void clean(AbstractResultContext re @Override protected void processDebug(AbstractResultContext resultContext) { - // copy all content in working directory into debug directory + // copy all content from working directory into debug directory DynamicSecurityAnalysisRunContext runContext = resultContext.getRunContext(); if (runContext.getWorkDir() != null && runContext.getDebugDir() != null) { try { From d70b24108453c4bee2a9cbec1c3fe06d040454d6 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 24 Jun 2025 08:22:30 +0200 Subject: [PATCH 10/11] move s3 config to ws-commons Signed-off-by: Thang PHAM --- src/main/resources/application-local.yml | 1 - src/main/resources/config/application.yaml | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6658419..46173ab 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -7,7 +7,6 @@ spring: cloud: aws: endpoint: http://localhost:19000 - bucket: bucket-gridsuite powsybl-ws: database: diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 7babdbf..1288d3f 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -40,18 +40,7 @@ spring: aws: s3: - path-style-access-enabled: true enabled: true - # classic minio port, useful default for exploring - endpoint: http://s3-storage:9000 - region: - profile: - name: default - static: test - bucket: bucket-gridsuite - credentials: - access-key: minioadmin - secret-key: minioadmin debug-subpath: debug From 0656c78b55704527a0562067e9a985e45630259b Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 24 Jun 2025 09:24:38 +0200 Subject: [PATCH 11/11] create debug chanel Signed-off-by: Thang PHAM --- src/main/resources/config/application.yaml | 4 +++- .../AbstractDynamicSecurityAnalysisControllerTest.java | 3 ++- .../controller/DynamicSecurityAnalysisControllerTest.java | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 1288d3f..9498d17 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -15,6 +15,8 @@ spring: max-attempts: 1 publishRun-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dsa.run + publishDebug-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dsa.debug publishResult-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dsa.result consumeCancel-in-0: @@ -25,7 +27,7 @@ spring: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dsa.stopped publishCancelFailed-out-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}dsa.cancelfailed - output-bindings: publishRun-out-0;publishResult-out-0;publishCancel-out-0;publishStopped-out-0;publishCancelFailed-out-0 + output-bindings: publishRun-out-0;publishDebug-out-0;publishResult-out-0;publishCancel-out-0;publishStopped-out-0;publishCancelFailed-out-0 rabbit: bindings: consumeRun-in-0: diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java index c5766cc..317090b 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/AbstractDynamicSecurityAnalysisControllerTest.java @@ -53,6 +53,7 @@ public abstract class AbstractDynamicSecurityAnalysisControllerTest extends Abst protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected final String dsaDebugDestination = "dsa.debug.destination"; protected final String dsaResultDestination = "dsa.result.destination"; protected final String dsaStoppedDestination = "dsa.stopped.destination"; protected final String dsaCancelFailedDestination = "dsa.cancelfailed.destination"; @@ -121,7 +122,7 @@ public void tearDown() { // check messages in rabbitmq OutputDestination output = getOutputDestination(); - List destinations = List.of(dsaResultDestination, dsaStoppedDestination, dsaCancelFailedDestination); + List destinations = List.of(dsaDebugDestination, dsaResultDestination, dsaStoppedDestination, dsaCancelFailedDestination); TestUtils.assertQueuesEmptyThenClear(destinations, output); } diff --git a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java index 073f363..3625935 100644 --- a/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/dynamicsecurityanalysis/server/controller/DynamicSecurityAnalysisControllerTest.java @@ -207,10 +207,9 @@ void testResult() throws Exception { assertThat(messageSwitch.getHeaders()).containsEntry(HEADER_RESULT_UUID, runUuid.toString()); // check notification of debug - messageSwitch = output.receive(1000 * 10, dsaResultDestination); + messageSwitch = output.receive(1000 * 10, dsaDebugDestination); assertThat(messageSwitch.getHeaders()) - .containsEntry(HEADER_RESULT_UUID, runUuid.toString()) - .containsEntry(HEADER_DEBUG, true); + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()); // download debug zip file is ok mockMvc.perform(get("/v1/results/{resultUuid}/download-debug-file", runUuid))