package prometheus import ( "fmt" "math" "net/http" "net/http/httptest" "net/url" "os" "testing" "time" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/fields" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/testutil" ) const sampleTextFormat = `# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 0.00010425500000000001 go_gc_duration_seconds{quantile="0.25"} 0.000139108 go_gc_duration_seconds{quantile="0.5"} 0.00015749400000000002 go_gc_duration_seconds{quantile="0.75"} 0.000331463 go_gc_duration_seconds{quantile="1"} 0.000667154 go_gc_duration_seconds_sum 0.0018183950000000002 go_gc_duration_seconds_count 7 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 15 # HELP test_metric An untyped metric with a timestamp # TYPE test_metric untyped test_metric{label="value"} 1.0 1490802350000` const sampleSummaryTextFormat = `# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 0.00010425500000000001 go_gc_duration_seconds{quantile="0.25"} 0.000139108 go_gc_duration_seconds{quantile="0.5"} 0.00015749400000000002 go_gc_duration_seconds{quantile="0.75"} 0.000331463 go_gc_duration_seconds{quantile="1"} 0.000667154 go_gc_duration_seconds_sum 0.0018183950000000002 go_gc_duration_seconds_count 7` const sampleGaugeTextFormat = ` # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 15 1490802350000` func TestPrometheusGeneratesMetrics(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ Log: testutil.Logger{}, URLs: []string{ts.URL}, URLTag: "url", } var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) require.True(t, acc.HasFloatField("go_gc_duration_seconds", "count")) require.True(t, acc.HasFloatField("go_goroutines", "gauge")) require.True(t, acc.HasFloatField("test_metric", "value")) require.True(t, acc.HasTimestamp("test_metric", time.Unix(1490802350, 0))) require.False(t, acc.HasTag("test_metric", "address")) require.True(t, acc.TagValue("test_metric", "url") == ts.URL+"/metrics") } func TestPrometheusGeneratesMetricsWithHostNameTag(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ Log: testutil.Logger{}, KubernetesServices: []string{ts.URL}, URLTag: "url", } u, _ := url.Parse(ts.URL) tsAddress := u.Hostname() var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) require.True(t, acc.HasFloatField("go_gc_duration_seconds", "count")) require.True(t, acc.HasFloatField("go_goroutines", "gauge")) require.True(t, acc.HasFloatField("test_metric", "value")) require.True(t, acc.HasTimestamp("test_metric", time.Unix(1490802350, 0))) require.True(t, acc.TagValue("test_metric", "address") == tsAddress) require.True(t, acc.TagValue("test_metric", "url") == ts.URL) } func TestPrometheusGeneratesMetricsAlthoughFirstDNSFailsIntegration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ Log: testutil.Logger{}, URLs: []string{ts.URL}, KubernetesServices: []string{"http://random.telegraf.local:88/metrics"}, } var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) require.True(t, acc.HasFloatField("go_gc_duration_seconds", "count")) require.True(t, acc.HasFloatField("go_goroutines", "gauge")) require.True(t, acc.HasFloatField("test_metric", "value")) require.True(t, acc.HasTimestamp("test_metric", time.Unix(1490802350, 0))) } func TestPrometheusGeneratesSummaryMetricsV2(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleSummaryTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ URLs: []string{ts.URL}, URLTag: "url", MetricVersion: 2, } var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) require.True(t, acc.TagSetValue("prometheus", "quantile") == "0") require.True(t, acc.HasFloatField("prometheus", "go_gc_duration_seconds_sum")) require.True(t, acc.HasFloatField("prometheus", "go_gc_duration_seconds_count")) require.True(t, acc.TagValue("prometheus", "url") == ts.URL+"/metrics") } func TestSummaryMayContainNaN(t *testing.T) { const data = `# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} NaN go_gc_duration_seconds{quantile="1"} NaN go_gc_duration_seconds_sum 42.0 go_gc_duration_seconds_count 42` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, data) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ URLs: []string{ts.URL}, URLTag: "", MetricVersion: 2, } var acc testutil.Accumulator err := p.Gather(&acc) require.NoError(t, err) expected := []telegraf.Metric{ testutil.MustMetric( "prometheus", map[string]string{ "quantile": "0", }, map[string]interface{}{ "go_gc_duration_seconds": math.NaN(), }, time.Unix(0, 0), telegraf.Summary, ), testutil.MustMetric( "prometheus", map[string]string{ "quantile": "1", }, map[string]interface{}{ "go_gc_duration_seconds": math.NaN(), }, time.Unix(0, 0), telegraf.Summary, ), testutil.MustMetric( "prometheus", map[string]string{}, map[string]interface{}{ "go_gc_duration_seconds_sum": 42.0, "go_gc_duration_seconds_count": 42.0, }, time.Unix(0, 0), telegraf.Summary, ), } testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime(), testutil.SortMetrics()) } func TestPrometheusGeneratesGaugeMetricsV2(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleGaugeTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ URLs: []string{ts.URL}, URLTag: "url", MetricVersion: 2, } var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) require.True(t, acc.HasFloatField("prometheus", "go_goroutines")) require.True(t, acc.TagValue("prometheus", "url") == ts.URL+"/metrics") require.True(t, acc.HasTimestamp("prometheus", time.Unix(1490802350, 0))) } func TestPrometheusGeneratesMetricsWithIgnoreTimestamp(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, sampleTextFormat) require.NoError(t, err) })) defer ts.Close() p := &Prometheus{ Log: testutil.Logger{}, URLs: []string{ts.URL}, URLTag: "url", IgnoreTimestamp: true, } var acc testutil.Accumulator err := acc.GatherError(p.Gather) require.NoError(t, err) m, _ := acc.Get("test_metric") require.WithinDuration(t, time.Now(), m.Time, 5*time.Second) } func TestUnsupportedFieldSelector(t *testing.T) { fieldSelectorString := "spec.containerName=container" prom := &Prometheus{Log: testutil.Logger{}, KubernetesFieldSelector: fieldSelectorString} fieldSelector, _ := fields.ParseSelector(prom.KubernetesFieldSelector) isValid, invalidSelector := fieldSelectorIsSupported(fieldSelector) require.Equal(t, false, isValid) require.Equal(t, "spec.containerName", invalidSelector) } func TestInitConfigErrors(t *testing.T) { p := &Prometheus{ MetricVersion: 2, Log: testutil.Logger{}, URLs: nil, URLTag: "url", MonitorPods: true, PodScrapeScope: "node", PodScrapeInterval: 60, } // Both invalid IP addresses p.NodeIP = "10.240.0.0.0" require.NoError(t, os.Setenv("NODE_IP", "10.000.0.0.0")) err := p.Init() require.Error(t, err) expectedMessage := "the node_ip config and the environment variable NODE_IP are not set or invalid; cannot get pod list for monitor_kubernetes_pods using node scrape scope" require.Equal(t, expectedMessage, err.Error()) require.NoError(t, os.Setenv("NODE_IP", "10.000.0.0")) p.KubernetesLabelSelector = "label0==label0, label0 in (=)" err = p.Init() expectedMessage = "error parsing the specified label selector(s): unable to parse requirement: found '=', expected: ',', ')' or identifier" require.Error(t, err, expectedMessage) p.KubernetesLabelSelector = "label0==label" p.KubernetesFieldSelector = "field," err = p.Init() expectedMessage = "error parsing the specified field selector(s): invalid selector: 'field,'; can't understand 'field'" require.Error(t, err, expectedMessage) p.KubernetesFieldSelector = "spec.containerNames=containerNames" err = p.Init() expectedMessage = "the field selector spec.containerNames is not supported for pods" require.Error(t, err, expectedMessage) }