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();