revisit checks

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2026-03-24 13:11:18 +01:00
parent aebf15fe81
commit 2f04639a52
6 changed files with 59 additions and 68 deletions

View File

@@ -6,22 +6,31 @@ import (
"github.com/opencloud-eu/opencloud/pkg/handlers"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
)
// NewGRPCCheck checks the reachability of a grpc server.
func NewGRPCCheck(address string) func(context.Context) error {
return func(_ context.Context) error {
address, err := handlers.FailSaveAddress(address)
if err != nil {
return err
address, err := handlers.FailSaveAddress(address)
if err != nil {
return func(context.Context) error {
return fmt.Errorf("invalid address: %v", err)
}
}
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return func(context.Context) error {
return fmt.Errorf("could not connect to grpc server: %v", err)
}
_ = conn.Close()
}
return func(ctx context.Context) error {
s := conn.GetState()
if s == connectivity.TransientFailure || s == connectivity.Shutdown {
return fmt.Errorf("grpc connection in bad state: %v", s)
}
return nil
}
}

View File

@@ -12,20 +12,25 @@ import (
// NewHTTPCheck checks the reachability of a http server.
func NewHTTPCheck(url string) func(context.Context) error {
return func(_ context.Context) error {
url, err := handlers.FailSaveAddress(url)
url, err := handlers.FailSaveAddress(url)
if err != nil {
return func(context.Context) error {
return fmt.Errorf("invalid url: %v", err)
}
}
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "http://" + url
}
c := &http.Client{
Timeout: 3 * time.Second,
}
return func(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "http://" + url
}
c := http.Client{
Timeout: 3 * time.Second,
}
resp, err := c.Get(url)
resp, err := c.Do(req)
if err != nil {
return fmt.Errorf("could not connect to http server: %v", err)
}

View File

@@ -9,14 +9,16 @@ import (
// NewNatsCheck checks the reachability of a nats server.
func NewNatsCheck(natsCluster string, options ...nats.Option) func(context.Context) error {
return func(_ context.Context) error {
n, err := nats.Connect(natsCluster, options...)
if err != nil {
conn, err := nats.Connect(natsCluster, options...)
if err != nil {
return func(context.Context) error {
return fmt.Errorf("could not connect to nats server: %v", err)
}
defer n.Close()
if n.Status() != nats.CONNECTED {
return fmt.Errorf("nats server not connected")
}
return func(_ context.Context) error {
if conn.Status() != nats.CONNECTED {
return fmt.Errorf("nats server not connected: %v", conn.Status())
}
return nil
}

View File

@@ -10,22 +10,19 @@ import (
// NewTCPCheck returns a check that connects to a given tcp endpoint.
func NewTCPCheck(address string) func(context.Context) error {
return func(_ context.Context) error {
address, err := handlers.FailSaveAddress(address)
address, err := handlers.FailSaveAddress(address)
if err != nil {
return func(context.Context) error {
return err
}
}
return func(ctx context.Context) error {
d := net.Dialer{Timeout: 3 * time.Second}
conn, err := d.DialContext(ctx, "tcp", address)
if err != nil {
return err
}
conn, err := net.DialTimeout("tcp", address, 3*time.Second)
if err != nil {
return err
}
err = conn.Close()
if err != nil {
return err
}
return nil
return conn.Close()
}
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"maps"
"net"
"net/http"
"strings"
@@ -117,32 +116,11 @@ func (h *CheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// FailSaveAddress replaces wildcard addresses with the outbound IP.
func FailSaveAddress(address string) (string, error) {
if strings.Contains(address, "0.0.0.0") || strings.Contains(address, "::") {
outboundIp, err := getOutBoundIP()
if err != nil {
return "", err
}
address = strings.Replace(address, "0.0.0.0", outboundIp, 1)
address = strings.Replace(address, "::", "["+outboundIp+"]", 1)
address = strings.Replace(address, "[::]", "["+outboundIp+"]", 1)
if strings.Contains(address, "0.0.0.0") {
return strings.Replace(address, "0.0.0.0", "localhost", 1), nil
}
if strings.Contains(address, "::") {
return strings.Replace(strings.Replace(address, "[::]", "localhost", 1), "::", "localhost", 1), nil
}
return address, nil
}
// getOutBoundIP returns the outbound IP address.
func getOutBoundIP() (string, error) {
interfacesAddresses, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, address := range interfacesAddresses {
if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
}
return "", fmt.Errorf("no IP found")
}

View File

@@ -18,11 +18,11 @@ func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
healthHandlerConfiguration := handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
WithLogger(options.Logger)
readyHandlerConfiguration := healthHandlerConfiguration.
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint)).
WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
var configDumpFunc http.HandlerFunc = configDump(options.Config)
return debug.NewService(