From cdc424de7635e5c741ebc34b67617bc367b92d92 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 8 Mar 2024 14:45:36 +0100 Subject: [PATCH 01/12] AbstractComputationObserver, AbstractComputationRunContext, ReportContext Signed-off-by: Mathieu DEHARBE --- .../server/SecurityAnalysisController.java | 5 +- .../service/AbstractComputationObserver.java | 67 +++++++++++++++++++ .../AbstractComputationRunContext.java | 30 +++++++++ .../computation/utils/ReportContext.java | 30 +++++++++ .../server/dto/ReportInfos.java | 28 -------- .../service/SecurityAnalysisObserver.java | 52 +++----------- .../SecurityAnalysisParametersService.java | 6 +- .../SecurityAnalysisResultContext.java | 10 +-- .../service/SecurityAnalysisRunContext.java | 39 ++--------- .../SecurityAnalysisWorkerService.java | 20 ++++-- 10 files changed, 168 insertions(+), 119 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationObserver.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationRunContext.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ReportContext.java delete mode 100644 src/main/java/org/gridsuite/securityanalysis/server/dto/ReportInfos.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java index 97530e73..2313f91b 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java @@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; import org.gridsuite.securityanalysis.server.dto.*; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisParametersService; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisResultService; @@ -77,7 +78,7 @@ public ResponseEntity run(@Parameter(description = "Netw @Parameter(description = "parametersUuid") @RequestParam(name = "parametersUuid", required = false) UUID parametersUuid, @Parameter(description = "loadFlow parameters uuid") @RequestParam(name = "loadFlowParametersUuid") UUID loadFlowParametersUuid, @RequestHeader(HEADER_USER_ID) String userId) { - SecurityAnalysisResult result = workerService.run(securityAnalysisParametersService.createRunContext(networkUuid, variantId, new RunContextParametersInfos(contigencyListNames, parametersUuid, loadFlowParametersUuid), null, new ReportInfos(reportUuid, reporterId, reportType), userId)); + SecurityAnalysisResult result = workerService.run(securityAnalysisParametersService.createRunContext(networkUuid, variantId, new RunContextParametersInfos(contigencyListNames, parametersUuid, loadFlowParametersUuid), null, new ReportContext(reportUuid, reporterId, reportType), userId)); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result); } @@ -103,7 +104,7 @@ public ResponseEntity runAndSave(@Parameter(description = "Network UUID") variantId, new RunContextParametersInfos(contigencyListNames, parametersUuid, loadFlowParametersUuid), receiver, - new ReportInfos(reportUuid, reporterId, reportType), + new ReportContext(reportUuid, reporterId, reportType), userId ) ); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationObserver.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationObserver.java new file mode 100644 index 00000000..47e9cfac --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationObserver.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import lombok.NonNull; + +/** + * @author Mathieu Deharbe powsybl Result class specific to the computation + * @param

powsybl and gridsuite parameters specifics to the computation + */ +public abstract class AbstractComputationObserver { + protected static final String OBSERVATION_PREFIX = "app.computation."; + protected static final String PROVIDER_TAG_NAME = "provider"; + protected static final String TYPE_TAG_NAME = "type"; + protected static final String STATUS_TAG_NAME = "status"; + protected static final String COMPUTATION_COUNTER_NAME = OBSERVATION_PREFIX + "count"; + protected final ObservationRegistry observationRegistry; + protected final MeterRegistry meterRegistry; + + protected AbstractComputationObserver(@NonNull ObservationRegistry observationRegistry, @NonNull MeterRegistry meterRegistry) { + this.observationRegistry = observationRegistry; + this.meterRegistry = meterRegistry; + } + + protected abstract String getComputationType(); + + protected Observation createObservation(String name, AbstractComputationRunContext

runContext) { + return Observation.createNotStarted(OBSERVATION_PREFIX + name, observationRegistry) + .lowCardinalityKeyValue(PROVIDER_TAG_NAME, runContext.getProvider()) + .lowCardinalityKeyValue(TYPE_TAG_NAME, getComputationType()); + } + + public void observe(String name, AbstractComputationRunContext

runContext, Observation.CheckedRunnable callable) throws E { + createObservation(name, runContext).observeChecked(callable); + } + + public T observe(String name, AbstractComputationRunContext

runContext, Observation.CheckedCallable callable) throws E { + return createObservation(name, runContext).observeChecked(callable); + } + + public T observeRun( + String name, AbstractComputationRunContext

runContext, Observation.CheckedCallable callable) throws E { + T result = createObservation(name, runContext).observeChecked(callable); + incrementCount(runContext, result); + return result; + } + + private void incrementCount(AbstractComputationRunContext

runContext, R result) { + Counter.builder(COMPUTATION_COUNTER_NAME) + .tag(PROVIDER_TAG_NAME, runContext.getProvider()) + .tag(TYPE_TAG_NAME, getComputationType()) + .tag(STATUS_TAG_NAME, getResultStatus(result)) + .register(meterRegistry) + .increment(); + } + + protected abstract String getResultStatus(R res); +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationRunContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationRunContext.java new file mode 100644 index 00000000..e5657c25 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationRunContext.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; + +import java.util.UUID; + +/** + * @author Mathieu Deharbe parameters structure specific to the computation + */ +@Getter +@AllArgsConstructor +public abstract class AbstractComputationRunContext

{ + private final UUID networkUuid; + private final String variantId; + private final String receiver; + private final ReportContext reportContext; + private final String userId; + @Setter protected String provider; + @Setter protected P parameters; +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ReportContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ReportContext.java new file mode 100644 index 00000000..cc790cc5 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ReportContext.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.utils; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ +@Getter +@Builder +@AllArgsConstructor +@Schema(description = "Report infos") // TODO : added in ReportInfos par Abdelsalem ==> à quoi cela sert-il ?? +public class ReportContext { + + private UUID reportId; + + private String reportName; + + private final String reportType; +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ReportInfos.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ReportInfos.java deleted file mode 100644 index 9b082938..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ReportInfos.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.UUID; - -/** - * @author Abdelsalem Hedhili - */ -@AllArgsConstructor -@Getter -@Schema(description = "Report infos") -public class ReportInfos { - private UUID reportUuid; - - private String reporterId; - - private String reportType; -} - diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisObserver.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisObserver.java index 01be8006..08d23cc6 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisObserver.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisObserver.java @@ -8,65 +8,33 @@ package org.gridsuite.securityanalysis.server.service; import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.security.SecurityAnalysisParameters; import com.powsybl.security.SecurityAnalysisResult; -import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import lombok.NonNull; +import org.gridsuite.securityanalysis.server.computation.service.AbstractComputationObserver; import org.springframework.stereotype.Service; /** * @author Kevin Le Saulnier */ @Service -public class SecurityAnalysisObserver { - - private static final String OBSERVATION_PREFIX = "app.computation."; - - private static final String PROVIDER_TAG_NAME = "provider"; - private static final String TYPE_TAG_NAME = "type"; - private static final String STATUS_TAG_NAME = "status"; +public class SecurityAnalysisObserver extends AbstractComputationObserver { private static final String COMPUTATION_TYPE = "sa"; - private static final String COMPUTATION_COUNTER_NAME = OBSERVATION_PREFIX + "count"; - - private final ObservationRegistry observationRegistry; - - private final MeterRegistry meterRegistry; - public SecurityAnalysisObserver(@NonNull ObservationRegistry observationRegistry, @NonNull MeterRegistry meterRegistry) { - this.observationRegistry = observationRegistry; - this.meterRegistry = meterRegistry; - } - - public void observe(String name, SecurityAnalysisRunContext runContext, Observation.CheckedRunnable runnable) throws E { - createObservation(name, runContext).observeChecked(runnable); - } - - public T observe(String name, SecurityAnalysisRunContext runContext, Observation.CheckedCallable callable) throws E { - return createObservation(name, runContext).observeChecked(callable); - } - - public T observeRun(String name, SecurityAnalysisRunContext runContext, Observation.CheckedCallable callable) throws E { - T result = createObservation(name, runContext).observeChecked(callable); - incrementCount(runContext, result); - return result; + super(observationRegistry, meterRegistry); } - private Observation createObservation(String name, SecurityAnalysisRunContext runContext) { - return Observation.createNotStarted(OBSERVATION_PREFIX + name, observationRegistry) - .lowCardinalityKeyValue(PROVIDER_TAG_NAME, runContext.getProvider()) - .lowCardinalityKeyValue(TYPE_TAG_NAME, COMPUTATION_TYPE); + @Override + protected String getComputationType() { + return COMPUTATION_TYPE; } - private void incrementCount(SecurityAnalysisRunContext runContext, SecurityAnalysisResult result) { - Counter.builder(COMPUTATION_COUNTER_NAME) - .tag(PROVIDER_TAG_NAME, runContext.getProvider()) - .tag(TYPE_TAG_NAME, COMPUTATION_TYPE) - .tag(STATUS_TAG_NAME, result != null && result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED ? "OK" : "NOK") - .register(meterRegistry) - .increment(); + @Override + protected String getResultStatus(SecurityAnalysisResult res) { + return res != null && res.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED ? "OK" : "NOK"; } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisParametersService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisParametersService.java index c3b37b76..5b0b14f7 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisParametersService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisParametersService.java @@ -8,7 +8,7 @@ import com.powsybl.security.SecurityAnalysisParameters; import org.gridsuite.securityanalysis.server.dto.LoadFlowParametersValues; -import org.gridsuite.securityanalysis.server.dto.ReportInfos; +import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; import org.gridsuite.securityanalysis.server.dto.RunContextParametersInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisParametersValues; import org.gridsuite.securityanalysis.server.entities.SecurityAnalysisParametersEntity; @@ -50,7 +50,7 @@ public SecurityAnalysisParametersService(SecurityAnalysisParametersRepository se } public SecurityAnalysisRunContext createRunContext(UUID networkUuid, String variantId, RunContextParametersInfos runContextParametersInfos, - String receiver, ReportInfos reportInfos, String userId) { + String receiver, ReportContext reportContext, String userId) { Optional securityAnalysisParametersEntity = Optional.empty(); if (runContextParametersInfos.getSecurityAnalysisParametersUuid() != null) { securityAnalysisParametersEntity = securityAnalysisParametersRepository.findById(runContextParametersInfos.getSecurityAnalysisParametersUuid()); @@ -72,7 +72,7 @@ public SecurityAnalysisRunContext createRunContext(UUID networkUuid, String vari providerToUse, parameters, loadFlowParametersValues, - new ReportInfos(reportInfos.getReportUuid(), reportInfos.getReporterId(), reportInfos.getReportType()), + new ReportContext(reportContext.getReportId(), reportContext.getReportName(), reportContext.getReportType()), userId); } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index cb8749d7..809c9071 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.commons.PowsyblException; import com.powsybl.security.SecurityAnalysisParameters; -import org.gridsuite.securityanalysis.server.dto.ReportInfos; +import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -88,7 +88,7 @@ public static SecurityAnalysisResultContext fromMessage(Message message, receiver, provider, parameters, - new ReportInfos(reportUuid, reporterId, reportType), + new ReportContext(reportUuid, reporterId, reportType), userId ); return new SecurityAnalysisResultContext(resultUuid, runContext); @@ -109,9 +109,9 @@ public Message toMessage(ObjectMapper objectMapper) { .setHeader(RECEIVER_HEADER, runContext.getReceiver()) .setHeader(HEADER_USER_ID, runContext.getUserId()) .setHeader(PROVIDER_HEADER, runContext.getProvider()) - .setHeader(REPORT_UUID_HEADER, runContext.getReportUuid()) - .setHeader(REPORTER_ID_HEADER, runContext.getReporterId()) - .setHeader(REPORT_TYPE_HEADER, runContext.getReportType()) + .setHeader(REPORT_UUID_HEADER, runContext.getReportContext().getReportId()) + .setHeader(REPORTER_ID_HEADER, runContext.getReportContext().getReportName()) + .setHeader(REPORT_TYPE_HEADER, runContext.getReportContext().getReportType()) .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java index 2406e3dd..b44e005c 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java @@ -12,8 +12,9 @@ import com.powsybl.loadflow.LoadFlowProvider; import com.powsybl.security.SecurityAnalysisParameters; import lombok.Getter; +import org.gridsuite.securityanalysis.server.computation.service.AbstractComputationRunContext; +import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; import org.gridsuite.securityanalysis.server.dto.LoadFlowParametersValues; -import org.gridsuite.securityanalysis.server.dto.ReportInfos; import java.util.List; import java.util.Objects; @@ -23,31 +24,13 @@ * @author Geoffroy Jamgotchian */ @Getter -public class SecurityAnalysisRunContext { - - private final UUID networkUuid; - - private final String variantId; +public class SecurityAnalysisRunContext extends AbstractComputationRunContext { private final List contingencyListNames; - private final String receiver; - - private final String provider; - - private final SecurityAnalysisParameters parameters; - - private final UUID reportUuid; - - private final String reporterId; - - private final String userId; - - private final String reportType; - public SecurityAnalysisRunContext(UUID networkUuid, String variantId, List contingencyListNames, String receiver, String provider, SecurityAnalysisParameters parameters, LoadFlowParametersValues loadFlowParametersValues, - ReportInfos reportInfos, String userId) { + ReportContext reportContext, String userId) { this( networkUuid, variantId, @@ -55,24 +38,16 @@ public SecurityAnalysisRunContext(UUID networkUuid, String variantId, List contingencyListNames, String receiver, String provider, SecurityAnalysisParameters parameters, - ReportInfos reportInfos, String userId) { - this.networkUuid = Objects.requireNonNull(networkUuid); - this.variantId = variantId; + ReportContext reportContext, String userId) { + super(networkUuid, variantId, receiver, reportContext, userId, provider, parameters); this.contingencyListNames = Objects.requireNonNull(contingencyListNames); - this.receiver = receiver; - this.provider = provider; - this.parameters = Objects.requireNonNull(parameters); - this.reportUuid = reportInfos.getReportUuid(); - this.reporterId = reportInfos.getReporterId(); - this.userId = userId; - this.reportType = reportInfos.getReportType(); } private static SecurityAnalysisParameters buildParameters(SecurityAnalysisParameters securityAnalysisParameters, diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index c1b92b52..940fe80a 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -205,13 +205,16 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu AtomicReference rootReporter = new AtomicReference<>(Reporter.NO_OP); Reporter reporter = Reporter.NO_OP; - if (context.getReportUuid() != null) { - final String reportType = context.getReportType(); - String rootReporterId = context.getReporterId() == null ? reportType : context.getReporterId() + "@" + reportType; + if (context.getReportContext().getReportId() != null) { + final String reportType = context.getReportContext().getReportType(); + String rootReporterId = context.getReportContext().getReportName() == null ? + reportType : + context.getReportContext().getReportName() + "@" + reportType; rootReporter.set(new ReporterModel(rootReporterId, rootReporterId)); reporter = rootReporter.get().createSubReporter(reportType, reportType + " (${providerToUse})", "providerToUse", securityAnalysisRunner.getName()); // Delete any previous SA computation logs - securityAnalysisObserver.observe("report.delete", context, () -> reportService.deleteReport(context.getReportUuid(), reportType)); + securityAnalysisObserver.observe("report.delete", + context, () -> reportService.deleteReport(context.getReportContext().getReportId(), reportType)); } CompletableFuture future = runASAsync(context, @@ -225,7 +228,7 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu resultUuid); SecurityAnalysisResult result = future == null ? null : securityAnalysisObserver.observeRun("run", context, future::get); - if (context.getReportUuid() != null) { + if (context.getReportContext().getReportId() != null) { List notFoundElementReports = new ArrayList<>(); contingencies.stream() .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) @@ -238,10 +241,13 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu .build()); }); if (!CollectionUtils.isEmpty(notFoundElementReports)) { - Reporter elementNotFoundSubReporter = reporter.createSubReporter(context.getReportUuid().toString() + "notFoundElements", "Elements not found"); + Reporter elementNotFoundSubReporter = reporter.createSubReporter( + context.getReportContext().getReportId().toString() + "notFoundElements", + "Elements not found"); notFoundElementReports.forEach(elementNotFoundSubReporter::report); } - securityAnalysisObserver.observe("report.send", context, () -> reportService.sendReport(context.getReportUuid(), rootReporter.get())); + securityAnalysisObserver.observe("report.send", + context, () -> reportService.sendReport(context.getReportContext().getReportId(), rootReporter.get())); } return result; } From 90afe870db035d4903abbe60b934d514ac2de57d Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 14 Mar 2024 14:08:40 +0100 Subject: [PATCH 02/12] NotificationService + PostCompletion annotation Signed-off-by: Mathieu DEHARBE --- .../server/SecurityAnalysisController.java | 2 +- .../service/AbstractResultContext.java | 15 +++ .../service/NotificationService.java | 120 ++++++++++++++++++ .../service/UuidGeneratorService.java | 2 +- .../utils/annotations/PostCompletion.java | 22 ++++ .../annotations/PostCompletionAdapter.java | 47 +++++++ .../PostCompletionAnnotationAspect.java | 51 ++++++++ .../server/service/NotificationService.java | 98 -------------- .../SecurityAnalysisCancelContext.java | 13 +- .../SecurityAnalysisResultContext.java | 16 ++- .../service/SecurityAnalysisService.java | 14 +- .../SecurityAnalysisWorkerService.java | 30 +++-- .../SecurityAnalysisControllerTest.java | 11 +- 13 files changed, 307 insertions(+), 134 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java rename src/main/java/org/gridsuite/securityanalysis/server/{ => computation}/service/UuidGeneratorService.java (88%) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletion.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletionAdapter.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletionAnnotationAspect.java delete mode 100644 src/main/java/org/gridsuite/securityanalysis/server/service/NotificationService.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java index 2313f91b..55769e9c 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java @@ -36,7 +36,7 @@ import java.util.List; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.service.NotificationService.HEADER_USER_ID; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_USER_ID; import static org.springframework.http.MediaType.*; /** diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java new file mode 100644 index 00000000..799d7d86 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java @@ -0,0 +1,15 @@ +package org.gridsuite.securityanalysis.server.computation.service; + +// TODO !! remplacer par le vrai, tmp pour faire marcher notification service +public abstract class AbstractResultContext { + + public static final String NETWORK_UUID_HEADER = "networkUuid"; + + public static final String VARIANT_ID_HEADER = "variantId"; + + public static final String REPORT_UUID_HEADER = "reportUuid"; + + public static final String REPORTER_ID_HEADER = "reporterId"; + + public static final String REPORT_TYPE_HEADER = "reportType"; +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java new file mode 100644 index 00000000..3ba7a8f2 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import org.gridsuite.securityanalysis.server.computation.utils.annotations.PostCompletion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.function.StreamBridge; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +/** + * @author Anis Touri message) { + RUN_MESSAGE_LOGGER.debug(SENDING_MESSAGE, message); + publisher.send("publishRun-out-0", message); + } + + public void sendCancelMessage(Message message) { + CANCEL_MESSAGE_LOGGER.debug(SENDING_MESSAGE, message); + publisher.send("publishCancel-out-0", message); + } + + @PostCompletion + public void sendResultMessage(UUID resultUuid, String receiver) { + Message message = MessageBuilder + .withPayload("") + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) + .setHeader(HEADER_RECEIVER, receiver) + .build(); + RESULT_MESSAGE_LOGGER.debug(SENDING_MESSAGE, message); + publisher.send("publishResult-out-0", message); + } + + @PostCompletion + public void publishStop(UUID resultUuid, String receiver, String computationLabel) { + Message message = MessageBuilder + .withPayload("") + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) + .setHeader(HEADER_RECEIVER, receiver) + .setHeader(HEADER_MESSAGE, getCancelMessage(computationLabel)) + .build(); + STOP_MESSAGE_LOGGER.debug(SENDING_MESSAGE, message); + publisher.send("publishStopped-out-0", message); + } + + @PostCompletion + public void publishFail(UUID resultUuid, String receiver, String causeMessage, String userId, String computationLabel) { + Message message = MessageBuilder + .withPayload("") + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) + .setHeader(HEADER_RECEIVER, receiver) + .setHeader(HEADER_MESSAGE, shortenMessage( + getFailedMessage(computationLabel) + " : " + causeMessage)) + .setHeader(HEADER_USER_ID, userId) + .build(); + FAILED_MESSAGE_LOGGER.debug(SENDING_MESSAGE, message); + publisher.send("publishFailed-out-0", message); + } + + public static String getCancelMessage(String computationLabel) { + return computationLabel + " was canceled"; + } + + public static String getFailedMessage(String computationLabel) { + return computationLabel + " has failed"; + } + + // prevent the message from being too long for rabbitmq + // the beginning and ending are both kept, it should make it easier to identify + public String shortenMessage(String msg) { + if (msg == null) { + return msg; + } + + return msg.length() > MSG_MAX_LENGTH ? + msg.substring(0, MSG_MAX_LENGTH / 2) + " ... " + msg.substring(msg.length() - MSG_MAX_LENGTH / 2, msg.length() - 1) + : msg; + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/UuidGeneratorService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/UuidGeneratorService.java similarity index 88% rename from src/main/java/org/gridsuite/securityanalysis/server/service/UuidGeneratorService.java rename to src/main/java/org/gridsuite/securityanalysis/server/computation/service/UuidGeneratorService.java index 979ca385..054588f0 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/UuidGeneratorService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/UuidGeneratorService.java @@ -4,7 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package org.gridsuite.securityanalysis.server.service; +package org.gridsuite.securityanalysis.server.computation.service; import org.springframework.stereotype.Service; diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletion.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletion.java new file mode 100644 index 00000000..8a9723a7 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletion.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.securityanalysis.server.computation.utils.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Anis Touri > RUNNABLE = new ThreadLocal<>(); + + // register a new runnable for post completion execution + public void execute(Runnable runnable) { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + List runnables = RUNNABLE.get(); + if (runnables == null) { + runnables = new ArrayList<>(Arrays.asList(runnable)); + } else { + runnables.add(runnable); + } + RUNNABLE.set(runnables); + TransactionSynchronizationManager.registerSynchronization(this); + return; + } + // if transaction synchronisation is not active + runnable.run(); + } + + @Override + public void afterCompletion(int status) { + List runnables = RUNNABLE.get(); + runnables.forEach(Runnable::run); + RUNNABLE.remove(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletionAnnotationAspect.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletionAnnotationAspect.java new file mode 100644 index 00000000..72ab053f --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/annotations/PostCompletionAnnotationAspect.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.utils.annotations; + +import lombok.AllArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author Anis Touri - */ - -// Today we don't send notification inside @Transactional block. If this behavior change, we should use @PostCompletion to -// make sure that the notification is sent only when all the work inside @Transactional block is done. -@Service -public class NotificationService { - public static final String RECEIVER_HEADER = "receiver"; - private static final String CATEGORY_BROKER_OUTPUT = NotificationService.class.getName() + ".output-broker-messages"; - private static final Logger OUTPUT_MESSAGE_LOGGER = LoggerFactory.getLogger(CATEGORY_BROKER_OUTPUT); - public static final String CANCEL_MESSAGE = "Security analysis was canceled"; - public static final String FAIL_MESSAGE = "Security analysis has failed"; - public static final String RESULT_UUID_HEADER = "resultUuid"; - public static final String MESSAGE_HEADER = "message"; - public static final String NETWORK_UUID_HEADER = "networkUuid"; - public static final String VARIANT_ID_HEADER = "variantId"; - public static final String CONTINGENCY_LIST_NAMES_HEADER = "contingencyListNames"; - public static final String PROVIDER_HEADER = "provider"; - public static final String REPORT_UUID_HEADER = "reportUuid"; - public static final String REPORTER_ID_HEADER = "reporterId"; - public static final String REPORT_TYPE_HEADER = "reportType"; - public static final String HEADER_USER_ID = "userId"; - - public static final int MSG_MAX_LENGTH = 256; - - @Autowired - private StreamBridge publisher; - - private void sendMessage(Message message, String bindingName) { - OUTPUT_MESSAGE_LOGGER.debug("Sending message : {}", message); - publisher.send(bindingName, message); - } - - public void emitAnalysisResultsMessage(String resultUuid, String receiver) { - sendMessage(MessageBuilder.withPayload("") - .setHeader(RESULT_UUID_HEADER, resultUuid) - .setHeader(RECEIVER_HEADER, receiver) - .build(), - "publishResult-out-0"); - } - - public void emitStopAnalysisMessage(String resultUuid, String receiver) { - sendMessage(MessageBuilder.withPayload("") - .setHeader(RESULT_UUID_HEADER, resultUuid) - .setHeader(RECEIVER_HEADER, receiver) - .setHeader(MESSAGE_HEADER, CANCEL_MESSAGE) - .build(), - "publishStopped-out-0"); - } - - public void emitFailAnalysisMessage(String resultUuid, String receiver, String causeMessage, String userId) { - sendMessage(MessageBuilder.withPayload("") - .setHeader(RESULT_UUID_HEADER, resultUuid) - .setHeader(RECEIVER_HEADER, receiver) - .setHeader(HEADER_USER_ID, userId) - .setHeader(MESSAGE_HEADER, shortenMessage(FAIL_MESSAGE + " : " + causeMessage)) - .build(), - "publishFailed-out-0"); - } - - public void emitRunAnalysisMessage(Message message) { - sendMessage(message, "publishRun-out-0"); - } - - public void emitCancelAnalysisMessage(Message message) { - sendMessage(message, "publishCancel-out-0"); - } - - // prevent the message from being too long for rabbitmq - // the beginning and ending are both kept, it should make it easier to identify - public String shortenMessage(String msg) { - if (msg == null) { - return msg; - } - - return msg.length() > MSG_MAX_LENGTH ? - msg.substring(0, MSG_MAX_LENGTH / 2) + " ... " + msg.substring(msg.length() - MSG_MAX_LENGTH / 2, msg.length() - 1) - : msg; - } -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java index 22f86bd1..1dd22718 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java @@ -14,8 +14,9 @@ import java.util.Objects; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.service.NotificationService.RESULT_UUID_HEADER; -import static org.gridsuite.securityanalysis.server.service.NotificationService.RECEIVER_HEADER; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_RESULT_UUID; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_RECEIVER; + /** * @author Franck Lecuyer */ @@ -49,15 +50,15 @@ private static String getNonNullHeader(MessageHeaders headers, String name) { public static SecurityAnalysisCancelContext fromMessage(Message message) { Objects.requireNonNull(message); MessageHeaders headers = message.getHeaders(); - UUID resultUuid = UUID.fromString(getNonNullHeader(headers, RESULT_UUID_HEADER)); - String receiver = (String) headers.get(RECEIVER_HEADER); + UUID resultUuid = UUID.fromString(getNonNullHeader(headers, HEADER_RESULT_UUID)); + String receiver = (String) headers.get(HEADER_RECEIVER); return new SecurityAnalysisCancelContext(resultUuid, receiver); } public Message toMessage() { return MessageBuilder.withPayload("") - .setHeader(RESULT_UUID_HEADER, resultUuid.toString()) - .setHeader(RECEIVER_HEADER, receiver) + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) + .setHeader(HEADER_RECEIVER, receiver) .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index 809c9071..3e079419 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -22,12 +22,14 @@ import java.util.Objects; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext.*; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; /** * @author Geoffroy Jamgotchian */ public class SecurityAnalysisResultContext { + public static final String CONTINGENCY_LIST_NAMES_HEADER = "contingencyListNames"; private final UUID resultUuid; @@ -65,12 +67,12 @@ private static String getNonNullHeader(MessageHeaders headers, String name) { public static SecurityAnalysisResultContext fromMessage(Message message, ObjectMapper objectMapper) { Objects.requireNonNull(message); MessageHeaders headers = message.getHeaders(); - UUID resultUuid = UUID.fromString(getNonNullHeader(headers, RESULT_UUID_HEADER)); + UUID resultUuid = UUID.fromString(getNonNullHeader(headers, HEADER_RESULT_UUID)); UUID networkUuid = UUID.fromString(getNonNullHeader(headers, NETWORK_UUID_HEADER)); String variantId = (String) headers.get(VARIANT_ID_HEADER); List contingencyListNames = getHeaderList(headers, CONTINGENCY_LIST_NAMES_HEADER); - String receiver = (String) headers.get(RECEIVER_HEADER); - String provider = (String) headers.get(PROVIDER_HEADER); + String receiver = (String) headers.get(HEADER_RECEIVER); + String provider = (String) headers.get(HEADER_PROVIDER); String userId = (String) headers.get(HEADER_USER_ID); SecurityAnalysisParameters parameters; try { @@ -102,13 +104,13 @@ public Message toMessage(ObjectMapper objectMapper) { throw new UncheckedIOException(e); } return MessageBuilder.withPayload(parametersJson) - .setHeader(RESULT_UUID_HEADER, resultUuid.toString()) + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) .setHeader(NETWORK_UUID_HEADER, runContext.getNetworkUuid().toString()) .setHeader(VARIANT_ID_HEADER, runContext.getVariantId()) .setHeader(CONTINGENCY_LIST_NAMES_HEADER, String.join(",", runContext.getContingencyListNames())) - .setHeader(RECEIVER_HEADER, runContext.getReceiver()) + .setHeader(HEADER_RECEIVER, runContext.getReceiver()) .setHeader(HEADER_USER_ID, runContext.getUserId()) - .setHeader(PROVIDER_HEADER, runContext.getProvider()) + .setHeader(HEADER_PROVIDER, runContext.getProvider()) .setHeader(REPORT_UUID_HEADER, runContext.getReportContext().getReportId()) .setHeader(REPORTER_ID_HEADER, runContext.getReportContext().getReportName()) .setHeader(REPORT_TYPE_HEADER, runContext.getReportContext().getReportType()) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index fa054cfb..b7e9936b 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java @@ -8,6 +8,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.security.SecurityAnalysisProvider; +import lombok.Getter; +import org.gridsuite.securityanalysis.server.computation.service.NotificationService; +import org.gridsuite.securityanalysis.server.computation.service.UuidGeneratorService; import org.gridsuite.securityanalysis.server.dto.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -23,6 +26,8 @@ */ @Service public class SecurityAnalysisService { + public static final String COMPUTATION_TYPE = "Security analysis"; + private final SecurityAnalysisResultService securityAnalysisResultService; private final UuidGeneratorService uuidGeneratorService; @@ -31,6 +36,7 @@ public class SecurityAnalysisService { private final ObjectMapper objectMapper; + @Getter private final String defaultProvider; public SecurityAnalysisService(SecurityAnalysisResultService securityAnalysisResultService, @@ -50,7 +56,7 @@ public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { var resultUuid = uuidGeneratorService.generate(); // update status to running status setStatus(List.of(resultUuid), SecurityAnalysisStatus.RUNNING); - notificationService.emitRunAnalysisMessage(new SecurityAnalysisResultContext(resultUuid, runContext).toMessage(objectMapper)); + notificationService.sendRunMessage(new SecurityAnalysisResultContext(resultUuid, runContext).toMessage(objectMapper)); return resultUuid; } @@ -72,7 +78,7 @@ public void setStatus(List resultUuids, SecurityAnalysisStatus status) { } public void stop(UUID resultUuid, String receiver) { - notificationService.emitCancelAnalysisMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage()); + notificationService.sendCancelMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage()); } public List getProviders() { @@ -80,8 +86,4 @@ public List getProviders() { .map(SecurityAnalysisProvider::getName) .collect(Collectors.toList()); } - - public String getDefaultProvider() { - return defaultProvider; - } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 940fe80a..dc9e41c5 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -25,6 +25,7 @@ import com.powsybl.security.SecurityAnalysisResult; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.ws.commons.LogUtils; +import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; @@ -51,8 +52,9 @@ import java.util.function.Consumer; import java.util.function.Function; -import static org.gridsuite.securityanalysis.server.service.NotificationService.CANCEL_MESSAGE; -import static org.gridsuite.securityanalysis.server.service.NotificationService.FAIL_MESSAGE; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getCancelMessage; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getFailedMessage; +import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisService.COMPUTATION_TYPE; /** * @author Geoffroy Jamgotchian @@ -123,11 +125,16 @@ public SecurityAnalysisResult run(SecurityAnalysisRunContext context) { Thread.currentThread().interrupt(); return null; } catch (Exception e) { - LOGGER.error(FAIL_MESSAGE, e); + LOGGER.error(getFailedMessage(getComputationType()), e); return null; } } + //@Override // TODO réOverride + protected String getComputationType() { + return COMPUTATION_TYPE; + } + private CompletableFuture runASAsync(SecurityAnalysisRunContext context, SecurityAnalysis.Runner securityAnalysisRunner, Network network, @@ -183,8 +190,8 @@ private void cancelASAsync(SecurityAnalysisCancelContext cancelContext) { private void cleanASResultsAndPublishCancel(UUID resultUuid, String receiver) { securityAnalysisResultService.delete(resultUuid); - notificationService.emitStopAnalysisMessage(resultUuid.toString(), receiver); - LOGGER.info(CANCEL_MESSAGE + " (resultUuid='{}')", resultUuid); + notificationService.publishStop(resultUuid, receiver, getComputationType()); + LOGGER.info(getCancelMessage(getComputationType()) + " (resultUuid='{}')", resultUuid); } private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resultUuid) throws Exception { @@ -276,7 +283,7 @@ public Consumer> consumeRun() { LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); if (result != null) { // result available - notificationService.emitAnalysisResultsMessage(resultContext.getResultUuid().toString(), resultContext.getRunContext().getReceiver()); + notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); LOGGER.info("Security analysis complete (resultUuid='{}')", resultContext.getResultUuid()); } else { // result not available : stop computation request if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { @@ -287,11 +294,12 @@ public Consumer> consumeRun() { Thread.currentThread().interrupt(); } catch (Exception e) { if (!(e instanceof CancellationException)) { - LOGGER.error(FAIL_MESSAGE, e); - notificationService.emitFailAnalysisMessage(resultContext.getResultUuid().toString(), - resultContext.getRunContext().getReceiver(), - e.getMessage(), - resultContext.getRunContext().getUserId()); + LOGGER.error(getFailedMessage(getComputationType()), e); + notificationService.publishFail(resultContext.getResultUuid(), + resultContext.getRunContext().getReceiver(), + e.getMessage(), + resultContext.getRunContext().getUserId(), + getComputationType()); securityAnalysisResultService.delete(resultContext.getResultUuid()); } } finally { diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java index e02bcccc..5fc82887 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java @@ -36,7 +36,7 @@ import org.gridsuite.securityanalysis.server.service.LoadFlowService; import org.gridsuite.securityanalysis.server.service.ReportService; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService; -import org.gridsuite.securityanalysis.server.service.UuidGeneratorService; +import org.gridsuite.securityanalysis.server.computation.service.UuidGeneratorService; import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; import org.gridsuite.securityanalysis.server.util.CsvExportUtils; import org.gridsuite.securityanalysis.server.util.MatcherJson; @@ -72,7 +72,10 @@ import static com.powsybl.network.store.model.NetworkStoreApi.VERSION; import static org.gridsuite.securityanalysis.server.SecurityAnalysisProviderMock.*; -import static org.gridsuite.securityanalysis.server.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_USER_ID; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getFailedMessage; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisService.COMPUTATION_TYPE; import static org.gridsuite.securityanalysis.server.util.DatabaseQueryUtils.assertRequestsCount; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; @@ -603,7 +606,7 @@ public void stopTest() throws Exception { Message message = output.receive(TIMEOUT * 3, "sa.stopped"); assertEquals(RESULT_UUID.toString(), message.getHeaders().get("resultUuid")); assertEquals("me", message.getHeaders().get("receiver")); - assertEquals(CANCEL_MESSAGE, message.getHeaders().get("message")); + assertEquals(getCancelMessage(COMPUTATION_TYPE), message.getHeaders().get("message")); } @Test @@ -631,7 +634,7 @@ public void runTestWithError() throws Exception { Message cancelMessage = output.receive(TIMEOUT, "sa.failed"); assertEquals(RESULT_UUID.toString(), cancelMessage.getHeaders().get("resultUuid")); assertEquals("me", cancelMessage.getHeaders().get("receiver")); - assertEquals(FAIL_MESSAGE + " : " + ERROR_MESSAGE, cancelMessage.getHeaders().get("message")); + assertEquals(getFailedMessage(COMPUTATION_TYPE) + " : " + ERROR_MESSAGE, cancelMessage.getHeaders().get("message")); // No result assertResultNotFound(RESULT_UUID); From 484df33218c9ea97e27a53c1427b0521cd840aaa Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 14 Mar 2024 15:22:23 +0100 Subject: [PATCH 03/12] AbstractResultContext Signed-off-by: Mathieu DEHARBE --- .../service/AbstractResultContext.java | 69 +++++++++++++++++-- .../SecurityAnalysisResultContext.java | 43 ++---------- 2 files changed, 71 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java index 799d7d86..1f317682 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java @@ -1,15 +1,74 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ package org.gridsuite.securityanalysis.server.computation.service; -// TODO !! remplacer par le vrai, tmp pour faire marcher notification service -public abstract class AbstractResultContext { +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; - public static final String NETWORK_UUID_HEADER = "networkUuid"; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; - public static final String VARIANT_ID_HEADER = "variantId"; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; + +/** + * @author Mathieu Deharbe run context specific to a computation, including parameters + */ +@Getter +public abstract class AbstractResultContext> { + + protected static final String RESULT_UUID_HEADER = "resultUuid"; - public static final String REPORT_UUID_HEADER = "reportUuid"; + protected static final String NETWORK_UUID_HEADER = "networkUuid"; + + protected static final String REPORT_UUID_HEADER = "reportUuid"; + + public static final String VARIANT_ID_HEADER = "variantId"; public static final String REPORTER_ID_HEADER = "reporterId"; public static final String REPORT_TYPE_HEADER = "reportType"; + + protected static final String MESSAGE_ROOT_NAME = "parameters"; + + protected final UUID resultUuid; + + protected final R runContext; + + protected AbstractResultContext(UUID resultUuid, R runContext) { + this.resultUuid = Objects.requireNonNull(resultUuid); + this.runContext = Objects.requireNonNull(runContext); + } + + public Message toMessage(ObjectMapper objectMapper) { + String parametersJson; + try { + parametersJson = objectMapper.writeValueAsString(runContext.getParameters()); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + return MessageBuilder.withPayload(parametersJson) + .setHeader(RESULT_UUID_HEADER, resultUuid.toString()) + .setHeader(NETWORK_UUID_HEADER, runContext.getNetworkUuid().toString()) + .setHeader(VARIANT_ID_HEADER, runContext.getVariantId()) + .setHeader(HEADER_RECEIVER, runContext.getReceiver()) + .setHeader(HEADER_PROVIDER, runContext.getProvider()) + .setHeader(HEADER_USER_ID, runContext.getUserId()) + .setHeader(REPORT_UUID_HEADER, runContext.getReportContext().getReportId() != null ? runContext.getReportContext().getReportId().toString() : null) + .setHeader(REPORTER_ID_HEADER, runContext.getReportContext().getReportName()) + .setHeader(REPORT_TYPE_HEADER, runContext.getReportContext().getReportType()) + .copyHeaders(getSpecificMsgHeaders()) + .build(); + } + + public abstract Map getSpecificMsgHeaders(); } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index 3e079419..e8990459 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -10,42 +10,29 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.commons.PowsyblException; import com.powsybl.security.SecurityAnalysisParameters; +import org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext; import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.MessageBuilder; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext.*; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; /** * @author Geoffroy Jamgotchian */ -public class SecurityAnalysisResultContext { +public class SecurityAnalysisResultContext extends AbstractResultContext { public static final String CONTINGENCY_LIST_NAMES_HEADER = "contingencyListNames"; - private final UUID resultUuid; - - private final SecurityAnalysisRunContext runContext; - public SecurityAnalysisResultContext(UUID resultUuid, SecurityAnalysisRunContext runContext) { - this.resultUuid = Objects.requireNonNull(resultUuid); - this.runContext = Objects.requireNonNull(runContext); - } - - public UUID getResultUuid() { - return resultUuid; - } - - public SecurityAnalysisRunContext getRunContext() { - return runContext; + super(resultUuid, runContext); } private static List getHeaderList(MessageHeaders headers, String name) { @@ -96,24 +83,8 @@ public static SecurityAnalysisResultContext fromMessage(Message message, return new SecurityAnalysisResultContext(resultUuid, runContext); } - public Message toMessage(ObjectMapper objectMapper) { - String parametersJson; - try { - parametersJson = objectMapper.writeValueAsString(runContext.getParameters()); - } catch (JsonProcessingException e) { - throw new UncheckedIOException(e); - } - return MessageBuilder.withPayload(parametersJson) - .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) - .setHeader(NETWORK_UUID_HEADER, runContext.getNetworkUuid().toString()) - .setHeader(VARIANT_ID_HEADER, runContext.getVariantId()) - .setHeader(CONTINGENCY_LIST_NAMES_HEADER, String.join(",", runContext.getContingencyListNames())) - .setHeader(HEADER_RECEIVER, runContext.getReceiver()) - .setHeader(HEADER_USER_ID, runContext.getUserId()) - .setHeader(HEADER_PROVIDER, runContext.getProvider()) - .setHeader(REPORT_UUID_HEADER, runContext.getReportContext().getReportId()) - .setHeader(REPORTER_ID_HEADER, runContext.getReportContext().getReportName()) - .setHeader(REPORT_TYPE_HEADER, runContext.getReportContext().getReportType()) - .build(); + public Map getSpecificMsgHeaders() { + return Map.of( + CONTINGENCY_LIST_NAMES_HEADER, String.join(",", runContext.getContingencyListNames())); } } From a00fb8cc7f98b2dbc98c4eff411c0496e73cedb8 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 14 Mar 2024 16:13:00 +0100 Subject: [PATCH 04/12] CancelContext Signed-off-by: Mathieu DEHARBE --- .../computation/service/CancelContext.java | 49 ++++++++++++++ .../SecurityAnalysisCancelContext.java | 64 ------------------- .../SecurityAnalysisResultContext.java | 10 +-- .../service/SecurityAnalysisService.java | 6 +- .../SecurityAnalysisWorkerService.java | 16 ++++- 5 files changed, 66 insertions(+), 79 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java delete mode 100644 src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java new file mode 100644 index 00000000..271045e6 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import lombok.Getter; +import org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; + +import java.util.Objects; +import java.util.UUID; + +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; + +/** + * @author Anis Touri + */ +@Getter +public class CancelContext { + + private final UUID resultUuid; + + private final String receiver; + + public CancelContext(UUID resultUuid, String receiver) { + this.resultUuid = Objects.requireNonNull(resultUuid); + this.receiver = Objects.requireNonNull(receiver); + } + + public static CancelContext fromMessage(Message message) { + Objects.requireNonNull(message); + MessageHeaders headers = message.getHeaders(); + UUID resultUuid = UUID.fromString(SecurityAnalysisWorkerService.getNonNullHeader(headers, HEADER_RESULT_UUID)); + String receiver = (String) headers.get(HEADER_RECEIVER); + return new CancelContext(resultUuid, receiver); + } + + public Message toMessage() { + return MessageBuilder.withPayload("") + .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) + .setHeader(HEADER_RECEIVER, receiver) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java deleted file mode 100644 index 1dd22718..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2021, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.service; - -import com.powsybl.commons.PowsyblException; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.MessageBuilder; - -import java.util.Objects; -import java.util.UUID; - -import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_RESULT_UUID; -import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.HEADER_RECEIVER; - -/** - * @author Franck Lecuyer - */ -public class SecurityAnalysisCancelContext { - - private final UUID resultUuid; - - private final String receiver; - - public SecurityAnalysisCancelContext(UUID resultUuid, String receiver) { - this.resultUuid = Objects.requireNonNull(resultUuid); - this.receiver = Objects.requireNonNull(receiver); - } - - public UUID getResultUuid() { - return resultUuid; - } - - public String getReceiver() { - return receiver; - } - - private static String getNonNullHeader(MessageHeaders headers, String name) { - String header = (String) headers.get(name); - if (header == null) { - throw new PowsyblException("Header '" + name + "' not found"); - } - return header; - } - - public static SecurityAnalysisCancelContext fromMessage(Message message) { - Objects.requireNonNull(message); - MessageHeaders headers = message.getHeaders(); - UUID resultUuid = UUID.fromString(getNonNullHeader(headers, HEADER_RESULT_UUID)); - String receiver = (String) headers.get(HEADER_RECEIVER); - return new SecurityAnalysisCancelContext(resultUuid, receiver); - } - - public Message toMessage() { - return MessageBuilder.withPayload("") - .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) - .setHeader(HEADER_RECEIVER, receiver) - .build(); - } -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index e8990459..5cc3e496 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.powsybl.commons.PowsyblException; import com.powsybl.security.SecurityAnalysisParameters; import org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext; import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; @@ -24,6 +23,7 @@ import java.util.UUID; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService.getNonNullHeader; /** * @author Geoffroy Jamgotchian @@ -43,14 +43,6 @@ private static List getHeaderList(MessageHeaders headers, String name) { return Arrays.asList(header.split(",")); } - private static String getNonNullHeader(MessageHeaders headers, String name) { - String header = (String) headers.get(name); - if (header == null) { - throw new PowsyblException("Header '" + name + "' not found"); - } - return header; - } - public static SecurityAnalysisResultContext fromMessage(Message message, ObjectMapper objectMapper) { Objects.requireNonNull(message); MessageHeaders headers = message.getHeaders(); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index b7e9936b..bcf164c4 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.security.SecurityAnalysisProvider; import lombok.Getter; +import org.gridsuite.securityanalysis.server.computation.service.CancelContext; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.computation.service.UuidGeneratorService; import org.gridsuite.securityanalysis.server.dto.*; @@ -18,7 +19,6 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; /** * @author Geoffroy Jamgotchian @@ -78,12 +78,12 @@ public void setStatus(List resultUuids, SecurityAnalysisStatus status) { } public void stop(UUID resultUuid, String receiver) { - notificationService.sendCancelMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage()); + notificationService.sendCancelMessage(new CancelContext(resultUuid, receiver).toMessage()); } public List getProviders() { return SecurityAnalysisProvider.findAll().stream() .map(SecurityAnalysisProvider::getName) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index dc9e41c5..82507132 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -25,6 +25,7 @@ import com.powsybl.security.SecurityAnalysisResult; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.ws.commons.LogUtils; +import org.gridsuite.securityanalysis.server.computation.service.CancelContext; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; @@ -34,6 +35,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.web.server.ResponseStatusException; @@ -79,7 +81,7 @@ public class SecurityAnalysisWorkerService { private Map> futures = new ConcurrentHashMap<>(); - private Map cancelComputationRequests = new ConcurrentHashMap<>(); + private Map cancelComputationRequests = new ConcurrentHashMap<>(); private Set runRequests = Sets.newConcurrentHashSet(); @@ -171,7 +173,7 @@ private CompletableFuture runASAsync(SecurityAnalysisRun } } - private void cancelASAsync(SecurityAnalysisCancelContext cancelContext) { + private void cancelASAsync(CancelContext cancelContext) { lockRunAndCancelAS.lock(); try { cancelComputationRequests.put(cancelContext.getResultUuid(), cancelContext); @@ -312,6 +314,14 @@ public Consumer> consumeRun() { @Bean public Consumer> consumeCancel() { - return message -> cancelASAsync(SecurityAnalysisCancelContext.fromMessage(message)); + return message -> cancelASAsync(CancelContext.fromMessage(message)); + } + + public static String getNonNullHeader(MessageHeaders headers, String name) { + String header = (String) headers.get(name); + if (header == null) { + throw new PowsyblException("Header '" + name + "' not found"); + } + return header; } } From 78ce479d57b5a23a40e0412518881a4d601f0246 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 Mar 2024 11:15:47 +0100 Subject: [PATCH 05/12] AbstractComputationService Signed-off-by: Mathieu DEHARBE --- .../service/AbstractComputationService.java | 64 +++++++++++++++++++ .../computation/service/CancelContext.java | 4 +- .../SecurityAnalysisResultContext.java | 2 +- .../service/SecurityAnalysisService.java | 23 +------ .../SecurityAnalysisWorkerService.java | 9 --- 5 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java new file mode 100644 index 00000000..7a0289a7 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.PowsyblException; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.MessageHeaders; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @author Mathieu Deharbe run context specific to a computation, including parameters + */ +public abstract class AbstractComputationService { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractComputationService.class); + + protected ObjectMapper objectMapper; + protected NotificationService notificationService; + @Getter + protected String defaultProvider; + + protected UuidGeneratorService uuidGeneratorService; + + protected AbstractComputationService(NotificationService notificationService, + ObjectMapper objectMapper, + UuidGeneratorService uuidGeneratorService, + String defaultProvider) { + this.notificationService = Objects.requireNonNull(notificationService); + this.objectMapper = Objects.requireNonNull(objectMapper); + this.uuidGeneratorService = Objects.requireNonNull(uuidGeneratorService); + this.defaultProvider = Objects.requireNonNull(defaultProvider); + } + + public void stop(UUID resultUuid, String receiver) { + notificationService.sendCancelMessage(new CancelContext(resultUuid, receiver).toMessage()); + } + + public abstract List getProviders(); + + public abstract UUID runAndSaveResult(R runContext); + + public abstract void deleteResult(UUID resultUuid); + + public abstract void deleteResults(); + + public static String getNonNullHeader(MessageHeaders headers, String name) { + String header = (String) headers.get(name); + if (header == null) { + throw new PowsyblException("Header '" + name + "' not found"); + } + return header; + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java index 271045e6..a2cb43f2 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java @@ -7,7 +7,6 @@ package org.gridsuite.securityanalysis.server.computation.service; import lombok.Getter; -import org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -15,6 +14,7 @@ import java.util.Objects; import java.util.UUID; +import static org.gridsuite.securityanalysis.server.computation.service.AbstractComputationService.getNonNullHeader; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; /** @@ -35,7 +35,7 @@ public CancelContext(UUID resultUuid, String receiver) { public static CancelContext fromMessage(Message message) { Objects.requireNonNull(message); MessageHeaders headers = message.getHeaders(); - UUID resultUuid = UUID.fromString(SecurityAnalysisWorkerService.getNonNullHeader(headers, HEADER_RESULT_UUID)); + UUID resultUuid = UUID.fromString(getNonNullHeader(headers, HEADER_RESULT_UUID)); String receiver = (String) headers.get(HEADER_RECEIVER); return new CancelContext(resultUuid, receiver); } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index 5cc3e496..68ee9cca 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -22,8 +22,8 @@ import java.util.Objects; import java.util.UUID; +import static org.gridsuite.securityanalysis.server.computation.service.AbstractComputationService.getNonNullHeader; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; -import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService.getNonNullHeader; /** * @author Geoffroy Jamgotchian diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index bcf164c4..41f0b040 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java @@ -8,8 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.security.SecurityAnalysisProvider; -import lombok.Getter; -import org.gridsuite.securityanalysis.server.computation.service.CancelContext; +import org.gridsuite.securityanalysis.server.computation.service.AbstractComputationService; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.computation.service.UuidGeneratorService; import org.gridsuite.securityanalysis.server.dto.*; @@ -25,30 +24,18 @@ * @author Franck Lecuyer */ @Service -public class SecurityAnalysisService { +public class SecurityAnalysisService extends AbstractComputationService { public static final String COMPUTATION_TYPE = "Security analysis"; private final SecurityAnalysisResultService securityAnalysisResultService; - private final UuidGeneratorService uuidGeneratorService; - - private final NotificationService notificationService; - - private final ObjectMapper objectMapper; - - @Getter - private final String defaultProvider; - public SecurityAnalysisService(SecurityAnalysisResultService securityAnalysisResultService, UuidGeneratorService uuidGeneratorService, ObjectMapper objectMapper, NotificationService notificationService, @Value("${security-analysis.default-provider}") String defaultProvider) { + super(notificationService, objectMapper, uuidGeneratorService, defaultProvider); this.securityAnalysisResultService = Objects.requireNonNull(securityAnalysisResultService); - this.uuidGeneratorService = Objects.requireNonNull(uuidGeneratorService); - this.objectMapper = Objects.requireNonNull(objectMapper); - this.notificationService = Objects.requireNonNull(notificationService); - this.defaultProvider = Objects.requireNonNull(defaultProvider); } public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { @@ -77,10 +64,6 @@ public void setStatus(List resultUuids, SecurityAnalysisStatus status) { securityAnalysisResultService.insertStatus(resultUuids, status); } - public void stop(UUID resultUuid, String receiver) { - notificationService.sendCancelMessage(new CancelContext(resultUuid, receiver).toMessage()); - } - public List getProviders() { return SecurityAnalysisProvider.findAll().stream() .map(SecurityAnalysisProvider::getName) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 82507132..28c4f1be 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -35,7 +35,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.web.server.ResponseStatusException; @@ -316,12 +315,4 @@ public Consumer> consumeRun() { public Consumer> consumeCancel() { return message -> cancelASAsync(CancelContext.fromMessage(message)); } - - public static String getNonNullHeader(MessageHeaders headers, String name) { - String header = (String) headers.get(name); - if (header == null) { - throw new PowsyblException("Header '" + name + "' not found"); - } - return header; - } } From 191c69c8c8c752ff601c0e9c4fb354444d72249f Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 Mar 2024 11:23:16 +0100 Subject: [PATCH 06/12] ExecutionService Signed-off-by: Mathieu DEHARBE --- .../service/ExecutionService.java} | 22 +++++++++---------- .../SecurityAnalysisWorkerService.java | 9 ++++---- 2 files changed, 15 insertions(+), 16 deletions(-) rename src/main/java/org/gridsuite/securityanalysis/server/{service/SecurityAnalysisExecutionService.java => computation/service/ExecutionService.java} (77%) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisExecutionService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/ExecutionService.java similarity index 77% rename from src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisExecutionService.java rename to src/main/java/org/gridsuite/securityanalysis/server/computation/service/ExecutionService.java index bdcbca82..bcd62c1c 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisExecutionService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/ExecutionService.java @@ -5,20 +5,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package org.gridsuite.securityanalysis.server.service; +package org.gridsuite.securityanalysis.server.computation.service; import com.powsybl.computation.ComputationManager; import com.powsybl.computation.local.LocalComputationManager; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.Getter; import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; + +/** + * @author David Braquart + */ @Service -public class SecurityAnalysisExecutionService { +@Getter +public class ExecutionService { private ExecutorService executorService; @@ -35,12 +41,4 @@ private void postConstruct() { private void preDestroy() { executorService.shutdown(); } - - public ExecutorService getExecutorService() { - return executorService; - } - - public ComputationManager getLocalComputationManager() { - return computationManager; - } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 28c4f1be..bfa30eb3 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -27,6 +27,7 @@ import com.powsybl.ws.commons.LogUtils; import org.gridsuite.securityanalysis.server.computation.service.CancelContext; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; +import org.gridsuite.securityanalysis.server.computation.service.ExecutionService; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; @@ -88,13 +89,13 @@ public class SecurityAnalysisWorkerService { private Function securityAnalysisFactorySupplier; - private SecurityAnalysisExecutionService securityAnalysisExecutionService; + private ExecutionService executionService; private final SecurityAnalysisObserver securityAnalysisObserver; public SecurityAnalysisWorkerService(NetworkStoreService networkStoreService, ActionsService actionsService, ReportService reportService, SecurityAnalysisResultService resultRepository, ObjectMapper objectMapper, - SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, SecurityAnalysisExecutionService securityAnalysisExecutionService, + SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, ExecutionService executionService, SecurityAnalysisObserver securityAnalysisObserver) { this.networkStoreService = Objects.requireNonNull(networkStoreService); this.actionsService = Objects.requireNonNull(actionsService); @@ -102,7 +103,7 @@ public SecurityAnalysisWorkerService(NetworkStoreService networkStoreService, Ac this.securityAnalysisResultService = Objects.requireNonNull(resultRepository); this.objectMapper = Objects.requireNonNull(objectMapper); this.notificationService = Objects.requireNonNull(notificationService); - this.securityAnalysisExecutionService = Objects.requireNonNull(securityAnalysisExecutionService); + this.executionService = Objects.requireNonNull(executionService); this.securityAnalysisFactorySupplier = securityAnalysisRunnerSupplier::getRunner; this.securityAnalysisObserver = securityAnalysisObserver; } @@ -154,7 +155,7 @@ private CompletableFuture runASAsync(SecurityAnalysisRun variantId, n -> contingencies, context.getParameters(), - securityAnalysisExecutionService.getLocalComputationManager(), + executionService.getComputationManager(), LimitViolationFilter.load(), new DefaultLimitViolationDetector(), Collections.emptyList(), From 4788b67da0bb021198f2d05ac09da33e5a3781e2 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 Mar 2024 15:10:43 +0100 Subject: [PATCH 07/12] ReportService Signed-off-by: Mathieu DEHARBE --- .../computation/service/ReportService.java | 86 +++++++++++++++++++ .../server/service/ReportService.java | 72 ---------------- .../SecurityAnalysisWorkerService.java | 1 + .../SecurityAnalysisControllerTest.java | 2 +- .../server/service/ReportServiceTest.java | 1 + 5 files changed, 89 insertions(+), 73 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/ReportService.java delete mode 100644 src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/ReportService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/ReportService.java new file mode 100644 index 00000000..305e322e --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/ReportService.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.reporter.Reporter; +import com.powsybl.commons.reporter.ReporterModelJsonModule; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author Anis Touri + */ +@Service +public class ReportService { + + static final String REPORT_API_VERSION = "v1"; + private static final String DELIMITER = "/"; + private static final String QUERY_PARAM_REPORT_TYPE_FILTER = "reportTypeFilter"; + private static final String QUERY_PARAM_REPORT_THROW_ERROR = "errorOnReportNotFound"; + @Setter + private String reportServerBaseUri; + + private final RestTemplate restTemplate; + + private final ObjectMapper objectMapper; + + public ReportService(ObjectMapper objectMapper, + @Value("${gridsuite.services.report-server.base-uri:http://report-server/}") String reportServerBaseUri, + RestTemplate restTemplate) { + this.reportServerBaseUri = reportServerBaseUri; + this.objectMapper = objectMapper; + this.restTemplate = restTemplate; + ReporterModelJsonModule reporterModelJsonModule = new ReporterModelJsonModule(); + objectMapper.registerModule(reporterModelJsonModule); + } + + private String getReportServerURI() { + return this.reportServerBaseUri + DELIMITER + REPORT_API_VERSION + DELIMITER + "reports" + DELIMITER; + } + + public void sendReport(UUID reportUuid, Reporter reporter) { + Objects.requireNonNull(reportUuid); + + var path = UriComponentsBuilder.fromPath("{reportUuid}") + .buildAndExpand(reportUuid) + .toUriString(); + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + try { + restTemplate.exchange(getReportServerURI() + path, HttpMethod.PUT, new HttpEntity<>(objectMapper.writeValueAsString(reporter), headers), Reporter.class); + } catch (JsonProcessingException error) { + throw new PowsyblException("Error sending report", error); + } + } + + public void deleteReport(UUID reportUuid, String reportType) { + Objects.requireNonNull(reportUuid); + + var path = UriComponentsBuilder.fromPath("{reportUuid}") + .queryParam(QUERY_PARAM_REPORT_TYPE_FILTER, reportType) + .queryParam(QUERY_PARAM_REPORT_THROW_ERROR, false) + .buildAndExpand(reportUuid) + .toUriString(); + var headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + restTemplate.exchange(getReportServerURI() + path, HttpMethod.DELETE, new HttpEntity<>(headers), Void.class); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java deleted file mode 100644 index 238a01e0..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.service; - -import com.powsybl.commons.reporter.Reporter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import java.net.URI; -import java.util.Objects; -import java.util.UUID; - -/** - * @author Geoffroy Jamgotchian - */ -@Service -public class ReportService { - - static final String REPORT_API_VERSION = "v1"; - - private static final String DELIMITER = "/"; - private static final String QUERY_PARAM_REPORT_TYPE_FILTER = "reportTypeFilter"; - private static final String QUERY_PARAM_REPORT_THROW_ERROR = "errorOnReportNotFound"; - - private String baseUri; - - @Autowired - private RestTemplate restTemplate; - - @Autowired - public ReportService(@Value("${gridsuite.services.report-server.base-uri:http://report-server/}") String baseUri) { - this.baseUri = baseUri; - } - - public void setReportServiceBaseUri(String baseUri) { - this.baseUri = baseUri; - } - - public void sendReport(UUID reportUuid, Reporter reporter) { - Objects.requireNonNull(reportUuid); - - URI path = UriComponentsBuilder - .fromPath(DELIMITER + REPORT_API_VERSION + "/reports/{reportUuid}") - .build(reportUuid); - - restTemplate.put(baseUri + path, reporter); - } - - public void deleteReport(UUID reportUuid, String reportType) { - Objects.requireNonNull(reportUuid); - - var path = UriComponentsBuilder.fromPath(DELIMITER + REPORT_API_VERSION + "/reports/{reportUuid}") - .queryParam(QUERY_PARAM_REPORT_TYPE_FILTER, reportType) - .queryParam(QUERY_PARAM_REPORT_THROW_ERROR, false) - .buildAndExpand(reportUuid) - .toUriString(); - var headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - restTemplate.exchange(baseUri + path, HttpMethod.DELETE, new HttpEntity<>(headers), Void.class); - } -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index bfa30eb3..1a41ab96 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -28,6 +28,7 @@ import org.gridsuite.securityanalysis.server.computation.service.CancelContext; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.computation.service.ExecutionService; +import org.gridsuite.securityanalysis.server.computation.service.ReportService; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java index 5fc82887..951afe89 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java @@ -34,7 +34,7 @@ import org.gridsuite.securityanalysis.server.repositories.specifications.SpecificationUtils; import org.gridsuite.securityanalysis.server.service.ActionsService; import org.gridsuite.securityanalysis.server.service.LoadFlowService; -import org.gridsuite.securityanalysis.server.service.ReportService; +import org.gridsuite.securityanalysis.server.computation.service.ReportService; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService; import org.gridsuite.securityanalysis.server.computation.service.UuidGeneratorService; import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; diff --git a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java index 75446e82..6a8d3536 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java @@ -13,6 +13,7 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import org.gridsuite.securityanalysis.server.computation.service.ReportService; import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; import org.junit.After; import org.junit.Before; From f8a6eaec57e538b06b3f5fe2b716a3ca4c5a8ecd Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 Mar 2024 18:06:24 +0100 Subject: [PATCH 08/12] AbstractWorkerService Signed-off-by: Mathieu DEHARBE --- .../service/AbstractWorkerService.java | 108 ++++++++++++++++++ .../SecurityAnalysisWorkerService.java | 87 +++----------- .../server/service/ReportServiceTest.java | 2 +- 3 files changed, 125 insertions(+), 72 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java new file mode 100644 index 00000000..35e84539 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.network.store.client.NetworkStoreService; +import com.powsybl.network.store.client.PreloadingStrategy; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +/** + * @author Mathieu Deharbe powsybl Result class specific to the computation + * @param Run context specific to a computation, including parameters + * @param

powsybl and gridsuite Parameters specifics to the computation + */ +public abstract class AbstractWorkerService, P> { + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWorkerService.class); + + protected final Lock lockRunAndCancel = new ReentrantLock(); + protected final ObjectMapper objectMapper; + protected final Set runRequests = Sets.newConcurrentHashSet(); + protected final NetworkStoreService networkStoreService; + protected final ReportService reportService; + protected final ExecutionService executionService; + protected final NotificationService notificationService; + protected final AbstractComputationObserver observer; + protected final Map> futures = new ConcurrentHashMap<>(); + protected final Map cancelComputationRequests = new ConcurrentHashMap<>(); + + protected AbstractWorkerService(NetworkStoreService networkStoreService, + NotificationService notificationService, + ReportService reportService, + ExecutionService executionService, + AbstractComputationObserver observer, + ObjectMapper objectMapper) { + this.networkStoreService = networkStoreService; + this.notificationService = notificationService; + this.reportService = reportService; + this.executionService = executionService; + this.observer = observer; + this.objectMapper = objectMapper; + } + + protected Network getNetwork(AbstractComputationRunContext

runContext) { + Network network; + try { + UUID networkUuid = runContext.getNetworkUuid(); + String variantId = runContext.getVariantId(); + network = networkStoreService.getNetwork(networkUuid, PreloadingStrategy.ALL_COLLECTIONS_NEEDED_FOR_BUS_VIEW); + String variant = StringUtils.isBlank(variantId) ? VariantManagerConstants.INITIAL_VARIANT_ID : variantId; + network.getVariantManager().setWorkingVariant(variant); + } catch (PowsyblException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); + } + return network; + } + + protected abstract void cleanResultsAndPublishCancel(UUID resultUuid, String receiver); + + private void cancelAsync(CancelContext cancelContext) { + lockRunAndCancel.lock(); + try { + cancelComputationRequests.put(cancelContext.getResultUuid(), cancelContext); + + // find the completableFuture associated with result uuid + CompletableFuture future = futures.get(cancelContext.getResultUuid()); + if (future != null) { + future.cancel(true); // cancel computation in progress + } + cleanResultsAndPublishCancel(cancelContext.getResultUuid(), cancelContext.getReceiver()); + } finally { + lockRunAndCancel.unlock(); + } + } + + @Bean + public abstract Consumer> consumeRun(); + + @Bean + public Consumer> consumeCancel() { + return message -> cancelAsync(CancelContext.fromMessage(message)); + } + + protected abstract String getComputationType(); +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 1a41ab96..f8ee362d 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -7,7 +7,6 @@ package org.gridsuite.securityanalysis.server.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; import com.powsybl.commons.PowsyblException; import com.powsybl.commons.reporter.Report; import com.powsybl.commons.reporter.Reporter; @@ -21,19 +20,18 @@ import com.powsybl.network.store.client.PreloadingStrategy; import com.powsybl.security.LimitViolationFilter; import com.powsybl.security.SecurityAnalysis; +import com.powsybl.security.SecurityAnalysisParameters; import com.powsybl.security.SecurityAnalysisReport; import com.powsybl.security.SecurityAnalysisResult; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.ws.commons.LogUtils; -import org.gridsuite.securityanalysis.server.computation.service.CancelContext; +import org.gridsuite.securityanalysis.server.computation.service.AbstractWorkerService; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.computation.service.ExecutionService; import org.gridsuite.securityanalysis.server.computation.service.ReportService; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; @@ -44,14 +42,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.UUID; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; @@ -64,49 +58,22 @@ * @author Franck Lecuyer */ @Service -public class SecurityAnalysisWorkerService { - - private static final Logger LOGGER = LoggerFactory.getLogger(SecurityAnalysisWorkerService.class); - - private NetworkStoreService networkStoreService; +public class SecurityAnalysisWorkerService extends AbstractWorkerService { private ActionsService actionsService; - private ReportService reportService; - - private NotificationService notificationService; - private SecurityAnalysisResultService securityAnalysisResultService; - private ObjectMapper objectMapper; - - private Map> futures = new ConcurrentHashMap<>(); - - private Map cancelComputationRequests = new ConcurrentHashMap<>(); - - private Set runRequests = Sets.newConcurrentHashSet(); - - private Lock lockRunAndCancelAS = new ReentrantLock(); - private Function securityAnalysisFactorySupplier; - private ExecutionService executionService; - - private final SecurityAnalysisObserver securityAnalysisObserver; - public SecurityAnalysisWorkerService(NetworkStoreService networkStoreService, ActionsService actionsService, ReportService reportService, SecurityAnalysisResultService resultRepository, ObjectMapper objectMapper, SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, ExecutionService executionService, - SecurityAnalysisObserver securityAnalysisObserver) { - this.networkStoreService = Objects.requireNonNull(networkStoreService); + SecurityAnalysisObserver observer) { + super(networkStoreService, notificationService, reportService, executionService, observer, objectMapper); this.actionsService = Objects.requireNonNull(actionsService); - this.reportService = Objects.requireNonNull(reportService); this.securityAnalysisResultService = Objects.requireNonNull(resultRepository); - this.objectMapper = Objects.requireNonNull(objectMapper); - this.notificationService = Objects.requireNonNull(notificationService); - this.executionService = Objects.requireNonNull(executionService); this.securityAnalysisFactorySupplier = securityAnalysisRunnerSupplier::getRunner; - this.securityAnalysisObserver = securityAnalysisObserver; } public void setSecurityAnalysisFactorySupplier(Function securityAnalysisFactorySupplier) { @@ -133,7 +100,7 @@ public SecurityAnalysisResult run(SecurityAnalysisRunContext context) { } } - //@Override // TODO réOverride + @Override protected String getComputationType() { return COMPUTATION_TYPE; } @@ -144,7 +111,7 @@ private CompletableFuture runASAsync(SecurityAnalysisRun List contingencies, Reporter reporter, UUID resultUuid) { - lockRunAndCancelAS.lock(); + lockRunAndCancel.lock(); try { if (resultUuid != null && cancelComputationRequests.get(resultUuid) != null) { return null; @@ -170,28 +137,11 @@ private CompletableFuture runASAsync(SecurityAnalysisRun } return future; } finally { - lockRunAndCancelAS.unlock(); + lockRunAndCancel.unlock(); } } - private void cancelASAsync(CancelContext cancelContext) { - lockRunAndCancelAS.lock(); - try { - cancelComputationRequests.put(cancelContext.getResultUuid(), cancelContext); - - // find the completableFuture associated with result uuid - CompletableFuture future = futures.get(cancelContext.getResultUuid()); - if (future != null) { - future.cancel(true); // cancel computation in progress - - cleanASResultsAndPublishCancel(cancelContext.getResultUuid(), cancelContext.getReceiver()); - } - } finally { - lockRunAndCancelAS.unlock(); - } - } - - private void cleanASResultsAndPublishCancel(UUID resultUuid, String receiver) { + protected void cleanResultsAndPublishCancel(UUID resultUuid, String receiver) { securityAnalysisResultService.delete(resultUuid); notificationService.publishStop(resultUuid, receiver, getComputationType()); LOGGER.info(getCancelMessage(getComputationType()) + " (resultUuid='{}')", resultUuid); @@ -202,9 +152,9 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu LOGGER.info("Run security analysis on contingency lists: {}", context.getContingencyListNames().stream().map(LogUtils::sanitizeParam).toList()); - Network network = securityAnalysisObserver.observe("network.load", context, () -> getNetwork(context.getNetworkUuid())); + Network network = observer.observe("network.load", context, () -> getNetwork(context.getNetworkUuid())); - List contingencies = securityAnalysisObserver.observe("contingencies.fetch", context, + List contingencies = observer.observe("contingencies.fetch", context, () -> context.getContingencyListNames().stream() .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, context.getNetworkUuid(), context.getVariantId())) .flatMap(List::stream) @@ -223,7 +173,7 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu rootReporter.set(new ReporterModel(rootReporterId, rootReporterId)); reporter = rootReporter.get().createSubReporter(reportType, reportType + " (${providerToUse})", "providerToUse", securityAnalysisRunner.getName()); // Delete any previous SA computation logs - securityAnalysisObserver.observe("report.delete", + observer.observe("report.delete", context, () -> reportService.deleteReport(context.getReportContext().getReportId(), reportType)); } @@ -237,7 +187,7 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu reporter, resultUuid); - SecurityAnalysisResult result = future == null ? null : securityAnalysisObserver.observeRun("run", context, future::get); + SecurityAnalysisResult result = future == null ? null : observer.observeRun("run", context, future::get); if (context.getReportContext().getReportId() != null) { List notFoundElementReports = new ArrayList<>(); contingencies.stream() @@ -256,7 +206,7 @@ private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resu "Elements not found"); notFoundElementReports.forEach(elementNotFoundSubReporter::report); } - securityAnalysisObserver.observe("report.send", + observer.observe("report.send", context, () -> reportService.sendReport(context.getReportContext().getReportId(), rootReporter.get())); } return result; @@ -275,7 +225,7 @@ public Consumer> consumeRun() { long nanoTime = System.nanoTime(); LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); - securityAnalysisObserver.observe("results.save", resultContext.getRunContext(), () -> securityAnalysisResultService.insert( + observer.observe("results.save", resultContext.getRunContext(), () -> securityAnalysisResultService.insert( resultContext.getResultUuid(), result, result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED @@ -290,7 +240,7 @@ public Consumer> consumeRun() { LOGGER.info("Security analysis complete (resultUuid='{}')", resultContext.getResultUuid()); } else { // result not available : stop computation request if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { - cleanASResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); + cleanResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); } } } catch (InterruptedException e) { @@ -312,9 +262,4 @@ public Consumer> consumeRun() { } }; } - - @Bean - public Consumer> consumeCancel() { - return message -> cancelASAsync(CancelContext.fromMessage(message)); - } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java index 6a8d3536..1250722d 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java @@ -53,7 +53,7 @@ public class ReportServiceTest { @Before public void setUp() throws IOException { String mockServerUri = initMockWebServer(); - reportService.setReportServiceBaseUri(mockServerUri); + reportService.setReportServerBaseUri(mockServerUri); } @After From c609437729b478a77989f86b4e03f3c8bbd5a16c Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Wed, 20 Mar 2024 12:32:42 +0100 Subject: [PATCH 09/12] AbstractComputationResultService and ContextUtils Signed-off-by: Mathieu DEHARBE --- .../AbstractComputationResultService.java | 25 +++++++++++++ .../service/AbstractComputationService.java | 29 +++++++++------ .../service/AbstractWorkerService.java | 17 +++++++-- .../computation/service/CancelContext.java | 4 +-- .../service/NotificationService.java | 17 ++------- .../computation/utils/ContextUtils.java | 36 +++++++++++++++++++ .../SecurityAnalysisResultContext.java | 2 +- .../SecurityAnalysisResultService.java | 3 +- .../service/SecurityAnalysisService.java | 23 ++---------- .../SecurityAnalysisWorkerService.java | 20 +++-------- 10 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationResultService.java create mode 100644 src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ContextUtils.java diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationResultService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationResultService.java new file mode 100644 index 00000000..ea3035ce --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationResultService.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.service; + +import java.util.List; +import java.util.UUID; + +/** + * @author Mathieu Deharbe status specific to the computation + */ +public abstract class AbstractComputationResultService { + + public abstract void insertStatus(List resultUuids, S status); + + public abstract void delete(UUID resultUuid); + + public abstract void deleteAll(); + + public abstract S findStatus(UUID resultUuid); +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java index 7a0289a7..55911d75 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java @@ -7,11 +7,9 @@ package org.gridsuite.securityanalysis.server.computation.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.powsybl.commons.PowsyblException; import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.messaging.MessageHeaders; import java.util.List; import java.util.Objects; @@ -20,8 +18,10 @@ /** * @author Mathieu Deharbe run context specific to a computation, including parameters + * @param run service specific to a computation + * @param enum status specific to a computation */ -public abstract class AbstractComputationService { +public abstract class AbstractComputationService, S> { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractComputationService.class); @@ -31,8 +31,10 @@ public abstract class AbstractComputationService { protected String defaultProvider; protected UuidGeneratorService uuidGeneratorService; + protected T resultService; protected AbstractComputationService(NotificationService notificationService, + T resultService, ObjectMapper objectMapper, UuidGeneratorService uuidGeneratorService, String defaultProvider) { @@ -40,6 +42,7 @@ protected AbstractComputationService(NotificationService notificationService, this.objectMapper = Objects.requireNonNull(objectMapper); this.uuidGeneratorService = Objects.requireNonNull(uuidGeneratorService); this.defaultProvider = Objects.requireNonNull(defaultProvider); + this.resultService = Objects.requireNonNull(resultService); } public void stop(UUID resultUuid, String receiver) { @@ -50,15 +53,19 @@ public void stop(UUID resultUuid, String receiver) { public abstract UUID runAndSaveResult(R runContext); - public abstract void deleteResult(UUID resultUuid); + public void setStatus(List resultUuids, S status) { + resultService.insertStatus(resultUuids, status); + } + + public void deleteResult(UUID resultUuid) { + resultService.delete(resultUuid); + } - public abstract void deleteResults(); + public void deleteResults() { + resultService.deleteAll(); + } - public static String getNonNullHeader(MessageHeaders headers, String name) { - String header = (String) headers.get(name); - if (header == null) { - throw new PowsyblException("Header '" + name + "' not found"); - } - return header; + public S getStatus(UUID resultUuid) { + return resultService.findStatus(resultUuid); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java index 35e84539..49ed22dc 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -22,6 +22,7 @@ import org.springframework.web.server.ResponseStatusException; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -35,8 +36,9 @@ * @param powsybl Result class specific to the computation * @param Run context specific to a computation, including parameters * @param

powsybl and gridsuite Parameters specifics to the computation + * @param result service specific to the computation */ -public abstract class AbstractWorkerService, P> { +public abstract class AbstractWorkerService, P, T extends AbstractComputationResultService> { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWorkerService.class); protected final Lock lockRunAndCancel = new ReentrantLock(); @@ -49,16 +51,19 @@ public abstract class AbstractWorkerService observer; protected final Map> futures = new ConcurrentHashMap<>(); protected final Map cancelComputationRequests = new ConcurrentHashMap<>(); + protected final T resultService; protected AbstractWorkerService(NetworkStoreService networkStoreService, NotificationService notificationService, ReportService reportService, + T resultService, ExecutionService executionService, AbstractComputationObserver observer, ObjectMapper objectMapper) { this.networkStoreService = networkStoreService; this.notificationService = notificationService; this.reportService = reportService; + this.resultService = Objects.requireNonNull(resultService); this.executionService = executionService; this.observer = observer; this.objectMapper = objectMapper; @@ -78,7 +83,15 @@ protected Network getNetwork(AbstractComputationRunContext

runContext) { return network; } - protected abstract void cleanResultsAndPublishCancel(UUID resultUuid, String receiver); + protected void cleanResultsAndPublishCancel(UUID resultUuid, String receiver) { + resultService.delete(resultUuid); + notificationService.publishStop(resultUuid, receiver, getComputationType()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("{} (resultUuid='{}')", + NotificationService.getCancelMessage(getComputationType()), + resultUuid); + } + } private void cancelAsync(CancelContext cancelContext) { lockRunAndCancel.lock(); diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java index a2cb43f2..c2d9ba29 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java @@ -7,6 +7,7 @@ package org.gridsuite.securityanalysis.server.computation.service; import lombok.Getter; +import org.gridsuite.securityanalysis.server.computation.utils.ContextUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; @@ -14,7 +15,6 @@ import java.util.Objects; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.computation.service.AbstractComputationService.getNonNullHeader; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; /** @@ -35,7 +35,7 @@ public CancelContext(UUID resultUuid, String receiver) { public static CancelContext fromMessage(Message message) { Objects.requireNonNull(message); MessageHeaders headers = message.getHeaders(); - UUID resultUuid = UUID.fromString(getNonNullHeader(headers, HEADER_RESULT_UUID)); + UUID resultUuid = UUID.fromString(ContextUtils.getNonNullHeader(headers, HEADER_RESULT_UUID)); String receiver = (String) headers.get(HEADER_RECEIVER); return new CancelContext(resultUuid, receiver); } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java index 3ba7a8f2..e94be104 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java @@ -6,6 +6,7 @@ */ package org.gridsuite.securityanalysis.server.computation.service; +import org.gridsuite.securityanalysis.server.computation.utils.ContextUtils; import org.gridsuite.securityanalysis.server.computation.utils.annotations.PostCompletion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +43,6 @@ public class NotificationService { public static final String SENDING_MESSAGE = "Sending message : {}"; - public static final int MSG_MAX_LENGTH = 256; - private final StreamBridge publisher; @Autowired @@ -90,7 +89,7 @@ public void publishFail(UUID resultUuid, String receiver, String causeMessage, S .withPayload("") .setHeader(HEADER_RESULT_UUID, resultUuid.toString()) .setHeader(HEADER_RECEIVER, receiver) - .setHeader(HEADER_MESSAGE, shortenMessage( + .setHeader(HEADER_MESSAGE, ContextUtils.shortenMessage( getFailedMessage(computationLabel) + " : " + causeMessage)) .setHeader(HEADER_USER_ID, userId) .build(); @@ -105,16 +104,4 @@ public static String getCancelMessage(String computationLabel) { public static String getFailedMessage(String computationLabel) { return computationLabel + " has failed"; } - - // prevent the message from being too long for rabbitmq - // the beginning and ending are both kept, it should make it easier to identify - public String shortenMessage(String msg) { - if (msg == null) { - return msg; - } - - return msg.length() > MSG_MAX_LENGTH ? - msg.substring(0, MSG_MAX_LENGTH / 2) + " ... " + msg.substring(msg.length() - MSG_MAX_LENGTH / 2, msg.length() - 1) - : msg; - } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ContextUtils.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ContextUtils.java new file mode 100644 index 00000000..7ce3e1eb --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/utils/ContextUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.computation.utils; + +import com.powsybl.commons.PowsyblException; +import org.springframework.messaging.MessageHeaders; + +public final class ContextUtils { + public static final int MSG_MAX_LENGTH = 256; + + private ContextUtils() { } + + public static String getNonNullHeader(MessageHeaders headers, String name) { + String header = (String) headers.get(name); + if (header == null) { + throw new PowsyblException("Header '" + name + "' not found"); + } + return header; + } + + // prevent the message from being too long for rabbitmq + // the beginning and ending are both kept, it should make it easier to identify + public static String shortenMessage(String msg) { + if (msg == null) { + return null; + } + + return msg.length() > MSG_MAX_LENGTH ? + msg.substring(0, MSG_MAX_LENGTH / 2) + " ... " + msg.substring(msg.length() - MSG_MAX_LENGTH / 2, msg.length() - 1) + : msg; + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java index 68ee9cca..fdef6aac 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -22,8 +22,8 @@ import java.util.Objects; import java.util.UUID; -import static org.gridsuite.securityanalysis.server.computation.service.AbstractComputationService.getNonNullHeader; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.computation.utils.ContextUtils.getNonNullHeader; /** * @author Geoffroy Jamgotchian diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java index 3c8bc11e..3b7a8038 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.security.SecurityAnalysisResult; +import org.gridsuite.securityanalysis.server.computation.service.AbstractComputationResultService; import org.gridsuite.securityanalysis.server.dto.*; import org.gridsuite.securityanalysis.server.entities.*; import org.gridsuite.securityanalysis.server.repositories.*; @@ -36,7 +37,7 @@ * @author Geoffroy Jamgotchian */ @Service -public class SecurityAnalysisResultService { +public class SecurityAnalysisResultService extends AbstractComputationResultService { private static final Logger LOGGER = LoggerFactory.getLogger(SecurityAnalysisResultService.class); private final SecurityAnalysisResultRepository securityAnalysisResultRepository; private final ContingencyRepository contingencyRepository; diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index 41f0b040..27ca79a2 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java @@ -24,18 +24,15 @@ * @author Franck Lecuyer */ @Service -public class SecurityAnalysisService extends AbstractComputationService { +public class SecurityAnalysisService extends AbstractComputationService { public static final String COMPUTATION_TYPE = "Security analysis"; - private final SecurityAnalysisResultService securityAnalysisResultService; - public SecurityAnalysisService(SecurityAnalysisResultService securityAnalysisResultService, UuidGeneratorService uuidGeneratorService, ObjectMapper objectMapper, NotificationService notificationService, @Value("${security-analysis.default-provider}") String defaultProvider) { - super(notificationService, objectMapper, uuidGeneratorService, defaultProvider); - this.securityAnalysisResultService = Objects.requireNonNull(securityAnalysisResultService); + super(notificationService, securityAnalysisResultService, objectMapper, uuidGeneratorService, defaultProvider); } public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { @@ -48,22 +45,6 @@ public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { return resultUuid; } - public void deleteResult(UUID resultUuid) { - securityAnalysisResultService.delete(resultUuid); - } - - public void deleteResults() { - securityAnalysisResultService.deleteAll(); - } - - public SecurityAnalysisStatus getStatus(UUID resultUuid) { - return securityAnalysisResultService.findStatus(resultUuid); - } - - public void setStatus(List resultUuids, SecurityAnalysisStatus status) { - securityAnalysisResultService.insertStatus(resultUuids, status); - } - public List getProviders() { return SecurityAnalysisProvider.findAll().stream() .map(SecurityAnalysisProvider::getName) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index f8ee362d..942ba1e5 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -49,7 +49,6 @@ import java.util.function.Consumer; import java.util.function.Function; -import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getCancelMessage; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getFailedMessage; import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisService.COMPUTATION_TYPE; @@ -58,21 +57,18 @@ * @author Franck Lecuyer */ @Service -public class SecurityAnalysisWorkerService extends AbstractWorkerService { +public class SecurityAnalysisWorkerService extends AbstractWorkerService { private ActionsService actionsService; - private SecurityAnalysisResultService securityAnalysisResultService; - private Function securityAnalysisFactorySupplier; public SecurityAnalysisWorkerService(NetworkStoreService networkStoreService, ActionsService actionsService, ReportService reportService, - SecurityAnalysisResultService resultRepository, ObjectMapper objectMapper, + SecurityAnalysisResultService resultService, ObjectMapper objectMapper, SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, ExecutionService executionService, SecurityAnalysisObserver observer) { - super(networkStoreService, notificationService, reportService, executionService, observer, objectMapper); + super(networkStoreService, notificationService, reportService, resultService, executionService, observer, objectMapper); this.actionsService = Objects.requireNonNull(actionsService); - this.securityAnalysisResultService = Objects.requireNonNull(resultRepository); this.securityAnalysisFactorySupplier = securityAnalysisRunnerSupplier::getRunner; } @@ -141,12 +137,6 @@ private CompletableFuture runASAsync(SecurityAnalysisRun } } - protected void cleanResultsAndPublishCancel(UUID resultUuid, String receiver) { - securityAnalysisResultService.delete(resultUuid); - notificationService.publishStop(resultUuid, receiver, getComputationType()); - LOGGER.info(getCancelMessage(getComputationType()) + " (resultUuid='{}')", resultUuid); - } - private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resultUuid) throws Exception { Objects.requireNonNull(context); @@ -225,7 +215,7 @@ public Consumer> consumeRun() { long nanoTime = System.nanoTime(); LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); - observer.observe("results.save", resultContext.getRunContext(), () -> securityAnalysisResultService.insert( + observer.observe("results.save", resultContext.getRunContext(), () -> resultService.insert( resultContext.getResultUuid(), result, result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED @@ -253,7 +243,7 @@ public Consumer> consumeRun() { e.getMessage(), resultContext.getRunContext().getUserId(), getComputationType()); - securityAnalysisResultService.delete(resultContext.getResultUuid()); + resultService.delete(resultContext.getResultUuid()); } } finally { futures.remove(resultContext.getResultUuid()); From 5777688b850d14b6abaf79df08ebaba5ba058f8b Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Wed, 20 Mar 2024 18:40:51 +0100 Subject: [PATCH 10/12] much more functions in AbstractWorkerService Signed-off-by: Mathieu DEHARBE --- .../service/AbstractWorkerService.java | 135 +++++++++- .../service/SecurityAnalysisRunContext.java | 4 + .../SecurityAnalysisWorkerService.java | 230 ++++++------------ 3 files changed, 202 insertions(+), 167 deletions(-) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java index 49ed22dc..c081de01 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.reporter.Reporter; +import com.powsybl.commons.reporter.ReporterModel; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VariantManagerConstants; import com.powsybl.network.store.client.NetworkStoreService; @@ -22,11 +24,13 @@ import org.springframework.web.server.ResponseStatusException; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -63,18 +67,20 @@ protected AbstractWorkerService(NetworkStoreService networkStoreService, this.networkStoreService = networkStoreService; this.notificationService = notificationService; this.reportService = reportService; - this.resultService = Objects.requireNonNull(resultService); + this.resultService = resultService; this.executionService = executionService; this.observer = observer; this.objectMapper = objectMapper; } - protected Network getNetwork(AbstractComputationRunContext

runContext) { + public PreloadingStrategy getNetworkPreloadingStrategy() { + return PreloadingStrategy.COLLECTION; + } + + protected Network getNetwork(UUID networkUuid, String variantId) { Network network; try { - UUID networkUuid = runContext.getNetworkUuid(); - String variantId = runContext.getVariantId(); - network = networkStoreService.getNetwork(networkUuid, PreloadingStrategy.ALL_COLLECTIONS_NEEDED_FOR_BUS_VIEW); + network = networkStoreService.getNetwork(networkUuid, getNetworkPreloadingStrategy()); String variant = StringUtils.isBlank(variantId) ? VariantManagerConstants.INITIAL_VARIANT_ID : variantId; network.getVariantManager().setWorkingVariant(variant); } catch (PowsyblException e) { @@ -109,13 +115,128 @@ private void cancelAsync(CancelContext cancelContext) { } } + protected abstract AbstractResultContext fromMessage(Message message); + @Bean - public abstract Consumer> consumeRun(); + public Consumer> consumeRun() { + return message -> { + AbstractResultContext resultContext = fromMessage(message); + try { + runRequests.add(resultContext.getResultUuid()); + AtomicReference startTime = new AtomicReference<>(); + startTime.set(System.nanoTime()); + + S result = run(resultContext.getRunContext(), resultContext.getResultUuid()); + + long nanoTime = System.nanoTime(); + LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); + + Network network = getNetwork(resultContext.getRunContext().getNetworkUuid(), + resultContext.getRunContext().getVariantId()); + observer.observe("results.save", resultContext.getRunContext(), () -> saveResult(network, resultContext, result)); + + long finalNanoTime = System.nanoTime(); + LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); + + if (result != null) { // result available + notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); + LOGGER.info("{} complete (resultUuid='{}')", getComputationType(), resultContext.getResultUuid()); + } else { // result not available : stop computation request + if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { + cleanResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + if (!(e instanceof CancellationException)) { + LOGGER.error(NotificationService.getFailedMessage(getComputationType()), e); + notificationService.publishFail( + resultContext.getResultUuid(), resultContext.getRunContext().getReceiver(), + e.getMessage(), resultContext.getRunContext().getUserId(), getComputationType()); + resultService.delete(resultContext.getResultUuid()); + } + } finally { + futures.remove(resultContext.getResultUuid()); + cancelComputationRequests.remove(resultContext.getResultUuid()); + runRequests.remove(resultContext.getResultUuid()); + } + }; + } @Bean public Consumer> consumeCancel() { return message -> cancelAsync(CancelContext.fromMessage(message)); } + protected abstract void saveResult(Network network, AbstractResultContext resultContext, S result); + + protected void logOnRun(R runContext) { + LOGGER.info("Run {} computation ...", getComputationType()); + } + + /** + * if this computation needs extra run data, add them to the run context here + */ + protected void enrichRunContext(R runContext) { } + + /** + * if this computation needs extra report operations, override this function and do them here + */ + protected void reportSpecificOperations(R runContext, Reporter reporter) { } + + protected S run(R runContext, UUID resultUuid) throws Exception { + logOnRun(runContext); + + enrichRunContext(runContext); + + String provider = runContext.getProvider(); + AtomicReference rootReporter = new AtomicReference<>(Reporter.NO_OP); + Reporter reporter = Reporter.NO_OP; + + if (runContext.getReportContext().getReportId() != null) { + final String reportType = runContext.getReportContext().getReportType(); + String rootReporterId = runContext.getReportContext().getReportName() == null ? reportType : runContext.getReportContext().getReportName() + "@" + reportType; + rootReporter.set(new ReporterModel(rootReporterId, rootReporterId)); + reporter = rootReporter.get().createSubReporter(reportType, String.format("%s (%s)", reportType, provider), "providerToUse", provider); + // Delete any previous computation logs + observer.observe("report.delete", + runContext, () -> reportService.deleteReport(runContext.getReportContext().getReportId(), reportType)); + } + + Network network = getNetwork(runContext.getNetworkUuid(), runContext.getVariantId()); + CompletableFuture future = runAsync(network, runContext, provider, reporter, resultUuid); + + S result = future == null ? null : observer.observeRun("run", runContext, future::get); + if (runContext.getReportContext().getReportId() != null) { + reportSpecificOperations(runContext, reporter); + observer.observe("report.send", runContext, () -> reportService.sendReport(runContext.getReportContext().getReportId(), rootReporter.get())); + } + return result; + } + + protected CompletableFuture runAsync( + Network network, + R runContext, + String provider, + Reporter reporter, + UUID resultUuid) { + lockRunAndCancel.lock(); + try { + if (resultUuid != null && cancelComputationRequests.get(resultUuid) != null) { + return null; + } + CompletableFuture future = getCompletableFuture(network, runContext, provider, reporter); + if (resultUuid != null) { + futures.put(resultUuid, future); + } + return future; + } finally { + lockRunAndCancel.unlock(); + } + } + protected abstract String getComputationType(); + + protected abstract CompletableFuture getCompletableFuture(Network network, R runContext, String provider, Reporter reporter); } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java index b44e005c..49c603b6 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java @@ -12,8 +12,10 @@ import com.powsybl.loadflow.LoadFlowProvider; import com.powsybl.security.SecurityAnalysisParameters; import lombok.Getter; +import lombok.Setter; import org.gridsuite.securityanalysis.server.computation.service.AbstractComputationRunContext; import org.gridsuite.securityanalysis.server.computation.utils.ReportContext; +import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.LoadFlowParametersValues; import java.util.List; @@ -27,6 +29,8 @@ public class SecurityAnalysisRunContext extends AbstractComputationRunContext { private final List contingencyListNames; + @Setter + private List contingencies; public SecurityAnalysisRunContext(UUID networkUuid, String variantId, List contingencyListNames, String receiver, String provider, SecurityAnalysisParameters parameters, LoadFlowParametersValues loadFlowParametersValues, diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 942ba1e5..55d57325 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -7,17 +7,14 @@ package org.gridsuite.securityanalysis.server.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.powsybl.commons.PowsyblException; import com.powsybl.commons.reporter.Report; import com.powsybl.commons.reporter.Reporter; -import com.powsybl.commons.reporter.ReporterModel; import com.powsybl.commons.reporter.TypedValue; import com.powsybl.contingency.Contingency; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VariantManagerConstants; import com.powsybl.loadflow.LoadFlowResult; import com.powsybl.network.store.client.NetworkStoreService; -import com.powsybl.network.store.client.PreloadingStrategy; import com.powsybl.security.LimitViolationFilter; import com.powsybl.security.SecurityAnalysis; import com.powsybl.security.SecurityAnalysisParameters; @@ -25,6 +22,7 @@ import com.powsybl.security.SecurityAnalysisResult; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.ws.commons.LogUtils; +import org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext; import org.gridsuite.securityanalysis.server.computation.service.AbstractWorkerService; import org.gridsuite.securityanalysis.server.computation.service.NotificationService; import org.gridsuite.securityanalysis.server.computation.service.ExecutionService; @@ -32,21 +30,15 @@ import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpStatus; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; import java.util.function.Function; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getFailedMessage; @@ -59,7 +51,7 @@ @Service public class SecurityAnalysisWorkerService extends AbstractWorkerService { - private ActionsService actionsService; + private final ActionsService actionsService; private Function securityAnalysisFactorySupplier; @@ -76,14 +68,6 @@ public void setSecurityAnalysisFactorySupplier(Function runASAsync(SecurityAnalysisRunContext context, - SecurityAnalysis.Runner securityAnalysisRunner, - Network network, - List contingencies, - Reporter reporter, - UUID resultUuid) { - lockRunAndCancel.lock(); - try { - if (resultUuid != null && cancelComputationRequests.get(resultUuid) != null) { - return null; - } - String variantId = context.getVariantId() != null ? context.getVariantId() : VariantManagerConstants.INITIAL_VARIANT_ID; - - CompletableFuture future = securityAnalysisRunner.runAsync( - network, - variantId, - n -> contingencies, - context.getParameters(), - executionService.getComputationManager(), - LimitViolationFilter.load(), - new DefaultLimitViolationDetector(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - reporter) + @Override + protected CompletableFuture getCompletableFuture(Network network, SecurityAnalysisRunContext runContext, String provider, Reporter reporter) { + SecurityAnalysis.Runner securityAnalysisRunner = securityAnalysisFactorySupplier.apply(provider); + String variantId = runContext.getVariantId() != null ? runContext.getVariantId() : VariantManagerConstants.INITIAL_VARIANT_ID; + + List contingencies = runContext.getContingencies().stream() + .map(ContingencyInfos::getContingency) + .filter(Objects::nonNull) + .toList(); + + return securityAnalysisRunner.runAsync( + network, + variantId, + n -> contingencies, + runContext.getParameters(), + executionService.getComputationManager(), + LimitViolationFilter.load(), + new DefaultLimitViolationDetector(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + reporter) .thenApply(SecurityAnalysisReport::getResult); - if (resultUuid != null) { - futures.put(resultUuid, future); - } - return future; - } finally { - lockRunAndCancel.unlock(); - } } - private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resultUuid) throws Exception { - Objects.requireNonNull(context); - - LOGGER.info("Run security analysis on contingency lists: {}", context.getContingencyListNames().stream().map(LogUtils::sanitizeParam).toList()); - - Network network = observer.observe("network.load", context, () -> getNetwork(context.getNetworkUuid())); - - List contingencies = observer.observe("contingencies.fetch", context, - () -> context.getContingencyListNames().stream() - .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, context.getNetworkUuid(), context.getVariantId())) - .flatMap(List::stream) - .toList()); - - SecurityAnalysis.Runner securityAnalysisRunner = securityAnalysisFactorySupplier.apply(context.getProvider()); - - AtomicReference rootReporter = new AtomicReference<>(Reporter.NO_OP); - Reporter reporter = Reporter.NO_OP; - - if (context.getReportContext().getReportId() != null) { - final String reportType = context.getReportContext().getReportType(); - String rootReporterId = context.getReportContext().getReportName() == null ? - reportType : - context.getReportContext().getReportName() + "@" + reportType; - rootReporter.set(new ReporterModel(rootReporterId, rootReporterId)); - reporter = rootReporter.get().createSubReporter(reportType, reportType + " (${providerToUse})", "providerToUse", securityAnalysisRunner.getName()); - // Delete any previous SA computation logs - observer.observe("report.delete", - context, () -> reportService.deleteReport(context.getReportContext().getReportId(), reportType)); + @Override + protected void reportSpecificOperations(SecurityAnalysisRunContext runContext, Reporter reporter) { + List notFoundElementReports = new ArrayList<>(); + runContext.getContingencies().stream() + .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) + .forEach(contingencyInfos -> { + String elementsIds = String.join(", ", contingencyInfos.getNotFoundElements()); + notFoundElementReports.add(Report.builder() + .withKey("contingencyElementNotFound_" + contingencyInfos.getId() + notFoundElementReports.size()) + .withDefaultMessage(String.format("Cannot find the following equipments %s in contingency %s", elementsIds, contingencyInfos.getId())) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + }); + if (!CollectionUtils.isEmpty(notFoundElementReports)) { + Reporter elementNotFoundSubReporter = reporter.createSubReporter( + runContext.getReportContext().getReportId().toString() + "notFoundElements", + "Elements not found"); + notFoundElementReports.forEach(elementNotFoundSubReporter::report); } + } - CompletableFuture future = runASAsync(context, - securityAnalysisRunner, - network, - contingencies.stream() - .map(ContingencyInfos::getContingency) - .filter(Objects::nonNull) - .toList(), - reporter, - resultUuid); - - SecurityAnalysisResult result = future == null ? null : observer.observeRun("run", context, future::get); - if (context.getReportContext().getReportId() != null) { - List notFoundElementReports = new ArrayList<>(); - contingencies.stream() - .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) - .forEach(contingencyInfos -> { - String elementsIds = String.join(", ", contingencyInfos.getNotFoundElements()); - notFoundElementReports.add(Report.builder() - .withKey("contingencyElementNotFound_" + contingencyInfos.getId() + notFoundElementReports.size()) - .withDefaultMessage(String.format("Cannot find the following equipments %s in contingency %s", elementsIds, contingencyInfos.getId())) - .withSeverity(TypedValue.WARN_SEVERITY) - .build()); - }); - if (!CollectionUtils.isEmpty(notFoundElementReports)) { - Reporter elementNotFoundSubReporter = reporter.createSubReporter( - context.getReportContext().getReportId().toString() + "notFoundElements", - "Elements not found"); - notFoundElementReports.forEach(elementNotFoundSubReporter::report); - } - observer.observe("report.send", - context, () -> reportService.sendReport(context.getReportContext().getReportId(), rootReporter.get())); - } - return result; + @Override + protected void logOnRun(SecurityAnalysisRunContext runContext) { + LOGGER.info("Run security analysis on contingency lists: {}", runContext.getContingencyListNames().stream().map(LogUtils::sanitizeParam).toList()); + } + + @Override + protected void enrichRunContext(SecurityAnalysisRunContext runContext) { + List contingencies = observer.observe("contingencies.fetch", runContext, + () -> runContext.getContingencyListNames().stream() + .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, runContext.getNetworkUuid(), runContext.getVariantId())) + .flatMap(List::stream) + .toList()); + + runContext.setContingencies(contingencies); } - @Bean - public Consumer> consumeRun() { - return message -> { - SecurityAnalysisResultContext resultContext = SecurityAnalysisResultContext.fromMessage(message, objectMapper); - try { - runRequests.add(resultContext.getResultUuid()); - AtomicReference startTime = new AtomicReference<>(); - - startTime.set(System.nanoTime()); - SecurityAnalysisResult result = run(resultContext.getRunContext(), resultContext.getResultUuid()); - long nanoTime = System.nanoTime(); - LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); - - observer.observe("results.save", resultContext.getRunContext(), () -> resultService.insert( - resultContext.getResultUuid(), - result, - result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED + @Override + protected void saveResult(Network network, AbstractResultContext resultContext, SecurityAnalysisResult result) { + resultService.insert( + resultContext.getResultUuid(), + result, + result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED ? SecurityAnalysisStatus.CONVERGED - : SecurityAnalysisStatus.DIVERGED)); - - long finalNanoTime = System.nanoTime(); - LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); - - if (result != null) { // result available - notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); - LOGGER.info("Security analysis complete (resultUuid='{}')", resultContext.getResultUuid()); - } else { // result not available : stop computation request - if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { - cleanResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - if (!(e instanceof CancellationException)) { - LOGGER.error(getFailedMessage(getComputationType()), e); - notificationService.publishFail(resultContext.getResultUuid(), - resultContext.getRunContext().getReceiver(), - e.getMessage(), - resultContext.getRunContext().getUserId(), - getComputationType()); - resultService.delete(resultContext.getResultUuid()); - } - } finally { - futures.remove(resultContext.getResultUuid()); - cancelComputationRequests.remove(resultContext.getResultUuid()); - runRequests.remove(resultContext.getResultUuid()); - } - }; + : SecurityAnalysisStatus.DIVERGED); + } + + @Override + protected SecurityAnalysisResultContext fromMessage(Message message) { + return SecurityAnalysisResultContext.fromMessage(message, objectMapper); } } From 9a1871a5d641f314365fcbe3a9ec547275c954f5 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Mon, 25 Mar 2024 18:13:42 +0100 Subject: [PATCH 11/12] load network only once, check null result before save, life cycle methods before and after for runAsync --- .../service/AbstractWorkerService.java | 48 +++++++-------- .../SecurityAnalysisWorkerService.java | 59 ++++++++----------- 2 files changed, 46 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java index c081de01..0c97a430 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -7,7 +7,6 @@ package org.gridsuite.securityanalysis.server.computation.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; import com.powsybl.commons.PowsyblException; import com.powsybl.commons.reporter.Reporter; import com.powsybl.commons.reporter.ReporterModel; @@ -24,7 +23,6 @@ import org.springframework.web.server.ResponseStatusException; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; @@ -45,16 +43,15 @@ public abstract class AbstractWorkerService, P, T extends AbstractComputationResultService> { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWorkerService.class); - protected final Lock lockRunAndCancel = new ReentrantLock(); + private final Lock lockRunAndCancel = new ReentrantLock(); protected final ObjectMapper objectMapper; - protected final Set runRequests = Sets.newConcurrentHashSet(); protected final NetworkStoreService networkStoreService; protected final ReportService reportService; protected final ExecutionService executionService; protected final NotificationService notificationService; protected final AbstractComputationObserver observer; protected final Map> futures = new ConcurrentHashMap<>(); - protected final Map cancelComputationRequests = new ConcurrentHashMap<>(); + private final Map cancelComputationRequests = new ConcurrentHashMap<>(); protected final T resultService; protected AbstractWorkerService(NetworkStoreService networkStoreService, @@ -122,23 +119,23 @@ public Consumer> consumeRun() { return message -> { AbstractResultContext resultContext = fromMessage(message); try { - runRequests.add(resultContext.getResultUuid()); AtomicReference startTime = new AtomicReference<>(); startTime.set(System.nanoTime()); - S result = run(resultContext.getRunContext(), resultContext.getResultUuid()); + Network network = getNetwork(resultContext.getRunContext().getNetworkUuid(), + resultContext.getRunContext().getVariantId()); + + S result = run(network, resultContext.getRunContext(), resultContext.getResultUuid()); long nanoTime = System.nanoTime(); LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); - Network network = getNetwork(resultContext.getRunContext().getNetworkUuid(), - resultContext.getRunContext().getVariantId()); - observer.observe("results.save", resultContext.getRunContext(), () -> saveResult(network, resultContext, result)); + if (result != null) { // result available + observer.observe("results.save", resultContext.getRunContext(), () -> saveResult(network, resultContext, result)); - long finalNanoTime = System.nanoTime(); - LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); + long finalNanoTime = System.nanoTime(); + LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); - if (result != null) { // result available notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); LOGGER.info("{} complete (resultUuid='{}')", getComputationType(), resultContext.getResultUuid()); } else { // result not available : stop computation request @@ -159,7 +156,6 @@ public Consumer> consumeRun() { } finally { futures.remove(resultContext.getResultUuid()); cancelComputationRequests.remove(resultContext.getResultUuid()); - runRequests.remove(resultContext.getResultUuid()); } }; } @@ -171,24 +167,19 @@ public Consumer> consumeCancel() { protected abstract void saveResult(Network network, AbstractResultContext resultContext, S result); - protected void logOnRun(R runContext) { - LOGGER.info("Run {} computation ...", getComputationType()); - } - /** - * if this computation needs extra run data, add them to the run context here + * Do some extra task before run the computation, e.g. print log or init extra data for the run context */ - protected void enrichRunContext(R runContext) { } + protected void onBeforeRunAsync(R runContext, Reporter reporter) { + LOGGER.info("Run {} computation ...", getComputationType()); + } /** - * if this computation needs extra report operations, override this function and do them here + * Do some extra task after run the computation, e.g. do some operations on report */ - protected void reportSpecificOperations(R runContext, Reporter reporter) { } - - protected S run(R runContext, UUID resultUuid) throws Exception { - logOnRun(runContext); + protected void onAfterRunAsync(R runContext, Reporter reporter) { } - enrichRunContext(runContext); + protected S run(Network network, R runContext, UUID resultUuid) throws Exception { String provider = runContext.getProvider(); AtomicReference rootReporter = new AtomicReference<>(Reporter.NO_OP); @@ -204,12 +195,13 @@ protected S run(R runContext, UUID resultUuid) throws Exception { runContext, () -> reportService.deleteReport(runContext.getReportContext().getReportId(), reportType)); } - Network network = getNetwork(runContext.getNetworkUuid(), runContext.getVariantId()); + onBeforeRunAsync(runContext, reporter); CompletableFuture future = runAsync(network, runContext, provider, reporter, resultUuid); S result = future == null ? null : observer.observeRun("run", runContext, future::get); + onAfterRunAsync(runContext, reporter); + if (runContext.getReportContext().getReportId() != null) { - reportSpecificOperations(runContext, reporter); observer.observe("report.send", runContext, () -> reportService.sendReport(runContext.getReportContext().getReportId(), rootReporter.get())); } return result; diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index 55d57325..1eb3d38a 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -15,18 +15,10 @@ import com.powsybl.iidm.network.VariantManagerConstants; import com.powsybl.loadflow.LoadFlowResult; import com.powsybl.network.store.client.NetworkStoreService; -import com.powsybl.security.LimitViolationFilter; -import com.powsybl.security.SecurityAnalysis; -import com.powsybl.security.SecurityAnalysisParameters; -import com.powsybl.security.SecurityAnalysisReport; -import com.powsybl.security.SecurityAnalysisResult; +import com.powsybl.security.*; import com.powsybl.security.detectors.DefaultLimitViolationDetector; import com.powsybl.ws.commons.LogUtils; -import org.gridsuite.securityanalysis.server.computation.service.AbstractResultContext; -import org.gridsuite.securityanalysis.server.computation.service.AbstractWorkerService; -import org.gridsuite.securityanalysis.server.computation.service.NotificationService; -import org.gridsuite.securityanalysis.server.computation.service.ExecutionService; -import org.gridsuite.securityanalysis.server.computation.service.ReportService; +import org.gridsuite.securityanalysis.server.computation.service.*; import org.gridsuite.securityanalysis.server.dto.ContingencyInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisRunnerSupplier; @@ -38,7 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.getFailedMessage; @@ -70,7 +62,9 @@ public void setSecurityAnalysisFactorySupplier(Function getCompletableFuture(Network } @Override - protected void reportSpecificOperations(SecurityAnalysisRunContext runContext, Reporter reporter) { - List notFoundElementReports = new ArrayList<>(); - runContext.getContingencies().stream() - .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) - .forEach(contingencyInfos -> { - String elementsIds = String.join(", ", contingencyInfos.getNotFoundElements()); - notFoundElementReports.add(Report.builder() - .withKey("contingencyElementNotFound_" + contingencyInfos.getId() + notFoundElementReports.size()) - .withDefaultMessage(String.format("Cannot find the following equipments %s in contingency %s", elementsIds, contingencyInfos.getId())) - .withSeverity(TypedValue.WARN_SEVERITY) - .build()); - }); - if (!CollectionUtils.isEmpty(notFoundElementReports)) { - Reporter elementNotFoundSubReporter = reporter.createSubReporter( - runContext.getReportContext().getReportId().toString() + "notFoundElements", - "Elements not found"); - notFoundElementReports.forEach(elementNotFoundSubReporter::report); + protected void onAfterRunAsync(SecurityAnalysisRunContext runContext, Reporter reporter) { + if (runContext.getReportContext().getReportId() != null) { + List notFoundElementReports = new ArrayList<>(); + runContext.getContingencies().stream() + .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) + .forEach(contingencyInfos -> { + String elementsIds = String.join(", ", contingencyInfos.getNotFoundElements()); + notFoundElementReports.add(Report.builder() + .withKey("contingencyElementNotFound_" + contingencyInfos.getId() + notFoundElementReports.size()) + .withDefaultMessage(String.format("Cannot find the following equipments %s in contingency %s", elementsIds, contingencyInfos.getId())) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + }); + if (!CollectionUtils.isEmpty(notFoundElementReports)) { + Reporter elementNotFoundSubReporter = reporter.createSubReporter( + runContext.getReportContext().getReportId().toString() + "notFoundElements", + "Elements not found"); + notFoundElementReports.forEach(elementNotFoundSubReporter::report); + } } } @Override - protected void logOnRun(SecurityAnalysisRunContext runContext) { + protected void onBeforeRunAsync(SecurityAnalysisRunContext runContext, Reporter reporter) { LOGGER.info("Run security analysis on contingency lists: {}", runContext.getContingencyListNames().stream().map(LogUtils::sanitizeParam).toList()); - } - @Override - protected void enrichRunContext(SecurityAnalysisRunContext runContext) { List contingencies = observer.observe("contingencies.fetch", runContext, () -> runContext.getContingencyListNames().stream() .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, runContext.getNetworkUuid(), runContext.getVariantId())) From b4f55c4d785b6fe31e5e8ab6c2ea2727be092f8f Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 26 Mar 2024 09:10:43 +0100 Subject: [PATCH 12/12] clean result and publish cancel once in every cases --- .../server/computation/service/AbstractWorkerService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java index 0c97a430..ee78f1dd 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -138,10 +138,6 @@ public Consumer> consumeRun() { notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); LOGGER.info("{} complete (resultUuid='{}')", getComputationType(), resultContext.getResultUuid()); - } else { // result not available : stop computation request - if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { - cleanResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); - } } } catch (InterruptedException e) { Thread.currentThread().interrupt();