diff --git a/pom.xml b/pom.xml index cac1b02..c9e686b 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,10 @@ powsybl/java-dynawo:3.0.0 org.gridsuite.dynamicsecurityanalysis.server gridsuite + 3.2.0 org.gridsuite:dynamic-security-analysis-server + + 1.26.0-SNAPSHOT @@ -86,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 @@ -114,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 @@ -130,6 +157,7 @@ com.powsybl powsybl-ws-commons + ${powsybl-ws-commons.version} com.powsybl @@ -138,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..f534b0d 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..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,14 +23,10 @@ @Setter @Table(name = "dynamic_security_analysis_result") @NoArgsConstructor +@AllArgsConstructor @Entity public class DynamicSecurityAnalysisResultEntity { - public DynamicSecurityAnalysisResultEntity(UUID id, DynamicSecurityAnalysisStatus status) { - this.id = id; - this.status = status; - } - @Id @Column(name = "result_uuid") private UUID id; @@ -38,4 +35,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..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; @@ -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 @@ -65,6 +65,16 @@ public void updateResult(UUID resultUuid, DynamicSecurityAnalysisStatus status) } @Override + @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)) + ); + } + + @Override + @Transactional public void delete(UUID resultUuid) { Objects.requireNonNull(resultUuid); resultRepository.deleteById(resultUuid); @@ -77,10 +87,20 @@ public void deleteAll() { } @Override + @Transactional(readOnly = true) public DynamicSecurityAnalysisStatus findStatus(UUID resultUuid) { Objects.requireNonNull(resultUuid); return resultRepository.findById(resultUuid) .map(DynamicSecurityAnalysisResultEntity::getStatus) .orElse(null); } + + @Override + @Transactional(readOnly = true) + 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 3ad6938..5215f44 100644 --- a/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/dynamicsecurityanalysis/server/service/DynamicSecurityAnalysisWorkerService.java @@ -29,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; @@ -60,7 +61,7 @@ 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 @@ -82,10 +83,11 @@ 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); @@ -167,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 @@ -251,14 +256,29 @@ protected void clean(AbstractResultContext re super.clean(resultContext); // clean working directory Path workDir = resultContext.getRunContext().getWorkDir(); - removeWorkingDirectory(workDir); + removeDirectory(workDir); + } + + @Override + protected void processDebug(AbstractResultContext resultContext) { + // copy all content from 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, "dynamic_security_analysis_"); + 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())); @@ -266,18 +286,6 @@ private Path createWorkingDirectory() { 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/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..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,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(HEADER_DEBUG); 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..46173ab 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -4,6 +4,9 @@ server: spring: rabbitmq: addresses: localhost + cloud: + aws: + endpoint: http://localhost:19000 powsybl-ws: database: diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index 1fa5e09..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: @@ -38,6 +40,12 @@ spring: enabled: true delivery-limit: 2 + aws: + s3: + enabled: true + +debug-subpath: debug + 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 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/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..3625935 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,58 @@ 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, dsaDebugDestination); + assertThat(messageSwitch.getHeaders()) + .containsEntry(HEADER_RESULT_UUID, runUuid.toString()); + + // 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 +290,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 +345,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 +392,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 35432e8..85837d7 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(); } @@ -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.saveDebugFileLocation(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.saveDebugFileLocation(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); @@ -89,8 +105,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();