From c2f39200e56f2a30b8970ff87f41730cb8294ced Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Mon, 29 Dec 2025 13:40:03 +0300 Subject: [PATCH 01/11] feat: check modify index before composite structure apply --- .../composite/CompositeController.java | 26 +++++++++++++++---- .../dto/composite/CompositeStructureDto.java | 3 +++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java index c84b272e..ca276958 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java @@ -5,25 +5,26 @@ import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; import com.netcracker.cloud.dbaas.exceptions.NamespaceCompositeValidationException; import com.netcracker.cloud.dbaas.service.composite.CompositeNamespaceService; -import org.eclipse.microprofile.openapi.annotations.Operation; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import jakarta.annotation.security.RolesAllowed; import jakarta.transaction.Transactional; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import static com.netcracker.cloud.dbaas.Constants.DB_CLIENT; import static com.netcracker.cloud.dbaas.controller.error.Utils.createTmfErrorResponse; +import static jakarta.ws.rs.core.Response.Status.CONFLICT; import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; @Slf4j @@ -32,6 +33,7 @@ @RolesAllowed(DB_CLIENT) public class CompositeController { + private final AtomicLong lastAppliedIndex = new AtomicLong(0); private final CompositeNamespaceService compositeService; public CompositeController(CompositeNamespaceService compositeService) { @@ -49,10 +51,12 @@ public CompositeController(CompositeNamespaceService compositeService) { "associated with another composite structure"), @APIResponse(responseCode = "500", description = "Internal server error") }) + @POST @Transactional public Response saveOrUpdateComposite(CompositeStructureDto compositeRequest) { log.info("Received request to save or update composite {}", compositeRequest); + if (StringUtils.isBlank(compositeRequest.getId())) { throw new NamespaceCompositeValidationException(Source.builder().pointer("/id").build(), "id field can't be empty"); } @@ -67,7 +71,19 @@ public Response saveOrUpdateComposite(CompositeStructureDto compositeRequest) { 409); } } - compositeService.saveOrUpdateCompositeStructure(compositeRequest); + + long currentIndex; + do { + currentIndex = lastAppliedIndex.get(); + if (compositeRequest.getIndex() < currentIndex) { + throw new NamespaceCompositeValidationException( + Source.builder().build(), + "New composite version %d is less that last applied version %d".formatted(compositeRequest.getIndex(), currentIndex), + CONFLICT.getStatusCode() + ); + } + compositeService.saveOrUpdateCompositeStructure(compositeRequest); + } while (!lastAppliedIndex.compareAndSet(currentIndex, compositeRequest.getIndex())); return Response.noContent().build(); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java index 8737fc4e..1abb981b 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java @@ -16,4 +16,7 @@ public class CompositeStructureDto { @Schema(description = "Namespaces that are included in composite structure (baseline and satellites)", required = true) @NonNull private Set namespaces; + + @Schema(description = "Index of composite structure (changes on each composite struct modification)", required = true) + private Long index; } From 0eff0694a624715834997f3558408da06075153b Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Mon, 29 Dec 2025 13:49:06 +0300 Subject: [PATCH 02/11] feat: check modify index before composite structure apply --- .../cloud/dbaas/dto/composite/CompositeStructureDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java index 1abb981b..f4a21c95 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java @@ -18,5 +18,5 @@ public class CompositeStructureDto { private Set namespaces; @Schema(description = "Index of composite structure (changes on each composite struct modification)", required = true) - private Long index; + private long index; } From 16830d86643e3851b92946fd25da28d0ffa327a6 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Mon, 29 Dec 2025 15:03:58 +0300 Subject: [PATCH 03/11] feat: check modify index before composite structure apply --- .../composite/CompositeController.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java index ca276958..043efcfd 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import static com.netcracker.cloud.dbaas.Constants.DB_CLIENT; @@ -33,7 +35,7 @@ @RolesAllowed(DB_CLIENT) public class CompositeController { - private final AtomicLong lastAppliedIndex = new AtomicLong(0); + private final ConcurrentMap lastAppliedIndexes = new ConcurrentHashMap<>(); private final CompositeNamespaceService compositeService; public CompositeController(CompositeNamespaceService compositeService) { @@ -72,18 +74,19 @@ public Response saveOrUpdateComposite(CompositeStructureDto compositeRequest) { } } - long currentIndex; - do { - currentIndex = lastAppliedIndex.get(); - if (compositeRequest.getIndex() < currentIndex) { + lastAppliedIndexes.compute(compositeRequest.getId(), (id, atomicIndex) -> { + AtomicLong index = (atomicIndex == null) ? new AtomicLong(0) : atomicIndex; + if (compositeRequest.getIndex() < index.get()) { throw new NamespaceCompositeValidationException( Source.builder().build(), - "New composite version %d is less that last applied version %d".formatted(compositeRequest.getIndex(), currentIndex), + "New composite version %d is less that last applied version %d".formatted(compositeRequest.getIndex(), index.get()), CONFLICT.getStatusCode() ); } compositeService.saveOrUpdateCompositeStructure(compositeRequest); - } while (!lastAppliedIndex.compareAndSet(currentIndex, compositeRequest.getIndex())); + index.set(compositeRequest.getIndex()); + return index; + }); return Response.noContent().build(); } From ef4dabbc8bf7e8447c9f4f6f6d65c287c2d0dd48 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Tue, 13 Jan 2026 14:04:07 +0300 Subject: [PATCH 04/11] feat: check modify index before composite structure apply --- .../composite/CompositeController.java | 33 ++---- ...spaceModifyIndexesDbaasRepositoryImpl.java | 28 +++++ .../dto/composite/CompositeStructureDto.java | 10 +- .../CompositeNamespaceModifyIndex.java | 31 ++++++ ...NamespaceModifyIndexesDbaasRepository.java | 12 +++ ...ositeNamespaceModifyIndexesRepository.java | 19 ++++ .../composite/CompositeNamespaceService.java | 19 +++- ...34__Composite_Namespace_Modify_Indexes.sql | 7 ++ .../composite/CompositeControllerTest.java | 102 +++++++++++------- .../CompositeNamespaceServiceTest.java | 17 ++- docs/OpenAPI.json | 8 +- 11 files changed, 217 insertions(+), 69 deletions(-) create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java create mode 100644 dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java create mode 100644 dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java index 043efcfd..df91345d 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/controller/composite/CompositeController.java @@ -20,13 +20,9 @@ import java.util.List; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; import static com.netcracker.cloud.dbaas.Constants.DB_CLIENT; import static com.netcracker.cloud.dbaas.controller.error.Utils.createTmfErrorResponse; -import static jakarta.ws.rs.core.Response.Status.CONFLICT; import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; @Slf4j @@ -35,7 +31,6 @@ @RolesAllowed(DB_CLIENT) public class CompositeController { - private final ConcurrentMap lastAppliedIndexes = new ConcurrentHashMap<>(); private final CompositeNamespaceService compositeService; public CompositeController(CompositeNamespaceService compositeService) { @@ -53,12 +48,10 @@ public CompositeController(CompositeNamespaceService compositeService) { "associated with another composite structure"), @APIResponse(responseCode = "500", description = "Internal server error") }) - @POST @Transactional public Response saveOrUpdateComposite(CompositeStructureDto compositeRequest) { log.info("Received request to save or update composite {}", compositeRequest); - if (StringUtils.isBlank(compositeRequest.getId())) { throw new NamespaceCompositeValidationException(Source.builder().pointer("/id").build(), "id field can't be empty"); } @@ -73,20 +66,7 @@ public Response saveOrUpdateComposite(CompositeStructureDto compositeRequest) { 409); } } - - lastAppliedIndexes.compute(compositeRequest.getId(), (id, atomicIndex) -> { - AtomicLong index = (atomicIndex == null) ? new AtomicLong(0) : atomicIndex; - if (compositeRequest.getIndex() < index.get()) { - throw new NamespaceCompositeValidationException( - Source.builder().build(), - "New composite version %d is less that last applied version %d".formatted(compositeRequest.getIndex(), index.get()), - CONFLICT.getStatusCode() - ); - } - compositeService.saveOrUpdateCompositeStructure(compositeRequest); - index.set(compositeRequest.getIndex()); - return index; - }); + compositeService.saveOrUpdateCompositeStructure(compositeRequest); return Response.noContent().build(); } @@ -104,7 +84,10 @@ public Response getAllCompositeStructures() { log.info("Received request to get all composite structures"); List compositeStructures = compositeService.getAllCompositeStructures(); List compositeStructureResponse = compositeStructures.stream() - .map(compositeStructure -> new CompositeStructureDto(compositeStructure.getBaseline(), compositeStructure.getNamespaces())) + .map(compositeStructure -> CompositeStructureDto.builder() + .id(compositeStructure.getBaseline()) + .namespaces(compositeStructure.getNamespaces()) + .build()) .toList(); return Response.ok(compositeStructureResponse).build(); } @@ -127,7 +110,11 @@ public Response getCompositeById(@PathParam("compositeId") String compositeId) { if (composite.isEmpty()) { return getNotFoundTmfErrorResponse(compositeId); } - return Response.ok(new CompositeStructureDto(composite.get().getBaseline(), composite.get().getNamespaces())).build(); + return Response.ok(CompositeStructureDto.builder() + .id(composite.get().getBaseline()) + .namespaces(composite.get().getNamespaces()) + .build()) + .build(); } @NotNull diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java new file mode 100644 index 00000000..97a0d485 --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java @@ -0,0 +1,28 @@ +package com.netcracker.cloud.dbaas.dao.jpa; + +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; +import com.netcracker.cloud.dbaas.repositories.pg.jpa.CompositeNamespaceModifyIndexesRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; + +import java.util.Optional; + +@AllArgsConstructor +@ApplicationScoped +public class CompositeNamespaceModifyIndexesDbaasRepositoryImpl implements CompositeNamespaceModifyIndexesDbaasRepository { + + private CompositeNamespaceModifyIndexesRepository compositeNamespaceModifyIndexesRepository; + + @Override + public Optional findByBaselineName(String baselineName) { + return compositeNamespaceModifyIndexesRepository.findByBaseline(baselineName); + } + + @Transactional + @Override + public void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex) { + compositeNamespaceModifyIndexesRepository.persist(compositeNamespacesModifyIndex); + } +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java index f4a21c95..b6429f62 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java @@ -1,12 +1,15 @@ package com.netcracker.cloud.dbaas.dto.composite; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.*; import org.eclipse.microprofile.openapi.annotations.media.Schema; -import lombok.Data; -import lombok.NonNull; +import java.math.BigDecimal; import java.util.Set; @Data +@AllArgsConstructor +@Builder public class CompositeStructureDto { @Schema(description = "Composite identifier. Usually it's baseline or origin baseline in blue-green scheme", required = true) @@ -18,5 +21,6 @@ public class CompositeStructureDto { private Set namespaces; @Schema(description = "Index of composite structure (changes on each composite struct modification)", required = true) - private long index; + @PositiveOrZero + private BigDecimal modifyIndex; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java new file mode 100644 index 00000000..3378830f --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java @@ -0,0 +1,31 @@ +package com.netcracker.cloud.dbaas.entity.pg.composite; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@NoArgsConstructor +@Table(name = "composite_namespace_modify_indexes") +@Entity(name = "CompositeNamespaceModifyIndex") +public class CompositeNamespaceModifyIndex { + @Id + @Column(name = "composite_namespace_id") + public UUID id; + + @OneToOne(fetch = FetchType.LAZY) + @MapsId + @JoinColumn(name = "composite_namespace_id") + private CompositeNamespace compositeNamespace; + + @Column(name = "modify_index", nullable = false, precision = 20) + private BigDecimal modifyIndex; + + public CompositeNamespaceModifyIndex(CompositeNamespace compositeNamespace, BigDecimal modifyIndex) { + this.compositeNamespace = compositeNamespace; + this.modifyIndex = modifyIndex; + } +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java new file mode 100644 index 00000000..07fb4dca --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java @@ -0,0 +1,12 @@ +package com.netcracker.cloud.dbaas.repositories.dbaas; + +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; + +import java.util.Optional; + +public interface CompositeNamespaceModifyIndexesDbaasRepository { + Optional findByBaselineName(String baselineName); + + void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex); + +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java new file mode 100644 index 00000000..63fd1f9d --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java @@ -0,0 +1,19 @@ +package com.netcracker.cloud.dbaas.repositories.pg.jpa; + +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.Optional; +import java.util.UUID; + +@ApplicationScoped +public class CompositeNamespaceModifyIndexesRepository implements PanacheRepositoryBase { + + public Optional findByBaseline(String baseline) { + return find( + "compositeNamespace.baseline", + baseline + ).firstResultOptional(); + } +} diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java index d082827f..77b69c40 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java @@ -1,12 +1,15 @@ package com.netcracker.cloud.dbaas.service.composite; +import com.netcracker.cloud.dbaas.dto.Source; import com.netcracker.cloud.dbaas.dto.composite.CompositeStructureDto; import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespace; +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; +import com.netcracker.cloud.dbaas.exceptions.NamespaceCompositeValidationException; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceDbaasRepository; +import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; - import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -19,14 +22,20 @@ @ApplicationScoped public class CompositeNamespaceService { - private CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository; + private final CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository; + private final CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository; - public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository) { + public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository, CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository) { this.compositeNamespaceDbaasRepository = compositeNamespaceDbaasRepository; + this.compositeNamespaceModifyIndexesDbaasRepository = compositeNamespaceModifyIndexesDbaasRepository; } @Transactional public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeRequest) { + Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); + if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex().compareTo(currentModifyIndex.get().getModifyIndex()) < 0) { + throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); + } deleteCompositeStructure(compositeRequest.getId()); compositeNamespaceDbaasRepository.flush(); // need to flush because jpa first tries to save data without deleting it compositeRequest.getNamespaces().add(compositeRequest.getId()); @@ -34,6 +43,10 @@ public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeReques .map(ns -> buildCompositeNamespace(compositeRequest, ns)) .toList(); compositeNamespaceDbaasRepository.saveAll(compositeNamespaces); + if (compositeRequest.getModifyIndex() != null) { + compositeNamespaceDbaasRepository.findBaselineByNamespace(compositeRequest.getId()) + .ifPresent(compositeNamespace -> compositeNamespaceModifyIndexesDbaasRepository.save(new CompositeNamespaceModifyIndex(compositeNamespace, compositeRequest.getModifyIndex()))); + } } @NotNull diff --git a/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql new file mode 100644 index 00000000..cbe8471d --- /dev/null +++ b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql @@ -0,0 +1,7 @@ +create table if not exists composite_namespace_modify_indexes +( + composite_namespace_id uuid primary key + references composite_namespace(id) + on delete cascade, + modify_index numeric(20) not null check (modify_index >= 0) +); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java index 8603dd42..4febe763 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java @@ -6,15 +6,15 @@ import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; import com.netcracker.cloud.dbaas.integration.config.PostgresqlContainerResource; import com.netcracker.cloud.dbaas.service.composite.CompositeNamespaceService; -import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectSpy; import io.restassured.common.mapper.TypeRef; import jakarta.ws.rs.core.MediaType; - import org.junit.jupiter.api.Test; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -22,9 +22,7 @@ import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.*; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.*; @@ -34,13 +32,13 @@ @TestHTTPEndpoint(CompositeController.class) class CompositeControllerTest { - @InjectMock - CompositeNamespaceService compositeService; + @InjectSpy + CompositeNamespaceService compositeServiceMock; @Test void testGetAllCompositeStructures_Success() { CompositeStructure expected = new CompositeStructure("ns-1", Set.of("ns-1", "ns-2")); - when(compositeService.getAllCompositeStructures()) + when(compositeServiceMock.getAllCompositeStructures()) .thenReturn(List.of(expected)); List allCompositeStructures = given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") @@ -58,7 +56,7 @@ void testGetAllCompositeStructures_Success() { @Test void testGetAllCompositeStructures_EmptyList() { - when(compositeService.getAllCompositeStructures()).thenReturn(Collections.emptyList()); + when(compositeServiceMock.getAllCompositeStructures()).thenReturn(Collections.emptyList()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get() @@ -66,14 +64,12 @@ void testGetAllCompositeStructures_EmptyList() { .statusCode(OK.getStatusCode()) .body(is("[]")); - verify(compositeService).getAllCompositeStructures(); - verifyNoMoreInteractions(compositeService); - + verify(compositeServiceMock).getAllCompositeStructures(); } @Test void testGetAllCompositeStructures_InternalServerError() { - when(compositeService.getAllCompositeStructures()).thenThrow(new RuntimeException("Internal Server Error")); + when(compositeServiceMock.getAllCompositeStructures()).thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get() @@ -86,7 +82,7 @@ void testGetAllCompositeStructures_InternalServerError() { @Test void testGetCompositeById_Success() { CompositeStructure expected = new CompositeStructure("test-id", Set.of("ns-1", "ns-2")); - when(compositeService.getCompositeStructure("test-id")).thenReturn(Optional.of(expected)); + when(compositeServiceMock.getCompositeStructure("test-id")).thenReturn(Optional.of(expected)); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/test-id") @@ -99,7 +95,7 @@ void testGetCompositeById_Success() { @Test void testGetCompositeById_NotFound() { - when(compositeService.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); + when(compositeServiceMock.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/non-existent-id") @@ -109,7 +105,7 @@ void testGetCompositeById_NotFound() { @Test void testGetCompositeById_InternalServerError() { - when(compositeService.getCompositeStructure("error-id")).thenThrow(new RuntimeException("Internal Server Error")); + when(compositeServiceMock.getCompositeStructure("error-id")).thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/error-id") @@ -121,7 +117,11 @@ void testGetCompositeById_InternalServerError() { @Test void testSaveOrUpdateComposite_Success() throws JsonProcessingException { - CompositeStructureDto request = new CompositeStructureDto("test-id", Set.of("ns-1", "ns-2")); + compositeServiceMock.deleteCompositeStructure("ns-1"); + CompositeStructureDto request = CompositeStructureDto.builder() + .id("ns-1") + .namespaces(Set.of("ns-1", "ns-2")) + .build(); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) .body((new ObjectMapper()).writeValueAsString(request)) @@ -129,15 +129,17 @@ void testSaveOrUpdateComposite_Success() throws JsonProcessingException { .then() .statusCode(NO_CONTENT.getStatusCode()); - verify(compositeService).saveOrUpdateCompositeStructure(request); - verify(compositeService).getBaselineByNamespace("ns-1"); - verify(compositeService).getBaselineByNamespace("ns-2"); - verifyNoMoreInteractions(compositeService); + verify(compositeServiceMock).saveOrUpdateCompositeStructure(request); + verify(compositeServiceMock).getBaselineByNamespace("ns-1"); + verify(compositeServiceMock).getBaselineByNamespace("ns-2"); } @Test void testSaveOrUpdateComposite_IdBlank() throws JsonProcessingException { - CompositeStructureDto request = new CompositeStructureDto("", Set.of("ns-1", "ns-2")); + CompositeStructureDto request = CompositeStructureDto.builder() + .id("") + .namespaces(Set.of("ns-1", "ns-2")) + .build(); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) @@ -147,12 +149,15 @@ void testSaveOrUpdateComposite_IdBlank() throws JsonProcessingException { .statusCode(BAD_REQUEST.getStatusCode()) .body("message", is("Validation error: 'id field can't be empty'")); - verifyNoInteractions(compositeService); + verifyNoInteractions(compositeServiceMock); } @Test void testSaveOrUpdateComposite_NamespacesEmpty() throws JsonProcessingException { - CompositeStructureDto request = new CompositeStructureDto("test-id", Collections.emptySet()); + CompositeStructureDto request = CompositeStructureDto.builder() + .id("test-id") + .namespaces(Collections.emptySet()) + .build(); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) @@ -162,13 +167,16 @@ void testSaveOrUpdateComposite_NamespacesEmpty() throws JsonProcessingException .statusCode(BAD_REQUEST.getStatusCode()) .body("message", is("Validation error: 'namespace field can't be empty'")); - verifyNoInteractions(compositeService); + verifyNoInteractions(compositeServiceMock); } @Test void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingException { - CompositeStructureDto request = new CompositeStructureDto("test-id", Set.of("ns-1", "ns-2")); - when(compositeService.getBaselineByNamespace("ns-2")).thenReturn(Optional.of("existing-id")); + CompositeStructureDto request = CompositeStructureDto.builder() + .id("test-id") + .namespaces(Set.of("ns-1", "ns-2")) + .build(); + when(compositeServiceMock.getBaselineByNamespace("ns-2")).thenReturn(Optional.of("existing-id")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) @@ -178,13 +186,35 @@ void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingExceptio .statusCode(CONFLICT.getStatusCode()) .body("message", is("Validation error: 'can't save or update composite structure because ns-2 namespace is registered in another composite'")); - verify(compositeService, never()).saveOrUpdateCompositeStructure(request); + verify(compositeServiceMock, never()).saveOrUpdateCompositeStructure(request); } + @Test + void testSaveOrUpdateComposite_WrongModifyIndex() throws JsonProcessingException { + given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") + .contentType(MediaType.APPLICATION_JSON) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.ONE))) + .when().post() + .then() + .statusCode(NO_CONTENT.getStatusCode()); + given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") + .contentType(MediaType.APPLICATION_JSON) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.TWO))) + .when().post() + .then() + .statusCode(NO_CONTENT.getStatusCode()); + given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") + .contentType(MediaType.APPLICATION_JSON) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.ONE))) + .when().post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", is("Validation error: 'new modify index '1' should be greater than current index '2''")); + } @Test void testDeleteCompositeById_Success() { - when(compositeService.getCompositeStructure("test-id")) + when(compositeServiceMock.getCompositeStructure("test-id")) .thenReturn(Optional.of(new CompositeStructure("test-id", Set.of("test-id", "ns-1")))); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") @@ -192,27 +222,27 @@ void testDeleteCompositeById_Success() { .then() .statusCode(NO_CONTENT.getStatusCode()); - verify(compositeService).getCompositeStructure("test-id"); - verify(compositeService).deleteCompositeStructure("test-id"); - verifyNoMoreInteractions(compositeService); + + verify(compositeServiceMock).getCompositeStructure("test-id"); + verify(compositeServiceMock).deleteCompositeStructure("test-id"); } @Test void testDeleteCompositeById_NotFound() { - when(compositeService.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); + when(compositeServiceMock.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().delete("/non-existent-id/delete") .then() .statusCode(NOT_FOUND.getStatusCode()); - verify(compositeService).getCompositeStructure("non-existent-id"); - verifyNoMoreInteractions(compositeService); + verify(compositeServiceMock).getCompositeStructure("non-existent-id"); + verifyNoMoreInteractions(compositeServiceMock); } @Test void testDeleteCompositeById_InternalServerError() { - when(compositeService.getCompositeStructure("error-id")) + when(compositeServiceMock.getCompositeStructure("error-id")) .thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java index fdca7ce2..a6c19c58 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java @@ -4,6 +4,7 @@ import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespace; import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceDbaasRepository; +import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -11,6 +12,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.math.BigDecimal; import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -22,26 +24,35 @@ class CompositeNamespaceServiceTest { @Mock private CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository; + @Mock + private CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository; + @InjectMocks private CompositeNamespaceService compositeNamespaceService; @Test void testSaveOrUpdateCompositeStructure_Success() { - CompositeStructureDto compositeRequest = new CompositeStructureDto("test-id", new HashSet<>(Set.of("ns-1", "ns-2"))); + CompositeStructureDto compositeRequest = CompositeStructureDto.builder() + .id("test-id") + .namespaces(new HashSet<>(Set.of("ns-1", "ns-2"))) + .modifyIndex(BigDecimal.ONE) + .build(); assertDoesNotThrow(() -> compositeNamespaceService.saveOrUpdateCompositeStructure(compositeRequest)); verify(compositeNamespaceDbaasRepository, times(1)).deleteByBaseline("test-id"); - ArgumentCaptor compositeNamespaceCaptureList = ArgumentCaptor.forClass(List.class); + ArgumentCaptor> compositeNamespaceCaptureList = ArgumentCaptor.forClass(List.class); verify(compositeNamespaceDbaasRepository, times(1)).saveAll(compositeNamespaceCaptureList.capture()); List compositeNamespaceList = compositeNamespaceCaptureList.getAllValues().stream().flatMap(Collection::stream).toList(); assertEquals(3, compositeNamespaceList.size()); - HashSet expectedNs = new HashSet(Arrays.asList("test-id", "ns-1", "ns-2")); + HashSet expectedNs = new HashSet<>(Arrays.asList("test-id", "ns-1", "ns-2")); for (CompositeNamespace compositeNamespace : compositeNamespaceList) { assertEquals("test-id", compositeNamespace.getBaseline()); assertTrue(expectedNs.remove(compositeNamespace.getNamespace()), "list does not contain %s but should".formatted(compositeNamespace.getNamespace())); } + + verify(compositeNamespaceModifyIndexesDbaasRepository).findByBaselineName("test-id"); } @Test diff --git a/docs/OpenAPI.json b/docs/OpenAPI.json index f45181c5..895c7e93 100644 --- a/docs/OpenAPI.json +++ b/docs/OpenAPI.json @@ -680,7 +680,8 @@ "type": "object", "required": [ "id", - "namespaces" + "namespaces", + "modifyIndex" ], "properties": { "id": { @@ -694,6 +695,11 @@ "type": "string" }, "description": "Namespaces that are included in composite structure (baseline and satellites)" + }, + "modifyIndex": { + "type": "number", + "description": "Index of composite structure (changes on each composite struct modification)", + "minimum": 0 } } }, From f9f9b4b23ecf8ce10d460fe48d13fb84ec71d649 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Tue, 13 Jan 2026 14:26:25 +0300 Subject: [PATCH 05/11] feat: check modify index before composite structure apply --- .../composite/CompositeControllerTest.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java index 4febe763..fcf7c005 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java @@ -33,12 +33,12 @@ class CompositeControllerTest { @InjectSpy - CompositeNamespaceService compositeServiceMock; + CompositeNamespaceService compositeService; @Test void testGetAllCompositeStructures_Success() { CompositeStructure expected = new CompositeStructure("ns-1", Set.of("ns-1", "ns-2")); - when(compositeServiceMock.getAllCompositeStructures()) + when(compositeService.getAllCompositeStructures()) .thenReturn(List.of(expected)); List allCompositeStructures = given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") @@ -56,7 +56,7 @@ void testGetAllCompositeStructures_Success() { @Test void testGetAllCompositeStructures_EmptyList() { - when(compositeServiceMock.getAllCompositeStructures()).thenReturn(Collections.emptyList()); + when(compositeService.getAllCompositeStructures()).thenReturn(Collections.emptyList()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get() @@ -64,12 +64,12 @@ void testGetAllCompositeStructures_EmptyList() { .statusCode(OK.getStatusCode()) .body(is("[]")); - verify(compositeServiceMock).getAllCompositeStructures(); + verify(compositeService).getAllCompositeStructures(); } @Test void testGetAllCompositeStructures_InternalServerError() { - when(compositeServiceMock.getAllCompositeStructures()).thenThrow(new RuntimeException("Internal Server Error")); + when(compositeService.getAllCompositeStructures()).thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get() @@ -82,7 +82,7 @@ void testGetAllCompositeStructures_InternalServerError() { @Test void testGetCompositeById_Success() { CompositeStructure expected = new CompositeStructure("test-id", Set.of("ns-1", "ns-2")); - when(compositeServiceMock.getCompositeStructure("test-id")).thenReturn(Optional.of(expected)); + when(compositeService.getCompositeStructure("test-id")).thenReturn(Optional.of(expected)); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/test-id") @@ -95,7 +95,7 @@ void testGetCompositeById_Success() { @Test void testGetCompositeById_NotFound() { - when(compositeServiceMock.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); + when(compositeService.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/non-existent-id") @@ -105,7 +105,7 @@ void testGetCompositeById_NotFound() { @Test void testGetCompositeById_InternalServerError() { - when(compositeServiceMock.getCompositeStructure("error-id")).thenThrow(new RuntimeException("Internal Server Error")); + when(compositeService.getCompositeStructure("error-id")).thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().get("/error-id") @@ -117,7 +117,7 @@ void testGetCompositeById_InternalServerError() { @Test void testSaveOrUpdateComposite_Success() throws JsonProcessingException { - compositeServiceMock.deleteCompositeStructure("ns-1"); + compositeService.deleteCompositeStructure("ns-1"); CompositeStructureDto request = CompositeStructureDto.builder() .id("ns-1") .namespaces(Set.of("ns-1", "ns-2")) @@ -129,9 +129,9 @@ void testSaveOrUpdateComposite_Success() throws JsonProcessingException { .then() .statusCode(NO_CONTENT.getStatusCode()); - verify(compositeServiceMock).saveOrUpdateCompositeStructure(request); - verify(compositeServiceMock).getBaselineByNamespace("ns-1"); - verify(compositeServiceMock).getBaselineByNamespace("ns-2"); + verify(compositeService).saveOrUpdateCompositeStructure(request); + verify(compositeService).getBaselineByNamespace("ns-1"); + verify(compositeService).getBaselineByNamespace("ns-2"); } @Test @@ -149,7 +149,7 @@ void testSaveOrUpdateComposite_IdBlank() throws JsonProcessingException { .statusCode(BAD_REQUEST.getStatusCode()) .body("message", is("Validation error: 'id field can't be empty'")); - verifyNoInteractions(compositeServiceMock); + verifyNoInteractions(compositeService); } @Test @@ -167,7 +167,7 @@ void testSaveOrUpdateComposite_NamespacesEmpty() throws JsonProcessingException .statusCode(BAD_REQUEST.getStatusCode()) .body("message", is("Validation error: 'namespace field can't be empty'")); - verifyNoInteractions(compositeServiceMock); + verifyNoInteractions(compositeService); } @Test @@ -176,7 +176,7 @@ void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingExceptio .id("test-id") .namespaces(Set.of("ns-1", "ns-2")) .build(); - when(compositeServiceMock.getBaselineByNamespace("ns-2")).thenReturn(Optional.of("existing-id")); + when(compositeService.getBaselineByNamespace("ns-2")).thenReturn(Optional.of("existing-id")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) @@ -186,7 +186,7 @@ void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingExceptio .statusCode(CONFLICT.getStatusCode()) .body("message", is("Validation error: 'can't save or update composite structure because ns-2 namespace is registered in another composite'")); - verify(compositeServiceMock, never()).saveOrUpdateCompositeStructure(request); + verify(compositeService, never()).saveOrUpdateCompositeStructure(request); } @Test @@ -214,7 +214,7 @@ void testSaveOrUpdateComposite_WrongModifyIndex() throws JsonProcessingException @Test void testDeleteCompositeById_Success() { - when(compositeServiceMock.getCompositeStructure("test-id")) + when(compositeService.getCompositeStructure("test-id")) .thenReturn(Optional.of(new CompositeStructure("test-id", Set.of("test-id", "ns-1")))); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") @@ -223,26 +223,26 @@ void testDeleteCompositeById_Success() { .statusCode(NO_CONTENT.getStatusCode()); - verify(compositeServiceMock).getCompositeStructure("test-id"); - verify(compositeServiceMock).deleteCompositeStructure("test-id"); + verify(compositeService).getCompositeStructure("test-id"); + verify(compositeService).deleteCompositeStructure("test-id"); } @Test void testDeleteCompositeById_NotFound() { - when(compositeServiceMock.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); + when(compositeService.getCompositeStructure("non-existent-id")).thenReturn(Optional.empty()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .when().delete("/non-existent-id/delete") .then() .statusCode(NOT_FOUND.getStatusCode()); - verify(compositeServiceMock).getCompositeStructure("non-existent-id"); - verifyNoMoreInteractions(compositeServiceMock); + verify(compositeService).getCompositeStructure("non-existent-id"); + verifyNoMoreInteractions(compositeService); } @Test void testDeleteCompositeById_InternalServerError() { - when(compositeServiceMock.getCompositeStructure("error-id")) + when(compositeService.getCompositeStructure("error-id")) .thenThrow(new RuntimeException("Internal Server Error")); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") From 395733a1e56906acc917ce881c35aa5255f921ee Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Tue, 13 Jan 2026 22:05:39 +0300 Subject: [PATCH 06/11] feat: check modify index before composite structure apply --- .../cloud/dbaas/dto/composite/CompositeStructureDto.java | 2 +- .../entity/pg/composite/CompositeNamespaceModifyIndex.java | 7 +++---- .../dbaas/service/composite/CompositeNamespaceService.java | 2 +- .../controller/composite/CompositeControllerTest.java | 7 +++---- .../service/composite/CompositeNamespaceServiceTest.java | 3 +-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java index b6429f62..d5159405 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dto/composite/CompositeStructureDto.java @@ -22,5 +22,5 @@ public class CompositeStructureDto { @Schema(description = "Index of composite structure (changes on each composite struct modification)", required = true) @PositiveOrZero - private BigDecimal modifyIndex; + private Long modifyIndex; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java index 3378830f..e7653ecd 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java @@ -4,7 +4,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.math.BigDecimal; import java.util.UUID; @Data @@ -21,10 +20,10 @@ public class CompositeNamespaceModifyIndex { @JoinColumn(name = "composite_namespace_id") private CompositeNamespace compositeNamespace; - @Column(name = "modify_index", nullable = false, precision = 20) - private BigDecimal modifyIndex; + @Column(name = "modify_index", nullable = false) + private long modifyIndex; - public CompositeNamespaceModifyIndex(CompositeNamespace compositeNamespace, BigDecimal modifyIndex) { + public CompositeNamespaceModifyIndex(CompositeNamespace compositeNamespace, long modifyIndex) { this.compositeNamespace = compositeNamespace; this.modifyIndex = modifyIndex; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java index 77b69c40..52efeeb7 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java @@ -33,7 +33,7 @@ public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeName @Transactional public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeRequest) { Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); - if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex().compareTo(currentModifyIndex.get().getModifyIndex()) < 0) { + if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex() < currentModifyIndex.get().getModifyIndex()) { throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); } deleteCompositeStructure(compositeRequest.getId()); diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java index fcf7c005..cc8d7265 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java @@ -14,7 +14,6 @@ import jakarta.ws.rs.core.MediaType; import org.junit.jupiter.api.Test; -import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -193,19 +192,19 @@ void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingExceptio void testSaveOrUpdateComposite_WrongModifyIndex() throws JsonProcessingException { given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) - .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.ONE))) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), 1L))) .when().post() .then() .statusCode(NO_CONTENT.getStatusCode()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) - .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.TWO))) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), 2L))) .when().post() .then() .statusCode(NO_CONTENT.getStatusCode()); given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") .contentType(MediaType.APPLICATION_JSON) - .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), BigDecimal.ONE))) + .body((new ObjectMapper()).writeValueAsString(new CompositeStructureDto("ns-1", Set.of("ns-1", "ns-2"), 1L))) .when().post() .then() .statusCode(BAD_REQUEST.getStatusCode()) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java index a6c19c58..04b9e221 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java @@ -12,7 +12,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.math.BigDecimal; import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -35,7 +34,7 @@ void testSaveOrUpdateCompositeStructure_Success() { CompositeStructureDto compositeRequest = CompositeStructureDto.builder() .id("test-id") .namespaces(new HashSet<>(Set.of("ns-1", "ns-2"))) - .modifyIndex(BigDecimal.ONE) + .modifyIndex(1L) .build(); assertDoesNotThrow(() -> compositeNamespaceService.saveOrUpdateCompositeStructure(compositeRequest)); From c198672bf6d4d89daee7924a77e4db7a85657b8e Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Wed, 14 Jan 2026 19:01:34 +0300 Subject: [PATCH 07/11] feat: check modify index before composite structure apply --- .../service/composite/CompositeNamespaceService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java index 52efeeb7..d45c8e97 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java @@ -32,10 +32,13 @@ public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeName @Transactional public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeRequest) { - Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); - if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex() < currentModifyIndex.get().getModifyIndex()) { - throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); + if (compositeRequest.getModifyIndex() != null) { + Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); + if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex() < currentModifyIndex.get().getModifyIndex()) { + throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); + } } + deleteCompositeStructure(compositeRequest.getId()); compositeNamespaceDbaasRepository.flush(); // need to flush because jpa first tries to save data without deleting it compositeRequest.getNamespaces().add(compositeRequest.getId()); From f2b4873f0de162a78cbe71b637cd42c0bdfb3e59 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Thu, 15 Jan 2026 18:10:31 +0300 Subject: [PATCH 08/11] feat: check modify index before composite structure apply --- .../composite/CompositeNamespaceService.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java index d45c8e97..c529a23c 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java @@ -9,6 +9,7 @@ import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceDbaasRepository; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -24,15 +25,20 @@ public class CompositeNamespaceService { private final CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository; private final CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository; + private final EntityManager entityManager; - public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository, CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository) { + public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository, + CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository, + EntityManager entityManager) { this.compositeNamespaceDbaasRepository = compositeNamespaceDbaasRepository; this.compositeNamespaceModifyIndexesDbaasRepository = compositeNamespaceModifyIndexesDbaasRepository; + this.entityManager = entityManager; } @Transactional public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeRequest) { if (compositeRequest.getModifyIndex() != null) { + txLock(compositeRequest.getId()); Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex() < currentModifyIndex.get().getModifyIndex()) { throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); @@ -111,4 +117,12 @@ public Optional getBaselineByNamespace(String namespace) { return compositeNamespaceDbaasRepository.findBaselineByNamespace(namespace) .map(CompositeNamespace::getBaseline); } + + private void txLock(String baseline) { + entityManager.createNativeQuery( + "SELECT pg_advisory_xact_lock(hashtext(:baseline))" + ) + .setParameter("baseline", baseline) + .getSingleResult(); + } } From 91deead87adb07966e6827876fbadfa07615af7e Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Thu, 15 Jan 2026 18:33:55 +0300 Subject: [PATCH 09/11] feat: check modify index before composite structure apply --- .../service/composite/CompositeNamespaceServiceTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java index 04b9e221..8cf7ac06 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceServiceTest.java @@ -5,6 +5,8 @@ import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceDbaasRepository; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -23,6 +25,9 @@ class CompositeNamespaceServiceTest { @Mock private CompositeNamespaceDbaasRepository compositeNamespaceDbaasRepository; + @Mock + private EntityManager entityManager; + @Mock private CompositeNamespaceModifyIndexesDbaasRepository compositeNamespaceModifyIndexesDbaasRepository; @@ -37,9 +42,11 @@ void testSaveOrUpdateCompositeStructure_Success() { .modifyIndex(1L) .build(); + Query query = mock(Query.class); + when(query.setParameter(anyString(), anyString())).thenReturn(query); + when(entityManager.createNativeQuery("SELECT pg_advisory_xact_lock(hashtext(:baseline))")).thenReturn(query); assertDoesNotThrow(() -> compositeNamespaceService.saveOrUpdateCompositeStructure(compositeRequest)); - verify(compositeNamespaceDbaasRepository, times(1)).deleteByBaseline("test-id"); ArgumentCaptor> compositeNamespaceCaptureList = ArgumentCaptor.forClass(List.class); verify(compositeNamespaceDbaasRepository, times(1)).saveAll(compositeNamespaceCaptureList.capture()); From c485779385bec690d3a69d470008d7e075345139 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Mon, 19 Jan 2026 16:25:03 +0300 Subject: [PATCH 10/11] feat: check modify index before composite structure apply --- .../composite/CompositeControllerTest.java | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java index cc8d7265..d0a8176d 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java @@ -10,20 +10,27 @@ import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.mockito.InjectSpy; +import io.restassured.RestAssured; import io.restassured.common.mapper.TypeRef; +import io.restassured.config.LogConfig; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; import jakarta.ws.rs.core.MediaType; import org.junit.jupiter.api.Test; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.IntStream; import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.Response.Status.*; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @QuarkusTest @@ -34,6 +41,9 @@ class CompositeControllerTest { @InjectSpy CompositeNamespaceService compositeService; + @Inject + EntityManager entityManager; + @Test void testGetAllCompositeStructures_Success() { CompositeStructure expected = new CompositeStructure("ns-1", Set.of("ns-1", "ns-2")); @@ -221,7 +231,6 @@ void testDeleteCompositeById_Success() { .then() .statusCode(NO_CONTENT.getStatusCode()); - verify(compositeService).getCompositeStructure("test-id"); verify(compositeService).deleteCompositeStructure("test-id"); } @@ -250,4 +259,38 @@ void testDeleteCompositeById_InternalServerError() { .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()) .body("message", is("Internal Server Error")); } + + @Test + void saveOrUpdateComposite_concurrent() { + RestAssured.config = RestAssured.config() + .logConfig(LogConfig.logConfig().enablePrettyPrinting(false)); + List> futures = + IntStream.range(0, 100) + .mapToObj(i -> + CompletableFuture.runAsync(() -> { + CompositeStructureDto request = CompositeStructureDto.builder() + .id("base") + .namespaces(Set.of("ns-%d".formatted(i))) + .modifyIndex(i == 50 ? 1000 : (long) ThreadLocalRandom.current().nextInt(1000)) + .build(); + try { + given().auth().preemptive().basic("cluster-dba", "someDefaultPassword") + .contentType(MediaType.APPLICATION_JSON) + .body((new ObjectMapper()).writeValueAsString(request)) + .when().post(); + } catch (JsonProcessingException e) { + fail("something went wrong", e); + } + } + ) + ).toList(); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + + Object singleResult = entityManager.createNativeQuery( + "SELECT MAX(modify_index) FROM composite_namespace_modify_indexes" + ) + .getSingleResult(); + assertEquals(BigDecimal.valueOf(1000), singleResult); + } } From 7539a7f3d8da20ac95ba5dbdb5344219092fd013 Mon Sep 17 00:00:00 2001 From: Evgenii Stetsenko Date: Mon, 19 Jan 2026 16:55:49 +0300 Subject: [PATCH 11/11] feat: check modify index before composite structure apply --- ...ompositeNamespaceModifyIndexesDbaasRepositoryImpl.java | 6 +++--- ...NamespaceModifyIndex.java => CompositeProperties.java} | 8 ++++---- .../CompositeNamespaceModifyIndexesDbaasRepository.java | 6 +++--- .../pg/jpa/CompositeNamespaceModifyIndexesRepository.java | 6 +++--- .../service/composite/CompositeNamespaceService.java | 6 +++--- .../V1.034__Composite_Namespace_Modify_Indexes.sql | 2 +- .../controller/composite/CompositeControllerTest.java | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) rename dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/{CompositeNamespaceModifyIndex.java => CompositeProperties.java} (71%) diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java index 97a0d485..4d0c77af 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/dao/jpa/CompositeNamespaceModifyIndexesDbaasRepositoryImpl.java @@ -1,6 +1,6 @@ package com.netcracker.cloud.dbaas.dao.jpa; -import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeProperties; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceModifyIndexesDbaasRepository; import com.netcracker.cloud.dbaas.repositories.pg.jpa.CompositeNamespaceModifyIndexesRepository; import jakarta.enterprise.context.ApplicationScoped; @@ -16,13 +16,13 @@ public class CompositeNamespaceModifyIndexesDbaasRepositoryImpl implements Compo private CompositeNamespaceModifyIndexesRepository compositeNamespaceModifyIndexesRepository; @Override - public Optional findByBaselineName(String baselineName) { + public Optional findByBaselineName(String baselineName) { return compositeNamespaceModifyIndexesRepository.findByBaseline(baselineName); } @Transactional @Override - public void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex) { + public void save(CompositeProperties compositeNamespacesModifyIndex) { compositeNamespaceModifyIndexesRepository.persist(compositeNamespacesModifyIndex); } } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeProperties.java similarity index 71% rename from dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java rename to dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeProperties.java index e7653ecd..c2a7692e 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeNamespaceModifyIndex.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/entity/pg/composite/CompositeProperties.java @@ -8,9 +8,9 @@ @Data @NoArgsConstructor -@Table(name = "composite_namespace_modify_indexes") -@Entity(name = "CompositeNamespaceModifyIndex") -public class CompositeNamespaceModifyIndex { +@Table(name = "composite_properties") +@Entity(name = "CompositeProperties") +public class CompositeProperties { @Id @Column(name = "composite_namespace_id") public UUID id; @@ -23,7 +23,7 @@ public class CompositeNamespaceModifyIndex { @Column(name = "modify_index", nullable = false) private long modifyIndex; - public CompositeNamespaceModifyIndex(CompositeNamespace compositeNamespace, long modifyIndex) { + public CompositeProperties(CompositeNamespace compositeNamespace, long modifyIndex) { this.compositeNamespace = compositeNamespace; this.modifyIndex = modifyIndex; } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java index 07fb4dca..d9c19e17 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/dbaas/CompositeNamespaceModifyIndexesDbaasRepository.java @@ -1,12 +1,12 @@ package com.netcracker.cloud.dbaas.repositories.dbaas; -import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeProperties; import java.util.Optional; public interface CompositeNamespaceModifyIndexesDbaasRepository { - Optional findByBaselineName(String baselineName); + Optional findByBaselineName(String baselineName); - void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex); + void save(CompositeProperties compositeNamespacesModifyIndex); } diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java index 63fd1f9d..d99b8bcc 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/repositories/pg/jpa/CompositeNamespaceModifyIndexesRepository.java @@ -1,6 +1,6 @@ package com.netcracker.cloud.dbaas.repositories.pg.jpa; -import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeProperties; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; @@ -8,9 +8,9 @@ import java.util.UUID; @ApplicationScoped -public class CompositeNamespaceModifyIndexesRepository implements PanacheRepositoryBase { +public class CompositeNamespaceModifyIndexesRepository implements PanacheRepositoryBase { - public Optional findByBaseline(String baseline) { + public Optional findByBaseline(String baseline) { return find( "compositeNamespace.baseline", baseline diff --git a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java index c529a23c..fbb6789f 100644 --- a/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java +++ b/dbaas/dbaas-aggregator/src/main/java/com/netcracker/cloud/dbaas/service/composite/CompositeNamespaceService.java @@ -3,7 +3,7 @@ import com.netcracker.cloud.dbaas.dto.Source; import com.netcracker.cloud.dbaas.dto.composite.CompositeStructureDto; import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespace; -import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeNamespaceModifyIndex; +import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeProperties; import com.netcracker.cloud.dbaas.entity.pg.composite.CompositeStructure; import com.netcracker.cloud.dbaas.exceptions.NamespaceCompositeValidationException; import com.netcracker.cloud.dbaas.repositories.dbaas.CompositeNamespaceDbaasRepository; @@ -39,7 +39,7 @@ public CompositeNamespaceService(CompositeNamespaceDbaasRepository compositeName public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeRequest) { if (compositeRequest.getModifyIndex() != null) { txLock(compositeRequest.getId()); - Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); + Optional currentModifyIndex = compositeNamespaceModifyIndexesDbaasRepository.findByBaselineName(compositeRequest.getId()); if (currentModifyIndex.isPresent() && compositeRequest.getModifyIndex() < currentModifyIndex.get().getModifyIndex()) { throw new NamespaceCompositeValidationException(Source.builder().pointer("/modifyIndex").build(), "new modify index '%s' should be greater than current index '%s'".formatted(compositeRequest.getModifyIndex(), currentModifyIndex.get().getModifyIndex())); } @@ -54,7 +54,7 @@ public void saveOrUpdateCompositeStructure(CompositeStructureDto compositeReques compositeNamespaceDbaasRepository.saveAll(compositeNamespaces); if (compositeRequest.getModifyIndex() != null) { compositeNamespaceDbaasRepository.findBaselineByNamespace(compositeRequest.getId()) - .ifPresent(compositeNamespace -> compositeNamespaceModifyIndexesDbaasRepository.save(new CompositeNamespaceModifyIndex(compositeNamespace, compositeRequest.getModifyIndex()))); + .ifPresent(compositeNamespace -> compositeNamespaceModifyIndexesDbaasRepository.save(new CompositeProperties(compositeNamespace, compositeRequest.getModifyIndex()))); } } diff --git a/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql index cbe8471d..b8097c47 100644 --- a/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql +++ b/dbaas/dbaas-aggregator/src/main/resources/db/migration/postgresql/V1.034__Composite_Namespace_Modify_Indexes.sql @@ -1,4 +1,4 @@ -create table if not exists composite_namespace_modify_indexes +create table if not exists composite_properties ( composite_namespace_id uuid primary key references composite_namespace(id) diff --git a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java index d0a8176d..5356c543 100644 --- a/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java +++ b/dbaas/dbaas-aggregator/src/test/java/com/netcracker/cloud/dbaas/controller/composite/CompositeControllerTest.java @@ -288,7 +288,7 @@ void saveOrUpdateComposite_concurrent() { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); Object singleResult = entityManager.createNativeQuery( - "SELECT MAX(modify_index) FROM composite_namespace_modify_indexes" + "SELECT MAX(modify_index) FROM composite_properties" ) .getSingleResult(); assertEquals(BigDecimal.valueOf(1000), singleResult);