Skip to content
Snippets Groups Projects
Commit 0db45a71 authored by André Sterba's avatar André Sterba Committed by Neil-Jocelyn Schark
Browse files

CTRL - Bootstrap routing service as example for folder structure and tests

parent 6d42907b
No related branches found
No related tags found
1 merge request!15CTRL - Bootstrap routing service as example for folder structure and tests
Pipeline #270066 passed
......@@ -4,16 +4,20 @@ import (
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/ports"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/infrastructure/interaction"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/infrastructure/store"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/service"
"code.fbi.h-da.de/danet/costaquanta/libs/logging"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.uber.org/zap"
)
type Application struct {
tracer *sdktrace.TracerProvider
HealthServer *interaction.HealthServer
RoutingServer *interaction.RoutingServer
KmsStore ports.KmsStore
tracer *sdktrace.TracerProvider
HealthServer *interaction.HealthServer
RoutingService ports.RoutingService
RoutingServer *interaction.RoutingServer
KmsStore ports.KmsStore
}
func NewApplication(log *zap.Logger, tracer *sdktrace.TracerProvider) *Application {
......@@ -21,9 +25,13 @@ func NewApplication(log *zap.Logger, tracer *sdktrace.TracerProvider) *Applicati
healthTracer := tracer.Tracer("healthServer")
healthSrv := interaction.NewHealthServer(healthLogger, healthTracer)
routingServiceLogger := logging.CreateChildLogger(log, "routingService")
routingServiceTracer := tracer.Tracer("routingService")
rSrv := service.NewRoutingService(routingServiceLogger, routingServiceTracer)
routingLogger := logging.CreateChildLogger(log, "routingServer")
routingTracer := tracer.Tracer("routingServer")
routerSrv := interaction.NewRoutingServer(routingLogger, routingTracer)
routerSrv := interaction.NewRoutingServer(routingLogger, routingTracer, rSrv)
// Create the in-memory KMS store, as we currently have no other option.
// Can be replaced by a store choice via flags or config file later.
......@@ -32,9 +40,10 @@ func NewApplication(log *zap.Logger, tracer *sdktrace.TracerProvider) *Applicati
kmsStore := store.NewInMemoryKmsStore(kmsStoreLogger, kmsStoreTracer)
return &Application{
tracer: tracer,
HealthServer: healthSrv,
RoutingServer: routerSrv,
KmsStore: kmsStore,
tracer: tracer,
HealthServer: healthSrv,
RoutingServer: routerSrv,
RoutingService: rSrv,
KmsStore: kmsStore,
}
}
package model
type RoutingCryptoAlgorithm int
const (
Unspecified RoutingCryptoAlgorithm = iota
AES256GCM
OTP
Hybrid
)
type Route struct {
ID string
NextHopID string
PrevHopID string
CryptoAlgorithm RoutingCryptoAlgorithm
}
package ports
import (
"context"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/model"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/service"
)
type RoutingService interface {
RequestRoute(context.Context, service.RequestRouteDTO) (model.Route, error)
AnnouncePayloadRelay(context.Context, service.AnnouncePayloadRelayRequestDTO) error
PayloadRelayFinished(context.Context, service.PayloadRelayFinishedRequestDTO) error
PayloadRelayError(context.Context, service.PayloadRelayErrorDTO) error
PushKeyStoreFillLevel(context.Context, service.PushKeyStoreFillLevelRequestDTO) error
}
package interaction
import (
"context"
"log"
"net"
"testing"
pb "code.fbi.h-da.de/danet/costaquanta/gen/go/ctrl/v1"
"code.fbi.h-da.de/danet/costaquanta/libs/tracing"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
func newHealthServer() pb.HealthCtrlServer {
logs := zap.NewExample()
traceProvider := tracing.GetTestTracer()
defer func() {
if err := traceProvider.Shutdown(context.Background()); err != nil {
logs.Info(err.Error())
}
}()
srv := NewHealthServer(
zap.NewExample().Sugar(),
traceProvider.Tracer("test"),
)
return srv
}
//nolint:staticcheck
func getTestServer(ctx context.Context) (pb.HealthCtrlClient, func()) {
buffer := 101024 * 1024
lis := bufconn.Listen(buffer)
baseServer := grpc.NewServer()
pb.RegisterHealthCtrlServer(baseServer, newHealthServer())
go func() {
if err := baseServer.Serve(lis); err != nil {
log.Printf("error serving server: %v", err)
}
}()
conn, err := grpc.DialContext(ctx, "",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("error connecting to server: %v", err)
}
closer := func() {
err := lis.Close()
if err != nil {
log.Printf("error closing listener: %v", err)
}
baseServer.Stop()
}
client := pb.NewHealthCtrlClient(conn)
return client, closer
}
//nolint:paralleltest
func TestHealthServer_Health(t *testing.T) {
ctx := context.Background()
client, closer := getTestServer(ctx)
defer closer()
type expectation struct {
out *pb.HealthResponse
err error
}
tests := map[string]struct {
in *pb.HealthRequest
expected expectation
}{
"echo-should-succeed": {
in: &pb.HealthRequest{
Message: "hello-world",
},
expected: expectation{
out: &pb.HealthResponse{
Message: "hello-world",
},
err: nil,
},
},
}
for scenario, tt := range tests {
t.Run(scenario, func(t *testing.T) {
out, err := client.Health(ctx, tt.in)
if err != nil {
if tt.expected.err.Error() != err.Error() {
t.Errorf("Err -> \nWant: %q\nGot: %q\n", tt.expected.err, err)
}
} else {
if tt.expected.out.GetMessage() != out.GetMessage() {
t.Errorf("Out -> \nWant: %q\nGot : %q", tt.expected.out, out)
}
}
})
}
}
package interaction
import (
"context"
"fmt"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/model"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/ports"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/service"
pb "code.fbi.h-da.de/danet/costaquanta/gen/go/ctrl/v1"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type RoutingServer struct {
......@@ -11,13 +19,128 @@ type RoutingServer struct {
logger *zap.SugaredLogger
tracer trace.Tracer
srv ports.RoutingService
}
func NewRoutingServer(logger *zap.SugaredLogger, tracer trace.Tracer) *RoutingServer {
func NewRoutingServer(
logger *zap.SugaredLogger,
tracer trace.Tracer,
srv *service.Routing,
) *RoutingServer {
s := &RoutingServer{
logger: logger,
tracer: tracer,
srv: srv,
}
return s
}
func (r *RoutingServer) RequestRoute(
ctx context.Context,
req *pb.RequestRouteRequest,
) (*pb.RequestRouteResponse, error) {
_, span := r.tracer.Start(ctx, "request-route")
defer span.End()
dto := service.RequestRouteDTO{
SourceKMSID: req.GetSourceKmsId(),
TargetKMSID: req.GetTargetKmsId(),
// TODO: I'm pretty sure this doesn't work yet.
// I need to figure out if there is an pretty way to convert an proto
// enum into a Go enum.
CryptoAlgorithm: model.RoutingCryptoAlgorithm(req.GetCryptoAlgorithm()),
}
route, err := r.srv.RequestRoute(ctx, dto)
if err != nil {
return nil, status.Errorf(codes.Internal, "%s", fmt.Sprintf("%s", err))
}
return &pb.RequestRouteResponse{
RouteId: route.ID,
}, nil
}
func (r *RoutingServer) AnnouncePayloadRelay(
ctx context.Context,
req *pb.AnnouncePayloadRelayRequest,
) (*pb.AnnouncePayloadRelayResponse, error) {
_, span := r.tracer.Start(ctx, "announce-payload-relay")
defer span.End()
dto := service.AnnouncePayloadRelayRequestDTO{
RequestID: req.GetRequestId(),
RouteID: req.GetRouteId(),
}
err := r.srv.AnnouncePayloadRelay(ctx, dto)
if err != nil {
return nil, status.Errorf(codes.Internal, "%s", fmt.Sprintf("%s", err))
}
return &pb.AnnouncePayloadRelayResponse{}, nil
}
func (r *RoutingServer) PayloadRelayFinished(
ctx context.Context,
req *pb.PayloadRelayFinishedRequest,
) (*pb.PayloadRelayFinishedResponse, error) {
_, span := r.tracer.Start(ctx, "payload-relay-finished")
defer span.End()
dto := service.PayloadRelayFinishedRequestDTO{
RequestID: req.GetRequestId(),
RouteID: req.GetRouteId(),
}
err := r.srv.PayloadRelayFinished(ctx, dto)
if err != nil {
return nil, status.Errorf(codes.Internal, "%s", fmt.Sprintf("%s", err))
}
return &pb.PayloadRelayFinishedResponse{}, nil
}
func (r *RoutingServer) PayloadRelayError(
ctx context.Context,
req *pb.PayloadRelayErrorRequest,
) (*pb.PayloadRelayErrorResponse, error) {
_, span := r.tracer.Start(ctx, "payload-relay-error")
defer span.End()
dto := service.PayloadRelayErrorDTO{
RequestID: req.GetRequestId(),
RouteID: req.GetRouteId(),
ErrorType: req.GetError().Enum().String(),
}
err := r.srv.PayloadRelayError(ctx, dto)
if err != nil {
return nil, status.Errorf(codes.Internal, "%s", fmt.Sprintf("%s", err))
}
return &pb.PayloadRelayErrorResponse{}, nil
}
func (r *RoutingServer) PushKeyStoreFillLevel(
ctx context.Context,
req *pb.PushKeyStoreFillLevelRequest,
) (*pb.PushKeyStoreFillLevelResponse, error) {
_, span := r.tracer.Start(ctx, "push-key-store-fill-level")
defer span.End()
dto := service.PushKeyStoreFillLevelRequestDTO{
KMSID: req.GetKmsId(),
PeerID: req.GetPeerId(),
FillLevel: req.GetFillLevel(),
}
err := r.srv.PushKeyStoreFillLevel(ctx, dto)
if err != nil {
return nil, status.Errorf(codes.Internal, "%s", fmt.Sprintf("%s", err))
}
return &pb.PushKeyStoreFillLevelResponse{}, nil
}
package interaction
import (
"context"
"log"
"net"
"testing"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/service"
pb "code.fbi.h-da.de/danet/costaquanta/gen/go/ctrl/v1"
sharedv1 "code.fbi.h-da.de/danet/costaquanta/gen/go/shared/v1"
"code.fbi.h-da.de/danet/costaquanta/libs/tracing"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
func newRoutingServer() pb.CtrlRoutingServiceServer {
logs := zap.NewExample()
traceProvider := tracing.GetTestTracer()
defer func() {
if err := traceProvider.Shutdown(context.Background()); err != nil {
logs.Info(err.Error())
}
}()
svc := service.NewRoutingService(logs.Sugar(), traceProvider.Tracer("test"))
srv := NewRoutingServer(
zap.NewExample().Sugar(),
traceProvider.Tracer("test"),
svc,
)
return srv
}
//nolint:staticcheck
func getTestRoutingServer(ctx context.Context) (pb.CtrlRoutingServiceClient, func()) {
buffer := 101024 * 1024
lis := bufconn.Listen(buffer)
baseServer := grpc.NewServer()
pb.RegisterCtrlRoutingServiceServer(baseServer, newRoutingServer())
go func() {
if err := baseServer.Serve(lis); err != nil {
log.Printf("error serving server: %v", err)
}
}()
conn, err := grpc.DialContext(ctx, "",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("error connecting to server: %v", err)
}
closer := func() {
err := lis.Close()
if err != nil {
log.Printf("error closing listener: %v", err)
}
baseServer.Stop()
}
client := pb.NewCtrlRoutingServiceClient(conn)
return client, closer
}
//nolint:paralleltest
func TestRoutingServer_RequestRoute(t *testing.T) {
ctx := context.Background()
client, closer := getTestRoutingServer(ctx)
defer closer()
type expectation struct {
out *pb.RequestRouteResponse
err error
}
tests := map[string]struct {
in *pb.RequestRouteRequest
expected expectation
}{
"first-test": {
in: &pb.RequestRouteRequest{
SourceKmsId: "1337",
TargetKmsId: "42",
CryptoAlgorithm: sharedv1.CryptoAlgorithm_CRYPTO_ALGORITHM_AES_256_GCM,
},
expected: expectation{
out: &pb.RequestRouteResponse{
RouteId: "1337",
},
err: nil,
},
},
}
for scenario, tt := range tests {
t.Run(scenario, func(t *testing.T) {
out, err := client.RequestRoute(ctx, tt.in)
if err != nil {
if tt.expected.err.Error() != err.Error() {
t.Errorf("Err -> \nWant: %q\nGot: %q\n", tt.expected.err, err)
}
} else {
if tt.expected.out.GetRouteId() != out.GetRouteId() {
t.Errorf("Out -> \nWant: %q\nGot : %q", tt.expected.out, out)
}
}
})
}
}
package service
import (
"context"
"time"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/model"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type Routing struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type RequestRouteDTO struct {
SourceKMSID string
TargetKMSID string
CryptoAlgorithm model.RoutingCryptoAlgorithm
}
type AnnouncePayloadRelayRequestDTO struct {
RequestID string
RouteID string
}
type PayloadRelayFinishedRequestDTO struct {
RequestID string
RouteID string
}
type PayloadRelayErrorDTO struct {
RequestID string
RouteID string
ErrorType string
}
type PushKeyStoreFillLevelRequestDTO struct {
KMSID string
PeerID string
FillLevel int64
}
func NewRoutingService(logger *zap.SugaredLogger, tracer trace.Tracer) *Routing {
return &Routing{
logger: logger,
tracer: tracer,
}
}
func (r *Routing) RequestRoute(ctx context.Context, req RequestRouteDTO) (model.Route, error) {
_, span := r.tracer.Start(ctx, "request-route")
defer span.End()
// calculate route
time.Sleep(time.Second)
return model.Route{
ID: "1337",
}, nil
}
func (r *Routing) AnnouncePayloadRelay(
ctx context.Context,
req AnnouncePayloadRelayRequestDTO,
) error {
_, span := r.tracer.Start(ctx, "announce-payload-relay")
defer span.End()
// gossip routes to KMS
return nil
}
func (r *Routing) PayloadRelayFinished(
ctx context.Context,
req PayloadRelayFinishedRequestDTO,
) error {
_, span := r.tracer.Start(ctx, "payload-relay-finished")
defer span.End()
return nil
}
func (r *Routing) PayloadRelayError(ctx context.Context, req PayloadRelayErrorDTO) error {
_, span := r.tracer.Start(ctx, "payload-relay-error")
defer span.End()
return nil
}
func (r *Routing) PushKeyStoreFillLevel(
ctx context.Context,
req PushKeyStoreFillLevelRequestDTO,
) error {
_, span := r.tracer.Start(ctx, "push-key-store-fill-level")
defer span.End()
return nil
}
package service
import (
"context"
"reflect"
"testing"
"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/core/model"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
"go.uber.org/zap"
)
func TestNewRouting(t *testing.T) {
t.Parallel()
type args struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
tests := []struct {
name string
args args
want *Routing
}{
{
name: "test",
args: args{
logger: zap.NewNop().Sugar(),
tracer: noop.NewTracerProvider().Tracer("test"),
},
want: &Routing{
logger: zap.NewNop().Sugar(),
tracer: noop.NewTracerProvider().Tracer("test"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := NewRoutingService(tt.args.logger, tt.args.tracer); !reflect.DeepEqual(
got,
tt.want,
) {
t.Errorf("NewRouting() = %v, want %v", got, tt.want)
}
})
}
}
func TestRouting_RequestRoute(t *testing.T) {
t.Parallel()
type fields struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type args struct {
ctx context.Context
req RequestRouteDTO
}
tests := []struct {
name string
fields fields
args args
want model.Route
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Routing{
logger: tt.fields.logger,
tracer: tt.fields.tracer,
}
got, err := r.RequestRoute(tt.args.ctx, tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("Routing.RequestRoute() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Routing.RequestRoute() = %v, want %v", got, tt.want)
}
})
}
}
func TestRouting_AnnouncePayloadRelay(t *testing.T) {
t.Parallel()
type fields struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type args struct {
ctx context.Context
req AnnouncePayloadRelayRequestDTO
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Routing{
logger: tt.fields.logger,
tracer: tt.fields.tracer,
}
if err := r.AnnouncePayloadRelay(tt.args.ctx, tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("Routing.AnnouncePayloadRelay() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestRouting_PayloadRelayFinished(t *testing.T) {
t.Parallel()
type fields struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type args struct {
ctx context.Context
req PayloadRelayFinishedRequestDTO
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Routing{
logger: tt.fields.logger,
tracer: tt.fields.tracer,
}
if err := r.PayloadRelayFinished(tt.args.ctx, tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("Routing.PayloadRelayFinished() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestRouting_PayloadRelayError(t *testing.T) {
t.Parallel()
type fields struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type args struct {
ctx context.Context
req PayloadRelayErrorDTO
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Routing{
logger: tt.fields.logger,
tracer: tt.fields.tracer,
}
if err := r.PayloadRelayError(tt.args.ctx, tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("Routing.PayloadRelayError() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestRouting_PushKeyStoreFillLevel(t *testing.T) {
t.Parallel()
type fields struct {
logger *zap.SugaredLogger
tracer trace.Tracer
}
type args struct {
ctx context.Context
req PushKeyStoreFillLevelRequestDTO
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Routing{
logger: tt.fields.logger,
tracer: tt.fields.tracer,
}
if err := r.PushKeyStoreFillLevel(tt.args.ctx, tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("Routing.PushKeyStoreFillLevel() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
......@@ -58,3 +58,12 @@ func GetTracer(ctx context.Context, target TraceExportTarget) (*sdktrace.TracerP
func SetRuntimeSettings(serviceName string) {
_ = os.Setenv("OTEL_SERVICE_NAME", serviceName)
}
func GetTestTracer() *sdktrace.TracerProvider {
traceProvider, err := GetTracer(context.Background(), Stdout)
if err != nil {
panic(err)
}
return traceProvider
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment