Skip to content

Commit

Permalink
feat: load check config on startup (#115)
Browse files Browse the repository at this point in the history
* feat: healthz endpoint

* feat: add healthz handler
* feat: add health checker component
* feat: check metrics endpoint health
* feat: check health of all check endpoints
* test: add checker tests
* test: add healthz handler tests

* fix: get runtime config once at sparrow startup

* refactor: simplify loader init run

* chore: rm formatting helper function

* refactor: share http.Client instance

* fix: format checker address

* refactor: use internal check states instead of api route states

* chore: add running state to traceroute check

* fix: rm ok handler from unhealthy response

* refactor: return http loader cfg by value

* revert: healthz endpoint

* refactor: test empty config scenario
  • Loading branch information
lvlcn-t authored Mar 12, 2024
1 parent a8948eb commit 3e86839
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 89 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ repos:
- id: go-generate-repo
name: go generate
entry: go
args: [ generate, ./... ]
args: [generate, ./...]
language: system
types: [ go ]
types: [go]
pass_filenames: false
always_run: true
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
- id: go-mod-tidy-repo
- id: go-test-repo-mod
args: [ -race, -count=1 ]
args: [-race, -count=1, -timeout 30s]
- id: go-vet-repo-mod
- id: go-fumpt-repo
args: [ -l, -w ]
args: [-l, -w]
- id: golangci-lint-repo-mod
args: [ --config, .golangci.yaml, --, --fix ]
args: [--config, .golangci.yaml, --, --fix]
- repo: https://github.com/norwoodj/helm-docs
rev: v1.13.0
hooks:
Expand Down
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,17 @@ module github.com/caas-team/sparrow
go 1.22

require (
github.com/aeden/traceroute v0.0.0-20210211061815-03f5f7cb7908
github.com/getkin/kin-openapi v0.120.0
github.com/go-chi/chi/v5 v5.0.10
github.com/google/go-cmp v0.6.0
github.com/jarcoal/httpmock v1.3.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.0
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)

require github.com/google/go-cmp v0.6.0

require github.com/aeden/traceroute v0.0.0-20210211061815-03f5f7cb7908

require github.com/mitchellh/mapstructure v1.5.0 // indirect

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand All @@ -34,6 +30,7 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
Expand Down
6 changes: 3 additions & 3 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (a *api) RegisterRoutes(ctx context.Context, routes ...Route) error {

// Handles requests with simple http ok
// Required for global tarMan in checks
a.router.Handle("/", okHandler(ctx))
a.router.Handle("/", OkHandler(ctx))

return nil
}
Expand All @@ -148,8 +148,8 @@ func (a *api) registerDefaultRoute(route Route) (err error) {
return nil
}

// okHandler returns a handler that will serve status ok
func okHandler(ctx context.Context) http.Handler {
// OkHandler returns a handler that will serve status ok
func OkHandler(ctx context.Context) http.Handler {
log := logger.FromContext(ctx)

return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TestAPI_ShutdownWhenContextCanceled(t *testing.T) {
}
}

func Test_okHandler(t *testing.T) {
func TestAPI_OkHandler(t *testing.T) {
ctx := context.Background()

req, err := http.NewRequestWithContext(ctx, "GET", "/okHandler", http.NoBody)
Expand All @@ -221,7 +221,7 @@ func Test_okHandler(t *testing.T) {
}

rr := httptest.NewRecorder()
handler := okHandler(ctx)
handler := OkHandler(ctx)

handler.ServeHTTP(rr, req)

Expand Down
2 changes: 1 addition & 1 deletion pkg/checks/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func (d *DNS) Run(ctx context.Context, cResult chan checks.ResultDTO) error {
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)
log.Info("Starting dns check", "interval", d.config.Interval.String())

log.Info("Starting dns check", "interval", d.config.Interval.String())
for {
select {
case <-ctx.Done():
Expand Down
2 changes: 1 addition & 1 deletion pkg/checks/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func (h *Health) Run(ctx context.Context, cResult chan checks.ResultDTO) error {
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)
log.Info("Starting healthcheck", "interval", h.config.Interval.String())

log.Info("Starting healthcheck", "interval", h.config.Interval.String())
for {
select {
case <-ctx.Done():
Expand Down
2 changes: 1 addition & 1 deletion pkg/checks/latency/latency.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ func (l *Latency) Run(ctx context.Context, cResult chan checks.ResultDTO) error
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)
log.Info("Starting latency check", "interval", l.config.Interval.String())

log.Info("Starting latency check", "interval", l.config.Interval.String())
for {
select {
case <-ctx.Done():
Expand Down
6 changes: 3 additions & 3 deletions pkg/checks/traceroute/traceroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ type Target struct {

func NewCheck() checks.Check {
return &Traceroute{
config: Config{},
traceroute: newTraceroute,
CheckBase: checks.CheckBase{
Mu: sync.Mutex{},
DoneChan: make(chan struct{}),
},
config: Config{},
traceroute: newTraceroute,
}
}

Expand Down Expand Up @@ -69,8 +69,8 @@ func (tr *Traceroute) Run(ctx context.Context, cResult chan checks.ResultDTO) er
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)
log.Info("Starting traceroute check", "interval", tr.config.Interval.String())

log.Info("Starting traceroute check", "interval", tr.config.Interval.String())
for {
select {
case <-ctx.Done():
Expand Down
18 changes: 10 additions & 8 deletions pkg/config/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,23 @@ func NewFileLoader(cfg *Config, cRuntime chan<- runtime.Config) *FileLoader {

// Run gets the runtime configuration from the local file.
// The config will be loaded periodically defined by the loader interval configuration.
// If the interval is 0, the configuration is only fetched once and the loader is disabled.
func (f *FileLoader) Run(ctx context.Context) error {
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)

if f.config.Interval == 0 {
cfg, err := f.getRuntimeConfig(ctx)
if err != nil {
log.Warn("Could not get local runtime configuration", "error", err)
return fmt.Errorf("could not get local runtime configuration: %w", err)
}
// Get the runtime configuration once on startup
cfg, err := f.getRuntimeConfig(ctx)
if err != nil {
log.Warn("Could not get local runtime configuration", "error", err)
err = fmt.Errorf("could not get local runtime configuration: %w", err)
}
f.cRuntime <- cfg

f.cRuntime <- cfg
if f.config.Interval == 0 {
log.Info("File Loader disabled")
return nil
return err
}

tick := time.NewTicker(f.config.Interval)
Expand Down
64 changes: 33 additions & 31 deletions pkg/config/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package config

import (
"context"
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -51,61 +52,62 @@ func NewHttpLoader(cfg *Config, cRuntime chan<- runtime.Config) *HttpLoader {

// Run gets the runtime configuration from the remote file of the configured http endpoint.
// The config will be loaded periodically defined by the loader interval configuration.
// Returns an error if the loader is shutdown or the context is done.
func (hl *HttpLoader) Run(ctx context.Context) error {
// If the interval is 0, the configuration is only fetched once and the loader is disabled.
func (h *HttpLoader) Run(ctx context.Context) error {
ctx, cancel := logger.NewContextWithLogger(ctx)
defer cancel()
log := logger.FromContext(ctx)

var runtimeCfg *runtime.Config
var cfg runtime.Config
getConfigRetry := helper.Retry(func(ctx context.Context) (err error) {
runtimeCfg, err = hl.getRuntimeConfig(ctx)
cfg, err = h.getRuntimeConfig(ctx)
return err
}, hl.cfg.Http.RetryCfg)
}, h.cfg.Http.RetryCfg)

if hl.cfg.Interval == 0 {
err := getConfigRetry(ctx)
if err != nil {
log.Warn("Could not get remote runtime configuration", "error", err)
return fmt.Errorf("could not get remote runtime configuration: %w", err)
}
// Get the runtime configuration once on startup
err := getConfigRetry(ctx)
if err != nil {
log.Warn("Could not get remote runtime configuration", "error", err)
err = fmt.Errorf("could not get remote runtime configuration: %w", err)
}
h.cRuntime <- cfg

hl.cRuntime <- *runtimeCfg
if h.cfg.Interval == 0 {
log.Info("HTTP Loader disabled")
return nil
return err
}

tick := time.NewTicker(hl.cfg.Interval)
tick := time.NewTicker(h.cfg.Interval)
defer tick.Stop()

for {
select {
case <-hl.done:
case <-h.done:
log.Info("HTTP Loader terminated")
return nil
case <-ctx.Done():
return ctx.Err()
case <-tick.C:
if err := getConfigRetry(ctx); err != nil {
log.Warn("Could not get remote runtime configuration", "error", err)
tick.Reset(hl.cfg.Interval)
tick.Reset(h.cfg.Interval)
continue
}

log.Info("Successfully got remote runtime configuration")
hl.cRuntime <- *runtimeCfg
tick.Reset(hl.cfg.Interval)
h.cRuntime <- cfg
tick.Reset(h.cfg.Interval)
}
}
}

// GetRuntimeConfig gets the remote runtime configuration
func (hl *HttpLoader) getRuntimeConfig(ctx context.Context) (*runtime.Config, error) {
func (hl *HttpLoader) getRuntimeConfig(ctx context.Context) (cfg runtime.Config, err error) {
log := logger.FromContext(ctx).With("url", hl.cfg.Http.Url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, hl.cfg.Http.Url, http.NoBody)
if err != nil {
log.Error("Could not create http GET request", "error", err.Error())
return nil, err
return cfg, err
}
if hl.cfg.Http.Token != "" {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", hl.cfg.Http.Token))
Expand All @@ -114,34 +116,34 @@ func (hl *HttpLoader) getRuntimeConfig(ctx context.Context) (*runtime.Config, er
res, err := hl.client.Do(req) //nolint:bodyclose
if err != nil {
log.Error("Http get request failed", "error", err.Error())
return nil, err
return cfg, err
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
log.Error("Failed to close response body", "error", err.Error())
cErr := Body.Close()
if cErr != nil {
log.Error("Failed to close response body", "error", cErr)
err = errors.Join(cErr, err)
}
}(res.Body)

if res.StatusCode != http.StatusOK {
log.Error("Http get request failed", "status", res.Status)
return nil, fmt.Errorf("request failed, status is %s", res.Status)
return cfg, fmt.Errorf("request failed, status is %s", res.Status)
}

body, err := io.ReadAll(res.Body)
b, err := io.ReadAll(res.Body)
if err != nil {
log.Error("Could not read response body", "error", err.Error())
return nil, err
return cfg, err
}
log.Debug("Successfully got response")

runtimeCfg := &runtime.Config{}
if err := yaml.Unmarshal(body, &runtimeCfg); err != nil {
if err := yaml.Unmarshal(b, &cfg); err != nil {
log.Error("Could not unmarshal response", "error", err.Error())
return nil, err
return cfg, err
}

return runtimeCfg, nil
return cfg, nil
}

// Shutdown stops the loader
Expand Down
Loading

0 comments on commit 3e86839

Please sign in to comment.