Skip to content
Snippets Groups Projects
Commit 9e129d02 authored by Felix Reichenbach's avatar Felix Reichenbach
Browse files

Merge branch 'OZG-7909-persist-server-data' into 'main'

Ozg 7909 persist server data

See merge request !20
parents 832c9678 60e726dc
Branches
Tags
1 merge request!20Ozg 7909 persist server data
Showing
with 455 additions and 4 deletions
...@@ -51,11 +51,26 @@ ...@@ -51,11 +51,26 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- gRPC -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
</dependency>
<!-- Own projects --> <!-- Own projects -->
<dependency> <dependency>
<groupId>de.ozgcloud.aggregation</groupId> <groupId>de.ozgcloud.aggregation</groupId>
<artifactId>aggregation-manager-interface</artifactId> <artifactId>aggregation-manager-interface</artifactId>
</dependency> </dependency>
<!--dev
tools-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -68,9 +68,24 @@ spec: ...@@ -68,9 +68,24 @@ spec:
value: {{ include "app.envSpringProfiles" . }} value: {{ include "app.envSpringProfiles" . }}
- name: ozgcloud_administration_address - name: ozgcloud_administration_address
value: {{ include "app.getOzgcloudAdministrationAddress" . }} value: {{ include "app.getOzgcloudAdministrationAddress" . }}
{{- if not (.Values.database).useExternal }}
- name: spring_data_mongodb_uri
valueFrom:
secretKeyRef:
name: {{ (.Values.database).secretName }}
key: connectionString.standardSrv
optional: false
- name: spring_data_mongodb_database
value: {{ (.Values.database).databaseName }}
{{- end }}
{{- with include "app.getCustomList" . }} {{- with include "app.getCustomList" . }}
{{ . | indent 8 }} {{ . | indent 8 }}
{{- end }} {{- end }}
envFrom:
{{- if (.Values.database).useExternal }}
- secretRef:
name: {{ (.Values.database).secretName }}
{{- end }}
ports: ports:
- containerPort: 8081 - containerPort: 8081
name: metrics name: metrics
......
...@@ -50,6 +50,13 @@ spec: ...@@ -50,6 +50,13 @@ spec:
{{ toYaml . | indent 2 }} {{ toYaml . | indent 2 }}
{{- end }} {{- end }}
egress: egress:
- to:
- podSelector:
matchLabels:
component: ozgcloud-mongodb
ports:
- port: 27017
protocol: TCP
- to: - to:
- namespaceSelector: - namespaceSelector:
matchLabels: matchLabels:
......
...@@ -33,6 +33,9 @@ metadata: ...@@ -33,6 +33,9 @@ metadata:
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: grpc-9090
port: 9090
protocol: TCP
- name: metrics - name: metrics
port: 8081 port: 8081
protocol: TCP protocol: TCP
......
package de.ozgcloud.aggregation.data;
import java.time.ZonedDateTime;
import java.util.Map;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@Builder(toBuilder = true)
@Getter
@EqualsAndHashCode
@TypeAlias("AggregationData")
@Document(collection = AggregationData.COLLECTION)
class AggregationData {
static final String COLLECTION = "AggregationData";
@Id
private String id;
private String kommune;
private String status;
private ZonedDateTime eingangDatum;
private String vorgangName;
private Map<String, Object> payload;
}
package de.ozgcloud.aggregation.data; package de.ozgcloud.aggregation.data;
import org.springframework.beans.factory.annotation.Lookup;
import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc.AggregationDataServiceImplBase; import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc.AggregationDataServiceImplBase;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
class AggregationDataGrpcService extends AggregationDataServiceImplBase { @GrpcService
abstract class AggregationDataGrpcService extends AggregationDataServiceImplBase {
@Override @Override
public StreamObserver<GrpcSendAggregationDataRequest> sendAggregationData(StreamObserver<GrpcSendAggregationDataResponse> responseObserver) { public StreamObserver<GrpcSendAggregationDataRequest> sendAggregationData(StreamObserver<GrpcSendAggregationDataResponse> responseObserver) {
// Implement in OZG-7909 return getGrpcAggregationDataRequestObserver().withResponseObserver(responseObserver);
return null;
} }
@Lookup
abstract GrpcAggregationDataRequestObserver getGrpcAggregationDataRequestObserver();
} }
package de.ozgcloud.aggregation.data;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(uses = GrpcObjectMapper.class)
interface AggregationDataMapper {
@Mapping(target = "kommune", ignore = true)
AggregationData fromGrpc(GrpcAggregationData grpcAggregationData);
}
package de.ozgcloud.aggregation.data;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
@Repository
interface AggregationDataRepository extends MongoRepository<AggregationData, String>, CustomAggregationDataRepository {
}
package de.ozgcloud.aggregation.data;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
class AggregationDataService {
private final AggregationDataRepository repository;
public void save(AggregationData aggregationData, String mappingName) {
repository.saveInCollection(aggregationData, mappingName);
}
}
package de.ozgcloud.aggregation.data;
interface CustomAggregationDataRepository {
AggregationData saveInCollection(AggregationData aggregationData, String collectionName);
}
package de.ozgcloud.aggregation.data;
import org.springframework.data.mongodb.core.MongoTemplate;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
class CustomAggregationDataRepositoryImpl implements CustomAggregationDataRepository {
private final MongoTemplate mongoTemplate;
@Override
public AggregationData saveInCollection(AggregationData aggregationData, String collectionName) {
return mongoTemplate.save(aggregationData, collectionName);
}
}
package de.ozgcloud.aggregation.data;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Component
@Scope("prototype")
class GrpcAggregationDataRequestObserver implements StreamObserver<GrpcSendAggregationDataRequest> {
private final AggregationDataMapper aggregationDataMapper;
private final AggregationDataService aggregationDataService;
private StreamObserver<GrpcSendAggregationDataResponse> responseObserver;
public GrpcAggregationDataRequestObserver withResponseObserver(StreamObserver<GrpcSendAggregationDataResponse> responseObserver) {
this.responseObserver = responseObserver;
return this;
}
@Override
public void onNext(GrpcSendAggregationDataRequest request) {
request.getAggregationDataList().stream().map(aggregationDataMapper::fromGrpc)
.forEach(data -> aggregationDataService.save(data.toBuilder().kommune(request.getMandant()).build(), request.getName()));
}
@Override
public void onError(Throwable t) {
responseObserver.onError(t);
}
@Override
public void onCompleted() {
responseObserver.onNext(GrpcSendAggregationDataResponse.getDefaultInstance());
responseObserver.onCompleted();
}
}
package de.ozgcloud.aggregation.data;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.mapstruct.Mapper;
@Mapper
interface GrpcObjectMapper {
default Map<String, Object> toMap(GrpcObject object) {
return object.getPropertiesList().stream().map(this::mapProperty).collect(
Collectors.toMap(Entry::getKey, Entry::getValue));
}
default Entry<String, Object> mapProperty(GrpcProperty property) {
return Map.entry(property.getKey(), mapElement(property.getValue()));
}
default Object mapElement(GrpcElement element) {
if (element.hasStringValue()) {
return element.getStringValue();
}
if (element.hasLongValue()) {
return element.getLongValue();
}
if (element.hasDoubleValue()) {
return element.getDoubleValue();
}
if (element.hasBoolValue()) {
return element.getBoolValue();
}
if (element.hasObjectValue()) {
return toMap(element.getObjectValue());
}
if (element.hasListValue()) {
return element.getListValue().getElementsList().stream()
.map(this::mapElement).toList();
}
return null;
}
}
...@@ -2,7 +2,9 @@ package de.ozgcloud.aggregation.mapping; ...@@ -2,7 +2,9 @@ package de.ozgcloud.aggregation.mapping;
import de.ozgcloud.aggregation.mapping.AggregationMappingServiceGrpc.AggregationMappingServiceImplBase; import de.ozgcloud.aggregation.mapping.AggregationMappingServiceGrpc.AggregationMappingServiceImplBase;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
class AggregationMappingGrpcService extends AggregationMappingServiceImplBase { class AggregationMappingGrpcService extends AggregationMappingServiceImplBase {
@Override @Override
......
#
# Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
# Ministerpräsidenten des Landes Schleswig-Holstein
# Staatskanzlei
# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
#
# Lizenziert unter der EUPL, Version 1.2 oder - sobald
# diese von der Europäischen Kommission genehmigt wurden -
# Folgeversionen der EUPL ("Lizenz");
# Sie dürfen dieses Werk ausschließlich gemäß
# dieser Lizenz nutzen.
# Eine Kopie der Lizenz finden Sie hier:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Sofern nicht durch anwendbare Rechtsvorschriften
# gefordert oder in schriftlicher Form vereinbart, wird
# die unter der Lizenz verbreitete Software "so wie sie
# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
# ausdrücklich oder stillschweigend - verbreitet.
# Die sprachspezifischen Genehmigungen und Beschränkungen
# unter der Lizenz sind dem Lizenztext zu entnehmen.
#
suite: test database settings
release:
name: aggregation-manager-server
namespace: sh-helm-test
templates:
- templates/deployment.yaml
set:
ozgcloud:
environment: dev
imagePullSecret: image-pull-secret
tests:
- it: should configure internal mongodb if not useExternal
set:
database.useExternal: false
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: spring_data_mongodb_uri
valueFrom:
secretKeyRef:
name: ozg-mongodb-admin-aggregation-manager-user
key: connectionString.standardSrv
optional: false
- contains:
path: spec.template.spec.containers[0].env
content:
name: spring_data_mongodb_database
value: aggregation-manager-server-database
- it: should configure external mongodb if useExternal
set:
database.useExternal: true
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: ozg-mongodb-admin-aggregation-manager-user
...@@ -63,6 +63,13 @@ tests: ...@@ -63,6 +63,13 @@ tests:
path: spec path: spec
value: value:
egress: egress:
- ports:
- port: 27017
protocol: TCP
to:
- podSelector:
matchLabels:
component: ozgcloud-mongodb
- ports: - ports:
- port: 53 - port: 53
protocol: UDP protocol: UDP
......
...@@ -44,7 +44,17 @@ tests: ...@@ -44,7 +44,17 @@ tests:
path: spec.type path: spec.type
value: ClusterIP value: ClusterIP
- it: has metrics port - it: should have grpc port
asserts:
- contains:
path: spec.ports
content:
name: grpc-9090
port: 9090
protocol: TCP
count: 1
any: true
- it: should have metrics port
asserts: asserts:
- contains: - contains:
path: spec.ports path: spec.ports
......
package de.ozgcloud.aggregation.data;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import io.grpc.stub.StreamObserver;
class AggregationDataGrpcServiceTest {
@InjectMocks
@Spy
private AggregationDataGrpcServiceImpl service;
@Mock
private static GrpcAggregationDataRequestObserver grpcAggregationDataRequestObserver;
@Nested
class TestSendAggregationData {
@Mock
private StreamObserver<GrpcSendAggregationDataResponse> responseObserver;
@Mock
private GrpcAggregationDataRequestObserver grpcAggregationDataRequestObserverWithRepsonseObserver;
@BeforeEach
void mock() {
when(grpcAggregationDataRequestObserver.withResponseObserver(any())).thenReturn(grpcAggregationDataRequestObserverWithRepsonseObserver);
}
@Test
void shouldGetGrpcAggregationDataRequestObserver() {
service.sendAggregationData(responseObserver);
verify(service).getGrpcAggregationDataRequestObserver();
}
@Test
void shouldSetResponseObserver() {
service.sendAggregationData(responseObserver);
verify(grpcAggregationDataRequestObserver).withResponseObserver(responseObserver);
}
@Test
void shouldReturnRequestObserverWithResponseObserver() {
var requestObserver = service.sendAggregationData(responseObserver);
assertThat(requestObserver).isSameAs(grpcAggregationDataRequestObserverWithRepsonseObserver);
}
}
static class AggregationDataGrpcServiceImpl extends AggregationDataGrpcService {
@Override
GrpcAggregationDataRequestObserver getGrpcAggregationDataRequestObserver() {
return grpcAggregationDataRequestObserver;
}
}
}
package de.ozgcloud.aggregation.data;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
class AggregationDataMapperTest {
@InjectMocks
private final AggregationDataMapper aggregationDataMapper = Mappers.getMapper(AggregationDataMapper.class);
@Mock
private GrpcObjectMapper grpcObjectMapper;
@Nested
class TestFromGrpc {
@Test
void shouldMapHeaderData() {
var aggregationData = aggregationDataMapper.fromGrpc(GrpcAggregationDataTestFactory.create());
assertThat(aggregationData).usingRecursiveComparison()
.ignoringFields("payload", "kommune")
.isEqualTo(AggregationDataTestFactory.create());
}
@Test
void shouldCallGrpcObjectMapper() {
aggregationDataMapper.fromGrpc(GrpcAggregationDataTestFactory.create());
verify(grpcObjectMapper).toMap(GrpcAggregationDataTestFactory.PAYLOAD);
}
@Test
void shouldSetPayload() {
when(grpcObjectMapper.toMap(any())).thenReturn(AggregationDataTestFactory.PAYLOAD);
var aggregationData = aggregationDataMapper.fromGrpc(GrpcAggregationDataTestFactory.create());
assertThat(aggregationData.getPayload()).isEqualTo(AggregationDataTestFactory.PAYLOAD);
}
}
}
package de.ozgcloud.aggregation.data;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import com.thedeanda.lorem.LoremIpsum;
class AggregationDataServiceTest {
@InjectMocks
private AggregationDataService service;
@Mock
private AggregationDataRepository repository;
@Nested
class TestSave {
@Test
void shouldSaveInRepository() {
var aggregationData = AggregationDataTestFactory.create();
var mappingName = LoremIpsum.getInstance().getWords(1);
service.save(aggregationData, mappingName);
verify(repository).saveInCollection(aggregationData, mappingName);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment