From 3cc6b2c0d04bf304d773e031b4c0b4ce9c24e94f Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Mon, 5 Jun 2023 23:17:30 +0200 Subject: [PATCH] CI: add tests for metrics configuration (#2251) --- cmd/crowdsec-cli/metrics.go | 67 ++++++++++++++++++++------------- pkg/csconfig/prometheus.go | 2 - pkg/csconfig/prometheus_test.go | 43 ++++++++------------- test/bats/01_cscli.bats | 1 - test/bats/08_metrics.bats | 60 +++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 test/bats/08_metrics.bats diff --git a/cmd/crowdsec-cli/metrics.go b/cmd/crowdsec-cli/metrics.go index f2577db8f..1ddf4ff66 100644 --- a/cmd/crowdsec-cli/metrics.go +++ b/cmd/crowdsec-cli/metrics.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net/http" - "os" "strconv" "strings" "time" @@ -23,6 +22,7 @@ import ( // FormatPrometheusMetrics is a complete rip from prom2json func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error { mfChan := make(chan *dto.MetricFamily, 1024) + errChan := make(chan error, 1) // Start with the DefaultTransport for sane defaults. transport := http.DefaultTransport.(*http.Transport).Clone() @@ -35,14 +35,21 @@ func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error defer trace.CatchPanic("crowdsec/ShowPrometheus") err := prom2json.FetchMetricFamilies(url, mfChan, transport) if err != nil { - log.Fatalf("failed to fetch prometheus metrics : %v", err) + errChan <- fmt.Errorf("failed to fetch prometheus metrics: %w", err) + return } + errChan <- nil }() result := []*prom2json.Family{} for mf := range mfChan { result = append(result, prom2json.NewFamily(mf)) } + + if err := <-errChan; err != nil { + return err + } + log.Debugf("Finished reading prometheus output, %d entries", len(result)) /*walk*/ lapi_decisions_stats := map[string]struct { @@ -262,36 +269,44 @@ func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error var noUnit bool + +func runMetrics(cmd *cobra.Command, args []string) error { + if err := csConfig.LoadPrometheus(); err != nil { + return fmt.Errorf("failed to load prometheus config: %w", err) + } + + if csConfig.Prometheus == nil { + return fmt.Errorf("prometheus section missing, can't show metrics") + } + + if !csConfig.Prometheus.Enabled { + return fmt.Errorf("prometheus is not enabled, can't show metrics") + } + + if prometheusURL == "" { + prometheusURL = csConfig.Cscli.PrometheusUrl + } + + if prometheusURL == "" { + return fmt.Errorf("no prometheus url, please specify in %s or via -u", *csConfig.FilePath) + } + + err := FormatPrometheusMetrics(color.Output, prometheusURL+"/metrics", csConfig.Cscli.Output) + if err != nil { + return fmt.Errorf("could not fetch prometheus metrics: %w", err) + } + return nil +} + + func NewMetricsCmd() *cobra.Command { - var cmdMetrics = &cobra.Command{ + cmdMetrics := &cobra.Command{ Use: "metrics", Short: "Display crowdsec prometheus metrics.", Long: `Fetch metrics from the prometheus server and display them in a human-friendly way`, Args: cobra.ExactArgs(0), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - if err := csConfig.LoadPrometheus(); err != nil { - log.Fatal(err) - } - if !csConfig.Prometheus.Enabled { - log.Warning("Prometheus is not enabled, can't show metrics") - os.Exit(1) - } - - if prometheusURL == "" { - prometheusURL = csConfig.Cscli.PrometheusUrl - } - - if prometheusURL == "" { - log.Errorf("No prometheus url, please specify in %s or via -u", *csConfig.FilePath) - os.Exit(1) - } - - err := FormatPrometheusMetrics(color.Output, prometheusURL+"/metrics", csConfig.Cscli.Output) - if err != nil { - log.Fatalf("could not fetch prometheus metrics: %s", err) - } - }, + RunE: runMetrics, } cmdMetrics.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url (http://:/metrics)") cmdMetrics.PersistentFlags().BoolVar(&noUnit, "no-unit", false, "Show the real number instead of formatted with units") diff --git a/pkg/csconfig/prometheus.go b/pkg/csconfig/prometheus.go index 31df85110..eea768ab7 100644 --- a/pkg/csconfig/prometheus.go +++ b/pkg/csconfig/prometheus.go @@ -2,7 +2,6 @@ package csconfig import "fmt" -/**/ type PrometheusCfg struct { Enabled bool `yaml:"enabled"` Level string `yaml:"level"` //aggregated|full @@ -16,6 +15,5 @@ func (c *Config) LoadPrometheus() error { c.Cscli.PrometheusUrl = fmt.Sprintf("http://%s:%d", c.Prometheus.ListenAddr, c.Prometheus.ListenPort) } } - return nil } diff --git a/pkg/csconfig/prometheus_test.go b/pkg/csconfig/prometheus_test.go index f7a483d32..3df9c298b 100644 --- a/pkg/csconfig/prometheus_test.go +++ b/pkg/csconfig/prometheus_test.go @@ -1,20 +1,19 @@ package csconfig import ( - "fmt" - "strings" "testing" - "github.com/stretchr/testify/assert" + "github.com/crowdsecurity/go-cs-lib/pkg/cstest" + + "github.com/stretchr/testify/require" ) func TestLoadPrometheus(t *testing.T) { - tests := []struct { - name string - Input *Config - expectedResult string - err string + name string + Input *Config + expectedURL string + expectedErr string }{ { name: "basic valid configuration", @@ -27,29 +26,17 @@ func TestLoadPrometheus(t *testing.T) { }, Cscli: &CscliCfg{}, }, - expectedResult: "http://127.0.0.1:6060", + expectedURL: "http://127.0.0.1:6060", }, } - for idx, test := range tests { - err := test.Input.LoadPrometheus() - if err == nil && test.err != "" { - fmt.Printf("TEST '%s': NOK\n", test.name) - t.Fatalf("%d/%d expected error, didn't get it", idx, len(tests)) - } else if test.err != "" { - if !strings.HasPrefix(fmt.Sprintf("%s", err), test.err) { - fmt.Printf("TEST '%s': NOK\n", test.name) - t.Fatalf("%d/%d expected '%s' got '%s'", idx, len(tests), - test.err, - fmt.Sprintf("%s", err)) - } - } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.Input.LoadPrometheus() + cstest.RequireErrorContains(t, err, tc.expectedErr) - isOk := assert.Equal(t, test.expectedResult, test.Input.Cscli.PrometheusUrl) - if !isOk { - t.Fatalf("test '%s' failed\n", test.name) - } else { - fmt.Printf("TEST '%s': OK\n", test.name) - } + require.Equal(t, tc.expectedURL, tc.Input.Cscli.PrometheusUrl) + }) } } diff --git a/test/bats/01_cscli.bats b/test/bats/01_cscli.bats index c765bf707..a01d936b7 100644 --- a/test/bats/01_cscli.bats +++ b/test/bats/01_cscli.bats @@ -228,7 +228,6 @@ teardown() { assert_output --partial "Route" assert_output --partial '/v1/watchers/login' assert_output --partial "Local Api Metrics:" - } @test "'cscli completion' with or without configuration file" { diff --git a/test/bats/08_metrics.bats b/test/bats/08_metrics.bats new file mode 100644 index 000000000..836e22048 --- /dev/null +++ b/test/bats/08_metrics.bats @@ -0,0 +1,60 @@ +#!/usr/bin/env bats +# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si: + +set -u + +setup_file() { + load "../lib/setup_file.sh" +} + +teardown_file() { + load "../lib/teardown_file.sh" +} + +setup() { + load "../lib/setup.sh" + ./instance-data load +} + +teardown() { + ./instance-crowdsec stop +} + +#---------- + +@test "cscli metrics (crowdsec not running)" { + rune -1 cscli metrics + # crowdsec is down + assert_stderr --partial "failed to fetch prometheus metrics" + assert_stderr --partial "connect: connection refused" +} + +@test "cscli metrics (bad configuration)" { + config_set '.prometheus.foo="bar"' + rune -1 cscli metrics + assert_stderr --partial "field foo not found in type csconfig.PrometheusCfg" +} + +@test "cscli metrics (.prometheus.enabled=false)" { + config_set '.prometheus.enabled=false' + rune -1 cscli metrics + assert_stderr --partial "prometheus is not enabled, can't show metrics" +} + +@test "cscli metrics (missing listen_addr)" { + config_set 'del(.prometheus.listen_addr)' + rune -1 cscli metrics + assert_stderr --partial "no prometheus url, please specify" +} + +@test "cscli metrics (missing listen_port)" { + config_set 'del(.prometheus.listen_addr)' + rune -1 cscli metrics + assert_stderr --partial "no prometheus url, please specify" +} + +@test "cscli metrics (missing prometheus section)" { + config_set 'del(.prometheus)' + rune -1 cscli metrics + assert_stderr --partial "prometheus section missing, can't show metrics" +}