CI: add tests for metrics configuration (#2251)

This commit is contained in:
mmetc 2023-06-05 23:17:30 +02:00 committed by GitHub
parent 9ccdddaab1
commit 3cc6b2c0d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 57 deletions

View file

@ -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) { RunE: runMetrics,
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)
}
},
} }
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")

View file

@ -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
} }

View file

@ -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 name string
Input *Config Input *Config
expectedResult string expectedURL string
err 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 { for _, tc := range tests {
err := test.Input.LoadPrometheus() tc := tc
if err == nil && test.err != "" { t.Run(tc.name, func(t *testing.T) {
fmt.Printf("TEST '%s': NOK\n", test.name) err := tc.Input.LoadPrometheus()
t.Fatalf("%d/%d expected error, didn't get it", idx, len(tests)) cstest.RequireErrorContains(t, err, tc.expectedErr)
} 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))
}
}
isOk := assert.Equal(t, test.expectedResult, test.Input.Cscli.PrometheusUrl) require.Equal(t, tc.expectedURL, tc.Input.Cscli.PrometheusUrl)
if !isOk { })
t.Fatalf("test '%s' failed\n", test.name)
} else {
fmt.Printf("TEST '%s': OK\n", test.name)
}
} }
} }

View file

@ -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
test/bats/08_metrics.bats Normal file
View file

@ -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"
}