CI: add tests for metrics configuration (#2251)
This commit is contained in:
parent
9ccdddaab1
commit
3cc6b2c0d0
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
60
test/bats/08_metrics.bats
Normal 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"
|
||||||
|
}
|
Loading…
Reference in a new issue