diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e4d9ccc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: + - 'main' + pull_request: + +jobs: + build: + uses: powsybl/github-ci/.github/workflows/build-backend-app-generic.yml@aaae75875296657028f2805e7a3fbdb1418b8b95 + with: + dockerImage: docker.io/gridsuite/dynamic-margin-calculation-server + dockerUsername: gridsuiteci + eventType: dynamic_margin_calculation_server_updated + secrets: + sonar-token: ${{ secrets.SONAR_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + repo-token: ${{ secrets.REPO_ACCESS_TOKEN }} diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml new file mode 100644 index 0000000..f34ed80 --- /dev/null +++ b/.github/workflows/patch.yml @@ -0,0 +1,22 @@ +name: Patch + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: version to patch (vX.X) + required: true + +jobs: + run-patch: + uses: powsybl/github-ci/.github/workflows/patch-backend-app-generic.yml@aaae75875296657028f2805e7a3fbdb1418b8b95 + with: + githubappId: ${{ vars.GRIDSUITE_ACTIONS_APPID }} + dockerImage: docker.io/gridsuite/dynamic-margin-calculation-server + dockerUsername: gridsuiteci + releaseVersion: ${{ github.event.inputs.releaseVersion }} + secrets: + VERSIONBUMP_GHAPP_PRIVATE_KEY: ${{ secrets.VERSIONBUMP_GHAPP_PRIVATE_KEY }} + sonar-token: ${{ secrets.SONAR_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0ceaecb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: Release version (vX.X) + required: true + gitReference: + description: SHA of the commit from where to release or branch name + required: true + +jobs: + run-release: + uses: powsybl/github-ci/.github/workflows/release-backend-app-generic.yml@aaae75875296657028f2805e7a3fbdb1418b8b95 + with: + githubappId: ${{ vars.GRIDSUITE_ACTIONS_APPID }} + dockerImage: docker.io/gridsuite/dynamic-margin-calculation-server + dockerUsername: gridsuiteci + releaseVersion: ${{ github.event.inputs.releaseVersion }} + gitReference: ${{ github.event.inputs.gitReference }} + secrets: + VERSIONBUMP_GHAPP_PRIVATE_KEY: ${{ secrets.VERSIONBUMP_GHAPP_PRIVATE_KEY }} + sonar-token: ${{ secrets.SONAR_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..4fa8250 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +import target/configs/powsybl-build-tools.jar!powsybl-build-tools/lombok.config diff --git a/pom.xml b/pom.xml index 71b5ffa..2c84f7d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ + 1.3.0 + + 1.28.0 + powsybl/java-dynawo:3.0.0 org.gridsuite.dynamicmargincalculation.server gridsuite - 3.2.0 org.gridsuite:dynamic-margin-calculation-server - - 1.26.0-SNAPSHOT @@ -89,21 +91,7 @@ - - com.powsybl - powsybl-computation-local - 6.8.0-SNAPSHOT - - - com.powsybl - powsybl-computation - 6.8.0-SNAPSHOT - - - com.powsybl - powsybl-iidm-api - 6.8.0-SNAPSHOT - + org.gridsuite @@ -119,6 +107,11 @@ + + org.gridsuite + gridsuite-computation + ${gridsuite-computation.version} + org.springframework.boot spring-boot-starter-web @@ -128,13 +121,12 @@ spring-boot-starter-data-jpa - org.springframework.cloud - spring-cloud-stream + org.springframework.boot + spring-boot-starter-actuator - io.awspring.cloud - spring-cloud-aws-starter-s3 - ${spring-cloud-aws.version} + org.springframework.cloud + spring-cloud-stream org.springdoc @@ -161,15 +153,9 @@ com.powsybl powsybl-dynawo-margin-calculation - 2.9.0-SNAPSHOT - - org.springframework.boot - spring-boot-starter-actuator - runtime - org.springframework.cloud spring-cloud-stream-binder-rabbit diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApi.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApi.java new file mode 100644 index 0000000..52a6b1c --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApi.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server; + +/** + * @author Thang PHAM + */ +public final class DynamicMarginCalculationApi { + + private DynamicMarginCalculationApi() { + } + + public static final String API_VERSION = "v1"; +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApplication.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApplication.java new file mode 100644 index 0000000..be80086 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationApplication.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Thang PHAM + */ +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") +@SpringBootApplication +public class DynamicMarginCalculationApplication { + + public static void main(String[] args) { + SpringApplication.run(DynamicMarginCalculationApplication.class, args); + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationException.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationException.java new file mode 100644 index 0000000..ee635dc --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/DynamicMarginCalculationException.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server; + +import lombok.Getter; + +/** + * @author Thang PHAM + */ +@Getter +public class DynamicMarginCalculationException extends RuntimeException { + + public enum Type { + RESULT_UUID_NOT_FOUND, + } + + private final Type type; + + public DynamicMarginCalculationException(Type type, String message) { + super(message); + this.type = type; + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/RestResponseEntityExceptionHandler.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/RestResponseEntityExceptionHandler.java new file mode 100644 index 0000000..5269b7d --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/RestResponseEntityExceptionHandler.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * @author Thang PHAM + */ +@ControllerAdvice +public class RestResponseEntityExceptionHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class); + + @ExceptionHandler(DynamicMarginCalculationException.class) + protected ResponseEntity handleDynamicSecurityAnalysisException(DynamicMarginCalculationException exception) { + if (LOGGER.isErrorEnabled()) { + LOGGER.error(exception.getMessage(), exception); + } + + DynamicMarginCalculationException.Type type = exception.getType(); + return switch (type) { + case RESULT_UUID_NOT_FOUND + -> ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage()); + }; + } + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/SwaggerConfig.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/SwaggerConfig.java new file mode 100644 index 0000000..25fb3cc --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/config/SwaggerConfig.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Thang PHAM + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI createOpenApi() { + return new OpenAPI() + .info(new Info() + .title("Dynamic Margin Calculation Server API") + .description("This is the documentation of the Dynamic-Margin-Calculation REST API") + .version(DynamicMarginCalculationApi.API_VERSION)); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionController.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionController.java new file mode 100644 index 0000000..46dce5c --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionController.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server.controller; + +import io.swagger.v3.oas.annotations.Operation; +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.dynamicmargincalculation.server.DynamicMarginCalculationApi; +import org.gridsuite.dynamicmargincalculation.server.service.SupervisionService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Thang PHAM + */ +@RestController +@RequestMapping(value = "/" + DynamicMarginCalculationApi.API_VERSION + "/supervision") +@Tag(name = "Dynamic margin calculation server - Supervision") +public class SupervisionController { + private final SupervisionService supervisionService; + + public SupervisionController(SupervisionService supervisionService) { + this.supervisionService = supervisionService; + } + + @GetMapping(value = "/results-count") + @Operation(summary = "Get results count") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The count of all results")}) + public ResponseEntity getResultsCount() { + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(supervisionService.getResultsCount()); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/DynamicMarginCalculationStatus.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/DynamicMarginCalculationStatus.java new file mode 100644 index 0000000..5bd3611 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/dto/DynamicMarginCalculationStatus.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.dto; + +/** + * @author Thang PHAM + */ +public enum DynamicMarginCalculationStatus { + NOT_DONE, + RUNNING, + SUCCEED, + FAILED +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationResultEntity.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationResultEntity.java new file mode 100644 index 0000000..d840a98 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/entities/DynamicMarginCalculationResultEntity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.entities; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; + +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Getter +@Setter +@Table(name = "dynamic_margin_calculation_result") +@NoArgsConstructor +@Entity +public class DynamicMarginCalculationResultEntity { + + public DynamicMarginCalculationResultEntity(UUID id, DynamicMarginCalculationStatus status) { + this.id = id; + this.status = status; + } + + @Id + @Column(name = "result_uuid") + private UUID id; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + private DynamicMarginCalculationStatus status; + +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationResultRepository.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationResultRepository.java new file mode 100644 index 0000000..c4ead81 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/repositories/DynamicMarginCalculationResultRepository.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.repositories; + +import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationResultEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +/** + * @author Thang PHAM + */ +@Repository +public interface DynamicMarginCalculationResultRepository extends JpaRepository { +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java new file mode 100644 index 0000000..e76b6f4 --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultService.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.service; + +import jakarta.transaction.Transactional; +import org.gridsuite.computation.service.AbstractComputationResultService; +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationException; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationResultEntity; +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationResultRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationException.Type.RESULT_UUID_NOT_FOUND; + +/** + * @author Thang PHAM + */ +@Service +public class DynamicMarginCalculationResultService extends AbstractComputationResultService { + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicMarginCalculationResultService.class); + + public static final String MSG_RESULT_UUID_NOT_FOUND = "Result uuid not found: "; + + private final DynamicMarginCalculationResultRepository resultRepository; + + public DynamicMarginCalculationResultService(DynamicMarginCalculationResultRepository resultRepository) { + this.resultRepository = resultRepository; + } + + @Override + @Transactional + public void insertStatus(List resultUuids, DynamicMarginCalculationStatus status) { + Objects.requireNonNull(resultUuids); + resultRepository.saveAll(resultUuids.stream() + .map(uuid -> new DynamicMarginCalculationResultEntity(uuid, status)).toList()); + } + + @Transactional + public List updateStatus(List resultUuids, DynamicMarginCalculationStatus status) { + // find result entities + List resultEntities = resultRepository.findAllById(resultUuids); + // set entity with new values + resultEntities.forEach(resultEntity -> resultEntity.setStatus(status)); + // save entities into database + return resultRepository.saveAllAndFlush(resultEntities).stream().map(DynamicMarginCalculationResultEntity::getId).toList(); + } + + @Transactional + public void updateResult(UUID resultUuid, DynamicMarginCalculationStatus status) { + LOGGER.debug("Update dynamic simulation [resultUuid={}, status={}", resultUuid, status); + DynamicMarginCalculationResultEntity resultEntity = resultRepository.findById(resultUuid) + .orElseThrow(() -> new DynamicMarginCalculationException(RESULT_UUID_NOT_FOUND, MSG_RESULT_UUID_NOT_FOUND + resultUuid)); + resultEntity.setStatus(status); + } + + @Override + public void delete(UUID resultUuid) { + Objects.requireNonNull(resultUuid); + resultRepository.deleteById(resultUuid); + } + + @Override + @Transactional + public void deleteAll() { + resultRepository.deleteAll(); + } + + @Override + public DynamicMarginCalculationStatus findStatus(UUID resultUuid) { + Objects.requireNonNull(resultUuid); + return resultRepository.findById(resultUuid) + .map(DynamicMarginCalculationResultEntity::getStatus) + .orElse(null); + } +} diff --git a/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/SupervisionService.java b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/SupervisionService.java new file mode 100644 index 0000000..b8a927d --- /dev/null +++ b/src/main/java/org/gridsuite/dynamicmargincalculation/server/service/SupervisionService.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server.service; + +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationResultRepository; +import org.springframework.stereotype.Service; + +/** + * @author Thang PHAM + */ +@Service +public class SupervisionService { + private final DynamicMarginCalculationResultRepository resultRepository; + + public SupervisionService(DynamicMarginCalculationResultRepository resultRepository) { + this.resultRepository = resultRepository; + } + + public Integer getResultsCount() { + return (int) resultRepository.count(); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..13f935c --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,22 @@ +server: + port: 5041 + +spring: + rabbitmq: + addresses: localhost + +powsybl-ws: + database: + host: localhost + +powsybl: + services: + network-store-server: + base-uri: http://localhost:8080/ + +gridsuite: + services: + dynamic-mapping-server: + base-uri: http://localhost:5036 + report-server: + base-uri: http://localhost:5028 diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml new file mode 100644 index 0000000..43bcde6 --- /dev/null +++ b/src/main/resources/config/application.yaml @@ -0,0 +1,47 @@ +spring: + application: + name: dynamic-margin-calculation-server + + cloud: + function: + definition: consumeRun;consumeCancel + stream: + bindings: + consumeRun-in-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.run + group: dmcGroup + consumer: + concurrency: 2 + max-attempts: 1 + publishRun-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.run + publishResult-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.result + consumeCancel-in-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.cancel + publishCancel-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.cancel + publishStopped-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.stopped + publishCancelFailed-out-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.cancelfailed + output-bindings: publishRun-out-0;publishResult-out-0;publishCancel-out-0;publishStopped-out-0;publishCancelFailed-out-0 + rabbit: + bindings: + consumeRun-in-0: + consumer: + auto-bind-dlq: true + dead-letter-exchange: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.run.dlx + dead-letter-queue-name: ${powsybl-ws.rabbitmq.destination.prefix:}dmc.run.dlx.dlq + dead-letter-exchange-type: topic + quorum: + enabled: true + delivery-limit: 2 + +powsybl-ws: + database: + name: dynamicmargincalculation + +# default values for dynamic margin calculation providers is "Dynawo" +dynamic-margin-calculation: + default-provider: Dynawo diff --git a/src/main/resources/db/changelog/changesets/changelog_20250715T091816Z.xml b/src/main/resources/db/changelog/changesets/changelog_20250715T091816Z.xml new file mode 100644 index 0000000..9b5e126 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20250715T091816Z.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000..d35e7ca --- /dev/null +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,5 @@ +databaseChangeLog: + + - include: + file: changesets/changelog_20250715T091816Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java new file mode 100644 index 0000000..63e1c78 --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/controller/SupervisionControllerTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2025, 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.dynamicmargincalculation.server.controller; + +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationApi; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Thang PHAM + */ +@AutoConfigureMockMvc +@SpringBootTest +class SupervisionControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void testResultCount() throws Exception { + //get count of the result uuids + mockMvc.perform(get("/" + DynamicMarginCalculationApi.API_VERSION + "/supervision/results-count")) + .andExpect(status().isOk()) + .andExpect(content().string("0")); + } +} diff --git a/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java new file mode 100644 index 0000000..f41780f --- /dev/null +++ b/src/test/java/org/gridsuite/dynamicmargincalculation/server/service/DynamicMarginCalculationResultServiceTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025, 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.dynamicmargincalculation.server.service; + +import org.gridsuite.dynamicmargincalculation.server.DynamicMarginCalculationException; +import org.gridsuite.dynamicmargincalculation.server.dto.DynamicMarginCalculationStatus; +import org.gridsuite.dynamicmargincalculation.server.entities.DynamicMarginCalculationResultEntity; +import org.gridsuite.dynamicmargincalculation.server.repositories.DynamicMarginCalculationResultRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Thang PHAM + */ +@SpringBootTest +class DynamicMarginCalculationResultServiceTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicMarginCalculationResultServiceTest.class); + + @Autowired + DynamicMarginCalculationResultRepository resultRepository; + + @Autowired + DynamicMarginCalculationResultService dynamicSecurityAnalysisResultService; + + @AfterEach + void cleanDB() { + resultRepository.deleteAll(); + } + + @Test + void testCrud() { + // --- insert an entity in the db --- // + LOGGER.info("Test insert status"); + UUID resultUuid = UUID.randomUUID(); + dynamicSecurityAnalysisResultService.insertStatus(List.of(resultUuid), DynamicMarginCalculationStatus.SUCCEED); + + Optional insertedResultEntityOpt = resultRepository.findById(resultUuid); + assertThat(insertedResultEntityOpt).isPresent(); + LOGGER.info("Expected result status = {}", DynamicMarginCalculationStatus.SUCCEED); + LOGGER.info("Actual inserted result status = {}", insertedResultEntityOpt.get().getStatus()); + assertThat(insertedResultEntityOpt.get().getStatus()).isSameAs(DynamicMarginCalculationStatus.SUCCEED); + + // --- get status of the entity -- // + LOGGER.info("Test find status"); + DynamicMarginCalculationStatus status = dynamicSecurityAnalysisResultService.findStatus(resultUuid); + + LOGGER.info("Expected result status = {}", DynamicMarginCalculationStatus.SUCCEED); + LOGGER.info("Actual get result status = {}", insertedResultEntityOpt.get().getStatus()); + assertThat(status).isEqualTo(DynamicMarginCalculationStatus.SUCCEED); + + // --- update the entity --- // + LOGGER.info("Test update status"); + List updatedResultUuids = dynamicSecurityAnalysisResultService.updateStatus(List.of(resultUuid), DynamicMarginCalculationStatus.NOT_DONE); + + Optional updatedResultEntityOpt = resultRepository.findById(updatedResultUuids.get(0)); + + // status must be changed + assertThat(updatedResultEntityOpt).isPresent(); + LOGGER.info("Expected result status = {}", DynamicMarginCalculationStatus.NOT_DONE); + LOGGER.info("Actual updated result status = {}", updatedResultEntityOpt.get().getStatus()); + assertThat(updatedResultEntityOpt.get().getStatus()).isSameAs(DynamicMarginCalculationStatus.NOT_DONE); + + // --- update entity with non-existing UUID --- // + LOGGER.info("Test update status with non-existing UUID"); + UUID nonExistingUuid = UUID.randomUUID(); + + // should throw DynamicMarginCalculationException since the UUID doesn't exist + assertThatThrownBy(() -> dynamicSecurityAnalysisResultService.updateResult(nonExistingUuid, DynamicMarginCalculationStatus.FAILED)) + .isInstanceOf(DynamicMarginCalculationException.class) + .hasMessageContaining("Result uuid not found: " + nonExistingUuid); + + LOGGER.info("Non-existing UUID update threw expected exception"); + + // --- delete result --- // + LOGGER.info("Test delete a result"); + dynamicSecurityAnalysisResultService.delete(resultUuid); + + Optional foundResultEntity = resultRepository.findById(resultUuid); + assertThat(foundResultEntity).isNotPresent(); + + // --- get status of a deleted entity --- // + status = dynamicSecurityAnalysisResultService.findStatus(resultUuid); + assertThat(status).isNull(); + + // --- delete all --- // + LOGGER.info("Test delete all results"); + resultRepository.saveAllAndFlush(List.of( + new DynamicMarginCalculationResultEntity(UUID.randomUUID(), DynamicMarginCalculationStatus.RUNNING), + new DynamicMarginCalculationResultEntity(UUID.randomUUID(), DynamicMarginCalculationStatus.RUNNING) + )); + + dynamicSecurityAnalysisResultService.deleteAll(); + assertThat(resultRepository.findAll()).isEmpty(); + } +} diff --git a/src/test/resources/application-default.yml b/src/test/resources/application-default.yml new file mode 100644 index 0000000..beccab7 --- /dev/null +++ b/src/test/resources/application-default.yml @@ -0,0 +1,19 @@ +spring: + jpa: + database: H2 + properties: + dialect: org.hibernate.dialect.H2Dialect + hibernate.format_sql: true + +logging: + level: + org.springframework.orm.jpa: INFO + org.springframework.transaction: INFO + org.hibernate.SQL: INFO + org.hibernate.orm.jdbc.bind: INFO + +powsybl-ws: + database: + vendor: h2:mem + query: ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL + hostPort: ":" diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..4c9c04f --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,38 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + +