diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d8cb0b1..751c7cb4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,9 @@ "args": [ "run", "--config", - ".vscode/config/local.config.yaml" + "config.yaml", + "--apiAddress", + ":9090" ] }, { diff --git a/README.md b/README.md index 1600e362..fe613035 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,35 @@ checks: healthEndpoint: false ``` +### Check: Latency + +Available configuration options: + +- `checks` + - `latency` + - `enabled` (boolean): Currently not used. + - `interval` (integer): Interval in seconds to perform the latency check. + - `timeout` (integer): Timeout in seconds for the latency check. + - `retry` + - `count` (integer): Number of retries for the latency check. + - `delay` (integer): Delay in seconds between retries for the latency check. + - `targets` (list of strings): List of targets to send latency probe. Needs to be a valid url. Can be another `sparrow` instance. Use latency endpoint, e.g. `https://sparrow-dns.telekom.de/checks/latency`. The remote `sparrow` instance needs the `latencyEndpoint` enabled. + - `latencyEndpoint` (boolean): Needs to be activated when the `sparrow` should expose its own latency endpoint. Mandatory if another `sparrow` instance wants perform a latency check. +Example configuration: + +```yaml +checks: + latency: + enabled: true + interval: 1 + timeout: 3 + retry: + count: 3 + delay: 1 + targets: + - https://example.com/ + - https://google.com/ +``` ### API diff --git a/cmd/run.go b/cmd/run.go index 704cb82b..e3c8958b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -32,7 +32,7 @@ import ( // NewCmdRun creates a new run command func NewCmdRun() *cobra.Command { flagMapping := config.RunFlagsNameMapping{ - ApiListeningAddress: "apiListeningAddress", + ApiAddress: "apiAddress", LoaderType: "loaderType", LoaderInterval: "loaderInterval", LoaderHttpUrl: "loaderHttpUrl", @@ -50,7 +50,7 @@ func NewCmdRun() *cobra.Command { Run: run(&flagMapping), } - cmd.PersistentFlags().String(flagMapping.ApiListeningAddress, ":8080", "api: The address the server is listening on") + cmd.PersistentFlags().String(flagMapping.ApiAddress, ":8080", "api: The address the server is listening on") cmd.PersistentFlags().StringP(flagMapping.LoaderType, "l", "http", "defines the loader type that will load the checks configuration during the runtime. The fallback is the fileLoader") @@ -62,7 +62,7 @@ func NewCmdRun() *cobra.Command { cmd.PersistentFlags().Int(flagMapping.LoaderHttpRetryDelay, 1, "http loader: The initial delay between retries in seconds") cmd.PersistentFlags().String(flagMapping.LoaderFilePath, "config.yaml", "file loader: The path to the file to read the runtime config from") - viper.BindPFlag(flagMapping.ApiListeningAddress, cmd.PersistentFlags().Lookup(flagMapping.ApiListeningAddress)) + viper.BindPFlag(flagMapping.ApiAddress, cmd.PersistentFlags().Lookup(flagMapping.ApiAddress)) viper.BindPFlag(flagMapping.LoaderType, cmd.PersistentFlags().Lookup(flagMapping.LoaderType)) viper.BindPFlag(flagMapping.LoaderInterval, cmd.PersistentFlags().Lookup(flagMapping.LoaderInterval)) @@ -84,7 +84,7 @@ func run(fm *config.RunFlagsNameMapping) func(cmd *cobra.Command, args []string) cfg := config.NewConfig() - cfg.SetApiListeningAddress(viper.GetString(fm.ApiListeningAddress)) + cfg.SetApiAddress(viper.GetString(fm.ApiAddress)) cfg.SetLoaderType(viper.GetString(fm.LoaderType)) cfg.SetLoaderInterval(viper.GetInt(fm.LoaderInterval)) diff --git a/docs/sparrow.md b/docs/sparrow.md index c37922b5..f433887b 100644 --- a/docs/sparrow.md +++ b/docs/sparrow.md @@ -20,4 +20,4 @@ The check results are exposed via an API. * [sparrow gen-docs](sparrow_gen-docs.md) - Generate markdown documentation * [sparrow run](sparrow_run.md) - Run sparrow -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_completion.md b/docs/sparrow_completion.md index fc13b561..97d7cdb8 100644 --- a/docs/sparrow_completion.md +++ b/docs/sparrow_completion.md @@ -28,4 +28,4 @@ See each sub-command's help for details on how to use the generated script. * [sparrow completion powershell](sparrow_completion_powershell.md) - Generate the autocompletion script for powershell * [sparrow completion zsh](sparrow_completion_zsh.md) - Generate the autocompletion script for zsh -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_completion_bash.md b/docs/sparrow_completion_bash.md index 250d0890..2f979571 100644 --- a/docs/sparrow_completion_bash.md +++ b/docs/sparrow_completion_bash.md @@ -47,4 +47,4 @@ sparrow completion bash * [sparrow completion](sparrow_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_completion_fish.md b/docs/sparrow_completion_fish.md index 9768a0df..1e3e5c40 100644 --- a/docs/sparrow_completion_fish.md +++ b/docs/sparrow_completion_fish.md @@ -38,4 +38,4 @@ sparrow completion fish [flags] * [sparrow completion](sparrow_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_completion_powershell.md b/docs/sparrow_completion_powershell.md index e9fa323f..493d4db8 100644 --- a/docs/sparrow_completion_powershell.md +++ b/docs/sparrow_completion_powershell.md @@ -35,4 +35,4 @@ sparrow completion powershell [flags] * [sparrow completion](sparrow_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_completion_zsh.md b/docs/sparrow_completion_zsh.md index efff49fd..e98c55a2 100644 --- a/docs/sparrow_completion_zsh.md +++ b/docs/sparrow_completion_zsh.md @@ -49,4 +49,4 @@ sparrow completion zsh [flags] * [sparrow completion](sparrow_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_gen-docs.md b/docs/sparrow_gen-docs.md index d009341d..14d0dd0f 100644 --- a/docs/sparrow_gen-docs.md +++ b/docs/sparrow_gen-docs.md @@ -27,4 +27,4 @@ sparrow gen-docs [flags] * [sparrow](sparrow.md) - Sparrow, the infrastructure monitoring agent -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/docs/sparrow_run.md b/docs/sparrow_run.md index 268750d4..1f4f879b 100644 --- a/docs/sparrow_run.md +++ b/docs/sparrow_run.md @@ -13,16 +13,16 @@ sparrow run [flags] ### Options ``` - --apiListeningAddress string api: The address the server is listening on (default ":8080") - -h, --help help for run - --loaderFilePath string file loader: The path to the file to read the runtime config from (default "config.yaml") - --loaderHttpRetryCount int http loader: Amount of retries trying to load the configuration (default 3) - --loaderHttpRetryDelay int http loader: The initial delay between retries in seconds (default 1) - --loaderHttpTimeout int http loader: The timeout for the http request in seconds (default 30) - --loaderHttpToken string http loader: Bearer token to authenticate the http endpoint - --loaderHttpUrl string http loader: The url where to get the remote configuration - --loaderInterval int defines the interval the loader reloads the configuration in seconds (default 300) - -l, --loaderType string defines the loader type that will load the checks configuration during the runtime. The fallback is the fileLoader (default "http") + --apiAddress string api: The address the server is listening on (default ":8080") + -h, --help help for run + --loaderFilePath string file loader: The path to the file to read the runtime config from (default "config.yaml") + --loaderHttpRetryCount int http loader: Amount of retries trying to load the configuration (default 3) + --loaderHttpRetryDelay int http loader: The initial delay between retries in seconds (default 1) + --loaderHttpTimeout int http loader: The timeout for the http request in seconds (default 30) + --loaderHttpToken string http loader: Bearer token to authenticate the http endpoint + --loaderHttpUrl string http loader: The url where to get the remote configuration + --loaderInterval int defines the interval the loader reloads the configuration in seconds (default 300) + -l, --loaderType string defines the loader type that will load the checks configuration during the runtime. The fallback is the fileLoader (default "http") ``` ### Options inherited from parent commands @@ -35,4 +35,4 @@ sparrow run [flags] * [sparrow](sparrow.md) - Sparrow, the infrastructure monitoring agent -###### Auto generated by spf13/cobra on 27-Nov-2023 +###### Auto generated by spf13/cobra on 30-Nov-2023 diff --git a/go.mod b/go.mod index 38bc5df6..5a666714 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 + golang.org/x/sync v0.3.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 7f66ce71..7b78dc7e 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/checks/checks.go b/pkg/checks/checks.go index 942eefe0..e8f05fb5 100644 --- a/pkg/checks/checks.go +++ b/pkg/checks/checks.go @@ -31,7 +31,8 @@ import ( // The key is the name of the Check // The name needs to map the configuration item key var RegisteredChecks = map[string]func() Check{ - "health": NewHealthCheck, + "health": NewHealthCheck, + "latency": NewLatencyCheck, } //go:generate moq -out checks_moq.go . Check diff --git a/pkg/checks/latency.go b/pkg/checks/latency.go new file mode 100644 index 00000000..5c222539 --- /dev/null +++ b/pkg/checks/latency.go @@ -0,0 +1,179 @@ +package checks + +import ( + "context" + "net/http" + "sync" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/mitchellh/mapstructure" + "golang.org/x/sync/errgroup" + + "github.com/caas-team/sparrow/internal/helper" + "github.com/caas-team/sparrow/internal/logger" + "github.com/caas-team/sparrow/pkg/api" +) + +var _ Check = (*Latency)(nil) + +func NewLatencyCheck() Check { + return &Latency{ + mu: sync.Mutex{}, + cfg: LatencyConfig{}, + c: nil, + done: make(chan bool, 1), + } + +} + +type Latency struct { + cfg LatencyConfig + mu sync.Mutex + c chan<- Result + done chan bool +} + +type LatencyConfig struct { + Targets []string + Interval time.Duration + Timeout time.Duration + Retry helper.RetryConfig +} + +type LatencyResult struct { + Code int `json:"code"` + Error *string `json:"error"` + Total int64 `json:"total"` +} + +func (l *Latency) Run(ctx context.Context) error { + log := logger.FromContext(ctx).WithGroup("Latency") + log.Info(l.cfg.Interval.String()) + for { + select { + case <-ctx.Done(): + log.Error("context canceled", "err", ctx.Err()) + return ctx.Err() + case <-l.done: + return nil + case <-time.After(l.cfg.Interval): + results, err := l.check(ctx) + errval := "" + if err != nil { + errval = err.Error() + } + checkResult := Result{ + Data: results, + Err: errval, + Timestamp: time.Now(), + } + + l.c <- checkResult + } + } +} + +func (l *Latency) Startup(ctx context.Context, cResult chan<- Result) error { + log := logger.FromContext(ctx).WithGroup("latency") + log.Debug("Starting latency check") + + l.c = cResult + return nil +} + +func (l *Latency) Shutdown(ctx context.Context) error { + l.done <- true + close(l.done) + + return nil +} + +func (l *Latency) SetConfig(ctx context.Context, config any) error { + var c LatencyConfig + err := mapstructure.Decode(config, &c) + if err != nil { + return ErrInvalidConfig + } + c.Interval = time.Second * c.Interval + c.Retry.Delay = time.Second * c.Retry.Delay + l.mu.Lock() + defer l.mu.Unlock() + l.cfg = c + + return nil +} + +func (l *Latency) Schema() (*openapi3.SchemaRef, error) { + return OpenapiFromPerfData(make(map[string]LatencyResult)) +} + +func (l *Latency) RegisterHandler(ctx context.Context, router *api.RoutingTree) { + router.Add(http.MethodGet, "v1alpha1/latency", l.Handler) +} + +func (l *Latency) DeregisterHandler(ctx context.Context, router *api.RoutingTree) { + router.Remove(http.MethodGet, "v1alpha1/latency") +} + +func (l *Latency) Handler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func (l *Latency) check(ctx context.Context) (map[string]LatencyResult, error) { + log := logger.FromContext(ctx).WithGroup("check") + log.Debug("Checking latency") + + var resultMutex sync.Mutex + results := map[string]LatencyResult{} + + wg, ctx := errgroup.WithContext(ctx) + for _, e := range l.cfg.Targets { + wg.Go(func(ctx context.Context, e string) func() error { + return func() error { + cl := http.Client{ + Timeout: l.cfg.Timeout * time.Second, + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, e, nil) + if err != nil { + log.Error("Error while creating request", "error", err) + return err + } + + var latencyresult LatencyResult + + req = req.WithContext(ctx) + + helper.Retry(func(ctx context.Context) error { + start := time.Now() + response, err := cl.Do(req) + if err != nil { + errval := err.Error() + latencyresult.Error = &errval + log.Error("Error while checking latency", "error", err) + + } else { + latencyresult.Code = response.StatusCode + } + end := time.Now() + + latencyresult.Total = end.Sub(start).Milliseconds() + + resultMutex.Lock() + defer resultMutex.Unlock() + results[e] = latencyresult + + return err + }, l.cfg.Retry)(ctx) // ignore return value, since we set it in the closure + return nil + } + }(ctx, e)) + } + + if err := wg.Wait(); err != nil { + log.Error("Error while checking latency", "error", err) + return nil, err + } + + return results, nil +} diff --git a/pkg/checks/latency_test.go b/pkg/checks/latency_test.go new file mode 100644 index 00000000..245b83d4 --- /dev/null +++ b/pkg/checks/latency_test.go @@ -0,0 +1,237 @@ +package checks + +import ( + "context" + "net/http" + "net/http/httptest" + "reflect" + "sync" + "testing" + "time" + + "github.com/caas-team/sparrow/pkg/api" + "github.com/jarcoal/httpmock" +) + +func stringPointer(s string) *string { + return &s +} +func TestLatency_check(t *testing.T) { + httpmock.Activate() + defer httpmock.Deactivate() + + httpmock.RegisterResponder(http.MethodGet, "http://success.com", httpmock.NewStringResponder(200, "ok")) + httpmock.RegisterResponder(http.MethodGet, "http://fail.com", httpmock.NewStringResponder(500, "fail")) + httpmock.RegisterResponder(http.MethodGet, "http://timeout.com", httpmock.NewErrorResponder(context.DeadlineExceeded)) + + cResult := make(chan Result, 1) + c := Latency{ + cfg: LatencyConfig{}, + mu: sync.Mutex{}, + c: cResult, + done: make(chan bool, 1), + } + results := make(chan Result, 1) + c.Startup(context.Background(), results) + + c.SetConfig(context.Background(), LatencyConfig{ + Targets: []string{"http://success.com", "http://fail.com", "http://timeout.com"}, + Interval: time.Second * 120, + Timeout: time.Second * 1, + }) + defer c.Shutdown(context.Background()) + + data, err := c.check(context.Background()) + + if err != nil { + t.Errorf("Latency.check() error = %v", err) + } + + wantData := map[string]LatencyResult{ + "http://success.com": { + Code: 200, + Error: nil, + Total: 0, + }, + "http://fail.com": { + Code: 500, + Error: nil, + Total: 0, + }, + "http://timeout.com": { + Code: 0, + Error: stringPointer("Get \"http://timeout.com\": context deadline exceeded"), + Total: 0, + }, + } + + for k, v := range wantData { + if v.Code != data[k].Code { + t.Errorf("Latency.Run() = %v, want %v", data[k].Code, v.Code) + } + if v.Total != data[k].Total { + t.Errorf("Latency.Run() = %v, want %v", data[k].Total, v.Total) + } + if v.Error != nil && data[k].Error != nil { + if *v.Error != *data[k].Error { + t.Errorf("Latency.Run() = %v, want %v", *data[k].Error, *v.Error) + } + } + } + +} + +func TestLatency_Run(t *testing.T) { + httpmock.Activate() + defer httpmock.Deactivate() + + httpmock.RegisterResponder(http.MethodGet, "http://success.com", httpmock.NewStringResponder(200, "ok")) + httpmock.RegisterResponder(http.MethodGet, "http://fail.com", httpmock.NewStringResponder(500, "fail")) + httpmock.RegisterResponder(http.MethodGet, "http://timeout.com", httpmock.NewErrorResponder(context.DeadlineExceeded)) + + c := NewLatencyCheck() + results := make(chan Result, 1) + c.Startup(context.Background(), results) + + c.SetConfig(context.Background(), LatencyConfig{ + Targets: []string{"http://success.com", "http://fail.com", "http://timeout.com"}, + Interval: time.Second * 120, + Timeout: time.Second * 1, + }) + go c.Run(context.Background()) + defer c.Shutdown(context.Background()) + + result := <-results + wantResult := Result{ + Timestamp: result.Timestamp, + Err: "", + Data: map[string]LatencyResult{ + "http://success.com": { + Code: 200, + Error: nil, + Total: 0, + }, + "http://fail.com": { + Code: 500, + Error: nil, + Total: 0, + }, + "http://timeout.com": { + Code: 0, + Error: stringPointer("Get \"http://timeout.com\": context deadline exceeded"), + Total: 0, + }, + }, + } + + if wantResult.Timestamp != result.Timestamp { + t.Errorf("Latency.Run() = %v, want %v", result.Timestamp, wantResult.Timestamp) + } + if wantResult.Err != result.Err { + t.Errorf("Latency.Run() = %v, want %v", result.Err, wantResult.Err) + } + wantData := wantResult.Data.(map[string]LatencyResult) + data := result.Data.(map[string]LatencyResult) + + for k, v := range wantData { + if v.Code != data[k].Code { + t.Errorf("Latency.Run() = %v, want %v", data[k].Code, v.Code) + } + if v.Total != data[k].Total { + t.Errorf("Latency.Run() = %v, want %v", data[k].Total, v.Total) + } + if v.Error != nil && data[k].Error != nil { + if *v.Error != *data[k].Error { + t.Errorf("Latency.Run() = %v, want %v", *data[k].Error, *v.Error) + } + } + } +} + +func TestLatency_Startup(t *testing.T) { + c := Latency{} + + if err := c.Startup(context.Background(), make(chan<- Result, 1)); err != nil { + t.Errorf("Startup() error = %v", err) + } +} + +func TestLatency_Shutdown(t *testing.T) { + cDone := make(chan bool, 1) + c := Latency{ + done: cDone, + } + err := c.Shutdown(context.Background()) + + if err != nil { + t.Errorf("Shutdown() error = %v", err) + } + + if !<-cDone { + t.Error("Shutdown() should be ok") + } + +} + +func TestLatency_SetConfig(t *testing.T) { + c := Latency{} + wantCfg := LatencyConfig{ + Targets: []string{"http://localhost:9090"}, + } + + err := c.SetConfig(context.Background(), wantCfg) + + if err != nil { + t.Errorf("SetConfig() error = %v", err) + } + if !reflect.DeepEqual(c.cfg, wantCfg) { + t.Errorf("SetConfig() = %v, want %v", c.cfg, wantCfg) + } +} + +func TestLatency_RegisterHandler(t *testing.T) { + c := Latency{} + + rt := api.NewRoutingTree() + c.RegisterHandler(context.Background(), &rt) + + h, ok := rt.Get("GET", "v1alpha1/latency") + + if !ok { + t.Error("RegisterHandler() should be ok") + } + if h == nil { + t.Error("RegisterHandler() should not be nil") + } + c.DeregisterHandler(context.Background(), &rt) + h, ok = rt.Get("GET", "v1alpha1/latency") + + if ok { + t.Error("DeregisterHandler() should not be ok") + } + + if h != nil { + t.Error("DeregisterHandler() should be nil") + } + +} + +func TestLatency_Handler(t *testing.T) { + c := Latency{} + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/v1alpha1/latency", nil) + + c.Handler(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("Handler() should be ok, got %d", rec.Code) + } + +} + +func TestNewLatencyCheck(t *testing.T) { + c := NewLatencyCheck() + if c == nil { + t.Error("NewLatencyCheck() should not be nil") + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index a4616073..50f9fb15 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -63,7 +63,7 @@ func NewConfig() *Config { } } -func (c *Config) SetApiListeningAddress(address string) { +func (c *Config) SetApiAddress(address string) { c.Api.ListeningAddress = address } diff --git a/pkg/config/file_test.go b/pkg/config/file_test.go index 74282532..bee4f547 100644 --- a/pkg/config/file_test.go +++ b/pkg/config/file_test.go @@ -25,7 +25,7 @@ import ( ) func TestNewFileLoader(t *testing.T) { - l := NewFileLoader(&Config{Loader: LoaderConfig{file: FileLoaderConfig{path: "config.yaml"}}}, make(chan<- map[string]any)) + l := NewFileLoader(&Config{Loader: LoaderConfig{file: FileLoaderConfig{path: "config.yaml"}}}, make(chan<- map[string]any, 1)) if l.path != "config.yaml" { t.Errorf("Expected path to be config.yaml, got %s", l.path) @@ -53,7 +53,7 @@ func TestFileLoader_Run(t *testing.T) { args args want want }{ - {name: "Loads config from file", fields: fields{path: "testdata/config.yaml", c: make(chan map[string]any)}, args: func() args { + {name: "Loads config from file", fields: fields{path: "testdata/config.yaml", c: make(chan map[string]any, 1)}, args: func() args { ctx, cancel := context.WithCancel(context.Background()) return args{ctx: &ctx, cancel: &cancel} }(), want: want{cfg: map[string]any{"testCheck1": map[string]any{"enabled": true}}}}, diff --git a/pkg/config/flags.go b/pkg/config/flags.go index f7b1031f..45eea6ac 100644 --- a/pkg/config/flags.go +++ b/pkg/config/flags.go @@ -19,7 +19,7 @@ package config type RunFlagsNameMapping struct { - ApiListeningAddress string + ApiAddress string LoaderType string LoaderInterval string diff --git a/pkg/config/http_test.go b/pkg/config/http_test.go index 2933aa9c..ffa9b761 100644 --- a/pkg/config/http_test.go +++ b/pkg/config/http_test.go @@ -143,7 +143,7 @@ func TestHttpLoader_GetRuntimeConfig(t *testing.T) { gl := &HttpLoader{ cfg: tt.cfg, - cCfgChecks: make(chan<- map[string]any), + cCfgChecks: make(chan<- map[string]any, 1), } gl.cfg.Loader.http.url = endpoint diff --git a/pkg/config/loader.go b/pkg/config/loader.go index 11d241fd..c3fe48d9 100644 --- a/pkg/config/loader.go +++ b/pkg/config/loader.go @@ -22,10 +22,6 @@ import ( "context" ) -const ( - gitlabLoader = "GITLAB" - localLoader = "LOCAL" -) type Loader interface { Run(context.Context) diff --git a/pkg/sparrow/api.go b/pkg/sparrow/api.go index 989df634..18b9d4d5 100644 --- a/pkg/sparrow/api.go +++ b/pkg/sparrow/api.go @@ -58,14 +58,15 @@ func (s *Sparrow) register(ctx context.Context) { // // Blocks until context is done func (s *Sparrow) api(ctx context.Context) error { - log := logger.FromContext(ctx) - cErr := make(chan error) + log := logger.FromContext(ctx).WithGroup("api") + cErr := make(chan error, 1) s.register(ctx) server := http.Server{Addr: s.cfg.Api.ListeningAddress, Handler: s.router} // run http server in goroutine go func(cErr chan error) { defer close(cErr) + log.Info("serving api", "addr", s.cfg.Api.ListeningAddress) if err := server.ListenAndServe(); err != nil { log.Error("failed to serve api", "error", err) cErr <- err diff --git a/pkg/sparrow/run.go b/pkg/sparrow/run.go index 36cfa81a..05e115e0 100644 --- a/pkg/sparrow/run.go +++ b/pkg/sparrow/run.go @@ -53,10 +53,10 @@ func New(cfg *config.Config) *Sparrow { router: chi.NewRouter(), routingTree: api.NewRoutingTree(), checks: make(map[string]checks.Check), - cResult: make(chan checks.ResultDTO), + cResult: make(chan checks.ResultDTO, 1), resultFanIn: make(map[string]chan checks.Result), cfg: cfg, - cCfgChecks: make(chan map[string]any), + cCfgChecks: make(chan map[string]any, 1), db: db.NewInMemory(), } @@ -116,7 +116,7 @@ func (s *Sparrow) ReconcileChecks(ctx context.Context) { s.checks[name] = check // Create a fan in channel for the check - checkChan := make(chan checks.Result) + checkChan := make(chan checks.Result, 1) s.resultFanIn[name] = checkChan err := check.SetConfig(ctx, checkCfg) diff --git a/pkg/sparrow/run_test.go b/pkg/sparrow/run_test.go index 53f19ae9..72e98091 100644 --- a/pkg/sparrow/run_test.go +++ b/pkg/sparrow/run_test.go @@ -86,7 +86,7 @@ func TestSparrow_ReconcileChecks(t *testing.T) { fields: fields{ checks: map[string]checks.Check{}, cfg: &config.Config{}, - cCfgChecks: make(chan map[string]any), + cCfgChecks: make(chan map[string]any, 1), resultFanIn: make(map[string]chan checks.Result), }, newChecksConfig: map[string]any{ @@ -100,7 +100,7 @@ func TestSparrow_ReconcileChecks(t *testing.T) { "alpha": checks.RegisteredChecks["alpha"](), }, cfg: &config.Config{}, - cCfgChecks: make(chan map[string]any), + cCfgChecks: make(chan map[string]any, 1), resultFanIn: make(map[string]chan checks.Result), }, newChecksConfig: map[string]any{ @@ -115,7 +115,7 @@ func TestSparrow_ReconcileChecks(t *testing.T) { "alpha": checks.RegisteredChecks["alpha"](), }, cfg: &config.Config{}, - cCfgChecks: make(chan map[string]any), + cCfgChecks: make(chan map[string]any, 1), resultFanIn: make(map[string]chan checks.Result), }, newChecksConfig: map[string]any{}, @@ -128,7 +128,7 @@ func TestSparrow_ReconcileChecks(t *testing.T) { "gamma": checks.RegisteredChecks["alpha"](), }, cfg: &config.Config{}, - cCfgChecks: make(chan map[string]any), + cCfgChecks: make(chan map[string]any, 1), resultFanIn: make(map[string]chan checks.Result), }, newChecksConfig: map[string]any{ @@ -163,8 +163,8 @@ func TestSparrow_ReconcileChecks(t *testing.T) { } func Test_fanInResults(t *testing.T) { - checkChan := make(chan checks.Result) - cResult := make(chan checks.ResultDTO) + checkChan := make(chan checks.Result, 1) + cResult := make(chan checks.ResultDTO, 1) name := "check" go fanInResults(checkChan, cResult, name)