Browse Source

CI: add tests for metrics configuration (#2251)

mmetc 2 years ago
parent
commit
3cc6b2c0d0

+ 41 - 26
cmd/crowdsec-cli/metrics.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
-	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -23,6 +22,7 @@ import (
 // FormatPrometheusMetrics is a complete rip from prom2json
 // FormatPrometheusMetrics is a complete rip from prom2json
 func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error {
 func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error {
 	mfChan := make(chan *dto.MetricFamily, 1024)
 	mfChan := make(chan *dto.MetricFamily, 1024)
+	errChan := make(chan error, 1)
 
 
 	// Start with the DefaultTransport for sane defaults.
 	// Start with the DefaultTransport for sane defaults.
 	transport := http.DefaultTransport.(*http.Transport).Clone()
 	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")
 		defer trace.CatchPanic("crowdsec/ShowPrometheus")
 		err := prom2json.FetchMetricFamilies(url, mfChan, transport)
 		err := prom2json.FetchMetricFamilies(url, mfChan, transport)
 		if err != nil {
 		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{}
 	result := []*prom2json.Family{}
 	for mf := range mfChan {
 	for mf := range mfChan {
 		result = append(result, prom2json.NewFamily(mf))
 		result = append(result, prom2json.NewFamily(mf))
 	}
 	}
+
+	if err := <-errChan; err != nil {
+		return err
+	}
+
 	log.Debugf("Finished reading prometheus output, %d entries", len(result))
 	log.Debugf("Finished reading prometheus output, %d entries", len(result))
 	/*walk*/
 	/*walk*/
 	lapi_decisions_stats := map[string]struct {
 	lapi_decisions_stats := map[string]struct {
@@ -262,36 +269,44 @@ func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error
 
 
 var noUnit bool
 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 {
 func NewMetricsCmd() *cobra.Command {
-	var cmdMetrics = &cobra.Command{
+	cmdMetrics := &cobra.Command{
 		Use:               "metrics",
 		Use:               "metrics",
 		Short:             "Display crowdsec prometheus metrics.",
 		Short:             "Display crowdsec prometheus metrics.",
 		Long:              `Fetch metrics from the prometheus server and display them in a human-friendly way`,
 		Long:              `Fetch metrics from the prometheus server and display them in a human-friendly way`,
 		Args:              cobra.ExactArgs(0),
 		Args:              cobra.ExactArgs(0),
 		DisableAutoGenTag: true,
 		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://<ip>:<port>/metrics)")
 	cmdMetrics.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url (http://<ip>:<port>/metrics)")
 	cmdMetrics.PersistentFlags().BoolVar(&noUnit, "no-unit", false, "Show the real number instead of formatted with units")
 	cmdMetrics.PersistentFlags().BoolVar(&noUnit, "no-unit", false, "Show the real number instead of formatted with units")

+ 0 - 2
pkg/csconfig/prometheus.go

@@ -2,7 +2,6 @@ package csconfig
 
 
 import "fmt"
 import "fmt"
 
 
-/**/
 type PrometheusCfg struct {
 type PrometheusCfg struct {
 	Enabled    bool   `yaml:"enabled"`
 	Enabled    bool   `yaml:"enabled"`
 	Level      string `yaml:"level"` //aggregated|full
 	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)
 			c.Cscli.PrometheusUrl = fmt.Sprintf("http://%s:%d", c.Prometheus.ListenAddr, c.Prometheus.ListenPort)
 		}
 		}
 	}
 	}
-
 	return nil
 	return nil
 }
 }

+ 15 - 28
pkg/csconfig/prometheus_test.go

@@ -1,20 +1,19 @@
 package csconfig
 package csconfig
 
 
 import (
 import (
-	"fmt"
-	"strings"
 	"testing"
 	"testing"
 
 
-	"github.com/stretchr/testify/assert"
+	"github.com/crowdsecurity/go-cs-lib/pkg/cstest"
+
+	"github.com/stretchr/testify/require"
 )
 )
 
 
 func TestLoadPrometheus(t *testing.T) {
 func TestLoadPrometheus(t *testing.T) {
-
 	tests := []struct {
 	tests := []struct {
-		name           string
-		Input          *Config
-		expectedResult string
-		err            string
+		name        string
+		Input       *Config
+		expectedURL string
+		expectedErr string
 	}{
 	}{
 		{
 		{
 			name: "basic valid configuration",
 			name: "basic valid configuration",
@@ -27,29 +26,17 @@ func TestLoadPrometheus(t *testing.T) {
 				},
 				},
 				Cscli: &CscliCfg{},
 				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)
+		})
 	}
 	}
 }
 }

+ 0 - 1
test/bats/01_cscli.bats

@@ -228,7 +228,6 @@ teardown() {
     assert_output --partial "Route"
     assert_output --partial "Route"
     assert_output --partial '/v1/watchers/login'
     assert_output --partial '/v1/watchers/login'
     assert_output --partial "Local Api Metrics:"
     assert_output --partial "Local Api Metrics:"
-
 }
 }
 
 
 @test "'cscli completion' with or without configuration file" {
 @test "'cscli completion' with or without configuration file" {

+ 60 - 0
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"
+}