diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 7bd4087ee..acaff7d4c 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -43,6 +43,7 @@ import ( "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/pkg/gate" "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/pkg/textparse" "github.com/prometheus/prometheus/pkg/timestamp" "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/promql" @@ -55,6 +56,28 @@ import ( "github.com/prometheus/prometheus/util/testutil" ) +// testMetaStore satisfies the scrape.MetricMetadataStore interface. +// It is used to inject specific metadata as part of a test case. +type testMetaStore struct { + Metadata []scrape.MetricMetadata +} + +func (s *testMetaStore) ListMetadata() []scrape.MetricMetadata { + return s.Metadata +} + +func (s *testMetaStore) GetMetadata(metric string) (scrape.MetricMetadata, bool) { + for _, m := range s.Metadata { + if metric == m.Metric { + return m, true + } + } + + return scrape.MetricMetadata{}, false +} + +// testTargetRetriever represents a list of targets to scrape. +// It is used to represent targets as part of test cases. type testTargetRetriever struct { activeTargets map[string][]*scrape.Target droppedTargets map[string][]*scrape.Target @@ -113,6 +136,20 @@ func (t testTargetRetriever) TargetsDropped() map[string][]*scrape.Target { return t.droppedTargets } +func (t testTargetRetriever) setMetadataStoreForTargets(identifier string, metadata scrape.MetricMetadataStore) error { + targets, ok := t.activeTargets[identifier] + + if !ok { + return errors.New("targets not found") + } + + for _, at := range targets { + at.SetMetadataStore(metadata) + } + + return nil +} + type testAlertmanagerRetriever struct{} func (t testAlertmanagerRetriever) Alertmanagers() []*url.URL { @@ -250,48 +287,7 @@ func TestEndpoints(t *testing.T) { algr.RuleGroups() - targets := []*testTargetParams{ - { - Identifier: "test", - Labels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "example.com:8080", - model.MetricsPathLabel: "/metrics", - model.JobLabel: "test", - }), - DiscoveredLabels: nil, - Params: url.Values{}, - Reports: []*testReport{{scrapeStart, 70 * time.Millisecond, nil}}, - Active: true, - }, - { - Identifier: "blackbox", - Labels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "localhost:9115", - model.MetricsPathLabel: "/probe", - model.JobLabel: "blackbox", - }), - DiscoveredLabels: nil, - Params: url.Values{"target": []string{"example.com"}}, - Reports: []*testReport{{scrapeStart, 100 * time.Millisecond, errors.New("failed")}}, - Active: true, - }, - { - Identifier: "blackbox", - Labels: nil, - DiscoveredLabels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "http://dropped.example.com:9115", - model.MetricsPathLabel: "/probe", - model.JobLabel: "blackbox", - }), - Params: url.Values{}, - Active: false, - }, - } - - testTargetRetriever := newTestTargetRetriever(targets) + testTargetRetriever := setupTestTargetRetriever(t) api := &API{ Queryable: suite.Storage(), @@ -355,48 +351,7 @@ func TestEndpoints(t *testing.T) { algr.RuleGroups() - targets := []*testTargetParams{ - { - Identifier: "test", - Labels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "example.com:8080", - model.MetricsPathLabel: "/metrics", - model.JobLabel: "test", - }), - DiscoveredLabels: nil, - Params: url.Values{}, - Reports: []*testReport{{scrapeStart, 70 * time.Millisecond, nil}}, - Active: true, - }, - { - Identifier: "blackbox", - Labels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "localhost:9115", - model.MetricsPathLabel: "/probe", - model.JobLabel: "blackbox", - }), - DiscoveredLabels: nil, - Params: url.Values{"target": []string{"example.com"}}, - Reports: []*testReport{{scrapeStart, 100 * time.Millisecond, errors.New("failed")}}, - Active: true, - }, - { - Identifier: "blackbox", - Labels: nil, - DiscoveredLabels: labels.FromMap(map[string]string{ - model.SchemeLabel: "http", - model.AddressLabel: "http://dropped.example.com:9115", - model.MetricsPathLabel: "/probe", - model.JobLabel: "blackbox", - }), - Params: url.Values{}, - Active: false, - }, - } - - testTargetRetriever := newTestTargetRetriever(targets) + testTargetRetriever := setupTestTargetRetriever(t) api := &API{ Queryable: remote, @@ -450,6 +405,76 @@ func TestLabelNames(t *testing.T) { } } +func setupTestTargetRetriever(t *testing.T) *testTargetRetriever { + t.Helper() + + targets := []*testTargetParams{ + { + Identifier: "test", + Labels: labels.FromMap(map[string]string{ + model.SchemeLabel: "http", + model.AddressLabel: "example.com:8080", + model.MetricsPathLabel: "/metrics", + model.JobLabel: "test", + }), + DiscoveredLabels: nil, + Params: url.Values{}, + Reports: []*testReport{{scrapeStart, 70 * time.Millisecond, nil}}, + Active: true, + }, + { + Identifier: "blackbox", + Labels: labels.FromMap(map[string]string{ + model.SchemeLabel: "http", + model.AddressLabel: "localhost:9115", + model.MetricsPathLabel: "/probe", + model.JobLabel: "blackbox", + }), + DiscoveredLabels: nil, + Params: url.Values{"target": []string{"example.com"}}, + Reports: []*testReport{{scrapeStart, 100 * time.Millisecond, errors.New("failed")}}, + Active: true, + }, + { + Identifier: "blackbox", + Labels: nil, + DiscoveredLabels: labels.FromMap(map[string]string{ + model.SchemeLabel: "http", + model.AddressLabel: "http://dropped.example.com:9115", + model.MetricsPathLabel: "/probe", + model.JobLabel: "blackbox", + }), + Params: url.Values{}, + Active: false, + }, + } + targetRetriever := newTestTargetRetriever(targets) + + targetRetriever.setMetadataStoreForTargets("test", &testMetaStore{ + Metadata: []scrape.MetricMetadata{ + { + Metric: "go_threads", + Type: textparse.MetricTypeGauge, + Help: "Number of OS threads created.", + Unit: "", + }, + }, + }) + + targetRetriever.setMetadataStoreForTargets("blackbox", &testMetaStore{ + Metadata: []scrape.MetricMetadata{ + { + Metric: "prometheus_tsdb_storage_blocks_bytes", + Type: textparse.MetricTypeGauge, + Help: "The number of bytes that are currently used for local storage by all blocks.", + Unit: "", + }, + }, + }) + + return targetRetriever +} + func setupRemote(s storage.Storage) *httptest.Server { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req, err := remote.DecodeReadRequest(r) @@ -926,6 +951,73 @@ func testEndpoints(t *testing.T, api *API, testLabelAPI bool) { }, }, }, + // With a matching metric. + { + endpoint: api.targetMetadata, + query: url.Values{ + "metric": []string{"go_threads"}, + }, + response: []metricMetadata{ + { + Target: labels.FromMap(map[string]string{ + "job": "test", + }), + Help: "Number of OS threads created.", + Type: textparse.MetricTypeGauge, + Unit: "", + }, + }, + }, + // With a matching target. + { + endpoint: api.targetMetadata, + query: url.Values{ + "match_target": []string{"{job=\"blackbox\"}"}, + }, + response: []metricMetadata{ + { + Target: labels.FromMap(map[string]string{ + "job": "blackbox", + }), + Metric: "prometheus_tsdb_storage_blocks_bytes", + Help: "The number of bytes that are currently used for local storage by all blocks.", + Type: textparse.MetricTypeGauge, + Unit: "", + }, + }, + }, + // Without a target or metric. + { + endpoint: api.targetMetadata, + response: []metricMetadata{ + { + Target: labels.FromMap(map[string]string{ + "job": "test", + }), + Metric: "go_threads", + Help: "Number of OS threads created.", + Type: textparse.MetricTypeGauge, + Unit: "", + }, + { + Target: labels.FromMap(map[string]string{ + "job": "blackbox", + }), + Metric: "prometheus_tsdb_storage_blocks_bytes", + Help: "The number of bytes that are currently used for local storage by all blocks.", + Type: textparse.MetricTypeGauge, + Unit: "", + }, + }, + }, + // Without a matching metric. + { + endpoint: api.targetMetadata, + query: url.Values{ + "match_target": []string{"{job=\"non-existentblackbox\"}"}, + }, + errType: errorNotFound, + }, { endpoint: api.alertmanagers, response: &AlertmanagerDiscovery{