-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10163 from fschade/ready-health-checks
Bugfix: Improve Ready and Health Checks
- Loading branch information
Showing
98 changed files
with
1,609 additions
and
1,444 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package handlers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"maps" | ||
"net/http" | ||
|
||
"golang.org/x/sync/errgroup" | ||
|
||
"github.com/owncloud/ocis/v2/ocis-pkg/log" | ||
) | ||
|
||
// check is a function that performs a check. | ||
type check func(ctx context.Context) error | ||
|
||
// CheckHandlerConfiguration defines the configuration for the CheckHandler. | ||
type CheckHandlerConfiguration struct { | ||
logger log.Logger | ||
checks map[string]check | ||
limit int | ||
statusFailed int | ||
statusSuccess int | ||
} | ||
|
||
// NewCheckHandlerConfiguration initializes a new CheckHandlerConfiguration. | ||
func NewCheckHandlerConfiguration() CheckHandlerConfiguration { | ||
return CheckHandlerConfiguration{ | ||
checks: make(map[string]check), | ||
limit: -1, | ||
statusFailed: http.StatusInternalServerError, | ||
statusSuccess: http.StatusOK, | ||
} | ||
} | ||
|
||
// WithLogger sets the logger for the CheckHandlerConfiguration. | ||
func (c CheckHandlerConfiguration) WithLogger(l log.Logger) CheckHandlerConfiguration { | ||
c.logger = l | ||
return c | ||
} | ||
|
||
// WithCheck sets a check for the CheckHandlerConfiguration. | ||
func (c CheckHandlerConfiguration) WithCheck(name string, f check) CheckHandlerConfiguration { | ||
if _, ok := c.checks[name]; ok { | ||
c.logger.Panic().Str("check", name).Msg("check already exists") | ||
} | ||
|
||
c.checks[name] = f | ||
return c | ||
} | ||
|
||
// WithLimit limits the number of active goroutines for the checks to at most n | ||
func (c CheckHandlerConfiguration) WithLimit(n int) CheckHandlerConfiguration { | ||
c.limit = n | ||
return c | ||
} | ||
|
||
// WithStatusFailed sets the status code for the failed checks. | ||
func (c CheckHandlerConfiguration) WithStatusFailed(status int) CheckHandlerConfiguration { | ||
c.statusFailed = status | ||
return c | ||
} | ||
|
||
// WithStatusSuccess sets the status code for the successful checks. | ||
func (c CheckHandlerConfiguration) WithStatusSuccess(status int) CheckHandlerConfiguration { | ||
c.statusSuccess = status | ||
return c | ||
} | ||
|
||
// CheckHandler is a http Handler that performs different checks. | ||
type CheckHandler struct { | ||
conf CheckHandlerConfiguration | ||
} | ||
|
||
// NewCheckHandler initializes a new CheckHandler. | ||
func NewCheckHandler(c CheckHandlerConfiguration) *CheckHandler { | ||
c.checks = maps.Clone(c.checks) // prevent check duplication after initialization | ||
return &CheckHandler{ | ||
conf: c, | ||
} | ||
} | ||
|
||
// AddCheck adds a check to the CheckHandler. | ||
func (h *CheckHandler) AddCheck(name string, c check) { | ||
h.conf.WithCheck(name, c) | ||
} | ||
|
||
func (h *CheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
g, ctx := errgroup.WithContext(r.Context()) | ||
g.SetLimit(h.conf.limit) | ||
|
||
for name, check := range h.conf.checks { | ||
checker := check | ||
checkerName := name | ||
g.Go(func() error { // https://go.dev/blog/loopvar-preview per iteration scope since go 1.22 | ||
if err := checker(ctx); err != nil { // since go 1.22 for loops have a per-iteration scope instead of per-loop scope, no need to pin the check... | ||
return fmt.Errorf("'%s': %w", checkerName, err) | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
status := h.conf.statusSuccess | ||
if err := g.Wait(); err != nil { | ||
status = h.conf.statusFailed | ||
h.conf.logger.Error().Err(err).Msg("check failed") | ||
} | ||
|
||
w.Header().Set("Content-Type", "text/plain") | ||
w.WriteHeader(status) | ||
|
||
if _, err := io.WriteString(w, http.StatusText(status)); err != nil { // io.WriteString should not fail, but if it does, we want to know. | ||
h.conf.logger.Panic().Err(err).Msg("failed to write response") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package handlers_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/owncloud/ocis/v2/ocis-pkg/handlers" | ||
) | ||
|
||
func TestCheckHandler_AddCheck(t *testing.T) { | ||
c := handlers.NewCheckHandlerConfiguration().WithCheck("shared-check", func(ctx context.Context) error { return nil }) | ||
|
||
t.Run("configured checks are unique once added", func(t *testing.T) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
t.Errorf("checks should be unique, got %v", r) | ||
} | ||
}() | ||
|
||
h1 := handlers.NewCheckHandler(c) | ||
h1.AddCheck("check-with-same-name", func(ctx context.Context) error { return nil }) | ||
|
||
h2 := handlers.NewCheckHandler(c) | ||
h2.AddCheck("check-with-same-name", func(ctx context.Context) error { return nil }) | ||
|
||
fmt.Print(1) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package handlers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/nats-io/nats.go" | ||
) | ||
|
||
// 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 { | ||
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 nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package handlers | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"time" | ||
) | ||
|
||
// NewTCPCheck returns a check that connects to a given tcp endpoint. | ||
func NewTCPCheck(address string) func(ctx context.Context) error { | ||
return func(_ context.Context) error { | ||
conn, err := net.DialTimeout("tcp", address, 3*time.Second) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = conn.Close() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.