Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@
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;
Expand Down Expand Up @@ -85,7 +84,10 @@ public Response getAllCompositeStructures() {
log.info("Received request to get all composite structures");
List<CompositeStructure> compositeStructures = compositeService.getAllCompositeStructures();
List<CompositeStructureDto> 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();
}
Expand All @@ -108,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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CompositeNamespaceModifyIndex> findByBaselineName(String baselineName) {
return compositeNamespaceModifyIndexesRepository.findByBaseline(baselineName);
}

@Transactional
@Override
public void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex) {
compositeNamespaceModifyIndexesRepository.persist(compositeNamespacesModifyIndex);
}
}
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -16,4 +19,8 @@ public class CompositeStructureDto {
@Schema(description = "Namespaces that are included in composite structure (baseline and satellites)", required = true)
@NonNull
private Set<String> namespaces;

@Schema(description = "Index of composite structure (changes on each composite struct modification)", required = true)
@PositiveOrZero
private Long modifyIndex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.netcracker.cloud.dbaas.entity.pg.composite;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

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)
private long modifyIndex;

public CompositeNamespaceModifyIndex(CompositeNamespace compositeNamespace, long modifyIndex) {
this.compositeNamespace = compositeNamespace;
this.modifyIndex = modifyIndex;
}
}
Original file line number Diff line number Diff line change
@@ -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<CompositeNamespaceModifyIndex> findByBaselineName(String baselineName);

void save(CompositeNamespaceModifyIndex compositeNamespacesModifyIndex);

}
Original file line number Diff line number Diff line change
@@ -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<CompositeNamespaceModifyIndex, UUID> {

public Optional<CompositeNamespaceModifyIndex> findByBaseline(String baseline) {
return find(
"compositeNamespace.baseline",
baseline
).firstResultOptional();
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -19,21 +22,34 @@
@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) {
if (compositeRequest.getModifyIndex() != null) {
Optional<CompositeNamespaceModifyIndex> 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());
List<CompositeNamespace> compositeNamespaces = compositeRequest.getNamespaces().stream()
.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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
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.util.Collections;
Expand All @@ -22,9 +21,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.*;
Expand All @@ -34,7 +31,7 @@
@TestHTTPEndpoint(CompositeController.class)
class CompositeControllerTest {

@InjectMock
@InjectSpy
CompositeNamespaceService compositeService;

@Test
Expand Down Expand Up @@ -67,8 +64,6 @@ void testGetAllCompositeStructures_EmptyList() {
.body(is("[]"));

verify(compositeService).getAllCompositeStructures();
verifyNoMoreInteractions(compositeService);

}

@Test
Expand Down Expand Up @@ -121,7 +116,11 @@ void testGetCompositeById_InternalServerError() {

@Test
void testSaveOrUpdateComposite_Success() throws JsonProcessingException {
CompositeStructureDto request = new CompositeStructureDto("test-id", Set.of("ns-1", "ns-2"));
compositeService.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))
Expand All @@ -132,12 +131,14 @@ void testSaveOrUpdateComposite_Success() throws JsonProcessingException {
verify(compositeService).saveOrUpdateCompositeStructure(request);
verify(compositeService).getBaselineByNamespace("ns-1");
verify(compositeService).getBaselineByNamespace("ns-2");
verifyNoMoreInteractions(compositeService);
}

@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)
Expand All @@ -152,7 +153,10 @@ void testSaveOrUpdateComposite_IdBlank() throws JsonProcessingException {

@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)
Expand All @@ -167,7 +171,10 @@ void testSaveOrUpdateComposite_NamespacesEmpty() throws JsonProcessingException

@Test
void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingException {
CompositeStructureDto request = new CompositeStructureDto("test-id", Set.of("ns-1", "ns-2"));
CompositeStructureDto request = CompositeStructureDto.builder()
.id("test-id")
.namespaces(Set.of("ns-1", "ns-2"))
.build();
when(compositeService.getBaselineByNamespace("ns-2")).thenReturn(Optional.of("existing-id"));

given().auth().preemptive().basic("cluster-dba", "someDefaultPassword")
Expand All @@ -181,6 +188,28 @@ void testSaveOrUpdateComposite_NamespaceConflict() throws JsonProcessingExceptio
verify(compositeService, 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"), 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"), 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"), 1L)))
.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() {
Expand All @@ -192,9 +221,9 @@ void testDeleteCompositeById_Success() {
.then()
.statusCode(NO_CONTENT.getStatusCode());


verify(compositeService).getCompositeStructure("test-id");
verify(compositeService).deleteCompositeStructure("test-id");
verifyNoMoreInteractions(compositeService);
}

@Test
Expand Down
Loading
Loading