diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java index 97530e73..55769e9c 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; @@ -35,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.*; /** @@ -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/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/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/service/AbstractComputationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java new file mode 100644 index 00000000..55911d75 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractComputationService.java @@ -0,0 +1,71 @@ +/** + * 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 lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @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, S> { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractComputationService.class); + + protected ObjectMapper objectMapper; + protected NotificationService notificationService; + @Getter + protected String defaultProvider; + + protected UuidGeneratorService uuidGeneratorService; + protected T resultService; + + protected AbstractComputationService(NotificationService notificationService, + T resultService, + 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); + this.resultService = Objects.requireNonNull(resultService); + } + + 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 void setStatus(List resultUuids, S status) { + resultService.insertStatus(resultUuids, status); + } + + public void deleteResult(UUID resultUuid) { + resultService.delete(resultUuid); + } + + public void deleteResults() { + resultService.deleteAll(); + } + + public S getStatus(UUID resultUuid) { + return resultService.findStatus(resultUuid); + } +} 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..1f317682 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractResultContext.java @@ -0,0 +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; + +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; + +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +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"; + + 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/computation/service/AbstractWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java new file mode 100644 index 00000000..ee78f1dd --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/AbstractWorkerService.java @@ -0,0 +1,230 @@ +/** + * 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 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; +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.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; + +/** + * @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 + * @param result service specific to the computation + */ +public abstract class AbstractWorkerService, P, T extends AbstractComputationResultService> { + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractWorkerService.class); + + private final Lock lockRunAndCancel = new ReentrantLock(); + protected final ObjectMapper objectMapper; + 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<>(); + private 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 = resultService; + this.executionService = executionService; + this.observer = observer; + this.objectMapper = objectMapper; + } + + public PreloadingStrategy getNetworkPreloadingStrategy() { + return PreloadingStrategy.COLLECTION; + } + + protected Network getNetwork(UUID networkUuid, String variantId) { + Network network; + try { + network = networkStoreService.getNetwork(networkUuid, getNetworkPreloadingStrategy()); + 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 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(); + 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(); + } + } + + protected abstract AbstractResultContext fromMessage(Message message); + + @Bean + public Consumer> consumeRun() { + return message -> { + AbstractResultContext resultContext = fromMessage(message); + try { + AtomicReference startTime = new AtomicReference<>(); + startTime.set(System.nanoTime()); + + 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))); + + 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))); + + notificationService.sendResultMessage(resultContext.getResultUuid(), resultContext.getRunContext().getReceiver()); + LOGGER.info("{} complete (resultUuid='{}')", getComputationType(), resultContext.getResultUuid()); + } + } 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()); + } + }; + } + + @Bean + public Consumer> consumeCancel() { + return message -> cancelAsync(CancelContext.fromMessage(message)); + } + + protected abstract void saveResult(Network network, AbstractResultContext resultContext, S result); + + /** + * Do some extra task before run the computation, e.g. print log or init extra data for the run context + */ + protected void onBeforeRunAsync(R runContext, Reporter reporter) { + LOGGER.info("Run {} computation ...", getComputationType()); + } + + /** + * Do some extra task after run the computation, e.g. do some operations on report + */ + protected void onAfterRunAsync(R runContext, Reporter reporter) { } + + protected S run(Network network, R runContext, UUID resultUuid) throws Exception { + + 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)); + } + + 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) { + 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/computation/service/CancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/CancelContext.java new file mode 100644 index 00000000..c2d9ba29 --- /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.computation.utils.ContextUtils; +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(ContextUtils.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/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/computation/service/NotificationService.java b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java new file mode 100644 index 00000000..e94be104 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/computation/service/NotificationService.java @@ -0,0 +1,107 @@ +/** + * 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.ContextUtils; +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, ContextUtils.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"; + } +} 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/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/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/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/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 - */ -@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/NotificationService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/NotificationService.java deleted file mode 100644 index f31df48a..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/NotificationService.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2022, 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 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; - -/** - * @author Seddik Yengui - */ - -// 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/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/SecurityAnalysisCancelContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java deleted file mode 100644 index 22f86bd1..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisCancelContext.java +++ /dev/null @@ -1,63 +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.service.NotificationService.RESULT_UUID_HEADER; -import static org.gridsuite.securityanalysis.server.service.NotificationService.RECEIVER_HEADER; -/** - * @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, RESULT_UUID_HEADER)); - String receiver = (String) headers.get(RECEIVER_HEADER); - return new SecurityAnalysisCancelContext(resultUuid, receiver); - } - - public Message toMessage() { - return MessageBuilder.withPayload("") - .setHeader(RESULT_UUID_HEADER, resultUuid.toString()) - .setHeader(RECEIVER_HEADER, receiver) - .build(); - } -} 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..fdef6aac 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultContext.java @@ -8,42 +8,31 @@ 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.dto.ReportInfos; +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.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.computation.service.NotificationService.*; +import static org.gridsuite.securityanalysis.server.computation.utils.ContextUtils.getNonNullHeader; /** * @author Geoffroy Jamgotchian */ -public class SecurityAnalysisResultContext { - - private final UUID resultUuid; - - private final SecurityAnalysisRunContext runContext; +public class SecurityAnalysisResultContext extends AbstractResultContext { + public static final String CONTINGENCY_LIST_NAMES_HEADER = "contingencyListNames"; 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) { @@ -54,23 +43,15 @@ 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(); - 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 { @@ -88,30 +69,14 @@ 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); } - 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(CONTINGENCY_LIST_NAMES_HEADER, String.join(",", runContext.getContingencyListNames())) - .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()) - .build(); + public Map getSpecificMsgHeaders() { + return Map.of( + CONTINGENCY_LIST_NAMES_HEADER, String.join(",", runContext.getContingencyListNames())); } } 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/SecurityAnalysisRunContext.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisRunContext.java index 2406e3dd..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,11 @@ 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 org.gridsuite.securityanalysis.server.dto.ReportInfos; import java.util.List; import java.util.Objects; @@ -23,31 +26,15 @@ * @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; + @Setter + private List contingencies; 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 +42,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/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index fa054cfb..27ca79a2 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 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.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -15,34 +18,21 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; /** * @author Geoffroy Jamgotchian * @author Franck Lecuyer */ @Service -public class SecurityAnalysisService { - private final SecurityAnalysisResultService securityAnalysisResultService; - - private final UuidGeneratorService uuidGeneratorService; - - private final NotificationService notificationService; - - private final ObjectMapper objectMapper; - - private final String defaultProvider; +public class SecurityAnalysisService extends AbstractComputationService { + public static final String COMPUTATION_TYPE = "Security analysis"; public SecurityAnalysisService(SecurityAnalysisResultService securityAnalysisResultService, UuidGeneratorService uuidGeneratorService, ObjectMapper objectMapper, NotificationService notificationService, @Value("${security-analysis.default-provider}") String 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); + super(notificationService, securityAnalysisResultService, objectMapper, uuidGeneratorService, defaultProvider); } public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { @@ -50,38 +40,14 @@ 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; } - 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 void stop(UUID resultUuid, String receiver) { - notificationService.emitCancelAnalysisMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage()); - } - public List getProviders() { return SecurityAnalysisProvider.findAll().stream() .map(SecurityAnalysisProvider::getName) - .collect(Collectors.toList()); - } - - public String getDefaultProvider() { - return defaultProvider; + .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 c1b92b52..1eb3d38a 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -7,227 +7,109 @@ 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; -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.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.*; 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; 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.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.concurrent.CompletableFuture; 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.getFailedMessage; +import static org.gridsuite.securityanalysis.server.service.SecurityAnalysisService.COMPUTATION_TYPE; /** * @author Geoffroy Jamgotchian * @author Franck Lecuyer */ @Service -public class SecurityAnalysisWorkerService { +public class SecurityAnalysisWorkerService extends AbstractWorkerService { - private static final Logger LOGGER = LoggerFactory.getLogger(SecurityAnalysisWorkerService.class); - - private NetworkStoreService networkStoreService; - - 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 final ActionsService actionsService; private Function securityAnalysisFactorySupplier; - private SecurityAnalysisExecutionService securityAnalysisExecutionService; - - private final SecurityAnalysisObserver securityAnalysisObserver; - public SecurityAnalysisWorkerService(NetworkStoreService networkStoreService, ActionsService actionsService, ReportService reportService, - SecurityAnalysisResultService resultRepository, ObjectMapper objectMapper, - SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, SecurityAnalysisExecutionService securityAnalysisExecutionService, - SecurityAnalysisObserver securityAnalysisObserver) { - this.networkStoreService = Objects.requireNonNull(networkStoreService); + SecurityAnalysisResultService resultService, ObjectMapper objectMapper, + SecurityAnalysisRunnerSupplier securityAnalysisRunnerSupplier, NotificationService notificationService, ExecutionService executionService, + SecurityAnalysisObserver observer) { + super(networkStoreService, notificationService, reportService, resultService, 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.securityAnalysisExecutionService = Objects.requireNonNull(securityAnalysisExecutionService); this.securityAnalysisFactorySupplier = securityAnalysisRunnerSupplier::getRunner; - this.securityAnalysisObserver = securityAnalysisObserver; } public void setSecurityAnalysisFactorySupplier(Function securityAnalysisFactorySupplier) { this.securityAnalysisFactorySupplier = Objects.requireNonNull(securityAnalysisFactorySupplier); } - private Network getNetwork(UUID networkUuid) { - try { - return networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION); - } catch (PowsyblException e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); - } - } - public SecurityAnalysisResult run(SecurityAnalysisRunContext context) { try { - return run(context, null); + Network network = getNetwork(context.getNetworkUuid(), + context.getVariantId()); + return run(network, context, null); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } catch (Exception e) { - LOGGER.error(FAIL_MESSAGE, e); + LOGGER.error(getFailedMessage(getComputationType()), e); return null; } } - private CompletableFuture runASAsync(SecurityAnalysisRunContext context, - SecurityAnalysis.Runner securityAnalysisRunner, - Network network, - List contingencies, - Reporter reporter, - UUID resultUuid) { - lockRunAndCancelAS.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(), - securityAnalysisExecutionService.getLocalComputationManager(), - 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 { - lockRunAndCancelAS.unlock(); - } - } - - private void cancelASAsync(SecurityAnalysisCancelContext 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(); - } + @Override + protected String getComputationType() { + return COMPUTATION_TYPE; } - private void cleanASResultsAndPublishCancel(UUID resultUuid, String receiver) { - securityAnalysisResultService.delete(resultUuid); - notificationService.emitStopAnalysisMessage(resultUuid.toString(), receiver); - LOGGER.info(CANCEL_MESSAGE + " (resultUuid='{}')", resultUuid); + @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); } - 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 = securityAnalysisObserver.observe("network.load", context, () -> getNetwork(context.getNetworkUuid())); - - List contingencies = securityAnalysisObserver.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.getReportUuid() != null) { - final String reportType = context.getReportType(); - String rootReporterId = context.getReporterId() == null ? reportType : context.getReporterId() + "@" + 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)); - } - - CompletableFuture future = runASAsync(context, - securityAnalysisRunner, - network, - contingencies.stream() - .map(ContingencyInfos::getContingency) - .filter(Objects::nonNull) - .toList(), - reporter, - resultUuid); - - SecurityAnalysisResult result = future == null ? null : securityAnalysisObserver.observeRun("run", context, future::get); - if (context.getReportUuid() != null) { + @Override + protected void onAfterRunAsync(SecurityAnalysisRunContext runContext, Reporter reporter) { + if (runContext.getReportContext().getReportId() != null) { List notFoundElementReports = new ArrayList<>(); - contingencies.stream() + runContext.getContingencies().stream() .filter(contingencyInfos -> !CollectionUtils.isEmpty(contingencyInfos.getNotFoundElements())) .forEach(contingencyInfos -> { String elementsIds = String.join(", ", contingencyInfos.getNotFoundElements()); @@ -238,66 +120,39 @@ 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( + runContext.getReportContext().getReportId().toString() + "notFoundElements", + "Elements not found"); notFoundElementReports.forEach(elementNotFoundSubReporter::report); } - securityAnalysisObserver.observe("report.send", context, () -> reportService.sendReport(context.getReportUuid(), rootReporter.get())); } - return result; } - @Bean - public Consumer> consumeRun() { - return message -> { - SecurityAnalysisResultContext resultContext = SecurityAnalysisResultContext.fromMessage(message, objectMapper); - try { - runRequests.add(resultContext.getResultUuid()); - AtomicReference startTime = new AtomicReference<>(); + @Override + protected void onBeforeRunAsync(SecurityAnalysisRunContext runContext, Reporter reporter) { + LOGGER.info("Run security analysis on contingency lists: {}", runContext.getContingencyListNames().stream().map(LogUtils::sanitizeParam).toList()); - 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))); + List contingencies = observer.observe("contingencies.fetch", runContext, + () -> runContext.getContingencyListNames().stream() + .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, runContext.getNetworkUuid(), runContext.getVariantId())) + .flatMap(List::stream) + .toList()); - securityAnalysisObserver.observe("results.save", resultContext.getRunContext(), () -> securityAnalysisResultService.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))); + runContext.setContingencies(contingencies); + } - if (result != null) { // result available - notificationService.emitAnalysisResultsMessage(resultContext.getResultUuid().toString(), resultContext.getRunContext().getReceiver()); - 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()); - } - } - } catch (InterruptedException e) { - 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()); - securityAnalysisResultService.delete(resultContext.getResultUuid()); - } - } finally { - futures.remove(resultContext.getResultUuid()); - cancelComputationRequests.remove(resultContext.getResultUuid()); - runRequests.remove(resultContext.getResultUuid()); - } - }; + @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); } - @Bean - public Consumer> consumeCancel() { - return message -> cancelASAsync(SecurityAnalysisCancelContext.fromMessage(message)); + @Override + protected SecurityAnalysisResultContext fromMessage(Message message) { + return SecurityAnalysisResultContext.fromMessage(message, objectMapper); } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java index e02bcccc..951afe89 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java @@ -34,9 +34,9 @@ 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.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); 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..1250722d 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; @@ -52,7 +53,7 @@ public class ReportServiceTest { @Before public void setUp() throws IOException { String mockServerUri = initMockWebServer(); - reportService.setReportServiceBaseUri(mockServerUri); + reportService.setReportServerBaseUri(mockServerUri); } @After