diff --git a/api/services/as4-message-dispatcher/cmd/main.go b/api/services/as4-message-dispatcher/cmd/main.go index b7cb0bbd2c3788f8ee58e5f1e91001fed19ccf5c..311f3cba26bff6879ba60775a96a6196d57a450d 100644 --- a/api/services/as4-message-dispatcher/cmd/main.go +++ b/api/services/as4-message-dispatcher/cmd/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "net" "net/http" "net/url" "os" @@ -12,9 +13,11 @@ import ( "syscall" "time" - "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/httplog" - "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/polling" + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/message" + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/polling" + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/handlers" "code.europa.eu/healthdataeu-nodes/hdeupoc/foundation/edelivery/domibus" + "code.europa.eu/healthdataeu-nodes/hdeupoc/foundation/externalapi" "gopkg.in/yaml.v3" ) @@ -39,9 +42,14 @@ func run(ctx context.Context, logger *slog.Logger) error { confPath := path.Join("config", "conf.yaml") cfg := struct { - Poller struct { - TimeInterval time.Duration `yaml:"time_interval"` + Web struct { + ReadTimeout time.Duration `yaml:"read_timeout"` + WriteTimeout time.Duration `yaml:"write_timeout"` + IdleTimeout time.Duration `yaml:"idle_timeout"` ShutdownTimeout time.Duration `yaml:"shutdown_timeout"` + APIHost string `yaml:"api_host"` + EnablePolling bool `yaml:"enable_polling"` + PollingInterval time.Duration `yaml:"polling_interval"` } `yaml:"as4_message_dispatcher"` Domibus struct { Username string `yaml:"username"` @@ -116,44 +124,98 @@ func run(ctx context.Context, logger *slog.Logger) error { } // =================================================================================== - // Start API Poller + // Message processing support - logger.InfoContext(ctx, "startup", "status", "initializing poller support") + logger.InfoContext(ctx, "startup", "status", "initializing message processing support") + + msgCfg := &message.Config{ + HTTPClient: externalapi.NewHTTPClient(logger), + DomibusSOAP: domibusSOAP, + TargetURLs: targetURLs, + } + + // =================================================================================== + // Start API Service + + logger.InfoContext(ctx, "startup", "status", "initializing V1 API support") shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM) - pollerErrors := make(chan error, 1) - pollerStop := make(chan struct{}) + muxConfig := handlers.NewAPIMuxConfig(logger, msgCfg) + apiMux := handlers.APIMux(muxConfig) + + serverErrors := make(chan error, 1) + + _, port, err := net.SplitHostPort(cfg.Web.APIHost) + if err != nil { + return fmt.Errorf("parsing port from %s: %w", cfg.Web.APIHost, err) + } - pollerConfig := polling.Config{ - Logger: logger, - HTTPClient: httplog.New(logger), - DomibusSOAP: domibusSOAP, - TimeInterval: cfg.Poller.TimeInterval, - Stop: pollerStop, - TargetURLs: targetURLs, + api := http.Server{ + Addr: ":" + port, + Handler: apiMux, + ReadTimeout: cfg.Web.ReadTimeout, + WriteTimeout: cfg.Web.WriteTimeout, + IdleTimeout: cfg.Web.IdleTimeout, + ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), } go func() { - logger.InfoContext(ctx, "startup", "status", "poller started") - pollerErrors <- polling.Run(ctx, pollerConfig) + logger.InfoContext(ctx, "startup", "status", "api router started", "host", api.Addr) + serverErrors <- api.ListenAndServe() }() + // =================================================================================== + // Synchronize messages on startup + // If polling is disabled, we perform a one-time message synchronization when the application starts. + + if !cfg.Web.EnablePolling { + logger.InfoContext(ctx, "startup", "status", "performing initial message synchronization...") + if err := message.Process(ctx, logger, msgCfg); err != nil { + logger.ErrorContext(ctx, "initial message synchronization", "SYNC-ERROR", err) + } + } + + // =================================================================================== + // Start Polling if enabled + + pollerStop := make(chan struct{}) + pollerErrors := make(chan error, 1) + if cfg.Web.EnablePolling { + logger.InfoContext(ctx, "startup", "status", "initializing Polling support") + + go func() { + logger.InfoContext(ctx, "startup", "status", "polling started") + pollerErrors <- polling.Run(ctx, logger, cfg.Web.PollingInterval, pollerStop, msgCfg) + }() + } + // =================================================================================== // Shutdown select { + case err := <-serverErrors: + return fmt.Errorf("server error: %w", err) + case err := <-pollerErrors: return fmt.Errorf("poller error: %w", err) case sig := <-shutdown: logger.InfoContext(ctx, "shutdown", "status", "shutdown started", "signal", sig) - ctx, cancel := context.WithTimeout(ctx, cfg.Poller.ShutdownTimeout) + if cfg.Web.EnablePolling { + close(pollerStop) + logger.InfoContext(ctx, "shutdown", "status", "polling stopped") + } + defer logger.InfoContext(ctx, "shutdown", "status", "shutdown complete", "signal", sig) + + ctx, cancel := context.WithTimeout(ctx, cfg.Web.ShutdownTimeout) defer cancel() - close(pollerStop) - logger.InfoContext(ctx, "shutdown", "status", "shutdown complete", "signal", sig) + if err := api.Shutdown(ctx); err != nil { + api.Close() + return fmt.Errorf("could not stop server gracefully: %w", err) + } } return nil diff --git a/api/services/as4-message-dispatcher/polling/polling.go b/api/services/as4-message-dispatcher/core/message/message.go similarity index 52% rename from api/services/as4-message-dispatcher/polling/polling.go rename to api/services/as4-message-dispatcher/core/message/message.go index 65a627f4b5df34e5120231ba06b7a3b4a9dd8278..6e7713979806c2b967b7f3fddbb9cf52684295f3 100644 --- a/api/services/as4-message-dispatcher/polling/polling.go +++ b/api/services/as4-message-dispatcher/core/message/message.go @@ -1,7 +1,9 @@ -// Package polling contains polls functionalities of a domibus server. -package polling +// Package message provides functionality for retrieving and handling messages using both +// polling and webhook-triggered methods. +package message import ( + "bytes" "context" "encoding/json" "fmt" @@ -10,52 +12,31 @@ import ( "net/url" "time" - "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/handler" "code.europa.eu/healthdataeu-nodes/hdeupoc/foundation/edelivery/domibus" ) -// Config contains all the mandatory systems required for polling. +// Config contains all the mandatory systems required for processing a message. type Config struct { - Logger *slog.Logger - HTTPClient *http.Client - DomibusSOAP *domibus.SOAP - TimeInterval time.Duration - Stop chan struct{} - TargetURLs map[string]*url.URL + HTTPClient *http.Client + DomibusSOAP *domibus.SOAP + TargetURLs map[string]*url.URL } -func Run(ctx context.Context, cfg Config) error { - ticker := time.NewTicker(cfg.TimeInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-cfg.Stop: - return nil - case <-ticker.C: - if err := process(ctx, cfg); err != nil { - cfg.Logger.Error("Polling", "PROCESS-ERROR", err) - } - } - } -} - -// process checks messages in domibus and dispatch them to the right URL. -func process(ctx context.Context, cfg Config) error { +// Process checks incoming Domibus messages, reads their content, and dispatches them to +// the appropriate URL based on the message type. +func Process(ctx context.Context, logger *slog.Logger, cfg *Config) error { resp, err := cfg.DomibusSOAP.ListPendingMsgRequest(ctx, "", domibus.Party{}) if err != nil { return fmt.Errorf("retrieving list pending messages: %w", err) } - cfg.Logger.Info("checking-messages...", + logger.Info("checking-messages...", slog.Int("message-found", len(resp.MessageID)), ) for _, msgID := range resp.MessageID { if msgID == nil { - cfg.Logger.Error("checking-message", + logger.Error("checking-message", slog.String("messageID:", "NULL"), ) continue @@ -66,7 +47,7 @@ func process(ctx context.Context, cfg Config) error { return fmt.Errorf("retrieve message: %w", err) } - cfg.Logger.Info("reading-message", + logger.Info("reading-message", slog.String("messageID:", *msgID), ) @@ -84,10 +65,6 @@ func process(ctx context.Context, cfg Config) error { endpoint := cfg.TargetURLs[domibusMsg.DataType] endpoint.Path = domibusMsg.OperationType - httpClient := handler.Client{ - HTTPCLient: cfg.HTTPClient, - } - md := domibus.MessageDispatch{ Party: party, MessageID: *msgID, @@ -99,7 +76,7 @@ func process(ctx context.Context, cfg Config) error { return fmt.Errorf("marshaling a MessageDispatch") } - if err := httpClient.Dispatch(ctx, endpoint, payload); err != nil { + if err := dispatch(ctx, cfg.HTTPClient, endpoint, payload); err != nil { return fmt.Errorf("dispatch message: %w", err) } } @@ -107,3 +84,23 @@ func process(ctx context.Context, cfg Config) error { return nil } + +// dispatch sends the payload of a domibus message to the given URL. +func dispatch(ctx context.Context, httpClient *http.Client, endpoint *url.URL, msg []byte) error { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewBuffer(msg)) + if err != nil { + return fmt.Errorf("create a new request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + + _, err = httpClient.Do(req) + if err != nil { + return fmt.Errorf("doing request: %w", err) + } + + return nil +} diff --git a/api/services/as4-message-dispatcher/core/polling/polling.go b/api/services/as4-message-dispatcher/core/polling/polling.go new file mode 100644 index 0000000000000000000000000000000000000000..8610e35222ce226b5986c0ea49c55bcffb693634 --- /dev/null +++ b/api/services/as4-message-dispatcher/core/polling/polling.go @@ -0,0 +1,28 @@ +// Package polling contains polls functionalities of a domibus server. +package polling + +import ( + "context" + "log/slog" + "time" + + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/message" +) + +func Run(ctx context.Context, logger *slog.Logger, timeInterval time.Duration, stop chan struct{}, msgCfg *message.Config) error { + ticker := time.NewTicker(timeInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-stop: + return nil + case <-ticker.C: + if err := message.Process(ctx, logger, msgCfg); err != nil { + logger.Error("polling", "PROCESS-ERROR", err) + } + } + } +} diff --git a/api/services/as4-message-dispatcher/handler/handler.go b/api/services/as4-message-dispatcher/handler/handler.go deleted file mode 100644 index 32a3c1023f19fc67526c733917208d9335c72340..0000000000000000000000000000000000000000 --- a/api/services/as4-message-dispatcher/handler/handler.go +++ /dev/null @@ -1,41 +0,0 @@ -// Package handler provides functionality for routing and handling notifications -// to specific endpoints based on data type. -package handler - -import ( - "bytes" - "context" - "fmt" - "net/http" - "net/url" - "time" -) - -// ======================================================================================= - -// Client contains an http CLient. -type Client struct { - HTTPCLient *http.Client -} - -// Dispatch sends the payload of a domibus message to the given URL. -func (c Client) Dispatch(ctx context.Context, endpoint *url.URL, msg []byte) error { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewBuffer(msg)) - if err != nil { - return fmt.Errorf("create a new request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - - client := c.HTTPCLient - - _, err = client.Do(req) - if err != nil { - return fmt.Errorf("doing request: %w", err) - } - - return nil -} diff --git a/api/services/as4-message-dispatcher/handlers/handlers.go b/api/services/as4-message-dispatcher/handlers/handlers.go new file mode 100644 index 0000000000000000000000000000000000000000..733bb5d375c774515005eca60f8156a0c23ecb9e --- /dev/null +++ b/api/services/as4-message-dispatcher/handlers/handlers.go @@ -0,0 +1,44 @@ +// Package handlers manages the different versions of the API. +package handlers + +import ( + "log/slog" + + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/message" + v1 "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/handlers/v1" + "code.europa.eu/healthdataeu-nodes/hdeupoc/foundation/web/mid" + + "github.com/gorilla/mux" +) + +// APIMuxConfig contains all the mandatory systems required by handlers. +type APIMuxConfig struct { + Logger *slog.Logger + MessageConfig *message.Config +} + +// NewAPIMuxConfig constructs a new mux config. +func NewAPIMuxConfig(logger *slog.Logger, msgCfg *message.Config) APIMuxConfig { + return APIMuxConfig{ + Logger: logger, + MessageConfig: msgCfg, + } +} + +// APIMux constructs a http.Handler with all application routes defined. +func APIMux(cfg APIMuxConfig) *mux.Router { + router := mux.NewRouter() + + log := mid.Log{ + Logger: cfg.Logger, + } + + router.Use(log.Middleware) + + v1.Routes(router, v1.Config{ + Logger: cfg.Logger, + MessageConfig: cfg.MessageConfig, + }) + + return router +} diff --git a/api/services/as4-message-dispatcher/handlers/v1/eventgrp/eventgrp.go b/api/services/as4-message-dispatcher/handlers/v1/eventgrp/eventgrp.go new file mode 100644 index 0000000000000000000000000000000000000000..69cd71cac9eb51fd5f73976244f93a19e328d285 --- /dev/null +++ b/api/services/as4-message-dispatcher/handlers/v1/eventgrp/eventgrp.go @@ -0,0 +1,34 @@ +// Package eventgrp maintains the group of handlers for domibus webhook. +package eventgrp + +import ( + "log/slog" + "net/http" + + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/message" +) + +type Handler struct { + logger *slog.Logger + messageConfig *message.Config +} + +// New constructs a handlers for a route access. +func New(log *slog.Logger, msgCfg *message.Config) *Handler { + return &Handler{ + logger: log, + messageConfig: msgCfg, + } +} + +// ProcessMessages process messages from domibus when a webhook event is received. +func (h *Handler) ProcessMessages(w http.ResponseWriter, r *http.Request) { + + if err := message.Process(r.Context(), h.logger, h.messageConfig); err != nil { + h.logger.Error("webhook-notification", "process-messages", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/api/services/as4-message-dispatcher/handlers/v1/healthgrp/healthgrp.go b/api/services/as4-message-dispatcher/handlers/v1/healthgrp/healthgrp.go new file mode 100644 index 0000000000000000000000000000000000000000..de9a6fd2868a5e89b2ef9eb4f496330d06f7810f --- /dev/null +++ b/api/services/as4-message-dispatcher/handlers/v1/healthgrp/healthgrp.go @@ -0,0 +1,63 @@ +// Package healthgrp provides readiness and liveness check functions for system health monitoring. +package healthgrp + +import ( + "context" + "log/slog" + "net/http" + "os" + "time" + + "code.europa.eu/healthdataeu-nodes/hdeupoc/foundation/web" +) + +type Handler struct { + logger *slog.Logger +} + +func NewHandler(logger *slog.Logger) *Handler { + return &Handler{ + logger: logger, + } +} + +// Readiness checks if the database is ready and if not will return a 500 status. +// Do not respond by just returning an error because further up in the call +// stack it will interpret that as a non-trusted error. +func (h *Handler) Readiness(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), time.Second) + defer cancel() + + status := "ready" + statusCode := http.StatusOK + + data := struct { + Status string `json:"status"` + }{ + Status: status, + } + + web.Respond(ctx, w, data, statusCode) +} + +// Liveness returns simple status info if the service is alive. If the +// app is deployed to a Kubernetes cluster, it will also return pod, node, and +// namespace details via the Downward API. The Kubernetes environment variables +// need to be set within your Pod/Deployment manifest. +func (h *Handler) Liveness(w http.ResponseWriter, r *http.Request) { + status := "alive" + _, err := os.Hostname() + if err != nil { + status = "not alive" + } + + data := struct { + Status string `json:"status,omitempty"` + }{ + Status: status, + } + + // This handler provides a free timer loop. + + web.Respond(r.Context(), w, data, http.StatusOK) +} diff --git a/api/services/as4-message-dispatcher/handlers/v1/v1.go b/api/services/as4-message-dispatcher/handlers/v1/v1.go new file mode 100644 index 0000000000000000000000000000000000000000..348fb22fd7b9a07aaee87ecfd2d48dcc9b251bbe --- /dev/null +++ b/api/services/as4-message-dispatcher/handlers/v1/v1.go @@ -0,0 +1,31 @@ +// Package v1 contains the full set of handler functions and routes +// supported by the v1 web api. +package v1 + +import ( + "log/slog" + "net/http" + + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/core/message" + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/handlers/v1/eventgrp" + "code.europa.eu/healthdataeu-nodes/hdeupoc/api/services/as4-message-dispatcher/handlers/v1/healthgrp" + + "github.com/gorilla/mux" +) + +// Config contains all the mandatory systems required by handlers. +type Config struct { + Logger *slog.Logger + MessageConfig *message.Config +} + +// Routes binds all the version 1 routes. +func Routes(router *mux.Router, cfg Config) { + mgh := eventgrp.New(cfg.Logger, cfg.MessageConfig) + router.HandleFunc("/events/process-messages", mgh.ProcessMessages).Methods(http.MethodPost) + + hth := healthgrp.NewHandler(cfg.Logger) + // Check API + router.HandleFunc("/readiness", hth.Readiness).Methods(http.MethodGet) + router.HandleFunc("/liveness", hth.Liveness).Methods(http.MethodGet) +} diff --git a/api/services/as4-message-dispatcher/httplog/httplog.go b/api/services/as4-message-dispatcher/httplog/httplog.go deleted file mode 100644 index 72e3b73c7fd09b139108eea98e57752ca3169304..0000000000000000000000000000000000000000 --- a/api/services/as4-message-dispatcher/httplog/httplog.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package httplog create a clients with specific logs. -package httplog - -import ( - "log/slog" - "net/http" -) - -// httpLog contains mandatory data to create an http client. -type httpLog struct { - roundTripper http.RoundTripper - logger *slog.Logger -} - -// New creates an http client with proper log for this client. -func New(logger *slog.Logger) *http.Client { - return &http.Client{ - Transport: httpLog{ - roundTripper: http.DefaultTransport, - logger: logger, - }, - } -} - -// RoundTrip implements the RoudTripper interface of the http standard library. -func (h httpLog) RoundTrip(r *http.Request) (*http.Response, error) { - h.logger.Info("request started", - slog.Group("poller", "method", r.Method, "URL", r.URL.String()), - ) - - resp, err := h.roundTripper.RoundTrip(r) - if err != nil { - h.logger.Error("request error", slog.Group("poller", "error", err.Error())) - return nil, err - } - - h.logger.Info("request completed", slog.Group("poller", "statuscode", resp.Status)) - - return resp, nil -} diff --git a/compose.yaml b/compose.yaml index 361c1b1c2daed7984e5c0a8c2d211bcfba7ec91c..ce891898dbbd7a79ae6795559ebf8a469bcba48c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,7 +6,7 @@ services: # Dev profile api-gateway: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/api-gateway:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/api-gateway:v3.0.0 profiles: ["dev"] container_name: api-gateway mem_limit: 350m @@ -25,10 +25,11 @@ services: condition: service_started as4-message-dispatcher: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/as4-message-dispatcher:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/as4-message-dispatcher:v3.0.0 profiles: ["dev"] container_name: as4-message-dispatcher mem_limit: 350m + ports: [7900:7900] volumes: - ./config:/usr/src/app/config - ~/.domibuspass:/.domibuspass @@ -39,7 +40,7 @@ services: condition: service_started data-discovery: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-discovery:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-discovery:v3.0.0 profiles: ["dev"] container_name: data-discovery mem_limit: 350m @@ -55,7 +56,7 @@ services: condition: service_started data-permit: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-permit:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-permit:v3.0.0 profiles: ["dev"] container_name: data-permit mem_limit: 350m @@ -78,25 +79,25 @@ services: volumes: - /var/lib/postgresql/data/${POSTGRES_DB}:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 5 goose: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/goose:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/goose:v3.0.0 profiles: ["dev"] container_name: goose env_file: - .env command: > - goose postgres "host=postgres user=$POSTGRES_USER password=$POSTGRES_PASSWORD dbname=$POSTGRES_DB sslmode=disable" up + goose postgres "host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=${POSTGRES_DB} sslmode=disable" up depends_on: postgres: condition: service_healthy shacl-validator: - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/shacl-validator:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/shacl-validator:v3.0.0 profiles: ["dev"] container_name: shacl-validator ports: ["7400:8080"] @@ -108,7 +109,7 @@ services: api-gateway-local: build: ./api/services/api-gateway/cmd - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/api-gateway:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/api-gateway:v3.0.0 profiles: ["local"] container_name: api-gateway mem_limit: 350m @@ -128,10 +129,11 @@ services: as4-message-dispatcher-local: build: ./api/services/as4-message-dispatcher/cmd - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/as4-message-dispatcher:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/as4-message-dispatcher:v3.0.0 profiles: ["local"] container_name: as4-message-dispatcher mem_limit: 350m + ports: ["7900:7900"] volumes: - ./config:/usr/src/app/config - ~/.domibuspass:/.domibuspass @@ -143,7 +145,7 @@ services: data-discovery-local: build: ./api/services/data-discovery/cmd - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-discovery:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-discovery:v3.0.0 profiles: ["local"] container_name: data-discovery mem_limit: 350m @@ -161,7 +163,7 @@ services: data-permit-local: build: ./api/services/data-permit/cmd - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-permit:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/data-permit:v3.0.0 profiles: ["local"] container_name: data-permit mem_limit: 350m @@ -185,27 +187,27 @@ services: volumes: - /var/lib/postgresql/data/${POSTGRES_DB}-local:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 5 goose-local: build: ./api/services/goose - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/goose:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/goose:v3.0.0 profiles: ["local"] container_name: goose env_file: - .env command: > - goose postgres "host=postgres user=$POSTGRES_USER password=$POSTGRES_PASSWORD dbname=$POSTGRES_DB sslmode=disable" up + goose postgres "host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=${POSTGRES_DB} sslmode=disable" up depends_on: postgres-local: condition: service_healthy shacl-validator-local: build: ./foundation/shacl - image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/shacl-validator:v2.7.0 + image: code.europa.eu:4567/healthdataeu-nodes/hdeupoc/shacl-validator:v3.0.0 profiles: ["local"] container_name: shacl-validator ports: ["7400:8080"] diff --git a/config/conf.yaml b/config/conf.yaml index 1065a6ff87dd3a5719e96e0e1ccacf3ad4bb3d47..11b5b0e93fcd4defc8d3718af6f59ac73e858acf 100644 --- a/config/conf.yaml +++ b/config/conf.yaml @@ -9,8 +9,13 @@ api_gateway: tls_key_file: tmp.ssn.key as4_message_dispatcher: - time_interval: 1s + read_timeout: 5s + write_timeout: 10s + idle_timeout: 2m shutdown_timeout: 20s + api_host: as4-message-dispatcher:7900 + enable_polling: false + polling_interval: 1s # ingored if enable_polling set to false. data_discovery: read_timeout: 5s diff --git a/foundation/checkapi/checkapi.go b/foundation/checkapi/checkapi.go index 320d56b9d429390a9b330279ab0c47955f4a4ed3..f241677cd38d0937c1768f526cadcbb75cbb87df 100644 --- a/foundation/checkapi/checkapi.go +++ b/foundation/checkapi/checkapi.go @@ -1,4 +1,5 @@ -// Package checkapi maintains the web based api for system access. +// Package checkapi provides readiness and liveness check functions for system health monitoring +// for API with db. package checkapi import ( diff --git a/k8s/Chart.yaml b/k8s/Chart.yaml index 650ea1d417ea181b42893a926af48180c505c799..fb963d1ea6ccb5c60e665d133bda0c60296f3fc5 100644 --- a/k8s/Chart.yaml +++ b/k8s/Chart.yaml @@ -3,4 +3,4 @@ name: national-connector description: A Helm chart for Kubernetes type: application version: 0.5.0 -appVersion: "v2.7.0" +appVersion: "v3.0.0" diff --git a/k8s/templates/as4-message-dispatcher.yaml b/k8s/templates/as4-message-dispatcher.yaml index 44cda9597e30ecfa0be29f0cd5c6cf51ce72701b..d24a88861c4835ff4198a87a0b4529a371f452c8 100644 --- a/k8s/templates/as4-message-dispatcher.yaml +++ b/k8s/templates/as4-message-dispatcher.yaml @@ -39,4 +39,51 @@ spec: subPath: .domibuspass readOnly: true securityContext: {{ toYaml .Values.as4MessageDispatcher.securityContext | nindent 12 }} + ports: + - containerPort: 7900 + readinessProbe: + httpGet: + path: /readiness + port: 7900 + initialDelaySeconds: 15 + periodSeconds: 3 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /liveness + port: 7900 + initialDelaySeconds: 15 + periodSeconds: 3 + failureThreshold: 3 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-as4-message-dispatcher +spec: + selector: + app: as4-message-dispatcher + {{- if .Values.as4MessageDispatcher.nodePort }} + type: NodePort + {{- else if or .Values.as4MessageDispatcher.loadBalancerIP .Values.as4MessageDispatcher.loadBalancerSourceRanges }} + type: LoadBalancer + {{- with .Values.as4MessageDispatcher.loadBalancerIP }} + loadBalancerIP: {{ . }} + {{- end }} + {{- with .Values.as4MessageDispatcher.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{ . }} + {{- end }} + {{- else }} + type: ClusterIP + {{- with .Values.as4MessageDispatcher.clusterIP }} + clusterIP: {{ . }} + {{- end }} + {{- end }} + ports: + - protocol: TCP + port: 7900 + targetPort: 7900 + {{- with .Values.as4MessageDispatcher.nodePort }} + nodePort: {{ . }} + {{- end }} {{- end }} diff --git a/k8s/templates/configmap.yaml b/k8s/templates/configmap.yaml index f52d0ff3364c26cfe3cc8f5ecf7db14835b3cf90..944d763968daff7548188ae948716afa1b111093 100644 --- a/k8s/templates/configmap.yaml +++ b/k8s/templates/configmap.yaml @@ -5,8 +5,13 @@ metadata: data: conf.yaml: |- as4_message_dispatcher: - time_interval: {{ .Values.as4MessageDispatcher.connectorConfig.timeInterval }} + read_timeout: {{ .Values.as4MessageDispatcher.connectorConfig.readTimeout }} + write_timeout: {{ .Values.as4MessageDispatcher.connectorConfig.writeTimeout }} + idle_timeout: {{ .Values.as4MessageDispatcher.connectorConfig.idleTimeout }} shutdown_timeout: {{ .Values.as4MessageDispatcher.connectorConfig.shutdownTimeout }} + api_host: {{ .Release.Name }}-as4-message-dispatcher.{{ .Release.Namespace }}.svc.cluster.local:7900 + enable_polling: {{ .Values.as4MessageDispatcher.connectorConfig.enablePolling }} + polling_interval: {{ .Values.as4MessageDispatcher.connectorConfig.pollingInterval }} api_gateway: read_timeout: {{ .Values.apiGateway.connectorConfig.readTimeout }} diff --git a/k8s/templates/external-secret.example.yaml b/k8s/templates/external-secret.example.yaml deleted file mode 100644 index f346bef0face82ef7d77aadfa55cf6550da85015..0000000000000000000000000000000000000000 --- a/k8s/templates/external-secret.example.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: external-secrets.io/v1beta1 -kind: ExternalSecret -metadata: - name: secret -spec: - refreshInterval: 20s - secretStoreRef: - kind: SecretStore - name: secret-store - target: - name: connector-secrets - creationPolicy: Owner - data: - - secretKey: env - remoteRef: - key: # Provide the key that identifies your secret. - version: latest_enabled - - secretKey: .domibuspass - remoteRef: - key: # Provide the key that identifies your secret. - version: latest_enabled diff --git a/k8s/templates/migration.yaml b/k8s/templates/migration.yaml index b61861b419fabaa8c0c5a22da5672b6c4b789dfd..2e6415c68de3ae9d0f1e06e61d316d932370df58 100644 --- a/k8s/templates/migration.yaml +++ b/k8s/templates/migration.yaml @@ -31,7 +31,7 @@ spec: name: {{ .Release.Name }}-envfrom-secret env: - name: GOOSE_DBSTRING - value: "host={{ include "db-hostname" . }} port={{ include "db-port" . }} user=$(POSTGRES_USER) password=$(POSTGRES_PASSWORD) dbname=$(POSTGRES_DB) sslmode=disable" + value: "host={{ include "db-hostname" . }} port={{ include "db-port" . }} user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=${POSTGRES_DB} sslmode=disable" command: - goose - postgres diff --git a/k8s/templates/postgres.yaml b/k8s/templates/postgres.yaml index 4dbf219351564b7073651c7203f4df3df96e149d..b0ceb95a181040d07fdd6c510f06343891db872b 100644 --- a/k8s/templates/postgres.yaml +++ b/k8s/templates/postgres.yaml @@ -38,7 +38,7 @@ spec: command: - /bin/sh - -c - - exec pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + - exec pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} initialDelaySeconds: 15 periodSeconds: 3 failureThreshold: 5 @@ -47,7 +47,7 @@ spec: command: - /bin/sh - -c - - exec pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + - exec pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} initialDelaySeconds: 15 periodSeconds: 3 failureThreshold: 5 diff --git a/k8s/templates/secret-store.example.yaml b/k8s/templates/secret-store.example.yaml deleted file mode 100644 index 0f0a1225c5d31c80d335f52e097e703e89b0ec5c..0000000000000000000000000000000000000000 --- a/k8s/templates/secret-store.example.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: external-secrets.io/v1beta1 -kind: SecretStore -metadata: - name: secret-store -spec: - provider: # Specify the fields required by your secrets provider. - # Refer to https://external-secrets.io/latest/api/spec/ for the complete API specification diff --git a/k8s/values.yaml b/k8s/values.yaml index 4cfa5562eaae2025b81b9f563f67c4c050146104..9df42eacc23d15c289a7fdd49bb2992eb04cd7c7 100644 --- a/k8s/values.yaml +++ b/k8s/values.yaml @@ -12,7 +12,11 @@ as4MessageDispatcher: enabled: true image: name: as4-message-dispatcher - tag: v2.7.0 + tag: v3.0.0 + clusterIP: #8080 + nodePort: 31500 + loadBalancerIP: #127.0.0.1 + loadBalancerSourceRanges: #["127.0.0.1/255"] resources: limits: memory: 512Mi @@ -20,15 +24,19 @@ as4MessageDispatcher: runAsUser: 10000 runAsGroup: 10000 connectorConfig: - timeInterval: 1s + readTimeout: 5s + writeTimeout: 10s + idleTimeout: 2m shutdownTimeout: 20s + enablePolling: false + pollingInterval: 1s # ingored if enable_polling set to false. apiGateway: enabled: true replicas: 1 image: name: api-gateway - tag: v2.7.0 + tag: v3.0.0 clusterIP: #8080 nodePort: 31000 loadBalancerIP: #127.0.0.1 @@ -53,7 +61,7 @@ dataDiscovery: replicas: 1 image: name: data-discovery - tag: v2.7.0 + tag: v3.0.0 resources: limits: memory: 512Mi @@ -71,7 +79,7 @@ dataPermit: replicas: 1 image: name: data-permit - tag: v2.7.0 + tag: v3.0.0 resources: limits: memory: 512Mi @@ -116,7 +124,7 @@ migration: enabled: true image: name: goose - tag: v2.7.0 + tag: v3.0.0 domibus: username: "" @@ -130,7 +138,7 @@ shaclValidator: replicas: 1 image: name: shacl-validator - tag: v2.7.0 + tag: v3.0.0 resources: limits: memory: 512Mi